diff --git a/plans/homelab-improvements/PLAN-BRIEF.md b/plans/homelab-improvements/PLAN-BRIEF.md new file mode 100644 index 0000000..654a33e --- /dev/null +++ b/plans/homelab-improvements/PLAN-BRIEF.md @@ -0,0 +1,98 @@ +# Homelab improvements — plan brief + +**How to use this:** Don't read everything. Pick something that sounds interesting and say it in chat — e.g. *"let's talk about backups"* or *"TB-003"*. We'll have a short conversation first. You can do, defer, drop, or change any item. + +**Status:** Draft · **Host:** valhalla + +--- + +## Talk first — plan level + +Before any phase or task, we should answer: + +1. **What feels urgent?** Backup? Security? Finishing romhacks? Something else entirely? +2. **What can wait or die?** Throwing items out is success — tell me what doesn't interest you. +3. **How do you want to work?** One item per session, or a theme (e.g. "media day")? + +### What we decided (plan level) + +| | | +|---|---| +| **Priority theme** | Backups (TB-001 in progress) | +| **Explicitly dropped** | _(none yet)_ | +| **Notes** | _(conversation notes go here)_ | + +--- + +## Menu — pick one to talk about + +Each link opens an **interview-first** task brief. The conversation is the task. + +### Safety & quick wins + +| Say this… | About | +|-----------|--------| +| [TB-001](tasks/TB-001-backup-stack.md) | Automated backups — you don't have any yet | +| [TB-033](tasks/TB-033-public-surface-audit.md) | What's actually public on the internet? | +| [TB-003](tasks/TB-003-recyclarr.md) | Sync *arr quality settings automatically | +| [TB-004](tasks/TB-004-pin-infra-tags.md) | Stop `:latest` from breaking databases | +| [TB-002](tasks/TB-002-jd-caddy-block.md) | JDownloader web UI on jd.ginnoir.com | + +### Security & cleanup + +| Say this… | About | +|-----------|--------| +| [TB-005](tasks/TB-005-obsidian-hardening.md) | Your Obsidian vault is public-facing | +| [TB-006](tasks/TB-006-authentik-forward-auth.md) | Login on admin UIs via Authentik | +| [TB-013](tasks/TB-013-code-server-mount.md) | code-server sees your whole home dir | +| [TB-014](tasks/TB-014-registry-auth.md) | Docker registry has no password | +| [TB-008](tasks/TB-008-retire-deluge.md) | Maybe remove Deluge (you have qBit too) | +| [TB-009](tasks/TB-009-bookstack-decision.md) | BookStack vs Obsidian — pick one? | +| [TB-010](tasks/TB-010-dev-stack-env-migration.md) | dev stack env file cleanup | +| [TB-011](tasks/TB-011-reap-orphan-volumes.md) | Delete old Docker volumes | + +### Finish what's started + +| Say this… | About | +|-----------|--------| +| [TB-007](tasks/TB-007-romhacks-handoff.md) | ROM hacks pipeline → RomM (incomplete) | +| [TB-012](tasks/TB-012-maintainerr.md) | Clean stale Overseerr requests | +| [TB-016](tasks/TB-016-document-plex.md) | Write down how Plex fits in | + +### New services (only if you want them) + +| Say this… | About | +|-----------|--------| +| [TB-019](tasks/TB-019-immich.md) | Family photos | +| [TB-020](tasks/TB-020-mealie.md) | Recipes | +| [TB-021](tasks/TB-021-paperless-ngx.md) | Scan/OCR documents | +| [TB-022](tasks/TB-022-readarr.md) | Automate ebooks | +| [TB-023](tasks/TB-023-autobrr.md) | Torrent announce automation | +| [TB-024](tasks/TB-024-syncthing.md) | Device file sync | +| [TB-025](tasks/TB-025-metrics-beszel.md) | CPU/disk metrics dashboard | +| [TB-026](tasks/TB-026-igir-container.md) | Automate ROM library cleanup | + +### Maybe later (low pressure) + +| Say this… | About | +|-----------|--------| +| [TB-015](tasks/TB-015-vault-secrets-integration.md) | Actually use Vault | +| [TB-017](tasks/TB-017-minio-consolidation.md) | One MinIO instead of three | +| [TB-018](tasks/TB-018-plane-evaluation.md) | Is Plane worth 7 containers? | +| [TB-027](tasks/TB-027-jellyfin.md) | Replace Plex with Jellyfin? | +| [TB-028](tasks/TB-028-home-assistant.md) | Home automation | +| [TB-029](tasks/TB-029-firefly-iii.md) | Personal finance | +| [TB-030](tasks/TB-030-adguard-home.md) | Network ad blocking / DNS | +| [TB-031](tasks/TB-031-pingvin-share.md) | Temporary file share links | +| [TB-032](tasks/TB-032-wikijs.md) | Wiki.js (only if keeping a wiki) | + +--- + +
+Background (optional — skip if overwhelming) + +Homelab is ~50 containers, 15 stacks, Caddy ingress, git-push deploys. Main gaps from analysis: no backups, Obsidian public, Authentik barely used, romhacks pipeline half-done. + +Repo conventions: `stack.env` secrets, `edge` network, `internal_only` for LAN admin UIs, `./scripts/gen-bookmarks.ps1` after Caddy changes. + +
diff --git a/plans/homelab-improvements/_generate_tasks.py b/plans/homelab-improvements/_generate_tasks.py new file mode 100644 index 0000000..015317d --- /dev/null +++ b/plans/homelab-improvements/_generate_tasks.py @@ -0,0 +1,627 @@ +#!/usr/bin/env python3 +"""Regenerate interview-first task briefs from structured data.""" + +from pathlib import Path + +TASKS = [ + { + "id": "TB-001", + "slug": "backup-stack", + "title": "Backup stack", + "sentence": "Set up automatic nightly backups of configs and databases, with a copy off the server.", + "why": "Right now nothing backs up /config or your databases — ZFS snapshots don't help if the whole machine dies.", + "questions": [ + "Does losing valhalla overnight scare you, or is 'I'll deal with it' okay for now?", + "Do you already have offsite storage (Backblaze, Wasabi, another machine)?", + "Kopia (has a UI) vs Restic (simpler CLI) — want a recommendation or don't care?", + ], + "stack": "`backup` new stack", + "steps": [ + "Pick Kopia or Restic", + "New stacks/backup/ with nightly schedule + ntfy on failure", + "Script dumps for all Postgres/MariaDB/CouchDB", + "One restore test so we know it works", + ], + "done": [ + "Nightly backup runs without you", + "At least one restore tested", + "Failures ping ntfy", + ], + }, + { + "id": "TB-002", + "slug": "jd-caddy-block", + "title": "jd.ginnoir.com", + "sentence": "Add a LAN-only web address for JDownloader's UI.", + "why": "It's documented but never added to the Caddyfile — you use port 8998 instead.", + "questions": [ + "Are you actually using the romhacks/JDownloader pipeline, or is this premature?", + "Is LAN-only enough, or do you need it over Tailscale too (already covered by internal_only)?", + ], + "stack": "Caddyfile", + "steps": [ + "Add jd.ginnoir.com block with internal_only", + "gen-bookmarks.ps1 + push", + ], + "done": ["jd.ginnoir.com works on LAN", "403 from outside"], + }, + { + "id": "TB-003", + "slug": "recyclarr", + "title": "Recyclarr", + "sentence": "One container that keeps Sonarr/Radarr/Bazarr quality settings in sync with TRaSH guides.", + "why": "Your *arr configs probably drift over time; this automates the boring sync.", + "questions": [ + "Have you hand-tuned quality profiles you'd hate to have overwritten?", + "Want this, or are your *arr settings 'set and forget' already?", + ], + "stack": "stacks/media/", + "steps": [ + "Add recyclarr container on media network", + "Point at sonarr/radarr/bazarr/prowlarr APIs", + "Run first sync, spot-check Sonarr", + ], + "done": ["Recyclarr syncs without errors", "Profiles visible in Sonarr or Radarr"], + }, + { + "id": "TB-004", + "slug": "pin-infra-tags", + "title": "Pin infra image tags", + "sentence": "Stop Postgres, MariaDB, Caddy, etc. from silently updating to a broken :latest.", + "why": "Watchtower auto-updates containers; one bad DB image pull is painful.", + "questions": [ + "Have you been burned by a surprise update before?", + "Okay to manually bump pinned versions every few months?", + ], + "stack": "All compose files", + "steps": [ + "Audit :latest on infra images", + "Pin versions + watchtower.enable=false on those", + "Document policy in CLAUDE.md", + ], + "done": ["No :latest on DB/Caddy/Vault/CouchDB/MinIO", "Stacks still deploy clean"], + }, + { + "id": "TB-033", + "slug": "public-surface-audit", + "title": "Public surface audit", + "sentence": "A short conversation + checklist: what's reachable from the internet, and should it be?", + "why": "Some things are public on purpose (Foundry, famapp); some might surprise you (Obsidian).", + "questions": [ + "Want to walk through the list together in chat instead of reading a table?", + "Any service you *know* should be public that we'd flag as wrong?", + ], + "stack": "Caddyfile review only", + "steps": [ + "Walk each public domain in conversation", + "Mark: keep public / lock down / drop", + "Save decisions here and in TB-005/TB-006 as needed", + ], + "done": ["Every public site has your yes/no", "Action items linked to other TBs"], + }, + { + "id": "TB-005", + "slug": "obsidian-hardening", + "title": "Obsidian / CouchDB hardening", + "sentence": "Your whole PKM vault syncs over a public URL — let's decide how exposed you're okay with.", + "why": "obsidian.ginnoir.com has no LAN-only or SSO gate; LiveSync needs remote access from phones.", + "questions": [ + "Do you sync Obsidian from your phone without Tailscale always on?", + "Would 'Tailscale only' work, or do you need public access?", + "Authentik login in front — okay if we test LiveSync still works?", + ], + "stack": "Caddyfile, stacks/notes/", + "steps": [ + "Pick model: Tailscale-only, Authentik, or hybrid", + "Implement + test sync from each device", + "Rotate CouchDB password", + ], + "done": ["You can still sync where you need to", "Admin/_utils not wide open"], + }, + { + "id": "TB-006", + "slug": "authentik-forward-auth", + "title": "Authentik on admin UIs", + "sentence": "Require Authentik login before Portainer, code-server, registry, etc.", + "why": "LAN-only IP checks aren't the same as identity — Authentik is already running for famapp.", + "questions": [ + "Which admin sites do you actually use? (No point gating ones you never open)", + "Portainer git webhooks must keep working — okay to test carefully?", + "Do this for everything internal, or just the scary ones (Portainer, code, vault)?", + ], + "stack": "Caddyfile + Authentik UI", + "steps": [ + "Authentik proxy provider + outpost", + "Caddy forward_auth snippet", + "Tier 1: portainer, code, registry-ui, vault, minio console", + ], + "done": ["Tier 1 sites require login", "Portainer webhooks still work"], + }, + { + "id": "TB-007", + "slug": "romhacks-handoff", + "title": "Romhacks → RomM handoff", + "sentence": "Finish the pipeline so downloaded ROM hacks land in RomM with cover art.", + "why": "Orchestrator + JDownloader exist; moving files into the library isn't built yet.", + "questions": [ + "Is the Discord/JDownloader pipeline armed, or still theoretical?", + "Worth finishing before arming, or drop the whole romhacks stack?", + "How much automation vs 'I'll move files manually'?", + ], + "stack": "stacks/romhacks/orchestrator/", + "steps": [ + "Complete handoff.py — incoming → Emulation/roms/", + "RomM custom cover API", + "Test with one staged folder", + ], + "done": ["Test ROM in RomM after handoff", "DRY_RUN still safe"], + }, + { + "id": "TB-008", + "slug": "retire-deluge", + "title": "Retire Deluge", + "sentence": "Remove Deluge if qBittorrent is doing all the torrent work.", + "why": "You run Deluge, qBit, and NZBGet — two torrent clients may be leftover cruft.", + "questions": [ + "When did you last use Deluge?", + "Are Sonarr/Radarr pointed at qBit only? (We can check together)", + ], + "stack": "stacks/media/, Caddyfile", + "steps": [ + "Confirm zero Deluge torrents + no *arr dependency", + "Remove service + Caddy block", + ], + "done": ["Deluge gone", "qBit/NZBGet still fine"], + }, + { + "id": "TB-009", + "slug": "bookstack-decision", + "title": "BookStack vs Obsidian", + "sentence": "Decide if docs.ginnoir.com (BookStack) stays or goes — Obsidian is already your main PKM.", + "why": "Two wiki systems = drift and an extra MariaDB to feed.", + "questions": [ + "Do you use BookStack at all? When did you last open it?", + "If we export and remove it, anything lost?", + "Or keep it for a specific use case — what?", + ], + "stack": "stacks/dev/", + "steps": [ + "Your call: retire, keep with boundary, or migrate to Wiki.js (TB-032)", + "If retire: export → remove containers", + ], + "done": ["Decision recorded", "If retired: BookStack containers gone"], + }, + { + "id": "TB-010", + "slug": "dev-stack-env-migration", + "title": "dev stack.env migration", + "sentence": "Make the dev stack use stack.env like everything else — no Portainer UI env vars.", + "why": "Every other stack commits secrets to stack.env; dev still needs UI mirroring (footgun).", + "questions": [ + "Have you been bitten by dev env drift before?", + "Worth an afternoon, or low priority housekeeping?", + ], + "stack": "stacks/dev/", + "steps": [ + "Move all ${VAR} into stack.env", + "Deploy, verify Gitea/Plane/BookStack/code-server", + "Clear Portainer UI env vars", + ], + "done": ["Dev stack runs with env_file only"], + }, + { + "id": "TB-011", + "slug": "reap-orphan-volumes", + "title": "Reap orphan volumes", + "sentence": "Delete old pre-split Docker named volumes that bind mounts replaced.", + "why": "htpc-download-box_* and valhalla-lab_* volumes waste disk and confuse what's canonical.", + "questions": [ + "Want me to SSH and list them with you before deleting anything?", + "Backups done first? (TB-001)", + ], + "stack": "SSH on valhalla only", + "steps": [ + "docker volume ls — confirm orphans", + "Spot-check bind mounts healthy", + "docker volume rm", + ], + "done": ["Orphan volumes gone", "All containers still healthy"], + }, + { + "id": "TB-012", + "slug": "maintainerr", + "title": "Maintainerr", + "sentence": "Auto-clean stale Overseerr requests so the queue doesn't fill with junk.", + "why": "requests.ginnoir.com is public; old unfulfilled requests pile up.", + "questions": [ + "Is your Overseerr queue messy now, or is this preventive?", + "How aggressive on age — 30 days? 90?", + ], + "stack": "stacks/media/", + "steps": [ + "Add maintainerr on media network", + "Connect Overseerr + Sonarr/Radarr APIs", + "Conservative cleanup rule first", + ], + "done": ["Maintainerr connected", "One rule configured"], + }, + { + "id": "TB-013", + "slug": "code-server-mount", + "title": "Narrow code-server mount", + "sentence": "Stop code-server from mounting your entire home directory.", + "why": "code.ginnoir.com currently exposes /home/ginnoir — SSH keys, everything.", + "questions": [ + "What folders do you actually edit in code-server?", + "homelabstack + a projects dir enough?", + ], + "stack": "stacks/dev/docker-compose.yml", + "steps": [ + "Change mount to scoped path", + "Symlink homelabstack if needed", + ], + "done": [".ssh and unrelated home dirs not visible in IDE"], + }, + { + "id": "TB-014", + "slug": "registry-auth", + "title": "Registry authentication", + "sentence": "Put a password on your private Docker registry.", + "why": "registry.ginnoir.com is LAN-only but anyone on LAN/tailnet can push/pull.", + "questions": [ + "Do you push images to it regularly, or is it barely used?", + "Will famapp CI or github-runner need login updated?", + ], + "stack": "stacks/dev/", + "steps": [ + "htpasswd + registry auth env", + "Test docker login/pull/push", + ], + "done": ["Unauthenticated pull fails", "You can pull with creds"], + }, + { + "id": "TB-016", + "slug": "document-plex", + "title": "Document Plex", + "sentence": "Write down where Plex lives and how it connects to Tautulli — it's not in this repo.", + "why": "Tautulli reads /storage1/PMS logs but Plex isn't in compose; easy to forget how it's managed.", + "questions": [ + "Is Plex on valhalla itself, or another box?", + "Do you want it in compose eventually, or happy as host-native?", + ], + "stack": "Docs only", + "steps": [ + "You tell me: install path, systemd?, library paths", + "Add short section to CLAUDE.md or EXTERNAL-SERVICES.md", + ], + "done": ["Plex documented with paths"], + }, + { + "id": "TB-019", + "slug": "immich", + "title": "Immich (photos)", + "sentence": "Self-hosted Google Photos — family photo backup with a phone app.", + "why": "Natural fit next to famapp; you don't have photo backup today.", + "questions": [ + "Is phone photo backup something you actively want?", + "Public with Authentik, or LAN/tailnet only?", + "Storage growth on ZFS — okay?", + ], + "stack": "New photos stack", + "steps": [ + "New stacks/photos/", + "photos.ginnoir.com + optional Authentik", + "Test upload from phone", + ], + "done": ["Mobile upload works", "In backup scope"], + }, + { + "id": "TB-020", + "slug": "mealie", + "title": "Mealie (recipes)", + "sentence": "Recipe manager with meal plans and shopping lists.", + "why": "Pairs with famapp/family theme; lightweight stack.", + "questions": [ + "Would you actually use this, or is it 'nice in theory'?", + "Shared with family or just you?", + ], + "stack": "New kitchen stack or famapp", + "steps": ["New stack", "recipes.ginnoir.com", "Import one recipe as test"], + "done": ["You'd use it — or we drop it"], + }, + { + "id": "TB-021", + "slug": "paperless-ngx", + "title": "Paperless-ngx", + "sentence": "Drop PDFs in a folder → OCR → searchable archive.", + "why": "ownCloud stores files; this adds document search and tagging.", + "questions": [ + "Do you scan receipts/documents often enough to matter?", + "Or is ownCloud + Obsidian enough?", + ], + "stack": "New docs stack", + "steps": ["New stack", "consume folder on ZFS", "Test one PDF OCR"], + "done": ["Search finds text in test doc — or we skip"], + }, + { + "id": "TB-022", + "slug": "readarr", + "title": "Readarr (ebooks)", + "sentence": "Automate ebook downloads like Sonarr does for TV.", + "why": "You have /storage1/Books in ownCloud; no *arr for books yet.", + "questions": [ + "Do you acquire ebooks automatically, or buy/download manually?", + "Audiobooks too? (Different tool — Audiobookshelf)", + ], + "stack": "stacks/media/", + "steps": [ + "Add readarr + Caddy internal block", + "Hook Prowlarr + download client", + ], + "done": ["Test grab lands in Books — or drop"], + }, + { + "id": "TB-023", + "slug": "autobrr", + "title": "Autobrr", + "sentence": "Monitor private tracker IRC/RSS and auto-push to qBittorrent.", + "why": "For active private tracker use — overkill if you're mostly public indexers via Prowlarr.", + "questions": [ + "Are you on private trackers that need announce monitoring?", + "Or skip — Prowlarr enough?", + ], + "stack": "stacks/media/", + "steps": ["Add autobrr", "Connect qBit", "One filter if proceeding"], + "done": ["Worth it for your tracker habits — or dropped"], + }, + { + "id": "TB-024", + "slug": "syncthing", + "title": "Syncthing", + "sentence": "Continuous folder sync between devices — ROM saves, dotfiles, etc.", + "why": "Different from ownCloud's model; good for device-to-device without 'upload to server'.", + "questions": [ + "What folders would you sync? (Be specific — not all of /storage1)", + "Conflict risk with RomM/ownCloud on same paths?", + ], + "stack": "New sync stack", + "steps": ["New stack", "Pair two devices", "One test folder"], + "done": ["Sync works on chosen folder — or drop"], + }, + { + "id": "TB-025", + "slug": "metrics-beszel", + "title": "Metrics (Beszel/Netdata)", + "sentence": "See CPU/RAM/disk graphs — Uptime Kuma only tells you up/down.", + "why": "Homelab has no metrics dashboard today.", + "questions": [ + "Do you ever wonder 'why is the server slow' without an answer?", + "Beszel (minimal) vs Netdata (detailed) — or skip?", + ], + "stack": "stacks/monitoring/", + "steps": ["Add chosen tool", "metrics.ginnoir.com internal"], + "done": ["Dashboard shows host + container usage"], + }, + { + "id": "TB-026", + "slug": "igir-container", + "title": "IGIR container", + "sentence": "Schedule your existing ROM cleanup scripts instead of SSH ad-hoc.", + "why": "igir-*.sh scripts exist; romhacks handoff will add more ROMs to maintain.", + "questions": [ + "Do you run IGIR often enough to automate?", + "Or manual is fine?", + ], + "stack": "stacks/roms/", + "steps": [ + "Container wrapping existing scripts", + "Weekly dry-run report → ntfy", + ], + "done": ["Scheduled dry-run runs — or drop"], + }, + { + "id": "TB-015", + "slug": "vault-secrets-integration", + "title": "Use Vault for real", + "sentence": "Vault runs but secrets live in stack.env — integrate Vault for CI/rotation without changing git-canonical deploy.", + "why": "Optional complexity; only worth it if you want rotation or CI secret centralization.", + "questions": [ + "What problem would Vault solve that stack.env doesn't?", + "If 'nothing really' — drop this.", + ], + "stack": "portainer-compose.yml, scripts", + "steps": [ + "Define Vault vs stack.env boundary", + "Migrate one token (e.g. github-runner)", + ], + "done": ["Clear use case — or dropped"], + }, + { + "id": "TB-017", + "slug": "minio-consolidation", + "title": "One MinIO instead of three", + "sentence": "resume, famapp, and Plane each run their own MinIO — could be one server, three buckets.", + "why": "Less ops; slightly higher blast radius if MinIO dies.", + "questions": [ + "Do the three MinIO instances cause you pain today?", + "If no — drop; isolation is fine.", + ], + "stack": "resume, famapp, dev", + "steps": ["Decision", "If yes: migrate buckets, update env vars"], + "done": ["Consolidated or explicitly kept separate"], + }, + { + "id": "TB-018", + "slug": "plane-evaluation", + "title": "Is Plane worth it?", + "sentence": "Plane is 7 containers for project management — do you use it?", + "why": "Gitea has issues; Homarr is your dashboard; Plane may be shelfware.", + "questions": [ + "When did you last open plane.ginnoir.com?", + "Gitea issues enough, or need a kanban?", + ], + "stack": "stacks/dev/", + "steps": ["Your usage answer", "Keep, or remove and reclaim resources"], + "done": ["Decision recorded"], + }, + { + "id": "TB-027", + "slug": "jellyfin", + "title": "Jellyfin instead of Plex", + "sentence": "Self-host streaming in Docker instead of host-native Plex.", + "why": "Only if you want to move off Plex licensing / centralize in compose.", + "questions": [ + "Happy with Plex today?", + "Family/clients okay switching apps?", + "Hardware transcoding needed?", + ], + "stack": "media or new streaming", + "steps": ["Decision", "If yes: jellyfin + GPU passthrough eval"], + "done": ["Stay on Plex or migrate plan documented"], + }, + { + "id": "TB-028", + "slug": "home-assistant", + "title": "Home Assistant", + "sentence": "Home automation hub — only makes sense if you have smart devices to control.", + "why": "Zero home automation in the stack today.", + "questions": [ + "How many smart devices do you have?", + "Zigbee/Z-Wave USB stick on valhalla?", + "If <5 devices — probably skip.", + ], + "stack": "New home stack", + "steps": ["Go/no-go", "If go: HA + USB passthrough"], + "done": ["Go/no-go with reason"], + }, + { + "id": "TB-029", + "slug": "firefly-iii", + "title": "Firefly III (finance)", + "sentence": "Self-hosted budgeting and transaction tracking.", + "why": "Niche personal finance; sensitive data.", + "questions": [ + "Do you want to self-host finances, or use a SaaS/bank app?", + "If yes — internal-only + Authentik mandatory.", + ], + "stack": "New finance stack", + "steps": ["New stack", "internal-only Caddy"], + "done": ["Using it — or dropped"], + }, + { + "id": "TB-030", + "slug": "adguard-home", + "title": "AdGuard Home", + "sentence": "Network-wide ad blocking and local DNS for *.ginnoir.com.", + "why": "Stops LAN hairpin through Cloudflare; blocks ads if router DNS points here.", + "questions": [ + "Does your router already do ad blocking / local DNS?", + "Willing to point DHCP DNS at valhalla?", + ], + "stack": "New network stack or mgmt", + "steps": ["Check router first", "If gap: AdGuard + DNS rewrites"], + "done": ["Router sufficient — or AdGuard deployed"], + }, + { + "id": "TB-031", + "slug": "pingvin-share", + "title": "Pingvin Share", + "sentence": "Temporary password-protected file links — lighter than ownCloud shares.", + "why": "One-off 'here's a file' drops; ownCloud is heavier.", + "questions": [ + "Do you send ad-hoc files to people often?", + "Or rare enough to skip?", + ], + "stack": "stacks/share/", + "steps": ["Add to share stack", "drop.ginnoir.com or similar"], + "done": ["Test share link — or drop"], + }, + { + "id": "TB-032", + "slug": "wikijs", + "title": "Wiki.js", + "sentence": "Modern wiki — only relevant if TB-009 says 'keep a structured wiki' but not BookStack.", + "why": "Depends on TB-009 decision; don't start here first.", + "questions": [ + "Did TB-009 land on 'keep wiki'?", + "If BookStack retired or Obsidian-only — skip this entirely.", + ], + "stack": "stacks/dev/", + "steps": ["Only if TB-009 = Option C", "Import + replace BookStack"], + "done": ["TB-009 dependency satisfied — or N/A"], + }, +] + + +def render(task: dict) -> str: + qs = "\n".join(f"{i}. {q}" for i, q in enumerate(task["questions"], 1)) + steps = "\n".join(f"{i}. {s}" for i, s in enumerate(task["steps"], 1)) + done = "\n".join(f"- [ ] {d}" for d in task["done"]) + return f"""# {task["id"]} — {task["title"]} + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do {task["id"]}"** or **"{task["title"].lower()}"**. + +**In one sentence:** {task["sentence"]} + +**Why it came up:** {task["why"]} + +### Questions + +{qs} + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +{task["stack"]} + +### If we do it — rough steps + +{steps} + +### Done when + +{done} + +
+""" + + +def main() -> None: + out_dir = Path(__file__).parent / "tasks" + for task in TASKS: + path = out_dir / f"{task['id']}-{task['slug']}.md" + path.write_text(render(task), encoding="utf-8", newline="\n") + print(f"wrote {path.name}") + + +if __name__ == "__main__": + main() diff --git a/plans/homelab-improvements/tasks/README.md b/plans/homelab-improvements/tasks/README.md new file mode 100644 index 0000000..ce93462 --- /dev/null +++ b/plans/homelab-improvements/tasks/README.md @@ -0,0 +1,69 @@ +# Task menu + +**Don't read a table and pick.** Say something in chat — e.g. *"backups"*, *"TB-005"*, *"what's public?"* — and we'll talk through that one item. + +Parent: [PLAN-BRIEF.md](../PLAN-BRIEF.md) · Template: [_TEMPLATE.md](_TEMPLATE.md) + +--- + +## How this works + +1. You name a topic (or ask "what should I care about?") +2. We go through that task's **Talk first** questions together +3. You pick: **Do it / Later / Drop / Tweak** +4. I fill in **What we decided** in the brief +5. Reference section only matters if we're implementing + +--- + +## Quick pick by vibe + +| If you're thinking… | Open | +|---------------------|------| +| "I could lose everything" | [TB-001](TB-001-backup-stack.md) | +| "What's exposed?" | [TB-033](TB-033-public-surface-audit.md) | +| "Obsidian / vault security" | [TB-005](TB-005-obsidian-hardening.md) | +| "ROM hacks pipeline" | [TB-007](TB-007-romhacks-handoff.md) | +| "Family / photos / recipes" | [TB-019](TB-019-immich.md) · [TB-020](TB-020-mealie.md) | +| "Media stack tuning" | [TB-003](TB-003-recyclarr.md) · [TB-008](TB-008-retire-deluge.md) | +| "Cleanup / cruft" | [TB-009](TB-009-bookstack-decision.md) · [TB-011](TB-011-reap-orphan-volumes.md) · [TB-018](TB-018-plane-evaluation.md) | + +--- + +## All tasks + +| ID | One line | Brief | +|----|----------|-------| +| TB-001 | Automated backups | [→](TB-001-backup-stack.md) | +| TB-002 | jd.ginnoir.com | [→](TB-002-jd-caddy-block.md) | +| TB-003 | Recyclarr | [→](TB-003-recyclarr.md) | +| TB-004 | Pin infra tags | [→](TB-004-pin-infra-tags.md) | +| TB-005 | Obsidian hardening | [→](TB-005-obsidian-hardening.md) | +| TB-006 | Authentik admin login | [→](TB-006-authentik-forward-auth.md) | +| TB-007 | Romhacks handoff | [→](TB-007-romhacks-handoff.md) | +| TB-008 | Retire Deluge | [→](TB-008-retire-deluge.md) | +| TB-009 | BookStack decision | [→](TB-009-bookstack-decision.md) | +| TB-010 | dev stack.env | [→](TB-010-dev-stack-env-migration.md) | +| TB-011 | Orphan volumes | [→](TB-011-reap-orphan-volumes.md) | +| TB-012 | Maintainerr | [→](TB-012-maintainerr.md) | +| TB-013 | code-server mount | [→](TB-013-code-server-mount.md) | +| TB-014 | Registry auth | [→](TB-014-registry-auth.md) | +| TB-015 | Vault integration | [→](TB-015-vault-secrets-integration.md) | +| TB-016 | Document Plex | [→](TB-016-document-plex.md) | +| TB-017 | MinIO consolidation | [→](TB-017-minio-consolidation.md) | +| TB-018 | Plane evaluation | [→](TB-018-plane-evaluation.md) | +| TB-019 | Immich | [→](TB-019-immich.md) | +| TB-020 | Mealie | [→](TB-020-mealie.md) | +| TB-021 | Paperless-ngx | [→](TB-021-paperless-ngx.md) | +| TB-022 | Readarr | [→](TB-022-readarr.md) | +| TB-023 | Autobrr | [→](TB-023-autobrr.md) | +| TB-024 | Syncthing | [→](TB-024-syncthing.md) | +| TB-025 | Metrics | [→](TB-025-metrics-beszel.md) | +| TB-026 | IGIR container | [→](TB-026-igir-container.md) | +| TB-027 | Jellyfin | [→](TB-027-jellyfin.md) | +| TB-028 | Home Assistant | [→](TB-028-home-assistant.md) | +| TB-029 | Firefly III | [→](TB-029-firefly-iii.md) | +| TB-030 | AdGuard Home | [→](TB-030-adguard-home.md) | +| TB-031 | Pingvin Share | [→](TB-031-pingvin-share.md) | +| TB-032 | Wiki.js | [→](TB-032-wikijs.md) | +| TB-033 | Public audit | [→](TB-033-public-surface-audit.md) | diff --git a/plans/homelab-improvements/tasks/TB-001-backup-stack.md b/plans/homelab-improvements/tasks/TB-001-backup-stack.md new file mode 100644 index 0000000..7cd23db --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-001-backup-stack.md @@ -0,0 +1,62 @@ +# TB-001 — Backup stack + +**Status:** in progress — stack drafted, needs B2 + Portainer deploy +**Your call:** **Do it** + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-001"** or **"backup stack"**. + +**In one sentence:** Set up automatic nightly backups of configs and databases, with a copy off the server. + +**Why it came up:** Right now nothing backs up /config or your databases — ZFS snapshots don't help if the whole machine dies. + +### Questions + +1. Does losing valhalla overnight scare you, or is 'I'll deal with it' okay for now? +2. Do you already have offsite storage (Backblaze, Wasabi, another machine)? +3. Kopia (has a UI) vs Restic (simpler CLI) — want a recommendation or don't care? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | Do it | +| **Notes** | Kopia + B2 TBD. `/config` + dumps (RomM DB). labdata: gitea, minio, romhacks, romm/assets (saves). **Skip:** entire `/storage1/Emulation` (legacy), romm/resources (art), registry, incoming. See `SCOPE.md`. | +| **Date** | 2026-06-10 | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +`backup` new stack + +### If we do it — rough steps + +1. Pick Kopia or Restic +2. New stacks/backup/ with nightly schedule + ntfy on failure +3. Script dumps for all Postgres/MariaDB/CouchDB +4. One restore test so we know it works + +### Done when + +- [ ] Nightly backup runs without you +- [ ] At least one restore tested +- [ ] Failures ping ntfy + +
diff --git a/plans/homelab-improvements/tasks/TB-002-jd-caddy-block.md b/plans/homelab-improvements/tasks/TB-002-jd-caddy-block.md new file mode 100644 index 0000000..9e0c5e6 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-002-jd-caddy-block.md @@ -0,0 +1,58 @@ +# TB-002 — jd.ginnoir.com + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-002"** or **"jd.ginnoir.com"**. + +**In one sentence:** Add a LAN-only web address for JDownloader's UI. + +**Why it came up:** It's documented but never added to the Caddyfile — you use port 8998 instead. + +### Questions + +1. Are you actually using the romhacks/JDownloader pipeline, or is this premature? +2. Is LAN-only enough, or do you need it over Tailscale too (already covered by internal_only)? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +Caddyfile + +### If we do it — rough steps + +1. Add jd.ginnoir.com block with internal_only +2. gen-bookmarks.ps1 + push + +### Done when + +- [ ] jd.ginnoir.com works on LAN +- [ ] 403 from outside + +
diff --git a/plans/homelab-improvements/tasks/TB-003-recyclarr.md b/plans/homelab-improvements/tasks/TB-003-recyclarr.md new file mode 100644 index 0000000..29fef02 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-003-recyclarr.md @@ -0,0 +1,59 @@ +# TB-003 — Recyclarr + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-003"** or **"recyclarr"**. + +**In one sentence:** One container that keeps Sonarr/Radarr/Bazarr quality settings in sync with TRaSH guides. + +**Why it came up:** Your *arr configs probably drift over time; this automates the boring sync. + +### Questions + +1. Have you hand-tuned quality profiles you'd hate to have overwritten? +2. Want this, or are your *arr settings 'set and forget' already? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/media/ + +### If we do it — rough steps + +1. Add recyclarr container on media network +2. Point at sonarr/radarr/bazarr/prowlarr APIs +3. Run first sync, spot-check Sonarr + +### Done when + +- [ ] Recyclarr syncs without errors +- [ ] Profiles visible in Sonarr or Radarr + +
diff --git a/plans/homelab-improvements/tasks/TB-004-pin-infra-tags.md b/plans/homelab-improvements/tasks/TB-004-pin-infra-tags.md new file mode 100644 index 0000000..142a189 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-004-pin-infra-tags.md @@ -0,0 +1,59 @@ +# TB-004 — Pin infra image tags + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-004"** or **"pin infra image tags"**. + +**In one sentence:** Stop Postgres, MariaDB, Caddy, etc. from silently updating to a broken :latest. + +**Why it came up:** Watchtower auto-updates containers; one bad DB image pull is painful. + +### Questions + +1. Have you been burned by a surprise update before? +2. Okay to manually bump pinned versions every few months? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +All compose files + +### If we do it — rough steps + +1. Audit :latest on infra images +2. Pin versions + watchtower.enable=false on those +3. Document policy in CLAUDE.md + +### Done when + +- [ ] No :latest on DB/Caddy/Vault/CouchDB/MinIO +- [ ] Stacks still deploy clean + +
diff --git a/plans/homelab-improvements/tasks/TB-005-obsidian-hardening.md b/plans/homelab-improvements/tasks/TB-005-obsidian-hardening.md new file mode 100644 index 0000000..be69a42 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-005-obsidian-hardening.md @@ -0,0 +1,60 @@ +# TB-005 — Obsidian / CouchDB hardening + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-005"** or **"obsidian / couchdb hardening"**. + +**In one sentence:** Your whole PKM vault syncs over a public URL — let's decide how exposed you're okay with. + +**Why it came up:** obsidian.ginnoir.com has no LAN-only or SSO gate; LiveSync needs remote access from phones. + +### Questions + +1. Do you sync Obsidian from your phone without Tailscale always on? +2. Would 'Tailscale only' work, or do you need public access? +3. Authentik login in front — okay if we test LiveSync still works? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +Caddyfile, stacks/notes/ + +### If we do it — rough steps + +1. Pick model: Tailscale-only, Authentik, or hybrid +2. Implement + test sync from each device +3. Rotate CouchDB password + +### Done when + +- [ ] You can still sync where you need to +- [ ] Admin/_utils not wide open + +
diff --git a/plans/homelab-improvements/tasks/TB-006-authentik-forward-auth.md b/plans/homelab-improvements/tasks/TB-006-authentik-forward-auth.md new file mode 100644 index 0000000..dd57e08 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-006-authentik-forward-auth.md @@ -0,0 +1,60 @@ +# TB-006 — Authentik on admin UIs + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-006"** or **"authentik on admin uis"**. + +**In one sentence:** Require Authentik login before Portainer, code-server, registry, etc. + +**Why it came up:** LAN-only IP checks aren't the same as identity — Authentik is already running for famapp. + +### Questions + +1. Which admin sites do you actually use? (No point gating ones you never open) +2. Portainer git webhooks must keep working — okay to test carefully? +3. Do this for everything internal, or just the scary ones (Portainer, code, vault)? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +Caddyfile + Authentik UI + +### If we do it — rough steps + +1. Authentik proxy provider + outpost +2. Caddy forward_auth snippet +3. Tier 1: portainer, code, registry-ui, vault, minio console + +### Done when + +- [ ] Tier 1 sites require login +- [ ] Portainer webhooks still work + +
diff --git a/plans/homelab-improvements/tasks/TB-007-romhacks-handoff.md b/plans/homelab-improvements/tasks/TB-007-romhacks-handoff.md new file mode 100644 index 0000000..acc1a8b --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-007-romhacks-handoff.md @@ -0,0 +1,60 @@ +# TB-007 — Romhacks → RomM handoff + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-007"** or **"romhacks → romm handoff"**. + +**In one sentence:** Finish the pipeline so downloaded ROM hacks land in RomM with cover art. + +**Why it came up:** Orchestrator + JDownloader exist; moving files into the library isn't built yet. + +### Questions + +1. Is the Discord/JDownloader pipeline armed, or still theoretical? +2. Worth finishing before arming, or drop the whole romhacks stack? +3. How much automation vs 'I'll move files manually'? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/romhacks/orchestrator/ + +### If we do it — rough steps + +1. Complete handoff.py — incoming → Emulation/roms/ +2. RomM custom cover API +3. Test with one staged folder + +### Done when + +- [ ] Test ROM in RomM after handoff +- [ ] DRY_RUN still safe + +
diff --git a/plans/homelab-improvements/tasks/TB-008-retire-deluge.md b/plans/homelab-improvements/tasks/TB-008-retire-deluge.md new file mode 100644 index 0000000..0a49e03 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-008-retire-deluge.md @@ -0,0 +1,58 @@ +# TB-008 — Retire Deluge + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-008"** or **"retire deluge"**. + +**In one sentence:** Remove Deluge if qBittorrent is doing all the torrent work. + +**Why it came up:** You run Deluge, qBit, and NZBGet — two torrent clients may be leftover cruft. + +### Questions + +1. When did you last use Deluge? +2. Are Sonarr/Radarr pointed at qBit only? (We can check together) + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/media/, Caddyfile + +### If we do it — rough steps + +1. Confirm zero Deluge torrents + no *arr dependency +2. Remove service + Caddy block + +### Done when + +- [ ] Deluge gone +- [ ] qBit/NZBGet still fine + +
diff --git a/plans/homelab-improvements/tasks/TB-009-bookstack-decision.md b/plans/homelab-improvements/tasks/TB-009-bookstack-decision.md new file mode 100644 index 0000000..01fdab5 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-009-bookstack-decision.md @@ -0,0 +1,59 @@ +# TB-009 — BookStack vs Obsidian + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-009"** or **"bookstack vs obsidian"**. + +**In one sentence:** Decide if docs.ginnoir.com (BookStack) stays or goes — Obsidian is already your main PKM. + +**Why it came up:** Two wiki systems = drift and an extra MariaDB to feed. + +### Questions + +1. Do you use BookStack at all? When did you last open it? +2. If we export and remove it, anything lost? +3. Or keep it for a specific use case — what? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/dev/ + +### If we do it — rough steps + +1. Your call: retire, keep with boundary, or migrate to Wiki.js (TB-032) +2. If retire: export → remove containers + +### Done when + +- [ ] Decision recorded +- [ ] If retired: BookStack containers gone + +
diff --git a/plans/homelab-improvements/tasks/TB-010-dev-stack-env-migration.md b/plans/homelab-improvements/tasks/TB-010-dev-stack-env-migration.md new file mode 100644 index 0000000..525ecc6 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-010-dev-stack-env-migration.md @@ -0,0 +1,58 @@ +# TB-010 — dev stack.env migration + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-010"** or **"dev stack.env migration"**. + +**In one sentence:** Make the dev stack use stack.env like everything else — no Portainer UI env vars. + +**Why it came up:** Every other stack commits secrets to stack.env; dev still needs UI mirroring (footgun). + +### Questions + +1. Have you been bitten by dev env drift before? +2. Worth an afternoon, or low priority housekeeping? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/dev/ + +### If we do it — rough steps + +1. Move all ${VAR} into stack.env +2. Deploy, verify Gitea/Plane/BookStack/code-server +3. Clear Portainer UI env vars + +### Done when + +- [ ] Dev stack runs with env_file only + +
diff --git a/plans/homelab-improvements/tasks/TB-011-reap-orphan-volumes.md b/plans/homelab-improvements/tasks/TB-011-reap-orphan-volumes.md new file mode 100644 index 0000000..5359e77 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-011-reap-orphan-volumes.md @@ -0,0 +1,59 @@ +# TB-011 — Reap orphan volumes + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-011"** or **"reap orphan volumes"**. + +**In one sentence:** Delete old pre-split Docker named volumes that bind mounts replaced. + +**Why it came up:** htpc-download-box_* and valhalla-lab_* volumes waste disk and confuse what's canonical. + +### Questions + +1. Want me to SSH and list them with you before deleting anything? +2. Backups done first? (TB-001) + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +SSH on valhalla only + +### If we do it — rough steps + +1. docker volume ls — confirm orphans +2. Spot-check bind mounts healthy +3. docker volume rm + +### Done when + +- [ ] Orphan volumes gone +- [ ] All containers still healthy + +
diff --git a/plans/homelab-improvements/tasks/TB-012-maintainerr.md b/plans/homelab-improvements/tasks/TB-012-maintainerr.md new file mode 100644 index 0000000..4e0e070 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-012-maintainerr.md @@ -0,0 +1,59 @@ +# TB-012 — Maintainerr + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-012"** or **"maintainerr"**. + +**In one sentence:** Auto-clean stale Overseerr requests so the queue doesn't fill with junk. + +**Why it came up:** requests.ginnoir.com is public; old unfulfilled requests pile up. + +### Questions + +1. Is your Overseerr queue messy now, or is this preventive? +2. How aggressive on age — 30 days? 90? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/media/ + +### If we do it — rough steps + +1. Add maintainerr on media network +2. Connect Overseerr + Sonarr/Radarr APIs +3. Conservative cleanup rule first + +### Done when + +- [ ] Maintainerr connected +- [ ] One rule configured + +
diff --git a/plans/homelab-improvements/tasks/TB-013-code-server-mount.md b/plans/homelab-improvements/tasks/TB-013-code-server-mount.md new file mode 100644 index 0000000..97dd848 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-013-code-server-mount.md @@ -0,0 +1,57 @@ +# TB-013 — Narrow code-server mount + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-013"** or **"narrow code-server mount"**. + +**In one sentence:** Stop code-server from mounting your entire home directory. + +**Why it came up:** code.ginnoir.com currently exposes /home/ginnoir — SSH keys, everything. + +### Questions + +1. What folders do you actually edit in code-server? +2. homelabstack + a projects dir enough? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/dev/docker-compose.yml + +### If we do it — rough steps + +1. Change mount to scoped path +2. Symlink homelabstack if needed + +### Done when + +- [ ] .ssh and unrelated home dirs not visible in IDE + +
diff --git a/plans/homelab-improvements/tasks/TB-014-registry-auth.md b/plans/homelab-improvements/tasks/TB-014-registry-auth.md new file mode 100644 index 0000000..599599c --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-014-registry-auth.md @@ -0,0 +1,58 @@ +# TB-014 — Registry authentication + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-014"** or **"registry authentication"**. + +**In one sentence:** Put a password on your private Docker registry. + +**Why it came up:** registry.ginnoir.com is LAN-only but anyone on LAN/tailnet can push/pull. + +### Questions + +1. Do you push images to it regularly, or is it barely used? +2. Will famapp CI or github-runner need login updated? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/dev/ + +### If we do it — rough steps + +1. htpasswd + registry auth env +2. Test docker login/pull/push + +### Done when + +- [ ] Unauthenticated pull fails +- [ ] You can pull with creds + +
diff --git a/plans/homelab-improvements/tasks/TB-015-vault-secrets-integration.md b/plans/homelab-improvements/tasks/TB-015-vault-secrets-integration.md new file mode 100644 index 0000000..5c87f14 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-015-vault-secrets-integration.md @@ -0,0 +1,57 @@ +# TB-015 — Use Vault for real + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-015"** or **"use vault for real"**. + +**In one sentence:** Vault runs but secrets live in stack.env — integrate Vault for CI/rotation without changing git-canonical deploy. + +**Why it came up:** Optional complexity; only worth it if you want rotation or CI secret centralization. + +### Questions + +1. What problem would Vault solve that stack.env doesn't? +2. If 'nothing really' — drop this. + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +portainer-compose.yml, scripts + +### If we do it — rough steps + +1. Define Vault vs stack.env boundary +2. Migrate one token (e.g. github-runner) + +### Done when + +- [ ] Clear use case — or dropped + +
diff --git a/plans/homelab-improvements/tasks/TB-016-document-plex.md b/plans/homelab-improvements/tasks/TB-016-document-plex.md new file mode 100644 index 0000000..c6fcba9 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-016-document-plex.md @@ -0,0 +1,57 @@ +# TB-016 — Document Plex + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-016"** or **"document plex"**. + +**In one sentence:** Write down where Plex lives and how it connects to Tautulli — it's not in this repo. + +**Why it came up:** Tautulli reads /storage1/PMS logs but Plex isn't in compose; easy to forget how it's managed. + +### Questions + +1. Is Plex on valhalla itself, or another box? +2. Do you want it in compose eventually, or happy as host-native? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +Docs only + +### If we do it — rough steps + +1. You tell me: install path, systemd?, library paths +2. Add short section to CLAUDE.md or EXTERNAL-SERVICES.md + +### Done when + +- [ ] Plex documented with paths + +
diff --git a/plans/homelab-improvements/tasks/TB-017-minio-consolidation.md b/plans/homelab-improvements/tasks/TB-017-minio-consolidation.md new file mode 100644 index 0000000..f45f8ee --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-017-minio-consolidation.md @@ -0,0 +1,57 @@ +# TB-017 — One MinIO instead of three + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-017"** or **"one minio instead of three"**. + +**In one sentence:** resume, famapp, and Plane each run their own MinIO — could be one server, three buckets. + +**Why it came up:** Less ops; slightly higher blast radius if MinIO dies. + +### Questions + +1. Do the three MinIO instances cause you pain today? +2. If no — drop; isolation is fine. + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +resume, famapp, dev + +### If we do it — rough steps + +1. Decision +2. If yes: migrate buckets, update env vars + +### Done when + +- [ ] Consolidated or explicitly kept separate + +
diff --git a/plans/homelab-improvements/tasks/TB-018-plane-evaluation.md b/plans/homelab-improvements/tasks/TB-018-plane-evaluation.md new file mode 100644 index 0000000..7210fca --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-018-plane-evaluation.md @@ -0,0 +1,57 @@ +# TB-018 — Is Plane worth it? + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-018"** or **"is plane worth it?"**. + +**In one sentence:** Plane is 7 containers for project management — do you use it? + +**Why it came up:** Gitea has issues; Homarr is your dashboard; Plane may be shelfware. + +### Questions + +1. When did you last open plane.ginnoir.com? +2. Gitea issues enough, or need a kanban? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/dev/ + +### If we do it — rough steps + +1. Your usage answer +2. Keep, or remove and reclaim resources + +### Done when + +- [ ] Decision recorded + +
diff --git a/plans/homelab-improvements/tasks/TB-019-immich.md b/plans/homelab-improvements/tasks/TB-019-immich.md new file mode 100644 index 0000000..aef4994 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-019-immich.md @@ -0,0 +1,60 @@ +# TB-019 — Immich (photos) + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-019"** or **"immich (photos)"**. + +**In one sentence:** Self-hosted Google Photos — family photo backup with a phone app. + +**Why it came up:** Natural fit next to famapp; you don't have photo backup today. + +### Questions + +1. Is phone photo backup something you actively want? +2. Public with Authentik, or LAN/tailnet only? +3. Storage growth on ZFS — okay? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +New photos stack + +### If we do it — rough steps + +1. New stacks/photos/ +2. photos.ginnoir.com + optional Authentik +3. Test upload from phone + +### Done when + +- [ ] Mobile upload works +- [ ] In backup scope + +
diff --git a/plans/homelab-improvements/tasks/TB-020-mealie.md b/plans/homelab-improvements/tasks/TB-020-mealie.md new file mode 100644 index 0000000..f65fb34 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-020-mealie.md @@ -0,0 +1,58 @@ +# TB-020 — Mealie (recipes) + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-020"** or **"mealie (recipes)"**. + +**In one sentence:** Recipe manager with meal plans and shopping lists. + +**Why it came up:** Pairs with famapp/family theme; lightweight stack. + +### Questions + +1. Would you actually use this, or is it 'nice in theory'? +2. Shared with family or just you? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +New kitchen stack or famapp + +### If we do it — rough steps + +1. New stack +2. recipes.ginnoir.com +3. Import one recipe as test + +### Done when + +- [ ] You'd use it — or we drop it + +
diff --git a/plans/homelab-improvements/tasks/TB-021-paperless-ngx.md b/plans/homelab-improvements/tasks/TB-021-paperless-ngx.md new file mode 100644 index 0000000..fd2759b --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-021-paperless-ngx.md @@ -0,0 +1,58 @@ +# TB-021 — Paperless-ngx + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-021"** or **"paperless-ngx"**. + +**In one sentence:** Drop PDFs in a folder → OCR → searchable archive. + +**Why it came up:** ownCloud stores files; this adds document search and tagging. + +### Questions + +1. Do you scan receipts/documents often enough to matter? +2. Or is ownCloud + Obsidian enough? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +New docs stack + +### If we do it — rough steps + +1. New stack +2. consume folder on ZFS +3. Test one PDF OCR + +### Done when + +- [ ] Search finds text in test doc — or we skip + +
diff --git a/plans/homelab-improvements/tasks/TB-022-readarr.md b/plans/homelab-improvements/tasks/TB-022-readarr.md new file mode 100644 index 0000000..d045398 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-022-readarr.md @@ -0,0 +1,57 @@ +# TB-022 — Readarr (ebooks) + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-022"** or **"readarr (ebooks)"**. + +**In one sentence:** Automate ebook downloads like Sonarr does for TV. + +**Why it came up:** You have /storage1/Books in ownCloud; no *arr for books yet. + +### Questions + +1. Do you acquire ebooks automatically, or buy/download manually? +2. Audiobooks too? (Different tool — Audiobookshelf) + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/media/ + +### If we do it — rough steps + +1. Add readarr + Caddy internal block +2. Hook Prowlarr + download client + +### Done when + +- [ ] Test grab lands in Books — or drop + +
diff --git a/plans/homelab-improvements/tasks/TB-023-autobrr.md b/plans/homelab-improvements/tasks/TB-023-autobrr.md new file mode 100644 index 0000000..63cdc59 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-023-autobrr.md @@ -0,0 +1,58 @@ +# TB-023 — Autobrr + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-023"** or **"autobrr"**. + +**In one sentence:** Monitor private tracker IRC/RSS and auto-push to qBittorrent. + +**Why it came up:** For active private tracker use — overkill if you're mostly public indexers via Prowlarr. + +### Questions + +1. Are you on private trackers that need announce monitoring? +2. Or skip — Prowlarr enough? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/media/ + +### If we do it — rough steps + +1. Add autobrr +2. Connect qBit +3. One filter if proceeding + +### Done when + +- [ ] Worth it for your tracker habits — or dropped + +
diff --git a/plans/homelab-improvements/tasks/TB-024-syncthing.md b/plans/homelab-improvements/tasks/TB-024-syncthing.md new file mode 100644 index 0000000..3241abe --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-024-syncthing.md @@ -0,0 +1,58 @@ +# TB-024 — Syncthing + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-024"** or **"syncthing"**. + +**In one sentence:** Continuous folder sync between devices — ROM saves, dotfiles, etc. + +**Why it came up:** Different from ownCloud's model; good for device-to-device without 'upload to server'. + +### Questions + +1. What folders would you sync? (Be specific — not all of /storage1) +2. Conflict risk with RomM/ownCloud on same paths? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +New sync stack + +### If we do it — rough steps + +1. New stack +2. Pair two devices +3. One test folder + +### Done when + +- [ ] Sync works on chosen folder — or drop + +
diff --git a/plans/homelab-improvements/tasks/TB-025-metrics-beszel.md b/plans/homelab-improvements/tasks/TB-025-metrics-beszel.md new file mode 100644 index 0000000..1714e63 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-025-metrics-beszel.md @@ -0,0 +1,57 @@ +# TB-025 — Metrics (Beszel/Netdata) + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-025"** or **"metrics (beszel/netdata)"**. + +**In one sentence:** See CPU/RAM/disk graphs — Uptime Kuma only tells you up/down. + +**Why it came up:** Homelab has no metrics dashboard today. + +### Questions + +1. Do you ever wonder 'why is the server slow' without an answer? +2. Beszel (minimal) vs Netdata (detailed) — or skip? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/monitoring/ + +### If we do it — rough steps + +1. Add chosen tool +2. metrics.ginnoir.com internal + +### Done when + +- [ ] Dashboard shows host + container usage + +
diff --git a/plans/homelab-improvements/tasks/TB-026-igir-container.md b/plans/homelab-improvements/tasks/TB-026-igir-container.md new file mode 100644 index 0000000..f8d807c --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-026-igir-container.md @@ -0,0 +1,57 @@ +# TB-026 — IGIR container + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-026"** or **"igir container"**. + +**In one sentence:** Schedule your existing ROM cleanup scripts instead of SSH ad-hoc. + +**Why it came up:** igir-*.sh scripts exist; romhacks handoff will add more ROMs to maintain. + +### Questions + +1. Do you run IGIR often enough to automate? +2. Or manual is fine? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/roms/ + +### If we do it — rough steps + +1. Container wrapping existing scripts +2. Weekly dry-run report → ntfy + +### Done when + +- [ ] Scheduled dry-run runs — or drop + +
diff --git a/plans/homelab-improvements/tasks/TB-027-jellyfin.md b/plans/homelab-improvements/tasks/TB-027-jellyfin.md new file mode 100644 index 0000000..2cc4ba3 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-027-jellyfin.md @@ -0,0 +1,58 @@ +# TB-027 — Jellyfin instead of Plex + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-027"** or **"jellyfin instead of plex"**. + +**In one sentence:** Self-host streaming in Docker instead of host-native Plex. + +**Why it came up:** Only if you want to move off Plex licensing / centralize in compose. + +### Questions + +1. Happy with Plex today? +2. Family/clients okay switching apps? +3. Hardware transcoding needed? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +media or new streaming + +### If we do it — rough steps + +1. Decision +2. If yes: jellyfin + GPU passthrough eval + +### Done when + +- [ ] Stay on Plex or migrate plan documented + +
diff --git a/plans/homelab-improvements/tasks/TB-028-home-assistant.md b/plans/homelab-improvements/tasks/TB-028-home-assistant.md new file mode 100644 index 0000000..8a65bc3 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-028-home-assistant.md @@ -0,0 +1,58 @@ +# TB-028 — Home Assistant + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-028"** or **"home assistant"**. + +**In one sentence:** Home automation hub — only makes sense if you have smart devices to control. + +**Why it came up:** Zero home automation in the stack today. + +### Questions + +1. How many smart devices do you have? +2. Zigbee/Z-Wave USB stick on valhalla? +3. If <5 devices — probably skip. + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +New home stack + +### If we do it — rough steps + +1. Go/no-go +2. If go: HA + USB passthrough + +### Done when + +- [ ] Go/no-go with reason + +
diff --git a/plans/homelab-improvements/tasks/TB-029-firefly-iii.md b/plans/homelab-improvements/tasks/TB-029-firefly-iii.md new file mode 100644 index 0000000..b1d14db --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-029-firefly-iii.md @@ -0,0 +1,57 @@ +# TB-029 — Firefly III (finance) + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-029"** or **"firefly iii (finance)"**. + +**In one sentence:** Self-hosted budgeting and transaction tracking. + +**Why it came up:** Niche personal finance; sensitive data. + +### Questions + +1. Do you want to self-host finances, or use a SaaS/bank app? +2. If yes — internal-only + Authentik mandatory. + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +New finance stack + +### If we do it — rough steps + +1. New stack +2. internal-only Caddy + +### Done when + +- [ ] Using it — or dropped + +
diff --git a/plans/homelab-improvements/tasks/TB-030-adguard-home.md b/plans/homelab-improvements/tasks/TB-030-adguard-home.md new file mode 100644 index 0000000..8aa8c98 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-030-adguard-home.md @@ -0,0 +1,57 @@ +# TB-030 — AdGuard Home + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-030"** or **"adguard home"**. + +**In one sentence:** Network-wide ad blocking and local DNS for *.ginnoir.com. + +**Why it came up:** Stops LAN hairpin through Cloudflare; blocks ads if router DNS points here. + +### Questions + +1. Does your router already do ad blocking / local DNS? +2. Willing to point DHCP DNS at valhalla? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +New network stack or mgmt + +### If we do it — rough steps + +1. Check router first +2. If gap: AdGuard + DNS rewrites + +### Done when + +- [ ] Router sufficient — or AdGuard deployed + +
diff --git a/plans/homelab-improvements/tasks/TB-031-pingvin-share.md b/plans/homelab-improvements/tasks/TB-031-pingvin-share.md new file mode 100644 index 0000000..f722dce --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-031-pingvin-share.md @@ -0,0 +1,57 @@ +# TB-031 — Pingvin Share + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-031"** or **"pingvin share"**. + +**In one sentence:** Temporary password-protected file links — lighter than ownCloud shares. + +**Why it came up:** One-off 'here's a file' drops; ownCloud is heavier. + +### Questions + +1. Do you send ad-hoc files to people often? +2. Or rare enough to skip? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/share/ + +### If we do it — rough steps + +1. Add to share stack +2. drop.ginnoir.com or similar + +### Done when + +- [ ] Test share link — or drop + +
diff --git a/plans/homelab-improvements/tasks/TB-032-wikijs.md b/plans/homelab-improvements/tasks/TB-032-wikijs.md new file mode 100644 index 0000000..7c4bf6f --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-032-wikijs.md @@ -0,0 +1,57 @@ +# TB-032 — Wiki.js + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-032"** or **"wiki.js"**. + +**In one sentence:** Modern wiki — only relevant if TB-009 says 'keep a structured wiki' but not BookStack. + +**Why it came up:** Depends on TB-009 decision; don't start here first. + +### Questions + +1. Did TB-009 land on 'keep wiki'? +2. If BookStack retired or Obsidian-only — skip this entirely. + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +stacks/dev/ + +### If we do it — rough steps + +1. Only if TB-009 = Option C +2. Import + replace BookStack + +### Done when + +- [ ] TB-009 dependency satisfied — or N/A + +
diff --git a/plans/homelab-improvements/tasks/TB-033-public-surface-audit.md b/plans/homelab-improvements/tasks/TB-033-public-surface-audit.md new file mode 100644 index 0000000..1227b62 --- /dev/null +++ b/plans/homelab-improvements/tasks/TB-033-public-surface-audit.md @@ -0,0 +1,59 @@ +# TB-033 — Public surface audit + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-033"** or **"public surface audit"**. + +**In one sentence:** A short conversation + checklist: what's reachable from the internet, and should it be? + +**Why it came up:** Some things are public on purpose (Foundry, famapp); some might surprise you (Obsidian). + +### Questions + +1. Want to walk through the list together in chat instead of reading a table? +2. Any service you *know* should be public that we'd flag as wrong? + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +Caddyfile review only + +### If we do it — rough steps + +1. Walk each public domain in conversation +2. Mark: keep public / lock down / drop +3. Save decisions here and in TB-005/TB-006 as needed + +### Done when + +- [ ] Every public site has your yes/no +- [ ] Action items linked to other TBs + +
diff --git a/plans/homelab-improvements/tasks/_TEMPLATE.md b/plans/homelab-improvements/tasks/_TEMPLATE.md new file mode 100644 index 0000000..15fe601 --- /dev/null +++ b/plans/homelab-improvements/tasks/_TEMPLATE.md @@ -0,0 +1,58 @@ +# TB-XXX — Title + +**Status:** not started +**Your call:** _(unset — talk first)_ + +--- + +## Talk first + +> **Stop.** Don't read Reference until we've talked. +> In chat, say **"let's do TB-XXX"** and we'll go through this together. + +**In one sentence:** _(what this is)_ + +**Why it came up:** _(one line)_ + +### Questions + +1. _(question)_ +2. _(question)_ +3. _(question)_ + +### Your options + +| Option | Meaning | +|--------|---------| +| **Do it** | We're doing this — I'll implement or walk you through it | +| **Later** | Keep on the list, not now | +| **Drop** | Remove from plan — totally fine | +| **Tweak** | Change scope; tell me how | + +### What we decided + +| | | +|---|---| +| **Decision** | | +| **Notes** | | +| **Date** | | + +--- + +
+Reference (only open if we're doing it) + +### Stack / files + +_(where changes live)_ + +### If we do it — rough steps + +1. ... +2. ... + +### Done when + +- [ ] ... + +
diff --git a/stacks/backup/README.md b/stacks/backup/README.md new file mode 100644 index 0000000..d554f2d --- /dev/null +++ b/stacks/backup/README.md @@ -0,0 +1,39 @@ +# backup stack + +Kopia → Backblaze B2. Backs up `/config`, **selected** `labdata` paths (see `SCOPE.md`), and nightly SQL dumps. + +## Before first deploy + +1. **Backblaze B2** (recommended offsite — ~$6/TB/mo) + - Create a bucket (e.g. `valhalla-homelab-backup`) + - Create an **Application Key** scoped to that bucket + - Note the S3 endpoint for your region + +2. **Fill `stack.env`** + - `KOPIA_REPOSITORY_PASSWORD` — `openssl rand -hex 32` + - `B2_KEY_ID`, `B2_APPLICATION_KEY`, `B2_BUCKET`, `B2_ENDPOINT` + +3. **Host paths** (auto-created on first start if missing) + - `/config/kopia/{config,cache,logs}` + - `/config/backup/dumps` + +4. **Register** new Portainer git stack → `stacks/backup` (one-time) + +5. **Push** → Portainer redeploys within 5 min + +## After deploy + +- UI: `http://valhalla:51515` (or add `backup.ginnoir.com` internal-only in Caddy) +- Subscribe phone to ntfy topic `backup` (or path in `NTFY_URL`) +- **Restore test** (do once): pick a small dump from `/config/backup/dumps/`, restore to a throwaway container + +## Schedule + +| Time | What | +|------|------| +| 02:50 | `backup-dump-databases.sh` — SQL dumps | +| 03:00 | Kopia snapshots (paths in `snapshot-paths.txt`) | + +## If B2 creds are empty + +Kopia still starts with UI — configure repository manually, then set policies for each path in `snapshot-paths.txt`. diff --git a/stacks/backup/SCOPE.md b/stacks/backup/SCOPE.md new file mode 100644 index 0000000..c421466 --- /dev/null +++ b/stacks/backup/SCOPE.md @@ -0,0 +1,49 @@ +# What we back up (and skip) + +**Rule:** everything irreplaceable or slow to rebuild — **not** ROM files, **not** provider cover art. + +## Always + +| Path | Why | +|------|-----| +| `/config` | App state, DBs, CouchDB vault — **includes RomM MariaDB** (`/config/romm/mariadb`) | +| `/config/backup/dumps` | Nightly SQL dumps — **includes `romm-db`** | + +## RomM + +Per [RomM docs](https://docs.romm.app/): **`assets` = saves/states**; **`resources` = fetched covers**. + +| Included | Path | What | +|----------|------|------| +| Database | `/config/romm/mariadb` + nightly dump | Scans, hashes, matches (weeks of work) | +| Saves & states | `labdata/romm/assets` | RomM-managed save files | +| Config/cache | `/config/romm/config`, `/config/romm/redis` | Via `/config` snapshot | + +| Skipped | Path | What | +|---------|------|------| +| Cover art | `labdata/romm/resources` | IGDB/SteamGridDB — minutes to re-fetch | + +## Emulation (`/storage1/Emulation`) — excluded + +Legacy pre-RomM tree (EmuDeck, ES-DE, 13T `roms/`, symlinks). **Not backed up offsite.** RomM is the source of truth for saves via `labdata/romm/assets` + DB. + +## labdata — included + +| Path | Why | +|------|-----| +| `gitea/repositories`, `gitea/lfs` | Git + LFS | +| `famapp/minio`, `resume/minio`, `plane/minio` | User uploads | +| `romhacks/metadata`, `romhacks/library` | Catalog work + guides/spreadsheets | +| `romm/assets` | Saves (see RomM above) | + +## labdata — skipped + +| Path | Why | +|------|-----| +| `romm/resources` | Provider cover art | +| `registry` | Re-pull images | +| `romhacks/incoming` | Transient download queue | + +## Change scope + +Edit `snapshot-paths.txt`, bind mounts in `docker-compose.yml`, and `SCOPE.md`. diff --git a/stacks/backup/crontab b/stacks/backup/crontab new file mode 100644 index 0000000..b39aee2 --- /dev/null +++ b/stacks/backup/crontab @@ -0,0 +1,3 @@ +# min hour day month weekday command +50 2 * * * /bin/sh /scripts/backup-dump-databases.sh +0 3 * * * /bin/sh /scripts/backup-run-snapshots.sh diff --git a/stacks/backup/docker-compose.yml b/stacks/backup/docker-compose.yml new file mode 100644 index 0000000..5f34c9d --- /dev/null +++ b/stacks/backup/docker-compose.yml @@ -0,0 +1,85 @@ +# backup stack — Kopia snapshots of /config, selected /storage1/labdata paths, and DB dumps. +# Scope: stacks/backup/SCOPE.md + snapshot-paths.txt (excludes RomM art, registry, transient dirs). +# +# Offsite target: Backblaze B2 (S3-compatible). One-time repo setup — see README.md. +# Schedule: backup-scheduler dumps databases ~02:50, Kopia snapshots ~03:00 (policy). +# +# Env convention: stack.env only (no Portainer UI vars). Kopia UI is internal-only +# via Caddy after you add backup.ginnoir.com (optional, README). + +services: + kopia: + container_name: kopia + image: kopia/kopia:0.17 + hostname: kopia + restart: unless-stopped + networks: [backup, edge] + env_file: + - stack.env + environment: + - KOPIA_PASSWORD=${KOPIA_REPOSITORY_PASSWORD} + - TZ=${TZ} + - B2_BUCKET=${B2_BUCKET} + - B2_KEY_ID=${B2_KEY_ID} + - B2_APPLICATION_KEY=${B2_APPLICATION_KEY} + - B2_ENDPOINT=${B2_ENDPOINT} + cap_add: + - SYS_ADMIN + security_opt: + - apparmor:unconfined + devices: + - /dev/fuse:/dev/fuse + volumes: + - /config/kopia/config:/app/config + - /config/kopia/cache:/app/cache + - /config/kopia/logs:/app/logs + - /config:/backup/config:ro + - /config/backup/dumps:/backup/dumps:ro + - /storage1/labdata/gitea/repositories:/backup/labdata/gitea/repositories:ro + - /storage1/labdata/gitea/lfs:/backup/labdata/gitea/lfs:ro + - /storage1/labdata/famapp/minio:/backup/labdata/famapp/minio:ro + - /storage1/labdata/resume/minio:/backup/labdata/resume/minio:ro + - /storage1/labdata/plane/minio:/backup/labdata/plane/minio:ro + - /storage1/labdata/romhacks/metadata:/backup/labdata/romhacks/metadata:ro + - /storage1/labdata/romhacks/library:/backup/labdata/romhacks/library:ro + - /storage1/labdata/romm/assets:/backup/labdata/romm/assets:ro + - ./init-kopia.sh:/init-kopia.sh:ro + - ./snapshot-paths.txt:/snapshot-paths.txt:ro + ports: + - "51515:51515" + entrypoint: ["/bin/sh", "/init-kopia.sh"] + labels: + - "com.centurylabs.watchtower.enable=false" + + backup-scheduler: + container_name: backup_scheduler + image: alpine:3.20 + restart: unless-stopped + networks: [backup] + env_file: + - stack.env + environment: + - TZ=${TZ} + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /config/backup/dumps:/dumps + - ./scripts/backup-dump-databases.sh:/scripts/backup-dump-databases.sh:ro + - ./scripts/backup-run-snapshots.sh:/scripts/backup-run-snapshots.sh:ro + - ./snapshot-paths.txt:/snapshot-paths.txt:ro + - ./crontab:/etc/crontab:ro + entrypoint: ["/bin/sh", "-c"] + command: + - | + apk add --no-cache docker-cli curl tzdata >/dev/null + printenv | grep -E '^(NTFY_|TZ=)' > /etc/backup.env || true + crond -f -l 8 -c /etc + depends_on: + - kopia + +networks: + backup: + name: backup + driver: bridge + edge: + name: edge + external: true diff --git a/stacks/backup/init-kopia.sh b/stacks/backup/init-kopia.sh new file mode 100644 index 0000000..6f1e323 --- /dev/null +++ b/stacks/backup/init-kopia.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# One-time (or idempotent) Kopia repo + policy setup, then start server UI. +set -e + +CONFIG_DIR=/app/config +export KOPIA_CONFIG_PATH="${CONFIG_DIR}/repository.config" + +mkdir -p "${CONFIG_DIR}" /app/cache /app/logs + +# Connect or create B2 repository when credentials are present. +if [ -n "${B2_KEY_ID}" ] && [ -n "${B2_APPLICATION_KEY}" ] && [ -n "${B2_BUCKET}" ]; then + if [ ! -f "${KOPIA_CONFIG_PATH}" ]; then + echo "init-kopia: creating S3 repository on bucket ${B2_BUCKET}" + kopia repository create s3 \ + --bucket="${B2_BUCKET}" \ + --access-key="${B2_KEY_ID}" \ + --secret-access-key="${B2_APPLICATION_KEY}" \ + --endpoint="${B2_ENDPOINT}" \ + --password="${KOPIA_PASSWORD}" + else + echo "init-kopia: repository config exists, skipping create" + fi + + while IFS= read -r path || [ -n "${path}" ]; do + case "${path}" in ''|\#*) continue ;; esac + kopia policy set "${path}" \ + --keep-latest 14 \ + --keep-daily 30 \ + --keep-weekly 12 \ + --keep-monthly 24 \ + --compression=zstd \ + --snapshot-time=03:00 \ + --enable-actions=false \ + 2>/dev/null || kopia policy set "${path}" \ + --keep-latest 14 \ + --keep-daily 30 \ + --keep-weekly 12 \ + --keep-monthly 24 \ + --compression=zstd \ + --snapshot-time=03:00 + done < /snapshot-paths.txt +else + echo "init-kopia: B2 credentials empty — start UI only; configure repo manually (see README)" +fi + +exec kopia server start --ui --insecure --address=0.0.0.0:51515 diff --git a/stacks/backup/scripts/backup-dump-databases.sh b/stacks/backup/scripts/backup-dump-databases.sh new file mode 100644 index 0000000..98c4282 --- /dev/null +++ b/stacks/backup/scripts/backup-dump-databases.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# Dump all homelab databases to /config/backup/dumps (gzip SQL). +# Runs inside backup_scheduler; needs docker.sock. +set -eu + +DUMP_ROOT=/dumps +STAMP=$(date +%Y%m%d-%H%M%S) +DIR="${DUMP_ROOT}/${STAMP}" +mkdir -p "${DIR}" + +log() { echo "[backup-dump ${STAMP}] $*"; } + +dump_postgres() { + name="$1" + user="$2" + db="$3" + if ! docker ps --format '{{.Names}}' | grep -qx "${name}"; then + log "skip ${name} (not running)" + return 0 + fi + log "dump ${name} → ${db}" + docker exec "${name}" pg_dump -U "${user}" "${db}" | gzip -c > "${DIR}/${name}.sql.gz" +} + +dump_mariadb() { + name="$1" + db="$2" + if ! docker ps --format '{{.Names}}' | grep -qx "${name}"; then + log "skip ${name} (not running)" + return 0 + fi + log "dump ${name} → ${db}" + docker exec "${name}" sh -c "mariadb-dump -u root -p\"\$MYSQL_ROOT_PASSWORD\" \"${db}\"" \ + | gzip -c > "${DIR}/${name}.sql.gz" +} + +log "starting database dumps → ${DIR}" + +dump_postgres postgres_gitea gitea gitea +dump_postgres postgres_plane plane plane +dump_postgres postgres_authentik authentik authentik +dump_postgres postgres_famapp famapp famapp +dump_postgres postgres_resume postgres postgres + +dump_mariadb mariadb_bookstack bookstack +dump_mariadb owncloud_mariadb owncloud +dump_mariadb romm-db romm + +find "${DUMP_ROOT}" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} + 2>/dev/null || true + +log "done" diff --git a/stacks/backup/scripts/backup-run-snapshots.sh b/stacks/backup/scripts/backup-run-snapshots.sh new file mode 100644 index 0000000..33c9d73 --- /dev/null +++ b/stacks/backup/scripts/backup-run-snapshots.sh @@ -0,0 +1,33 @@ +#!/bin/sh +set -eu + +[ -f /etc/backup.env ] && . /etc/backup.env + +NTFY_URL="${NTFY_URL:-https://ntfy.ginnoir.com/backup}" +NTFY_TITLE="${NTFY_TITLE:-ValhallaBackup}" + +notify() { + priority="$1" + message="$2" + curl -sf -H "Title: ${NTFY_TITLE}" -H "Priority: ${priority}" -d "${message}" "${NTFY_URL}" >/dev/null 2>&1 || true +} + +if ! docker ps --format '{{.Names}}' | grep -qx kopia; then + notify high "Kopia container not running — snapshots skipped" + exit 1 +fi + +FAILED=0 +while IFS= read -r path || [ -n "${path}" ]; do + case "${path}" in ''|\#*) continue ;; esac + if ! docker exec kopia kopia snapshot create "${path}"; then + FAILED=1 + fi +done < /snapshot-paths.txt + +if [ "${FAILED}" -eq 0 ]; then + notify default "Snapshots OK ($(date +%Y-%m-%d\ %H:%M))" +else + notify high "One or more snapshots FAILED — check docker logs kopia" + exit 1 +fi diff --git a/stacks/backup/snapshot-paths.txt b/stacks/backup/snapshot-paths.txt new file mode 100644 index 0000000..2fe1db1 --- /dev/null +++ b/stacks/backup/snapshot-paths.txt @@ -0,0 +1,11 @@ +# Kopia snapshot roots (one path per line). Edit when scope changes. +/backup/config +/backup/dumps +/backup/labdata/gitea/repositories +/backup/labdata/gitea/lfs +/backup/labdata/famapp/minio +/backup/labdata/resume/minio +/backup/labdata/plane/minio +/backup/labdata/romhacks/metadata +/backup/labdata/romhacks/library +/backup/labdata/romm/assets diff --git a/stacks/backup/stack.env b/stacks/backup/stack.env new file mode 100644 index 0000000..000681c --- /dev/null +++ b/stacks/backup/stack.env @@ -0,0 +1,15 @@ +# backup stack — Kopia repository + Backblaze B2 offsite. +TZ=America/Chicago + +# Encrypts the Kopia repository (NOT your B2 login). +KOPIA_REPOSITORY_PASSWORD=8f4c2a9e1b7d6035f8e2c4a6910d3b7e5f9a8c2d6b4e1f0a3c7d9e2b5f8a1c4 + +# Backblaze B2 (S3-compatible API) +B2_BUCKET=ginnoir-backup +B2_KEY_ID=00452ab80ae44e10000000001 +B2_APPLICATION_KEY=K004P2lGTtRiCe430O5dVZJiTn7Kwr8 +B2_ENDPOINT=https://s3.us-west-004.backblazeb2.com + +# ntfy alerts (same pattern as watchtower) +NTFY_URL=https://ntfy.ginnoir.com/backup +NTFY_TITLE=ValhallaBackup diff --git a/stacks/roms/docker-compose.yml b/stacks/roms/docker-compose.yml index ea17a91..de1a8ae 100644 --- a/stacks/roms/docker-compose.yml +++ b/stacks/roms/docker-compose.yml @@ -11,7 +11,7 @@ # UI env vars for ${VAR} substitution. See memory portainer-env-interpolation. # # Tiered binds: DB + RomM config + bundled-redis -> /config/romm (SSD); -# RomM blobs (downloaded art + user saves/states) -> /storage1/labdata/romm (ZFS). +# RomM labdata (ZFS): assets = saves/states; resources = fetched cover art (IGDB etc.). # The labdata/romm/* targets are auto-created by Docker as root on first start. # # Only romm joins `edge` (Caddy proxies romm:8080); romm-db stays on `roms` only.