Commit Graph
62 Commits
Author SHA1 Message Date
Claude 4199f42f70 wolf: fix wrong-architecture AppImage downloads (aarch64 picked on x86_64 hosts)
_wolf_download_emulator_appimage picked whatever release asset happened to
be first in the GitHub API's asset list among files ending in .AppImage,
with no architecture filtering. Confirmed live: pkgforge-dev's Dolphin
AppImage release lists an aarch64 build ahead of the x86_64 one, so the
downloaded file silently failed at launch with a bare "exec format error"
and no indication why — the earlier "emulator not found" symlink fix
(previous commit) got ES-DE to find the file at all, which is what
surfaced this as the next failure.

Now prefers whichever release asset's filename actually tags the host's
own architecture, falls back to an untagged asset (Azahar/PCSX2's releases
carry no arch tag and are unaffected either way), and only then falls back
to "take the first one". Also verifies the downloaded file's real ELF
architecture against the host post-download and warns loudly if it still
doesn't match, since filename tagging isn't something every release can be
trusted to get right.
2026-09-01 15:03:57 +00:00
Claude 2c51ab5faa wolf: symlink downloaded Dolphin AppImage to the name ES-DE actually looks for
ES-DE's es_find_rules.xml DOLPHIN entry only auto-detects a file literally
named Dolphin_Emulator*.AppImage under ~/Applications. pkgforge-dev's own
community-build release asset isn't named that, so the AppImage downloaded
by install_wolf() sat in emulators/ unnoticed and ES-DE reported "Couldn't
launch game, emulator not found" (%EMULATOR_DOLPHIN% unresolved) even
though the file was right there — confirmed live on a real box.

Drop a same-directory Dolphin_Emulator.AppImage symlink pointing at
whatever the real download is named, without renaming/losing the vendor
filename. Runs unconditionally (not just after a fresh download) so it
also repairs installs that grabbed the file before this fix existed.
2026-09-01 14:37:01 +00:00
Claude 85b13d07a7 Fix wolf.sh: mount /home/retro/.local/share, not just .config
Confirmed live: Dolphin's "data is corrupted" / "could not write
to/read from Wii system memory" was ~/.local/share/dolphin-emu (the
emulated Wii NAND) not existing at all inside the container —

  ls: cannot access '/home/retro/.local/share/dolphin-emu/':
  No such file or directory

Only /home/retro/.config had a persistent mount (retro-home, added
earlier this session for the RetroArch permission-denied fix) —
/home/retro/.local/share, the other half of the XDG base directory
split many apps use (config vs. actual data), was never addressed and
started completely empty on every container launch. Any NAND state
Dolphin wrote mid-session vanished the moment that container was torn
down, which is consistent with "corrupted" rather than merely "empty"
if a session ended mid-write.

Same fix as the earlier .config mount: a new retro-home-data host
directory mounted at /home/retro/.local/share in both the esde and
retroarch app profiles (both CATALOG copies), forced to 1000:1000
ownership like the other retro-home dirs since the container's retro
user is hardcoded to that uid regardless of the host account.
2026-09-01 13:49:05 +00:00
Claude bef88e654d Fix two bugs in the AntiMicroX plumbing before it ever runs
1. Glob case mismatch that would have silently no-op'd the whole
   feature: the real GitHub release asset is AntiMicroX-x86_64.AppImage
   (capitalized) — confirmed against the actual release, not assumed.
   Both the helper's own already-downloaded check and this file's own
   post-download lookup used a lowercase-only "antimicrox*.AppImage"
   glob (copied from Azahar's/PCSX2's pattern, which really are
   lowercase). Linux glob matching is case-sensitive, so _AMX_APP would
   have come back empty even after a successful download, hitting the
   "didn't produce a usable AppImage" branch and silently skipping the
   Sway launch hook with the AppImage sitting there unused.

2. Sway match criteria used app_id, which only matches native Wayland
   clients. AntiMicroX is a Qt5/X11 app running under XWayland here —
   same as ES-DE and Steam already are in this exact stock config,
   which is why their own for_window rules use [class="..."], not
   app_id. Switched to class (and title as a fallback, in case the
   exact WM_CLASS AntiMicroX registers differs), with a (?i) case-
   insensitive prefix — confirmed sway uses PCRE2 for criteria matching
   and (?i) is the documented syntax for exactly this, not assumed.

