Compare commits
2 Commits
d65d95bd04
...
3298f4a973
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3298f4a973 | ||
|
|
1b5ee52b98 |
119
Sources/Homelab/gitea-migration-to-pve.md
Normal file
119
Sources/Homelab/gitea-migration-to-pve.md
Normal file
@ -0,0 +1,119 @@
|
||||
---
|
||||
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.
|
||||
96
Sources/Homelab/trellis-mcp-tool-surface-followups.md
Normal file
96
Sources/Homelab/trellis-mcp-tool-surface-followups.md
Normal file
@ -0,0 +1,96 @@
|
||||
---
|
||||
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)
|
||||
Loading…
Reference in New Issue
Block a user