Compare commits

..

No commits in common. "3298f4a973bcc9f1f9957c16ba46e02006671595" and "d65d95bd045dc3bd24a7acfdd0e34d5b31e738a0" have entirely different histories.

2 changed files with 0 additions and 215 deletions

View File

@ -1,119 +0,0 @@
---
created: '2026-05-23'
path: Sources/Homelab
project: gitea-migration-to-pve
tags:
- git
- proxmox
- lovebug
- pbs
- traefik
type: project-plan
---
## Goal
Relocate Gitea so its placement matches what it has actually become: critical homelab/dev infrastructure, not a service incidentally hosted on the PBS business server. Establish a two-instance architecture with cross-mirroring so each instance is the insurance copy for the other.
**Target architecture:**
```
PVE LXC Gitea PBS Gitea
───────────── ─────────
Homelab projects PBS business code
Lovebug pushes here (website, n8n, content automation)
Canonical for Canonical for
petal-power org PBS-business org
↕ cross-mirror for backup ↕
```
## Locked Decisions
- **Host:** PVE LXC (small footprint, native install, no Docker — VM/LXC is the isolation boundary)
- **Database:** SQLite on local LXC disk. Adequate for one-human + one-agent push volume; avoids the SQLite-over-NFS corruption footgun.
- **Repo storage:** NFS mount from NAS for `repos/`, `lfs/`, `attachments/`. Compute is disposable (rebuildable via Ansible/Terraform), state lives on NAS where it's already in the backup story.
- **Cross-mirror:** Gitea's native push-mirror feature in both directions. PVE LXC mirrors homelab repos to PBS; PBS mirrors business repos to PVE LXC.
- **Lovebug access:** Only on the new PVE LXC Gitea. PBS Gitea (PBS business code) stays outside Lovebug's reach by default — a natural security boundary.
- **Mirror direction:** Canonical lives where active work happens. New LXC is canonical for homelab; PBS stays canonical for PBS business code.
- **Migration mechanism:** `git push --mirror` from herbys-dev for the petal-power org repos (everything was created on herbys-dev anyway). Use Gitea's "Migrate Repository" feature only if any repo has meaningful issue/PR/wiki state worth preserving.
## Open Items
- [ ] Confirm petal-power repos don't have meaningful Gitea-native state (issues, PRs, wiki) that `git push --mirror` would lose. If any do, use Migrate Repository for those specific ones.
- [ ] Decide naming/DNS for new instance (e.g., `gitea.herbylab.dev` vs subdomain that signals "homelab-Gitea" vs PBS-Gitea)
- [ ] Verify NFS export path on NAS, sizing, and that it's covered by existing NAS backup
- [ ] Identify trigger to execute (see Notes below)
## Phases
### Phase 1: Provision
- [ ] Create LXC on PVE (size: small, ~2 vCPU / 2GB RAM is plenty for this workload)
- [ ] Configure NFS mount from NAS for repo storage path
- [ ] Install Gitea natively (no Docker)
- [ ] Configure Gitea: SQLite local, repo root + LFS + attachments pointed at NFS mount
- [ ] DNS record for new instance
- [ ] Traefik route + Cloudflare DNS-01 wildcard TLS
- [ ] Test with a throwaway repo to verify the full stack: web UI, SSH push, TLS
### Phase 2: Set up org and accounts
- [ ] Create `petal-power` org on new instance
- [ ] Create Lovebug user account, add SSH key, mirror permissions from PBS instance
- [ ] Pre-populate `~/.ssh/known_hosts` on herbys-dev with new Gitea's host key (avoid headless SSH hang)
### Phase 3: Migrate repos
- [ ] For each repo under `/opt/projects/` on herbys-dev: add new remote, `git push --mirror` to new Gitea
- [ ] Spot-check several repos: full commit history present, all branches present, tags intact
- [ ] If any repos have Gitea-native state worth preserving, use Migrate Repository for those specifically
### Phase 4: Cutover
- [ ] Update Lovebug's SSH config and any shell aliases on herbys-dev to point at new Gitea
- [ ] Update any Gitea API tokens Lovebug uses (new instance = new tokens)
- [ ] Test push from Lovebug against new instance
- [ ] Test push from Travis (travadmin) against new instance
### Phase 5: Invert the relationship
- [ ] Configure push-mirror on new Gitea: homelab repos mirror outbound to PBS Gitea
- [ ] PBS Gitea is now read-only-ish for homelab repos (writes propagate from new instance via mirror)
- [ ] Verify a test push lands on PBS via mirror within expected delay
- [ ] (Optional) Configure push-mirror PBS → new instance for PBS business repos, so new instance is insurance copy for those
## Notes
### The reframe that made this plan possible
The starting point: Gitea-on-PBS was bothering me. The reframe: PBS Gitea isn't "where my code lives" — it's the paid offsite insurance copy I already have. PBS is in a different physical location, backed up whole-drive to Linode, and has been correctly serving an insurance role the whole time. That makes the migration a *promotion* of the new instance to canonical rather than a *rescue* of code from PBS.
### Cost story
No new spend. PBS hosting is already paid. New LXC sits on existing PVE hardware. NAS storage is already in place. The "paid relationship for business-asset backup" principle is satisfied by the existing PBS arrangement — the architectural fix is about where the canonical lives, not about adding a paid service.
### Cross-domain consequence
The plan implicitly couples code backup strategy to the PBS server contract. If PBS is ever cancelled or downgraded, the insurance leg evaporates and a replacement (Backblaze B2, rsync.net, etc.) needs to slot in. Worth remembering this when making future decisions about PBS.
### Trigger to execute
Not scheduled. Migration is decided-and-waiting. Any of these fires it:
- A Traefik or OB1 phase wraps cleanly with no immediate next work queued
- Lovebug needs gnarly work against a repo where having a fully-controlled Gitea is useful prep
- Gitea-on-PBS needs hands-on attention anyway (upgrade, backup restore, webhook debug) — redirect that time into the migration
- A "huh, that was uncomfortable" moment on PBS that makes the coupling acute
- A Saturday morning where focused infra work sounds genuinely fun
Anti-pattern to avoid: letting this become a background nag. The bad state is "I keep meaning to but never do" — that converts reflection-energy into procrastination-guilt.
### Self-hosted git landscape (for future reference)
- **Forgejo** — community-governed fork of Gitea, near-painless migration path if Gitea Ltd. ever takes a turn. Most plausible exit ramp.
- **GitLab CE** — heavier, full DevOps platform. Overkill for this scale.
- **Cgit + Gitolite** — Unix-philosophy stack, no web platform. Wrong cluster for Lovebug-style workflow.
- **Sourcehut** — email-driven, niche-but-thoughtful. Wrong workflow fit.
- **Gogs** — Gitea's ancestor, basically a museum piece in 2026.
The Gitea-vs-Forgejo decision can be deferred indefinitely; not blocking this migration.

