Bring the full EditmaskwithAI application into the repo under paintplus/
(429 files) so the service is self-contained — the installer copies the
vendored source to ~/docker/paintplus/src instead of cloning at runtime.
Rename to PaintPlus (service + branding; app logic untouched):
- services/editmaskwithai.sh -> services/paintplus.sh (register_service
paintplus, install_paintplus, ~/docker/paintplus, Caddy paintplus:8000,
Authelia option preserved)
- container names -> paintplus across docker-compose*.yml; dev network
-> paintplus-network
- browser <title> -> "PaintPlus - AI Image Editor"; README heading ->
PaintPlus with upstream provenance note
- README utilities table: editmaskwithai -> paintplus
Backend/frontend code (help strings referencing the old container name,
the ai_photo_edit.db filename) is intentionally left as-is to avoid
touching application logic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
Package the self-hosted mask-based AI photo editor
(github.com/outis1one/EditmaskwithAI) as a utilities service. Clones the
repo into ~/docker/editmaskwithai and deploys on port 3080.
Two install modes:
- Cloud API (no GPU): writes .env from the repo template with the chosen
AI_PROVIDER (OpenAI gpt-image or Replicate), API key, and a generated
SECRET_KEY, then `docker compose up -d --build`.
- Local GPU (NVIDIA): runs the repo's own install-local-gpu.sh and
bring-up-local-gpu.sh (downloads ~13 GB of models).
The app has no built-in auth, so it offers Authelia SSO and reverse-proxies
via Caddy (ai-photo-edit:8000). Cloud mode joins caddy_net through a
docker-compose.override.yml; GPU mode is attached with `docker network
connect` since the GPU compose runs with an explicit -f and does not merge
overrides.
Also lists the service in the README utilities table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
When run directly without setup.sh, write_readme is not defined.
Add a minimal implementation to the else block of both kyber-server.sh
and sunshine.sh so standalone runs no longer fail with command not found.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Generalize the optional Groq toggle into a provider picker so the GPU AI
stack can wire Groq, DeepInfra, OpenAI, and/or OpenRouter into Open WebUI
at once. All are OpenAI-compatible, so they share Open WebUI's
OPENAI_API_BASE_URLS / OPENAI_API_KEYS (semicolon-separated, matched by
position). Keys are written to llm/.env; only ${VAR} references go into
docker-compose.yml.
Updates the install prompt, .env template, compose patch, README, the
dry-run note, and the final summary accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
If the kyber bin symlink and desktop entry are not present, prompt to
set up the launcher. Calls install_kyber_launcher directly if already
loaded (wizard), or sources kyber-launcher.sh when run standalone.
Skips the prompt on rerun if the launcher is already installed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
If auth.toml has no token, launch the Kyber AppImage as the actual user
(with DISPLAY/XAUTHORITY passed through), prompt the user to log in via
the EA Account button, then re-read auth.toml once they close Kyber.
Falls back to manual token paste if auth.toml still has nothing.
Skips the AppImage entirely on rerun when a token is already cached.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Running kyber_cli get_token under sudo opens a broken browser OAuth flow.
Instead, read the token directly from ~/.local/share/maxima/auth.toml which
Kyber writes after a normal GUI login. If missing, show clear instructions
to run the AppImage as yourself first, then re-run the installer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
On rerun, if ~/.local/share/maxima exists, offer to wipe it so EA login
starts completely fresh (fixes stuck/broken auth states).
Single-quote all .env values so passwords and tokens containing $, !, &,
spaces, and other shell special characters are written safely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
The standard Steam path search missed installs on secondary drives.
Now also reads libraryfolders.vdf to find all Steam library paths, then
falls back to a broad find across home/mnt/media. Wolf's copy is excluded
from the fallback search to prefer the native Steam install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Adds a Moonlight app entry that launches SWBF2 directly via Steam
(steam://rungameid/1237950) — both with and without the Kyber entry.
Moonlight app list now shows: Kyber SWBF2 | SWBF2 Single Player | Desktop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
When SWBF2 is not found locally, prompt to rsync from a remote host.
Asks for remote host, username, and path (defaults to the standard Steam
Linux path). Files land in ~/docker/kyber-server/swbf2/ and that path is
used automatically. Falls back to manual local path entry if rsync declined.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Instead of hard-failing, show an rsync example for copying game files from
another machine, then prompt for the path. Validates that starwarsbattlefrontii.exe
exists at the given location before continuing.
Also silence "register_service: command not found" in all three services when
run standalone (guard with command -v check).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Steam must be running when Kyber is used. The installer now detects whether
Steam is present (command or ~/.steam path), and if not, prompts to install
it via apt (steam-installer or steam package, with i386 multiarch enabled).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
When run directly (sudo bash services/foo.sh), bash executes the bootstrap
block before reaching the install_* function definition below it, causing
"command not found". Moving the bootstrap to the end of each file ensures
the function is defined by the time the bootstrap calls it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Installs the Kyber community multiplayer launcher (simonlinuxcraft/kyber-
linuxport-unofficial) via the setup.sh wizard. Handles glibc check, bwrap/
userns fix, GitHub release fetch, desktop entry, and bin symlink. Warns on
Intel integrated graphics and links to Sunshine as the streaming alternative.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Installs Sunshine (LizardByte) as a Moonlight-compatible streaming host,
offset to port 48090 so it coexists with Wolf. Auto-detects the Kyber
AppImage and adds a "Kyber SWBF2" app entry. Optional virtual display for
closed-lid/headless use. uinput udev rules for controller/mouse input.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
- 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
Mount retroarch/shaders and retroarch/overlays from the game drive into
~/.config/retroarch/{shaders,overlays} for both ES-DE and standalone
RetroArch (sibling to the existing cores mount). Anything pulled from the
Online Updater or set in the Quick Menu now persists across sessions and
survives a Wolf/OS reinstall, so a CRT shader or bezel set is configured
once. Dirs are pre-created on the game drive; storage summary and README
updated, including a fallback note to repoint Settings -> Directory if
RetroArch ever writes elsewhere.
gaming-backup: add opt-in snapshots for emulator BIOS (bios/) and the
RetroArch shaders/overlays dirs (cores excluded — re-downloadable). New
BACKUP_BIOS and BACKUP_RA_SHADERS flags wired through the prompts,
backup.conf, summary, and the worker script.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XV8mwKLFwUKt94dAKh3k79
The GoW es-de/retroarch images ship the RetroArch frontend but NO libretro
cores, and wolf.sh never mounted a BIOS directory. As a result every
libretro-based game (SNES/NES/Genesis/N64/PSX/GBA/...) failed to launch on a
fresh install, and BIOS-dependent systems had nowhere to read firmware from.
Changes (services/wolf.sh):
- Pre-download RetroArch cores at install into <game>/retroarch/cores and
bind-mount them into the ES-DE and RetroArch apps at
~/.config/retroarch/cores (where both ES-DE's bundled config and GoW's
rom_launcher.sh look). Defaults to the full libretro buildbot set.
- Add a <game>/bios dir mounted at ~/bioses (matches retroarch.cfg's
system_directory) so PSX/Saturn/Dreamcast/Neo-Geo/etc. find their BIOS.
- New './manage.sh cores [all|common] [force]' to download/refresh cores
later; wired into help text and bash tab-completion. Install reuses it.
- Also expose emulators/ at ~/Applications so ES-DE's app finder detects the
Azahar 3DS AppImage.
- Pre-create the new dirs; extend the install summary and README to document
BIOS placement, the cores command, that controllers auto-configure via SDL
(no manual input setup), and how to exit a game back to ES-DE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XV8mwKLFwUKt94dAKh3k79
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