- prompt_add_links: shared helper loops asking for /srv paths until
blank Enter, creates symlinks via docker exec, skips bad paths
- cmd_add: offers linked-dir prompt right after user creation
- cmd_scope: offers linked-dir prompt after a scope change
- menu_links: tighter submenu (list + add loop + remove) replacing
the old menu_symlinks; called from Modify option 5
- Scope prompts now show the leading / so the user only types the rest
- Note on delete: symlinks on disk survive user deletion (by design)
https://claude.ai/code/session_01UZus2Q9gNTfUdqSMrhuX29
New option 5 in the Modify submenu: "Manage linked directories"
- Lists existing symlinks inside the user's scope dir (via docker exec)
- Add: prompts for source path (/srv/...) and a display name, creates
the symlink inside /srv<scope>/<name> → /srv<source> in the container
- Remove: lists links, prompts for name, refuses to delete non-symlinks
- Warns if source path doesn't exist yet; offers to create anyway
- Auto-detects container name from docker-compose.yml next to the script
- Checks that the container is running before any docker exec calls
This is the recommended workaround for FileBrowser's single-scope
limitation: symlinks inside the scope dir appear as normal folders.
https://claude.ai/code/session_01UZus2Q9gNTfUdqSMrhuX29
- Interactive menu when run with no args (login once, reuse token)
- Modify submenu: change username, password, scope, or toggle admin
- Fix: prompt_password now uses local -n nameref (bash 4.3+) so the
caller's local variable is actually set; printf -v was writing to
global scope and being shadowed by the caller's local declaration
- One-shot commands unchanged: list/add/delete/passwd/scope/rename/info
- FileBrowser only supports one scope per user — documented clearly
https://claude.ai/code/session_01UZus2Q9gNTfUdqSMrhuX29
Undo the host.docker.internal approach from the previous commit — proper
Docker networking routes Caddy to services by container name on the shared
caddy_net, not via the host gateway.
- lib/common.sh: configure_caddy_for_service now accepts either a plain
port number (localhost:PORT fallback) or container:port (preferred).
The Caddyfile entry uses the container name for direct Docker DNS routing.
- services/caddy.sh: remove extra_hosts hack; update Caddyfile template
comments to show container_name:port format
- All service files: update configure_caddy_for_service calls to pass
container_name:internal_port (e.g. "filebrowser:80", "mealie:9000").
Services using network_mode:host keep plain port numbers.
- tools/manage_users.sh: new FileBrowser user-management script (deployed
to ~/docker/filebrowser/ during installation). Manages users via the
FileBrowser REST API: list, add, delete, passwd, scope, info commands.
Documents username format (letters/numbers/hyphens/underscores),
password rules (min 8 chars, letter + number required), and scope path
convention relative to /srv (= FB_PATH on the host).
https://claude.ai/code/session_01UZus2Q9gNTfUdqSMrhuX29
- lib/common.sh: fix broken reverse_proxy target — localhost inside Caddy's
container is the container's own loopback, not the host; change to
host.docker.internal so proxied ports are actually reachable
- services/caddy.sh: add extra_hosts host.docker.internal:host-gateway so
the above resolves correctly; create caddy_net bridge network in Caddy's
own compose so other services can reference it as external; update all
Caddyfile template comments and README examples to match
- services/filebrowser.sh: update image tag from deprecated :s6 to :latest;
remove non-functional PUID/PGID env vars (filebrowser/filebrowser does not
honour them); add configure_caddy_for_service call; add caddy_net
- services/ntfy.sh: add configure_caddy_for_service call; add caddy_net
- services/portainer.sh: add configure_caddy_for_service call; add caddy_net
- services/frigate-notify.sh, watchtower.sh: add caddy_net for
container-to-container comms (frigate, ntfy) without a Caddy call
- All remaining web-facing Docker services: add caddy_net network block to
docker-compose and CADDY_NET to .env where applicable; services using
network_mode: host (wolf-pair, lyrion) have the top-level block only
https://claude.ai/code/session_01UZus2Q9gNTfUdqSMrhuX29
- extras/test_backup_kopia.sh: stop → restore → compare → roll back test for Kopia
- extras/test_backup_borg.sh: stop → extract → compare → roll back test for Borg
- backup workers: timing (duration), service count, and pre-flight disk check (< 512 MB warns)
- backup workers: ntfy notifications include count, duration, and per-failure detail
- services/backup.sh: install test_backup_kopia.sh + optional weekly test timer
- services/borg-backup.sh: install test_backup_borg.sh + optional weekly test timer
- extras/test_backup_kopia.sh: stop → restore → compare → roll back test for Kopia
- extras/test_backup_borg.sh: stop → extract → compare → roll back test for Borg
- backup workers: add START_TS/BACKUP_COUNT/DURATION_STR to completion log and ntfy
- backup workers: pre-flight disk check warns and records failure if < 512 MB free
- services/backup.sh: install test_backup_kopia.sh + optional weekly test timer
- services/borg-backup.sh: install test_backup_borg.sh + optional weekly test timer
- ntfy notifications include service count, duration, and per-failure detail lines
extras/test_backup.sh — new unified test script (Kopia + Borg):
• Stops container, moves live data aside, restores latest backup,
compares restored vs live with diff -rq (content, not timestamps),
moves live data back and restarts container
• PASS = restore succeeded; diff output is informational (files changed
since last backup are normal)
• FAIL = restore command failed or target empty after restore
• --list flag, CLI service arg, interactive picker
• Handles both full-service dirs and sub-path sources (gaming-backup)
• Cleanup trap always restores live data even on error
• Sends ntfy notification on pass and fail
extras/backup_kopia.sh, backup_borg.sh, backup_gaming.sh:
• ntfy_send() + categorize_error() helpers added
• Each snapshot/archive failure captures stderr and categorizes:
disk full, remote unreachable, repository not found, wrong passphrase,
permission denied, unknown error
• Single ntfy notification at end: success (low priority) or failure
(urgent) with per-service failure reasons listed
• backup_borg.sh: changed 2>&1 | pipe to 2>"$_ERR" | so stdout logs
cleanly and stderr is captured for error categorization
services/backup.sh, borg-backup.sh, gaming-backup.sh:
• New ntfy prompt section in installer (URL + optional token)
• NTFY_URL / NTFY_TOKEN written to backup.conf
• test_backup.sh copied from extras/ into service dir
• Summary updated to show test_backup.sh commands and ntfy URL
https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
Replace embedded heredocs in the three backup service installers with
cp from versioned source files in extras/:
extras/backup_kopia.sh — Kopia worker (was inline in services/backup.sh)
extras/backup_borg.sh — Borg worker (was inline in services/borg-backup.sh)
extras/backup_gaming.sh — gaming saves worker (was inline in services/gaming-backup.sh)
extras/restore_kopia.sh — unified Kopia restore (multi-dest + single-dest)
extras/restore_borg.sh — unified Borg restore with destination picker
Each installer now does `cp extras/<script>.sh $DIR/<script>.sh` instead of
writing the script inline. Workers and restore scripts are now readable in the
repo rather than buried in heredocs.
Restore scripts are installed flat into the service directory root:
~/docker/backup/backup_kopia.sh ~/docker/backup/restore_kopia.sh
~/docker/borg-backup/backup_borg.sh ~/docker/borg-backup/restore_borg.sh
~/docker/gaming-backup/backup_gaming.sh ~/docker/gaming-backup/restore_kopia.sh
The new restore scripts handle destination selection internally, so a single
script replaces the old per-destination restore/<dest>/ layout.
Also fixes `local` used outside a function in restore_kopia.sh and
restore_borg.sh (destination picker loop), and removes the now-superseded
extras/restore_kopia_backup.sh and extras/restore_borg_backup.sh.
https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
New service: borg-backup (backup group)
- Installs borgbackup from apt
- Same coverage as Kopia backup: full ~/docker/<service>/ snapshots
- Minecraft: flush world (save-all), archive, no downtime
- All others: stop → archive → restart
- Multi-destination support with per-service routing
- SSH remote repos supported natively (user@host:/path)
- Per-service pruning: keep-daily/weekly/monthly
- repo compact after each run
- Systemd timer (with cron fallback)
- repokey-blake2 encryption; key export reminder in summary
- worker.sh: run / list / info subcommands
New restore helper: extras/restore_borg_backup.sh
- Installed to ~/docker/borg-backup/restore/<dest>/
- Interactive: lists services → pick archive → inspect (/tmp) or restore in-place
- In-place restore: stops container, moves current data aside, extracts, restarts
- Old data kept as .pre-restore-DATE with rollback instructions printed
https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
Place datapacks_<ver>.zip or crafting_tweaks_<ver>.zip in extras/datapacks/
before running the Minecraft installer and they will be detected automatically.
- Pattern match: *datapack* → unzip into datapacks-download/; *craft* → copy as-is
- Version extracted from filename; compared against selected MC version at picker step
- Early banner notice when ZIPs are absent (where to put them and naming convention)
- If ZIPs present: show found files + version check, offer "Use pre-packaged VT?" (default y)
- Accepting skips the individual datapack picker entirely
- Manual fallback instructions updated to hint at extras/datapacks/ for next time
- Removed broken VT share# links from manual instructions
- Added extras/datapacks/README.md (naming convention + download sources)
- Added .gitignore: extras/datapacks/*.zip never committed
https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
- backup: renamed from disaster-backup; full ~/docker/<service>/ snapshots,
Minecraft flushed not stopped, everything else stop/snap/start for DB
consistency; backup.conf / restore/ layout; post-install-backup timer
- gaming-backup: renamed from backup; frequent game-save snapshots (Minecraft
world data, emulator saves, Steam, Wolf state) with no service downtime;
defaults to hourly schedule; separate gaming-kopia repo + gaming-backup timer
- Deleted services/disaster-backup.sh
- README: updated backup row to reflect new names
https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
Minecraft instances are flushed to disk (save-all) and snapshotted while
the server keeps running — no player disruption. All other services stop
briefly for filesystem-consistent database snapshots, then restart.
Also update file header, install banner, schedule description, and README.
https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
- minecraft: prompt for world seed on new installs (skipped if world already exists); SEED env var wired into docker-compose.yml
- disaster-backup: new service that stops each Docker service, snapshots the full ~/docker/<service>/ directory (config + data + databases), then restarts it — multi-destination support so different services can go to different drives/repos; one restore_kopia_backup.sh per destination
- README: update backup row to list both backup and disaster-backup
https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
- extras/restore_kopia_backup.sh: interactive restore tool — lists all
snapshot sources, pick source + snapshot, choose inspect (restore to
/tmp) or restore-in-place (moves current data aside as .pre-restore-DATE
for easy rollback, auto-stops/starts the associated Docker service)
- services/backup.sh: copy restore script to ~/docker/backup/ during
install; update summary to show restore command
- CLAUDE.md: add note to update README services table when adding a
service; add Authelia section documenting which services have built-in
auth, the import-authelia Caddy pattern for no-auth services, and
remember_me_duration config for kiosk/persistent sessions
https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
Documents all packages installed by the base service, and adds a
Backup section covering Kopia, Borg, plain rsync, rsync --link-dest
(versioned snapshots with original folder structure), and rsnapshot —
with guidance on when to use each.
https://claude.ai/code/session_019XgsQ13XKm4Zj3cNsDNwHj
Keep only the two base install scripts (24.04, 26.04), the modular
system (setup.sh, lib/, services/, extras/, bootstrap.sh), and
LICENSE/README/VERSION. Everything else was superseded.
https://claude.ai/code/session_017WJtGcE5jjerAQCUBWUE3H
- SCP example used $(whoami) which evaluates to root; use $ACTUAL_USER instead
- VT_VERSION stripped to major.minor (e.g. 1.21) so the picker version
prompt is correct; previous awk printed the full patch string (1.21.4)
https://claude.ai/code/session_017WJtGcE5jjerAQCUBWUE3H
When you open the USB folder in the file manager, you're already there.
Right-click → Open in Terminal → sudo bash bootstrap.sh, or double-click
bootstrap.sh → Run in Terminal → sudo prompt. No /media/whoami/DRIVENAME
path hunting required.
https://claude.ai/code/session_017WJtGcE5jjerAQCUBWUE3H
The real workflow is: GitHub Code → Download ZIP → unzip → copy to USB.
No git, no auth, no file manager tricks. On the target machine: ls to find
the drive name, then sudo bash /media/$(whoami)/DRIVENAME/...bootstrap.sh.
https://claude.ai/code/session_017WJtGcE5jjerAQCUBWUE3H
bootstrap.sh: add self-elevation — if not root, re-exec under sudo.
Double-clicking the script in GNOME ("Run in Terminal") now prompts for
the sudo password automatically, no extra commands needed.
README: rewrite USB section around the real workflow:
- clone with "Open in Terminal" from the file manager sidebar
- Option A: right-click folder → Open in Terminal → sudo ./setup.sh
- Option B: double-click bootstrap.sh → "Run in Terminal?" → sudo prompt
- note on nautilus-extension-gnome-terminal and exFAT vs ext4
https://claude.ai/code/session_017WJtGcE5jjerAQCUBWUE3H
README: rewrite USB section with three steps:
1. Auto-detect USB mount point and clone repo onto it
2. Install nautilus-extension-gnome-terminal for right-click "Open in Terminal"
3. Double-click "Run Setup.desktop" to launch wizard with sudo (no terminal needed)
Run Setup.desktop: launcher file included in the repo so it's present on the
USB automatically. Uses %k to find its own location, cds to that directory,
and runs sudo ./setup.sh in a terminal window.
https://claude.ai/code/session_017WJtGcE5jjerAQCUBWUE3H
The USB section was documenting the wrong thing. Replace with instructions
for carrying the repo on a USB stick and running setup.sh directly from it
on any fresh Ubuntu machine — no internet required for the repo.
Covers: cloning/copying to USB, finding the mount point on the target
machine, running setup from USB, exFAT vs ext4 trade-offs, fixing
permissions after copy.
https://claude.ai/code/session_017WJtGcE5jjerAQCUBWUE3H
- lib/common.sh: require_docker now installs Docker CE + Compose plugin
via get.docker.com instead of erroring out if Docker is missing.
Also adds the calling user to the docker group automatically.
- README.md: fix 'tells you how to install Docker' → 'installs Docker
automatically'; add full USB drive usage section (mount, fstab,
DOCKER_DIR config, moving existing data, tips).
- services/minecraft.sh: replace single-source whitelist import with the
multi-source UI from the updated setup-minecraft.sh — collects players
from the current instance, saved backup files, and other servers' backups;
assigns letters to each source so you can import by letter (all from that
source) or by number (specific player).
https://claude.ai/code/session_017WJtGcE5jjerAQCUBWUE3H
Added exec sudo bash self-elevation so bootstrap.sh works correctly when
double-clicked ("Run in Terminal") without the user needing to prefix sudo.
If already root, the check is a no-op.
Removed bootstrap.desktop — too fragile across desktop environments.
README now covers both "Open in Terminal" and double-click paths.
https://claude.ai/code/session_01Y4dMKtkqkpvmgDKoRdzhTG