Neither bug was fatal to Wolf itself (both fail quietly rather than
crashing anything), but both would have cost real troubleshooting time
for something that looked like it should just work.
2026-09-01 02:32:45 +00:00
Claude f087984526 Add AntiMicroX plumbing for a universal controller-combo hotkey
Wires up the infrastructure for RetroArch's own hotkeys (global across
every libretro core already) to be matched by an equivalent for
Dolphin/PCSX2/Azahar, none of which support gamepad-bound hotkeys
natively (confirmed open feature requests: PCSX2/pcsx2#1082,
azahar-emu/azahar#722; Dolphin's are hardcoded to keyboard only).

AntiMicroX watches Wolf's virtual gamepad directly — a real uinput
device per Wolf's own "inputtino" docs, not a proprietary channel —
and injects whatever keyboard shortcut the focused emulator actually
expects.

Mechanism: GoW's own stock Sway config (baked into the esde/retroarch
images) has a deliberate first-line extension point,
`include /home/retro/.config/sway/custom-cfg` (confirmed by reading it
directly out of a running container). Since /home/retro/.config is
already the retro-home mount added earlier this session, writing a
file to $GAME_STORAGE_DIR/retro-home/sway/custom-cfg on the host lands
exactly there in both containers — zero changes to GoW's own image or
scripts needed.

Deliberately scoped to plumbing only: download AntiMicroX (reusing the
_wolf_download_emulator_appimage helper added for PCSX2/Dolphin), add
the Sway launch hook (floating window, not hidden, so it's reachable
through the Moonlight stream), and document the actual one-time setup
flow in the generated README. The button mapping itself, including
AntiMicroX's own per-application Auto Profile switching, is built
through its real GUI rather than a hand-authored .gamecontroller.amgp
this repo can't verify blind.

Marked experimental throughout (install prompt defaults to n, DRY-RUN
summary, README) — this is unverified against a live session.
2026-09-01 02:24:29 +00:00
Claude 1ed7fe89ea Add PCSX2 and Dolphin to wolf.sh's auto-download emulators
Factored the existing Azahar-only download block into a reusable
_wolf_download_emulator_appimage() helper (same fetch-latest-release-
and-find-.AppImage-asset logic, parameterized), then reused it for:

- PCSX2 (PS2) — officially publishes Linux AppImages via GitHub
  Releases (PCSX2/pcsx2), same pattern as Azahar.
- Dolphin (GameCube/Wii) — dolphin-emu.org's own Linux distribution
  is Flatpak-only, no official AppImage at all. Uses the well-regarded
  community AppImage build (pkgforge-dev/Dolphin-emu-AppImage)
  instead, with explicit warnings before the prompt and in the
  generated README that this is a third-party build, not an official
  Dolphin release — so the user can decide knowingly rather than this
  silently substituting an unofficial build for what looks like an
  official option.

Motivated by a live "could not write to/read from Wii system memory"
error — GameCube/Wii isn't handled by a RetroArch libretro core in any
stable way (Dolphin's libretro core is unstable for Wii specifically),
so ES-DE needs a real standalone Dolphin binary to hand those systems
off to, the same way it already does for 3DS via Azahar.

Also updated the DRY-RUN summary and the generated README's emulators
section to describe all three instead of just Azahar.
2026-08-31 23:44:24 +00:00
Claude 0ba83212d1 Fix wolf.sh: mount /home/retro/.config itself, not just .config/retroarch
Only .config/retroarch was mounted, so Docker still had to auto-create
.config itself (one level up) as an intermediate directory for that
bind mount to attach under — same root:root mode-755 stub problem as
before, just one level higher. Confirmed live: RetroArch got past its
own config.toml write this time, but then crashed on:

  mkdir: cannot create directory '/home/retro/.config/waybar': Permission denied

(Waybar/Sway — the on-screen overlay GoW's app-runner wrapper spawns
for every app, not RetroArch-specific.)

Fix: add a second mount at /home/retro/.config itself, backed by a new
retro-home/ host directory, alongside the existing more-specific
.config/retroarch mount (Docker resolves nested/overlapping bind mounts
correctly regardless of list order). Now nothing under .config is a
Docker-auto-created stub — it's real host-backed content throughout,
so anything an app writes directly under .config (Waybar, Sway state,
RetroArch's own non-cores/shaders/overlays files) just works.

Also force retro-home/ and the existing retroarch/ dir to 1000:1000
explicitly after the general ACTUAL_USER chown — the GoW app
containers' 'retro' user is hardcoded to uid 1000 regardless of what
uid the host account installing this actually has, so relying on the
general chown alone silently breaks this fix on any box where those
don't match.
2026-08-31 21:21:53 +00:00
Claude ed939e5826 Fix wolf.sh update_field(): scope field search to the app's own block
update_field() searched a blind +/-25-line window around an app's
name = '...' line to find and rewrite its mounts/env field. Once app
blocks got shorter (e.g. after collapsing a 3-line mounts array into
one line), two adjacent apps' blocks could end up close enough that
the window reached into a neighboring app's block instead — splicing
that block's own field or, worse, eating into its
[profiles.apps.runner] table header.

Confirmed live: this corrupted config.toml into invalid TOML and
crash-looped Wolf outright:

  ERROR | Unhandled exception: Error while parsing table header:
  cannot redefine existing table 'profiles.apps.runner'

Fix: bound the search to the enclosing [[profiles.apps]] block only —
walk backward from the name line to the nearest [[profiles.apps]]
header, forward to the next [[profiles.apps]] or [[profiles]] header,
and only look for the field within that range. Never crosses into a
neighboring block regardless of how short either one is.
2026-08-31 20:45:18 +00:00
Claude c1a97d8945 Fix wolf.sh manage.sh apps: detect installed apps regardless of indentation
INSTALLED=$(sudo grep "^    name = 'Wolf" ...) required exactly 4 leading
spaces before name = 'Wolf...' in Wolf's own generated config.toml.
Confirmed live: Wolf's TOML writer doesn't reliably indent that way, so
this came back empty even with apps clearly installed and running,
printing "No Wolf apps installed yet". That's silently wrong in two
ways: the "Already installed: ..." message under-reports, and pressing
Enter at the apps prompt ("update mounts only") falls through to the
hardcoded `steam esde` default instead of actually refreshing whatever
was already there — so a mount fix for an already-installed app (e.g.
RetroArch) never gets applied by the Enter/no-picks path at all.

Relaxed the anchor to tolerate any amount of leading whitespace,
matching the Python injector's own already-installed check a few lines
later, which never anchored on indentation to begin with.
2026-08-31 20:21:36 +00:00
Claude 76a494bcbf Fix wolf.sh: mount whole retroarch/ dir, not just its subdirectories
The esde and retroarch app profiles (both CATALOG dict copies) mounted
three subdirectories individually:

  {games}/retroarch/cores:/home/retro/.config/retroarch/cores:rw
  {games}/retroarch/shaders:/home/retro/.config/retroarch/shaders:rw
  {games}/retroarch/overlays:/home/retro/.config/retroarch/overlays:rw

but never mounted /home/retro/.config/retroarch itself. Docker
auto-creates that missing parent directory inside the container as
root:root mode 755 (standard behavior for a bind-mount target that
doesn't already exist in the image) — the retro user (uid 1000) can
read/traverse it but not write into it. RetroArch's own entrypoint
then fails outright trying to write its default config there:

  cp: cannot create regular file '/home/retro/.config/retroarch/retroarch.cfg': Permission denied

which happens on every single launch, for both apps — confirmed live
against a real box: the container starts, RetroArch dies on that cp
within ~1s, and Wolf tears the session down (the "black screen, back
to app grid" symptom, with nothing RetroArch-specific about it).

Fix: mount the parent {games}/retroarch directory itself onto
/home/retro/.config/retroarch instead of three separate subdirectory
mounts. cores/shaders/overlays already lived as the only subdirectories
under {games}/retroarch/ on the host, so this preserves the exact same
container-side paths — but now the parent is a real bind mount with no
auto-created stub in the way, and RetroArch's other generated config
(button remaps, core options, playlists, cheats, etc.) persists across
sessions too, which the old three-mount setup silently discarded.
2026-08-31 20:09:33 +00:00
Claude bec9228c55 Back up existing files before every service overwrites them
Confirmed live: install_frigate()'s fresh-install path overwrote a
working, hand-crafted docker-compose.yml (Frigate + mosquitto +
frigate-notify) with zero backup, because that file's shape didn't match
what frigate.sh's own "existing install" detection knew how to recognize.
Every service's own detection is a judgment call about what counts as
"already installed" and can miss a real setup built outside this repo's
conventions.

lib/common.sh gains backup_if_exists(FILE) — copies FILE to
FILE.bak.<timestamp> if it exists, no-ops otherwise (including DRY_RUN).
Applied before every service's own `cat > docker-compose.yml`/`cat > .env`
write across all 60 services that do one (115 call sites), plus a matching
standalone-mode stub added to every service's own bootstrap block, same
convention already used for port_in_use/find_free_port. This doesn't
replace a service's own update/fresh-reinstall detection — it's the safety
net underneath it, so a wrong detection costs a .bak file to restore from
instead of the original silently disappearing.

Also fixes the actual gap that surfaced this: services/frigate.sh's
Authelia offer only checked for Authelia installed locally on Frigate's
own box, which is never true for a dedicated NVR box with no local Caddy
either (the common shape — Caddy lives elsewhere, snippet-generation mode
already handles that). Now offers Authelia protection unconditionally and,
when Authelia isn't local, asks whether it lives on the same machine as
Caddy (still "import authelia", since that's local to wherever Caddy ends
up) or on a genuinely separate third machine (the explicit
header-pinned forward_auth form, per CLAUDE.md's "forward_auth to a remote
Authelia" note, needed because a bare authelia:9091 shortcut only works
one hop).
2026-08-26 17:26:17 +00:00
Claude 71b881889a wolf: persist RetroArch shaders & overlays; back up bios + shaders/overlays
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
2026-06-23 15:25:56 +00:00
Claude 9778696ac1 wolf: make ES-DE usable on first run (RetroArch cores, BIOS mount)
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
2026-06-23 14:45:27 +00:00
Claude d6c0fc2014 fix: _steam_home() prefers container that is currently running
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
2026-06-22 22:26:56 +00:00
Claude dd56f3f837 fix: search all Wolf Steam homes for AppID prefix, not just head -1
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
2026-06-22 20:40:39 +00:00
Claude 8084276738 feat(wolf): add setup-swbf2 command for full SWBF2 2017 Wolf/Moonlight setup
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
2026-06-22 12:38:05 +00:00
Claude b031d7e7d4 fix(wolf): use wineserver -f as anchor instead of cmd.exe timeout
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
2026-06-21 19:21:40 +00:00
Claude 70a7b83cc4 fix(wolf): keep wineserver alive during EA App install
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
2026-06-21 19:13:59 +00:00
Claude 7eadf642b7 wolf: fix install-ea-app — correct installer path, clear fake InstallSuccessful
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
2026-06-21 19:02:04 +00:00
Claude ddf481b72f wolf: add install-ea-app and diagnose-ea, fix link2ea registration
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
2026-06-21 18:50:22 +00:00
Claude 02ae1ae02c wolf: pre-download GE-Proton at install time, auto-extract on start
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
2026-06-21 17:27:19 +00:00
Claude ef6a63a908 wolf: register link2ea:// handler and add wait-ea-app subcommand
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
2026-06-21 16:50:03 +00:00
Claude a1b4107e3e fix(wolf): stop Steam container before patching EA registry to survive wineserver flush
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
2026-06-21 05:11:40 +00:00
Claude 74d4e0d0e7 wolf: register tab-completion for both 'manage.sh' and './manage.sh'
Bash treats these as different command names so both need registering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
2026-06-21 02:24:51 +00:00
Claude f462c6baa2 wolf: fix steam-cache mount missing from configure-apps CATALOG block
The first Python CATALOG (used at initial install time) was missing the
steam-cache mount that the update-apps block had. Both paths now include
the persistent cache mount.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
2026-06-21 02:09:52 +00:00
Claude 0785d30199 wolf: add install-completion to README manage section + tab-completion note
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
2026-06-21 02:08:17 +00:00
Claude 40bb80bc7a wolf: persist Steam shader cache across sessions
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
2026-06-21 01:50:25 +00:00
Claude 3f6e359c7b wolf: make 'games' interactive — pick a number to apply the EA fix
./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
2026-06-21 00:02:48 +00:00
Claude d32832ec75 wolf: add tab-completion + clarify fix-ea-game is universal for EA titles
- 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
2026-06-20 23:53:00 +00:00
Claude 3a09c6b6ea wolf: add 'games' AppID lister + GE-Proton version pin + README docs
- ./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
2026-06-20 23:47:03 +00:00
Claude dcb28adc4f wolf: add ge-proton + fix-ea-game manage commands for EA titles
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
2026-06-20 23:42:51 +00:00
Claude 2b6cf7700c wolf: drive app-container timezone from .env (WOLF_TZ) to fix Steam/EA time
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
2026-06-20 18:44:10 +00:00
Claude dd7df8ce18 wolf: mount host timezone into Steam container
/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
2026-06-20 17:19:04 +00:00
Claude 9d2e0848e6 wolf: make add-web actually open the URL (firefox policy) + replace on re-add
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
2026-06-20 16:23:38 +00:00
Claude 3aab169fbc wolf: fix config.toml corruption when re-running on an existing install
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
2026-06-20 15:55:44 +00:00
Claude 37f85d6adf wolf: add ./manage.sh add-web — URL shortcut tiles in Moonlight via Firefox kiosk
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
2026-06-20 15:21:30 +00:00
Claude b72e2bf549 wolf: add interactive 'manage.sh reorder' for Moonlight tile order
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
2026-06-20 15:11:34 +00:00
Claude f71edf1aad wolf: read WOLF_STATE_DIR from .env at runtime, not baked into compose
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
2026-06-20 13:56:22 +00:00
Claude 1a85ddf063 wolf: relocate Wolf's state folder to the game drive (no symlinks/vdf)
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
2026-06-20 13:44:10 +00:00
Claude df988cca3c wolf: put Steam home on the game drive via symlink, not libraryfolders.vdf
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
2026-06-20 13:13:50 +00:00
Claude 2ac7d6898d wolf: seed Steam libraryfolders.vdf into session dirs after first install
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
2026-06-20 12:53:50 +00:00
Claude b3b5cc9b6e wolf: auto-seed Steam library config on manage.sh start
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
2026-06-20 12:17:02 +00:00
Claude 16f4f56168 wolf: seed Steam libraryfolders.vdf into Wolf session home dir
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
2026-06-20 06:11:44 +00:00
Claude f735825ad9 wolf: list mounted drives via lsblk -P so whole-disk mounts show in picker
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
2026-06-20 06:00:46 +00:00
Claude bd84f1df1e wolf: pre-seed Steam libraryfolders.vdf so game storage dir is set on first launch
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
2026-06-20 05:50:19 +00:00
Claude e7f65d127c wolf: add fix-perms command and document app home-dir ownership fix
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
2026-06-20 05:44:13 +00:00
Claude 01c4dd8525 wolf: mount game storage at /mnt/games/* not /home/retro/* paths
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
2026-06-20 05:17:41 +00:00
Claude c10bf62c79 wolf: fix duplicate /home/retro mount — target Steam library path directly
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
2026-06-20 04:59:37 +00:00
Claude 87414bdc44 wolf: fix manage.sh apps permission denied on /etc/wolf/cfg/config.toml
config.toml is root-owned (Wolf container writes it as root). manage.sh
runs as the regular user, so python3 and grep against the file need sudo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
2026-06-20 04:56:04 +00:00
Claude 54082ad8b0 wolf: fix Steam storage mount, add Azahar 3DS emulator, emulators/ dir
- 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
2026-06-20 04:46:48 +00:00