Seeding libraryfolders.vdf never stuck: Steam rewrites that file on every
launch and forces its primary library back to its own install dir
(/home/retro/.local/share/Steam), which is the Wolf session home on the OS
drive. That's why Steam Storage kept showing /home/retro with the OS drive's
size no matter how many times we seeded the vdf.
New approach: relocate_steam_home symlinks each Wolf Steam session home
(/etc/wolf/<id>/Steam, bind-mounted to /home/retro) at
$GAME_STORAGE_DIR/steam-home. Steam's one and only library is then the game
drive, so installs, game files, and Proton prefixes all land there and the
Storage screen reports the game drive's free space. An existing Steam install
is moved over once (no re-download) before the symlink is created.
Wired into first install, manage.sh start (self-heals on reboot), and
manage.sh fix-perms. Dropped the redundant /mnt/games/steam mount and the
vdf seeding everywhere; updated the README heredoc with the one-time
connect / fix-perms / reconnect sequence.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
After app injection + docker compose restart, Wolf creates per-app session
dirs under /etc/wolf/<id>/Steam. Wait up to 30s for those dirs to appear,
then copy the pre-built libraryfolders.vdf into each one so Steam sees the
game storage location on its very first launch — no manual setup needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Wolf creates /etc/wolf/<id>/Steam/ session dirs when the container starts,
not at install time. Previously the user had to manually run fix-perms after
first start to propagate libraryfolders.vdf.
manage.sh start now calls _seed_steam_library() after docker compose up,
which copies libraryfolders.vdf from game storage into any Wolf session dir
that doesn't have it yet — so the correct library path (/mnt/games/steam)
is in place before the first Moonlight connection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Steam reads libraryfolders.vdf from /home/retro/.local/share/Steam/steamapps/
which Wolf serves from /etc/wolf/<session-id>/Steam/. Seeding it only in
/mnt/games/steam/steamapps/ was ignored since Steam doesn't know to look there.
- Seed the VDF into any existing /etc/wolf/<id>/Steam session dirs at install time
- manage.sh fix-perms now propagates the VDF from game storage into every
Wolf session dir so running fix-perms is the one-command remedy on existing
installs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
The drive picker enumerated mounts with `lsblk MOUNTPOINT | df` round-trips,
which could miss a whole-disk mount (e.g. an nvme formatted directly with no
partition table). Switch to `lsblk -Pno NAME,MOUNTPOINT,SIZE,LABEL,UUID` and
parse the key="value" pairs directly — handles whole-disk mounts and empty
LABEL/UUID fields reliably, so every mounted data drive appears as a numbered
choice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Creates steamapps/libraryfolders.vdf pointing at /mnt/games/steam during
install so Steam uses the correct library path without needing the user to
navigate Settings → Storage on first launch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Wolf mounts each app's home dir from /etc/wolf/<session-id>/<App> into the
container as /home/retro. If anything there is root-owned (e.g. left over
from an earlier mount layout), the in-container retro user (uid 1000) hits
"Permission denied" creating ~/.steam and the app exits on launch.
- Add `./manage.sh fix-perms`: chowns every /etc/wolf/<id>/ app home dir
and the on-disk game storage to uid/gid 1000
- Document the /mnt/games mount layout, first-Steam-launch library step,
3DS/Azahar emulator path, and the fix-perms troubleshooting flow in the
generated README
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Mounting into /home/retro subpaths causes Docker to create intermediate
directories (/.local/share/Steam etc.) as root-owned in the container
overlay before init runs. The GoW init chown step doesn't fully fix these,
so the retro user hits Permission denied on first write.
Move all mounts to neutral /mnt/games/* paths that don't interfere with
the container's home directory setup:
steam → /mnt/games/steam (add as Steam Library Folder via UI)
lutris → /mnt/games/lutris
kodi → /mnt/games/kodi
firefox → /mnt/games/firefox
minecraft → /mnt/games/minecraft
saves → /mnt/games/saves
emulators → /mnt/games/emulators
ROMs stay at /ROMs (GoW ES-DE hardcodes that path).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Mounting steam:/home/retro conflicted with the GoW Steam image's own
internal /home/retro mount. Mount steam:/home/retro/.local/share/Steam
instead — this is where steamapps/ and Proton prefixes actually live,
persists all game data, and doesn't collide.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Steam mount was steam:/home/retro/.steam — games download to
/home/retro/.local/share/Steam/steamapps/ which was NOT mounted and
lived in the ephemeral container layer. Fixed to steam:/home/retro
so the full home dir (library, user data, Proton prefixes) persists.
- Add emulators/ subdir to game storage layout; mounted into ES-DE
container at /home/retro/Applications (ES-DE's default AppImage search path)
- Add optional Azahar (open-source Citra fork) AppImage download at
install time — fetches latest release from GitHub, chmod +x, places
in emulators/ where ES-DE finds it automatically for nintendo3ds ROMs
- Update README to document new storage layout and 3DS emulation setup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Drive picker: numbered list with UUID display, mounted + unmounted drives,
explicit system-path exclusions (no regex bare-/ bug), skip whole disks
with mounted partitions
- Pre-create ES-DE ROM system directories (~50 systems) so ES-DE detects
all platforms on first launch
- App selection at install time: numbered menu for Steam, ES-DE, Lutris,
RetroArch, Prism Launcher, Kodi, Firefox, Desktop
- Python catalog-driven TOML injector shared between install and manage.sh;
supports update_mounts() to patch paths on re-run without duplicating entries
- manage.sh `apps` command replaces `add-apps` / `update-storage`: shows
installed apps, interactive picker, updates game storage path from .env
- Game storage written to ~/docker/wolf/.env as GAME_STORAGE_DIR
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Add write_readme() to the standalone bootstrap stubs (was defined in
wolf-pair.sh and other services but missing here)
- Only include the caddy_net network block in docker-compose.yml when
~/docker/caddy exists; otherwise docker compose up fails with
"network caddy_net declared as external, but could not be found"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
network_mode: host containers cannot join Docker networks, so the
caddy_net networks: block was dead code. The ${CADDY_NET:-caddy_net}
value (colon inside unquoted YAML scalar) was also causing
"mapping values are not allowed in this context" at parse time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
^(/|/boot|...) — the bare / alternate matches every absolute path,
so every non-home mount was silently skipped. Replace with explicit
glob checks so only root, system paths, and [SWAP] are excluded.
Fixes /home/user/drives/1tb-space (and any other secondary mount)
not appearing in the numbered list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Display UUID under each mounted and unmounted entry so users can
cross-reference with fstab / blkid output
- Fix detection bug: a whole disk (e.g. sdb) with a mounted partition
(sdb1) was incorrectly listed as unmounted; now checks lsblk children
before including a disk in the unmounted list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Replace the free-text path prompt with an interactive menu:
- Lists mounted non-system drives as numbered choices (1, 2, ...)
- Each shows mount point, drive label/name, total size, and free space
- Default path is <selected-mountpoint>/games
- Unmounted drives shown as U1, U2, ... (script formats + mounts them)
- Option c for a fully custom path
- Final confirm/edit step lets user tweak the subdir before committing
- Unattended mode defaults to ~/games
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Write GAME_STORAGE_DIR to ~/docker/wolf/.env so the chosen path is
visible and editable after setup
- Fix inline Python injection to UPDATE existing Steam/ES-DE mounts in
config.toml instead of silently skipping when entries already exist
(previously re-running setup with a new path had no effect)
- Add update-storage subcommand to manage.sh so the path can be changed
post-install without a full re-run: ./manage.sh update-storage /new/path
- Same fix applied to add-apps in manage.sh
- chown game storage dir to ACTUAL_USER after creating subdirectories
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Wires Groq (api.groq.com) into Open WebUI as an OpenAI-compatible
connection so its hosted models show up alongside local Ollama models.
Prompts for an API key during install, stores it in llm/.env, and
patches the cloned docker-compose.yml's open-webui environment block
(idempotently) to reference it.
The old CADDY_REMOTE_HOST variable was confusingly named — it sounded like
the Caddy server's address but actually stored this machine's IP (so Caddy
knew how to reach services here). Services don't need to know where Caddy
is; they only need to know whether to write a Caddyfile or create a snippet.
Changes in lib/common.sh:
- Add CADDY_MODE=local|remote|none as the authoritative setting
- load_site_config: parse CADDY_MODE; if old CADDY_REMOTE_HOST present and
CADDY_MODE unset, infer CADDY_MODE=remote (backward compat)
- save_site_config: write CADDY_MODE, drop CADDY_REMOTE_HOST output
- configure_caddy_for_service: use CADDY_MODE for mode detection; for remote
snippets auto-detect this machine's primary IP via hostname -I instead of
requiring a stored value (still falls back to CADDY_REMOTE_HOST if present
in an old .config)
Changes in setup.sh (run_site_configure wizard):
- Replace free-text "Caddy remote host" prompt with a 3-choice menu:
[1] This machine [2] Remote machine [3] None/skip
- Existing installs with CADDY_REMOTE_HOST pre-select option 2 automatically
https://claude.ai/code/session_01S7UecmQRG6CKTYPoBqbVLj
koha.sh:
- Replace named volume `koha_db_data` with bind-mount `./data:/var/lib/mysql`
so the MariaDB database is inside ~/docker/koha/ and covered by backup.sh
- Add interactive setup wizard: collects library name, code, admin credentials,
item types (with custom additions), and shelf locations before first start
- Generate ~/docker/koha/post-setup.sh at install time; after completing the
web installer the user runs it to auto-configure the library branch, item
types, shelf locations, and system preferences via the Koha REST API
- Updated README with exact step-by-step web installer instructions and
admin password prominently displayed
asterisk.sh:
- Replace all 5 named volumes (asterisk-config, easy-asterisk-config,
asterisk-logs, asterisk-spool, asterisk-lib) with bind-mounts inside
~/docker/asterisk/ so all config and state is covered by backup.sh
- mkdir -p for all bind-mount dirs before compose up
- Replace SSH-based standalone configure_caddy_for_service stub with the
snippet approach used everywhere else (local Caddy writes Caddyfile;
remote Caddy via VPN/Netbird saves ~/docker/caddy-snippets/<name>.caddy)
https://claude.ai/code/session_01S7UecmQRG6CKTYPoBqbVLj
iopaint:
- Add model selection menu (10 choices) with CPU/GPU/SD tiers and
size/use-case descriptions shown at install time
- Fix volume mount: ./models:/root/.cache (was only /root/.cache/iopaint)
— now persists both torch hub cache (LaMa) and HuggingFace cache (SD/PowerPaint)
- Refactor compose to use ${MODEL} and ${DEVICE} env vars so switching
models only requires editing .env + restart, no compose file edit needed
- Add PowerPaint-V2-filling and SD 1.5 inpainting as explicit menu choices
for text-guided object replacement
- Update header and README to document all three use cases (erase, fill, replace)
and note that IOPaint is local-only (cannot use a remote GPU)
ai-gpu:
- Add Ollama model selection menu (8 models, multi-select with sizes/descriptions)
defaulting to llama3.2:3b + nomic-embed-text
- Auto-pull selected Ollama models immediately after LLM stack starts
- Add InvokeAI starter model selection (SD 1.5 / SDXL Turbo / SDXL Base / skip)
- Queue InvokeAI model download via REST API (POST /api/v2/models/install)
with fallback instructions if the API is unavailable
- Add HuggingFace token prompt; stored as HUGGING_FACE_HUB_TOKEN in image-gen .env
- Wire SearXNG into Open WebUI via ENABLE_RAG_WEB_SEARCH + SEARXNG_QUERY_URL in llm .env
- Default start choice is now 2 (portal + LLM + Ollama pull) so the stack
is ready to use immediately after install
https://claude.ai/code/session_01JEu7LgCWXKhXo18MeYFRZp
iopaint: AI image inpainting (object removal, fill, restore) via IOPaint +
LaMa model. Runs CPU by default; GPU option writes nvidia deploy block.
No built-in auth — Authelia SSO prompt included. Port 8100.
ai-gpu: GPU AI stack from outis1one/ai-6gb-gpu. Clones repo and sets up
three stacks under ~/docker/ai-gpu/: InvokeAI image gen (port 9090),
Ollama + Open WebUI + SearXNG LLM stack (ports 11434/3000), and Flask
portal (port 8080) that hot-swaps the GPU between stacks. Patches
hardcoded home paths in portal docker-compose.yml to use ACTUAL_HOME.
Prompts for TZ (replaces hardcoded America/New_York). Caddy for both
portal (localai) and InvokeAI (images).
https://claude.ai/code/session_01JEu7LgCWXKhXo18MeYFRZp
- joplin: self-hosted sync server (PostgreSQL sidecar, APP_BASE_URL from SITE_DOMAIN)
- stirling-pdf: PDF toolkit with optional Authelia SSO (no built-in auth)
- n8n: workflow automation connecting self-hosted services (WEBHOOK_URL from SITE_DOMAIN)
- changedetection: web page change monitoring with playwright-chrome renderer
- archivebox: personal Wayback Machine, initializes data dir at install time
- calibre-web: ebook library UI with Calibre conversion support (linuxserver image)
- homebox: home inventory and asset management
All services follow the standalone bootstrap pattern and support local/remote Caddy.
README.md updated with new services in appropriate group rows.