View File

@ -1,96 +0,0 @@
---
created: '2026-05-23'
path: Sources/Homelab
project: trellis-mcp-tool-surface-followups
tags:
- mcp
- claude-code
- lovebug
type: project-plan
---
## Goal
Make the Trellis MCP tool surface more ergonomic for in-session use without changing the underlying data model. Two changes:
1. Rename `pause_thread` to `checkpoint_thread` so the verb matches the event it writes (and stops colliding with the `paused` status value).
2. Add optional `id` parameter to all tools that take `slug`, so threads can be addressed by their integer ID during a session. Slug remains canonical; ID is a convenience handle.
Both changes are small, scoped server-side edits. No data migrations, no breaking changes if done with aliases.
## Locked Decisions
- **Slug stays canonical.** Names are durable, meaningful, survive migrations. IDs are database-implementation artifacts. The model is: slug for discovery and persistence, ID for session-scoped operation. Same pattern as `ps` vs `kill <pid>`.
- **ID stability is explicitly not a goal.** IDs may not survive a restore or migration. That's fine — you're not going to remember `232` next week either. You'll discover the thread again via `list_threads`, see whatever ID it has now, and use that.
- **Rename via alias, not hard cutover.** Add `checkpoint_thread` as the new canonical name, leave `pause_thread` as a deprecated alias that calls through. Drop the alias after a grace period (TBD — could be one trellis release, could be a calendar deadline).
- **No `id`-only tools.** Every tool that gains `id` keeps `slug`. If both are passed, prefer `id` and warn on mismatch. If neither, error. This preserves slug-first workflows and adds id as additive.
## Open Items
- [ ] Decide grace period for `pause_thread` alias before removal
- [ ] Confirm whether `parent_id` field in `list_threads` already returns the integer (it filters by it, but does it return it?) — affects whether `list_threads` needs response-shape changes
- [ ] Decide whether `start_thread` and `fork_thread` should return the new thread's ID in their response payload (probably yes — otherwise you have to immediately `get_thread` to learn the ID)
- [ ] Audit the database schema to confirm `threads.id` is the right field to expose (vs some other internal identifier)
## Phases
### Phase 1 — Rename pause_thread → checkpoint_thread
- [ ] Add `checkpoint_thread` tool that takes the same args as `pause_thread` (`slug`, `state`) and writes the same `checkpoint` event
- [ ] Leave `pause_thread` registered as a deprecated alias — same handler, optionally logs a deprecation warning
- [ ] Update tool descriptions to reflect the rename
- [ ] Update any internal documentation / README references
### Phase 2 — Add id parameter to read/write tools
Tools to modify:
- `get_thread(slug, include_events)``get_thread(slug?, id?, include_events)`
- `checkpoint_thread(slug, state)``checkpoint_thread(slug?, id?, state)`
- `resume_thread(slug)``resume_thread(slug?, id?)`
- `update_thread(slug, ...)``update_thread(slug?, id?, ...)`
- `fork_thread(parent_slug, new_slug, ...)``fork_thread(parent_slug?, parent_id?, new_slug, ...)`
For each:
- [ ] Make `slug` optional in the schema
- [ ] Add optional `id` (integer)
- [ ] Server-side: branch on which was provided; error if neither; warn if both disagree
- [ ] Update tool descriptions
### Phase 3 — Expose id in responses
- [ ] `start_thread` response includes `id`
- [ ] `fork_thread` response includes `id` of new child
- [ ] `get_thread` response includes `id`
- [ ] `list_threads` response includes `id` per row (confirm whether already true)
### Phase 4 — Verification
- [ ] In-session test: address a thread by integer ID end-to-end (start → checkpoint → resume) without typing the slug
- [ ] In-session test: cross-chat handoff using ID ("can you pull thread 232")
- [ ] Confirm `pause_thread` alias still works for any prior tooling that calls it
## Notes
**Naming context — why pause is wrong for the action:**
The existing `pause_thread` tool writes a `checkpoint` event, not a status change. Per its own docstring: "the thread row is not changed except for `updated_at`." So the verb doesn't describe what's happening — checkpointing is happening; pausing is not.
Worse, there's a `paused` value in the status enum (`active | paused | done | abandoned`). So you have:
- A `paused` status that means "this thread is on hold"
- A `pause_thread` action that does *not* set that status
`checkpoint_thread` resolves the collision. Pausing-the-status remains a separate decision via `update_thread(slug, status='paused')`.
**ID lookup context — why this matters ergonomically:**
In session usage, slugs are friction: long, must be re-typed or re-quoted, easy to mistype. Integers are zero-friction: `pull thread 232`, `checkpoint 232`, `resume 232`. The slug stays useful for first-discovery ("which thread was the traefik debugging one?") but once you're operating on it, the integer wins.
Pattern parallel: Linux processes. `ps` shows names for discovery; `kill 4729` for operation. Names for finding, numbers for doing.
**What's explicitly out of scope:**
- Changing slug semantics (still required at creation, still kebab-case, still the URL-safe identifier)
- Migrating existing data
- Changing the event log structure
- Adding any new event types
- The three other trellis follow-ups already tracked (retry/cleanup on embedding failure, semantic dedup before insert, revisit enricher pause-during-reflector)