- Auto-detects SWBF2 install path; loud error with install instructions if missing
- Downloads Kyber AppImage if not present, extracts kyber_cli, runs get_token
(opens browser for EA OAuth — one-time step, token never expires)
- Prompts for EA credentials (written to .env, chmod 600) and server name
- Auto-detects GPU (NVIDIA/Intel/AMD) and adds appropriate passthrough config
- Writes docker-compose.yml + .env to ~/docker/kyber-server/
- Map rotation left blank by default with instructions in .env comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Add troubleshooting for 'Origin Error: title installed in language not
entitled to play' — Maxima's umu-run reg commands fail silently on some
systems; manual wine64 regedit import of Origin locale keys fixes it
- Add windowed-mode / HDR fix for SWBF2 crash during level load on
integrated GPUs (Intel Iris Xe, DXVK rendering crash)
- Document fix in both README.md and script header comments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Bots are set via BOTS TEAM 1 / BOTS TEAM 2 sliders in HOST panel (not automatic)
- Correct launch order: Steam open but don't click Play; Kyber launches SWBF2 itself
- Document Alt+Tab / taskbar click workaround for window focus issue after game loads
- Add bwrap unprivileged userns fix (Ubuntu 24.04 blocks by default); script applies
automatically with sudo and persists via /etc/sysctl.d/99-userns.conf
- Script now checks/fixes userns at step 0 before download
- Kyber manages its own GE-Proton; no need to configure Proton for SWBF2 in Steam
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
The Windows kyber_launcher.exe approach under Wine/Proton has two
unfixable problems on Linux: Wine's cmd.exe crashes with STATUS_ACCESS_VIOLATION
on the long EA OAuth URL, and EA's auth callback uses the eadesktop://
URI scheme which has no Linux handler.
The correct solution is the native Linux port AppImage maintained at
github.com/simonlinuxcraft/kyber-linuxport-unofficial. It bundles Maxima
(open-source EA Desktop replacement) and handles OAuth natively — no shims,
no watchers, no per-Proton-update maintenance.
Script now: fetches latest release from GitHub API, downloads AppImage,
installs desktop entry and ~/.local/bin/kyber symlink.
README: add Gaming scripts section documenting Kyber setup, private server
hosting with bots, requirements, and what does not work.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
After extensive testing, Wine/Proton loads cmd.exe exclusively from
Proton's own installation (files/lib/wine/x86_64-windows/cmd.exe).
Replacing the prefix system32 copy, IFEO registry keys, WINEDLLOVERRIDES,
and Wine DllOverrides registry entries all have no effect — Proton's copy
always takes precedence.
The working fix: compile a tiny shim (GetCommandLineA scan for http URLs,
write to C:\kyber_oauth_url.txt, ExitProcess(0)) and replace Proton's
cmd.exe directly, backing up the original as cmd.exe.bak. The script
detects size (shim ~9KB vs real cmd.exe ~1.2MB) to avoid double-replacing.
Note for users: re-run setup-kyber-linux.sh after any Proton Experimental
update since Steam restores the original cmd.exe on update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
system.reg values use \\ for one backslash, so C:\shim\kyber_cmd.exe
must be written as C:\\shim\\kyber_cmd.exe. The previous script wrote
C:\\\\shim\\\\kyber_cmd.exe (4 backslashes = two literal backslashes),
which Wine couldn't resolve.
Also detect when the shim slot contains Wine's cmd.exe (122231 bytes)
left behind by a failed earlier attempt, and recompile automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Proton's prefix-setup phase overwrites system32/cmd.exe on every game
launch, breaking the previous approach of placing a shim there. Switch to
Windows Image File Execution Options (IFEO): the shim is compiled to
drive_c/shim/kyber_cmd.exe (Proton never touches that directory) and the
IFEO Debugger key is written to system.reg so Wine intercepts cmd.exe at
CreateProcess time without touching the filesystem binary.
Also fix the wine64 path used for winetricks: correct path is
files/lib/wine/x86_64-unix/wine64 not files/bin/wine64.
The shim is simplified to scan all args for any http URL (IFEO passes the
debuggee path as an extra arg, so the /c start pattern match was fragile).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Steam sets STEAM_COMPAT_DATA_PATH to compatdata/<appid> when launching
a non-Steam shortcut. The prefix was named 'kyber' so Steam was launching
into an empty prefix, causing immediate exit. Change KYBER_COMPAT_ID to
match KYBER_APPID (9900000001) and migrate any existing 'kyber' prefix.
Also handle cmd.exe being a symlink: resolve and copy the real file as
cmd-real.exe, then remove the symlink before writing the shim so we only
affect the Kyber prefix and not Proton globally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Large stack array WCHAR[4096] triggered a stack probe (__chkstk_ms)
unavailable in -nostdlib builds. Fix: heap-allocate the pass-through
command line buffer, and add -mno-stack-arg-probe as a safety net.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
_wcsicmp is a CRT function unavailable with -nostdlib/-ffreestanding.
Replace with CompareStringOrdinal from kernel32 (always available).
Also fix step numbering (1-2 were still labeled /5 instead of /7),
and remove 2>/dev/null from compile so errors are visible if it fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Python shortcut script now checks for existing 'Kyber Launcher' entry
and exits early instead of appending a duplicate on each re-run
- Use sudo apt-get directly for mingw-w64 install so the password prompt
works (previous chained || approach suppressed the interactive prompt)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Wine's cmd.exe crashes (0xC0000005) when Kyber calls cmd /c start with
the EA auth URL, on native Linux Steam just as in Wolf. Fix:
1. Compile a minimal cmd shim with mingw-w64 (auto-installed if missing).
The shim intercepts cmd /c start <http url>, writes the URL to
C:\kyber_oauth_url.txt, and exits 0. All other cmd invocations are
passed through to the backed-up cmd-real.exe.
Installed into the Kyber prefix's system32 only — does not affect
other games or Proton globally.
2. Install a watcher script (~/.local/bin/kyber-oauth-watcher.sh) that
polls for the URL file and calls xdg-open to open it in the system
browser. Registered as a systemd user service so it starts on login.
Login flow: Kyber → cmd shim → URL file → watcher → xdg-open → browser
→ EA login → redirect to 127.0.0.1:PORT → Kyber loopback catches code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
On re-run the installer was trying to overwrite locked files in an existing
prefix, causing repeated Access Denied dialogs. Now check for Kyber.exe
first and skip the installer entirely if it's found.
Also kill any leftover Wine/Proton processes before a fresh install to
avoid file-lock conflicts, and move Windows path resolution to after the
installer/skip decision so it always runs regardless of path taken.
Default fallback path updated to match Kyber's actual install location:
C:\Program Files (x86)\KYBER Launcher\
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
If shortcuts.vdf exists but is not writable, attempt chmod 644 before
writing. If that also fails, print a clear instruction and skip the
shortcut step rather than crashing with a Python traceback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
The Evergreen bootstrapper (linkid=2124703) requires a second download from
inside the Wine process, which fails in Proton. Switch to the standalone
offline installer (linkid=2135547, ~150 MB) which installs without any
Wine-internal network calls.
Also prefer winetricks if installed — it uses a local cache, handles the
WINEPREFIX env correctly, and is the most reliable method. Fall back to
the standalone download if winetricks is not available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Instead of erroring when the installer is missing, attempt to fetch it
automatically: first scrape kyber.gg for a direct .exe link, then fall
back to a known CDN path. If download fails, print the manual instruction
and exit cleanly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Kyber uses a PKCE loopback OAuth flow (Maxima): it starts a temporary
HTTP server on 127.0.0.1 and calls cmd /c start to open the EA auth URL
in the system browser. On native Linux, Wine's cmd passes http:// URLs
to xdg-open, which works fine inside Steam's single bwrap layer.
Remove the incorrect claim that WebView2 intercepts qrc:// URIs and is
the only path to completing login. Keep the WebView2 runtime install
(harmless, prevents in-app render errors) but clarify it is not the
login mechanism.
Update end-of-script instructions to describe what the user will actually
see: browser opens EA page, redirects to 127.0.0.1:PORT, tab shows OK or
connection-refused (normal), login completes in Kyber. Add troubleshooting
notes for the two real failure modes: xdg-open not reaching the desktop
(headless/no DISPLAY), and login timeout (loopback server expired).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
The WolfSteam app container already runs seccomp=unconfined + apparmor=unconfined
+ SYS_ADMIN, so the WebView2 CLONE_NEWUSER failure is gated at the HOST level by
kernel.apparmor_restrict_unprivileged_userns=1 (default-on since Ubuntu 23.10),
which overrides the unconfined container. This script relaxes that sysctl (and
the legacy unprivileged_userns_clone) persistently, then probes userns creation
on the host and inside the running container so login can complete without
leaving Wolf.
Co-Authored-By: Claude <noreply@anthropic.com>
Runs Kyber outside Wolf so its embedded WebView2 can initialize without
nested-bwrap blocking CLONE_NEWUSER. Builds the Wine prefix, ensures the
WebView2 Evergreen runtime is installed (the component that intercepts EA's
qrc:// OAuth redirect), registers Kyber as a non-Steam shortcut forced to
Proton Experimental, and notes the Steam Remote Play path for headless boxes.
No fake cmd.exe / Firefox scaffolding needed here.
Co-Authored-By: Claude <noreply@anthropic.com>
Documents the complete Kyber/EA OAuth investigation: root cause is EA's
qrc:// redirect URI which only Kyber's embedded WebView2 can intercept;
WebView2 cannot initialize inside Wolf due to nested bwrap blocking
CLONE_NEWUSER. Script automates all discovered workarounds (fake cmd.exe,
Firefox profile with network.process.enabled: false) and clearly documents
why the real fix is running Kyber outside the Docker container.
Co-Authored-By: Claude <noreply@anthropic.com>
With multiple WolfSteam containers, the previous head -1 pick was
arbitrary. Now matches each Steam state directory against running
container IDs so ge-proton, fix-ea-game, and other commands all
target the active session.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
With multiple WolfSteam containers running, _steam_home() picks the first
Steam directory found which may not contain the target game's prefix.
_apply_ea_fix now scans all Wolf Steam homes and selects the one that has
the AppID's Proton prefix, falling back to the original behavior if none
match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Steam writes localconfig.vdf via atomic rename (write temp + rename over
original). chmod 444 on the file is bypassed by the rename. chmod 555 on
the directory blocks rename-into, preserving the launch option across
Steam restarts.
Also unlocks the directory at the start of step 6 so re-running the
script can update the launch option.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
On native Linux Steam, EA App self-installs via EAappInstaller.exe on first
SWBF2 launch. Link2EA.exe appears in the Wine prefix without any MSI extraction.
Script now checks for Link2EA.exe first and skips steps 2-3 (MSI locate +
extract) if EA Desktop is already installed, proceeding directly to writing
registry fix files and the launch wrapper.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
The MSI is bundled in steamapps/common with the game files. The script
was only checking drive_c (where Steam copies it after a launch), causing
a false "not found" error before the user had launched the game.
Now searches steamapps/common and all of steamapps as fallbacks, reports
which path it found the MSI at. Also remove stale references to the
"Origin is not installed" error which no longer appears.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Both scripts now detect and install msitools if missing, supporting
apt-get (Debian/Ubuntu), dnf (Fedora), and pacman (Arch). Falls back
to a clear error if the package manager is not recognised.
Remove msitools from manual prerequisites in the doc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Both scripts now download and install GE-Proton10-34 if not already present:
- setup-swbf2-linux.sh: installs to ~/.steam/root/compatibilitytools.d/
- setup-swbf2-wolf.sh: installs into the WolfSteam container via docker cp + tar
Both pause after install and prompt the user to set GE-Proton in Steam
(Compatibility → Force) before continuing, since that step requires the GUI.
Update docs to reflect GE-Proton is now handled automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- Remove editorial content (EA server speculation, 2005 alternative, opinions)
- Add native Linux Steam section covering all six setup steps
- Add separate key paths tables for Wolf and native
- Add native Linux troubleshooting commands alongside Wolf equivalents
- Rename title to reflect both platforms
- Fresh install table rewritten as factual survival matrix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Long-form document tracing the actual discovery process: the wrong turns,
the aha moments, and the reasoning behind each step. Covers:
- Why EA is involved in a Steam game at all (link2ea:// architecture)
- JunoConfigureRegistry failure and why DISABLEROLLBACK was a dead end
- msiextract bypass as the actual solution
- Why direct system.reg edits don't survive reboots (wineserver ownership)
- The launch wrapper approach for registry persistence
- RPC_S_SERVER_UNAVAILABLE and EALocalHostSvc
- Wolf-specific bwrap capabilities mismatch
- localconfig.vdf atomic rename problem and chmod 555 fix
Written for sharing — no identifying information, explains the "why"
for each fix so readers can adapt if EA changes their MSI structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Two self-contained scripts for getting SWBF2 (2017, AppID 1237950) working
with GE-Proton on Linux — no dependency on the ubuntu-post-install framework.
setup-swbf2-wolf.sh — for Wolf/Games-on-Whales + Moonlight streaming
setup-swbf2-linux.sh — for native Linux Steam (no Docker)
Both implement the same core fix:
- msiextract bypass for JunoConfigureRegistry Wine incompatibility
- EA Desktop files copied into Wine prefix from extracted MSI
- link2ea_fix.reg + ea_services.reg written to drive_c
- Launch wrapper that imports .reg files via Proton on every launch
(direct system.reg edits are overwritten by wineserver on shutdown)
Wolf version also handles Docker cp, container paths, STEAM_UNIX_SOCKET,
and localconfig.vdf directory locking inside the Wolf state folder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Automates the complete working solution for SWBF2 (AppID 1237950) on Wolf:
1. Extracts EA Desktop from ea_app.msi using msiextract on the host,
bypassing JunoConfigureRegistry Wine incompatibility that causes MSI rollback
2. Copies EA Desktop files (Link2EA.exe, EALocalHostSvc.exe, etc.) into the
Wine prefix at the versioned path with symlink
3. Writes link2ea_fix.reg and ea_services.reg into drive_c so they persist
across Wine prefix operations
4. Installs a launch wrapper (/home/retro/ea_install.sh) that runs regedit
via Steam's sniper+GE-Proton launch chain — required because direct edits
to system.reg are overwritten when wineserver flushes on shutdown
5. Sets LaunchOptions for AppID 1237950 in localconfig.vdf and locks the
config directory (chmod 555) to prevent Steam overwriting it via
atomic rename on shutdown
Run after: Steam open in Moonlight, GE-Proton set, SWBF2 launched once
(triggers Wine prefix + ea_app.msi creation), EA account linked at ea.com.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Documents the EA account requirement, first-time login flow, credential
caching for machine transfers, community server options if EA shuts down,
and the SWBF2 2005 alternative for a launcher-free experience.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Revised approach: skip link2ea:// and EA App account requirement entirely.
Launch starwarsbattlefrontii.exe directly via the wrapper, spoof Origin
registry entries so the game's built-in launcher check passes. Documents
what doesn't work and why (msiexec/bwrap/EA account issues).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Documents the EA App installation workaround for Star Wars Battlefront II
(2017) running under Wolf/WolfSteam with GE-Proton10-34. Covers the bwrap
capability problem, Steam launch wrapper trick, msiextract bypass for the
JunoConfigureRegistry Wine incompatibility, and correct EA Desktop file layout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
wine cmd.exe /c "timeout /t 900 > nul" failed silently (docker exec -d
hides errors), leaving no anchor process and letting wineserver die as
soon as EAappInstaller.exe exited.
Replace with wineserver -f (foreground mode) which keeps the server
alive unconditionally until explicitly killed with wineserver -k.
This is the correct primitive for holding a Wine session open.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
EAappInstaller.exe is a WiX/Burn bootstrapper that spawns the real
installer as a background Wine process and exits immediately. When the
foreground wine process exited, wineserver shut down and killed the
background installer before it could write any files.
Fix: launch a cmd.exe/timeout anchor before running the installer so
the shared wineserver stays up while background installer processes run.
Kill the anchor (wineserver -k) after EADesktop.exe is detected or the
15-minute timeout expires.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Two bugs in install-ea-app:
1. Installer search included compatdata (the Wine prefix), so it found the
temp-extracted copy at drive_c/windows/temp/{UUID}/.cr/EAappInstaller.exe
instead of the bundled copy at steamapps/common/…/__Installer/…/
Fixed: search only under steamapps/common.
2. Our fake InstallSuccessful=true (written by fix-ea-game to bypass the
install-script loop) caused EAappInstaller to detect EA App as "already
installed" and exit in ~15 seconds without doing anything.
Fixed: delete the InstallSuccessful values via wine reg delete inside the
container BEFORE launching the installer. The real EA App installer writes
them back correctly after a successful install.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
install-ea-app: runs EAappInstaller.exe inside the WolfSteam container
using GE-Proton's wine binary. The container has a virtual display so the
EA App installer GUI appears in Moonlight; the user clicks through it and
logs in to their EA account. After EA App installs, fix-ea-game registers
the link2ea:// handler and clears the install-script loop.
diagnose-ea: prints the current link2ea:// registry state from both
system.reg and user.reg, locates EADesktop.exe/Link2EA.exe, shows
StateFlags, and tails the relevant Proton log lines — the first thing to
run when the game returns straight to the Play screen.
_apply_ea_fix: now checks user.reg as well as system.reg for an existing
link2ea:// registration (GE-Proton writes to user.reg during prefix setup);
also searches for Link2EA.exe as a fallback handler when EADesktop.exe is
not yet installed; reports a clear next step when neither is found.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
GE-Proton is required for EA games but was a manual step users had to
remember after first Steam launch. This change eliminates that gap:
- install_wolf() downloads the GE-Proton tarball to
~/docker/wolf/ge-proton-cache/ during initial setup (~500 MB, runs
before the "Start Wolf now?" prompt so it's ready immediately)
- _cache_ge_proton() helper in manage.sh does the same on demand
- ge-proton subcommand checks the cache dir first and extracts from
there instead of re-downloading (~instant vs ~500 MB download)
- start subcommand auto-extracts the cached tarball into Steam's
compatibilitytools.d if Steam has already launched at least once,
so GE-Proton appears in the Compatibility dropdown without any
manual ./manage.sh ge-proton step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
SWBF2 (AppID 1237950) launches, calls ShellExecute("link2ea://..."),
and exits. Without a link2ea:// URL protocol handler registered in the
Wine registry, ShellExecute returns error 31 and the game never plays.
_apply_ea_fix now also writes the link2ea:// Classes registry keys
pointing to EADesktop.exe when it is found in the Proton prefix. If
EADesktop.exe is not yet present (EAappInstaller runs async via
RunType=1 in installScript.vdf), the function warns and skips the
handler registration with instructions to run wait-ea-app.
New 'wait-ea-app [appid]' subcommand polls every 15 seconds (up to 20
minutes) for EADesktop.exe to appear, then calls _apply_ea_fix so the
full fix including the link2ea:// handler is applied automatically once
EA App finishes installing in the background.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
wineserver holds the Wine registry in memory and flushes it back to
system.reg on shutdown, overwriting any edits made while the container
is running. _apply_ea_fix now stops the WolfSteam container first,
patches system.reg + appmanifest StateFlags on disk, then restarts Wolf
so the keys persist when wineserver next starts.
Also sets StateFlags to 6 (fully installed) so Steam skips the
install-script phase entirely and launches the game directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Mount GAME_STORAGE_DIR/steam-cache → /home/retro/.cache so DXVK/Mesa
Vulkan shader compilations survive container restarts. Without this the
cache lived in the ephemeral session home and was wiped on every Wolf
restart, forcing a full recompile each session.
Also create steam-cache/ in initial setup and update-storage so the
directory exists with correct uid-1000 ownership before the container
first starts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
./manage.sh games now lists installed games numbered with their AppIDs,
then prompts for a number to apply the EA App install-script fix to that
game (Enter to skip). Factored the reg-fix into a shared _apply_ea_fix
helper used by both 'games' and 'fix-ea-game'. The EA Desktop installed
marker is shared across all EA titles, so the same fix works for any EA
game — the picker just supplies the right per-game AppID.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- install-completion: writes a bash completion script to
~/.bash_completion.d/manage-wolf and registers it, so double-tab
after './manage.sh ' shows all available commands.
- fix-ea-game kept as general name (not fix-swbf2-2017): the EA Desktop
registry key is shared across all EA games; the Valve has-run key
follows the same EADesktopSetup pattern across EA's catalog.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- ./manage.sh games: lists installed Steam games with their AppIDs
(parsed from appmanifest_*.acf) so users can find the AppID to pass to
fix-ea-game instead of hunting for it.
- ./manage.sh ge-proton [version]: optional arg pins a specific
GE-Proton release for reproducibility; still defaults to latest.
- Document the full EA-games flow + AppID/version notes in the generated
~/docker/wolf/README.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Add two manage.sh commands that bake in the workarounds needed to run
EA Steam titles (e.g. Star Wars Battlefront II 2017) under Proton in a
container:
- ge-proton: downloads the latest GE-Proton into Steam's
compatibilitytools.d (the path Steam actually scans) and chowns it to
uid 1000 so the in-container user can launch it. GE-Proton carries the
EA App installer fixes stock Proton/Experimental lack.
- fix-ea-game [appid]: pre-satisfies the EA App install-script markers
in the game's Proton prefix (InstallSuccessful + Valve EADesktopSetup
has-run key) so Steam stops looping on 'running install script (EA
app)'. Opt-in per AppID, defaults to SWBF2 1237950, no-ops if the
prefix doesn't exist, idempotent, and keeps the prefix owned by uid
1000 so Wine accepts it.
Also document the EA-game flow in the post-install summary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
App containers were defaulting to a non-local timezone (e.g. BST), showing the
wrong local time in Steam — and a skewed clock can stall EA App's installer.
The kernel clock is shared with the host, so this is a display/TZ issue: fix
it by setting each app's TZ env var, sourced from SITE_TZ at install and from
WOLF_TZ in .env for ./manage.sh apps.
Also generalize the array updater to rewrite both `mounts` and `env` (Wolf
reformats either as multi-line), so re-running apps updates the TZ on
already-installed entries without corrupting the TOML.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
/etc/localtime and /etc/timezone are bind-mounted read-only so the Steam
container shows the correct local time. A wrong clock causes EA App install
scripts to hang indefinitely (SSL/token validation against EA's servers fails
when the client clock is skewed).
Fix the host clock first: sudo timedatectl set-ntp true
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
The GoW firefox image launches `firefox` with no URL and ignores START_URL,
so add-web tiles opened a blank default tab. Instead generate a per-app
Firefox enterprise policy (policies.json) that sets the homepage to the
target URL and bind-mount it over /etc/firefox/policies/policies.json so the
site loads on launch.
Also make add-web replace an existing tile of the same name instead of
erroring, so re-running it repairs a previously broken tile in place.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Wolf rewrites config.toml on every start and reformats `mounts` as a
multi-line TOML array. update_mounts() only replaced the single line
starting with `mounts =`, leaving the remaining array elements and the
closing `]` orphaned — producing invalid TOML ("Expected '=' after a key")
that crashed Wolf on the next start. This bit any re-run of the installer
(or `./manage.sh apps`) against a config Wolf had already reformatted.
Replace the entire array (from `mounts =` to its closing `]`), and also
clean up orphaned remnants left by a previously corrupted single-line
rewrite so re-running the script repairs a broken config in place.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Prompts for a display name and URL, auto-fetches the site's apple-touch-icon
or favicon.png, falls back to a manual URL/local-PNG prompt, then injects a
[[profiles.apps]] block using ghcr.io/games-on-whales/firefox:edge with
START_URL set to the target URL. Wolf's virtual gamepad injection means
controller-aware sites (Gamepad API) work naturally through the stream.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Moonlight shows app tiles in config.toml order with no in-client reordering.
Add ./manage.sh reorder: lists the current [[profiles.apps]] blocks by title,
prompts for a new order (read from /dev/tty since stdin is the heredoc),
rewrites config.toml preserving each block's nested runner subtable and
spacing, then restarts Wolf. Updated help text and README.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
docker compose reads variables from .env automatically. Use ${WOLF_STATE_DIR}
in docker-compose.yml so the state folder path is dynamic — editing .env is
enough to relocate it without regenerating the compose file. IP/MAC/render
node stay baked in (hardware-specific, not in .env).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
The right fix, finally. Instead of fighting Steam's libraryfolders.vdf or
symlinking individual app session homes, point Wolf's entire state folder at
the game drive. Steam's install, downloaded games, and Proton prefixes are
born there because Wolf creates every app's session home under
HOST_APPS_STATE_FOLDER.
- docker-compose.yml: mount $WOLF_STATE_DIR (=<game_dir>/wolf-state) at the
SAME path inside and outside the wolf container, set HOST_APPS_STATE_FOLDER
and WOLF_CFG_FOLDER to it. Same-path mount is required because Wolf hands
host paths to the Docker daemon when spawning sibling app containers.
- config.toml now lives at $WOLF_STATE_DIR/cfg/config.toml; all WOLF_CFG
references updated (install + manage.sh, via WOLF_STATE_DIR in .env).
- Optional migration: move existing /etc/wolf data onto the drive on upgrade.
- Removed all symlink machinery: relocate_steam_home, steam-home dir,
_relocate_steam_home in manage.sh, and the fix-perms relocation step.
fix-perms now just re-owns the state folder + game storage.
- Dropped the redundant Steam app mount; updated README/summary text.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
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.
New site config key: CADDY_REMOTE_HOST (set via 'sudo ./setup.sh configure').
When set, configure_caddy_for_service operates in "remote" mode instead of
writing to a local Caddyfile:
- Upstream uses CADDY_REMOTE_HOST:PORT (host IP, not container name)
- Snippet saved to ~/docker/caddy-snippets/<subdomain>.caddy
- User is shown scp/rsync commands to copy it to the Caddy machine
Three modes in configure_caddy_for_service (lib/common.sh and inline stubs):
local: ~/docker/caddy/ exists → write Caddyfile + reload (existing behavior)
remote: CADDY_REMOTE_HOST set → save snippet, print copy instructions
none: neither configured → silent return (unchanged)
All 31 service standalone bootstrap stubs updated with the new logic.
CADDY_REMOTE_HOST global added to all 42 standalone bootstrap sections.
setup.sh configure now prompts for CADDY_REMOTE_HOST with a clear explanation.
wolf.sh: add missing stubs (configure_caddy_for_service, write_readme,
prompt_yn, ensure_docker_dir_ownership) and the Authelia/Caddy/start calls
that were missing from the install function.
https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
New site config key: CADDY_REMOTE_HOST (set via 'sudo ./setup.sh configure').
When set, configure_caddy_for_service operates in "remote" mode instead of
writing to a local Caddyfile:
- Upstream uses CADDY_REMOTE_HOST:PORT (host IP, not container name)
- Snippet saved to ~/docker/caddy-snippets/<subdomain>.caddy
- User is shown scp/rsync commands to copy it to the Caddy machine
Three modes in configure_caddy_for_service (lib/common.sh and inline stubs):
local: ~/docker/caddy/ exists → write Caddyfile + reload (existing behavior)
remote: CADDY_REMOTE_HOST set → save snippet, print copy instructions
none: neither configured → silent return (unchanged)
All 31 service standalone bootstrap stubs updated with the new logic.
CADDY_REMOTE_HOST global added to all 42 standalone bootstrap sections.
setup.sh configure now prompts for CADDY_REMOTE_HOST with a clear explanation.
wolf.sh: add missing stubs (configure_caddy_for_service, write_readme,
prompt_yn, ensure_docker_dir_ownership) and the Authelia/Caddy/start calls
that were missing from the install function.
https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt