Merge pull request #94 from outis1one/claude/zealous-heisenberg-8ylloi
wolf: relocate Wolf's state folder to the game drive (no symlinks/vdf)
This commit is contained in:
+92
-149
@@ -200,40 +200,6 @@ fi
|
|||||||
|
|
||||||
register_service wolf gaming "Cloud gaming via Moonlight (Games-on-Whales Wolf)" 47989
|
register_service wolf gaming "Cloud gaming via Moonlight (Games-on-Whales Wolf)" 47989
|
||||||
|
|
||||||
# relocate_steam_home <game_storage_dir>
|
|
||||||
# Make Steam's home physically live on the game drive. Wolf bind-mounts each
|
|
||||||
# app's session home (/etc/wolf/<id>/Steam) to /home/retro. By replacing that
|
|
||||||
# session home with a symlink to <game_storage_dir>/steam-home, everything Steam
|
|
||||||
# writes — its install, game files, and Proton prefixes — lands on the game
|
|
||||||
# drive, and Steam's Storage screen reports that drive's free space. This is the
|
|
||||||
# reliable alternative to seeding libraryfolders.vdf, which Steam overwrites on
|
|
||||||
# every launch. Safe to run repeatedly; only acts on real dirs, never symlinks.
|
|
||||||
# Returns 0 if it relocated at least one session home.
|
|
||||||
relocate_steam_home() {
|
|
||||||
local game_dir="$1"
|
|
||||||
[ -n "$game_dir" ] || return 1
|
|
||||||
local target="$game_dir/steam-home"
|
|
||||||
mkdir -p "$target"
|
|
||||||
chown -R 1000:1000 "$target" 2>/dev/null || true
|
|
||||||
local did=0 src
|
|
||||||
for src in /etc/wolf/[0-9]*/Steam; do
|
|
||||||
[ -e "$src" ] || continue
|
|
||||||
[ -L "$src" ] && continue # already relocated
|
|
||||||
[ -d "$src" ] || continue
|
|
||||||
# Preserve an existing Steam install: move contents over once.
|
|
||||||
if [ -z "$(ls -A "$target" 2>/dev/null)" ] && [ -n "$(ls -A "$src" 2>/dev/null)" ]; then
|
|
||||||
cp -a "$src/." "$target/" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
rm -rf "$src"
|
|
||||||
ln -s "$target" "$src"
|
|
||||||
chown -h 1000:1000 "$src" 2>/dev/null || true
|
|
||||||
chown -R 1000:1000 "$target" 2>/dev/null || true
|
|
||||||
echo " Steam home → $target (via $src)"
|
|
||||||
did=1
|
|
||||||
done
|
|
||||||
return $((did ? 0 : 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
install_wolf() {
|
install_wolf() {
|
||||||
require_docker || return 1
|
require_docker || return 1
|
||||||
|
|
||||||
@@ -730,23 +696,40 @@ UDEV
|
|||||||
log_success "Game storage: $GAME_STORAGE_DIR"
|
log_success "Game storage: $GAME_STORAGE_DIR"
|
||||||
|
|
||||||
# Create the storage sub-directories
|
# Create the storage sub-directories
|
||||||
mkdir -p "$GAME_STORAGE_DIR/steam/steamapps" "$GAME_STORAGE_DIR/saves" \
|
mkdir -p "$GAME_STORAGE_DIR/saves" \
|
||||||
"$GAME_STORAGE_DIR/media" "$GAME_STORAGE_DIR/lutris" \
|
"$GAME_STORAGE_DIR/media" "$GAME_STORAGE_DIR/lutris" \
|
||||||
"$GAME_STORAGE_DIR/firefox" "$GAME_STORAGE_DIR/minecraft" \
|
"$GAME_STORAGE_DIR/firefox" "$GAME_STORAGE_DIR/minecraft" \
|
||||||
"$GAME_STORAGE_DIR/kodi" "$GAME_STORAGE_DIR/emulators"
|
"$GAME_STORAGE_DIR/kodi" "$GAME_STORAGE_DIR/emulators"
|
||||||
|
|
||||||
# Steam home lives on the game drive — the bulletproof way to make games
|
# ── Wolf state folder on the game drive ───────────────────────────────────
|
||||||
# land where the user chose. Seeding libraryfolders.vdf does NOT work:
|
# This is the clean way to keep Steam (and everything else) off the OS
|
||||||
# Steam rewrites that file on every launch and forces library "0" back to
|
# drive: relocate Wolf's ENTIRE state folder onto the game drive instead of
|
||||||
# its own install dir (/home/retro/.local/share/Steam). Instead we make
|
# symlinking individual app homes. Wolf stores each app's session home,
|
||||||
# /home/retro itself physically live on the game drive by symlinking the
|
# Steam install, downloaded games, and Proton prefixes under this folder,
|
||||||
# Wolf Steam session home (/etc/wolf/<id>/Steam) to $GAME_STORAGE_DIR/
|
# and writes its own config.toml under <state>/cfg/. We mount it at the
|
||||||
# steam-home. Then Steam's one and only library IS the game drive — its
|
# SAME path inside and outside the wolf container so that the app containers
|
||||||
# install, games, AND Proton prefixes all land there, and Steam's Storage
|
# Wolf spawns through the Docker socket (which receive HOST paths) resolve
|
||||||
# screen reports the game drive's free space. See _relocate_steam_home in
|
# correctly on the host. No symlinks, no libraryfolders.vdf, no fix-perms.
|
||||||
# manage.sh, which is run on start / fix-perms / first install.
|
local WOLF_STATE_DIR="$GAME_STORAGE_DIR/wolf-state"
|
||||||
mkdir -p "$GAME_STORAGE_DIR/steam-home"
|
local WOLF_CFG="$WOLF_STATE_DIR/cfg/config.toml"
|
||||||
chown -R 1000:1000 "$GAME_STORAGE_DIR/steam-home"
|
mkdir -p "$WOLF_STATE_DIR/cfg"
|
||||||
|
chown -R 1000:1000 "$WOLF_STATE_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Migrate any pre-existing Wolf state from the default OS-drive location so
|
||||||
|
# an upgrading user keeps their Steam install / games instead of losing them.
|
||||||
|
if [ -d /etc/wolf ] && [ -n "$(ls -A /etc/wolf 2>/dev/null)" ] \
|
||||||
|
&& [ -z "$(ls -A "$WOLF_STATE_DIR" 2>/dev/null | grep -v '^cfg$')" ]; then
|
||||||
|
local _MIGRATE=""
|
||||||
|
prompt_yn "Existing Wolf data found at /etc/wolf (OS drive). Move it to $WOLF_STATE_DIR now? (y/n):" "y" _MIGRATE
|
||||||
|
if [[ "$_MIGRATE" =~ ^[Yy]$ ]]; then
|
||||||
|
log_info "Migrating /etc/wolf → $WOLF_STATE_DIR (this can take a while for large Steam installs)..."
|
||||||
|
cp -a /etc/wolf/. "$WOLF_STATE_DIR"/ \
|
||||||
|
&& rm -rf /etc/wolf/* \
|
||||||
|
&& log_success "Migration complete — old data now on the game drive" \
|
||||||
|
|| log_warning "Migration hit an error; check $WOLF_STATE_DIR before deleting /etc/wolf"
|
||||||
|
chown -R 1000:1000 "$WOLF_STATE_DIR" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Pre-create ES-DE ROM directories so the user knows where to drop files
|
# Pre-create ES-DE ROM directories so the user knows where to drop files
|
||||||
# and ES-DE shows the system in its list immediately on first launch.
|
# and ES-DE shows the system in its list immediately on first launch.
|
||||||
@@ -856,7 +839,7 @@ UDEV
|
|||||||
|
|
||||||
# ── docker-compose.yml ────────────────────────────────────────────────────
|
# ── docker-compose.yml ────────────────────────────────────────────────────
|
||||||
log_info "Generating docker-compose.yml..."
|
log_info "Generating docker-compose.yml..."
|
||||||
mkdir -p /etc/wolf/cfg
|
mkdir -p "$WOLF_STATE_DIR/cfg"
|
||||||
mkdir -p "$WOLF_DIR"
|
mkdir -p "$WOLF_DIR"
|
||||||
ensure_docker_dir_ownership "$WOLF_DIR"
|
ensure_docker_dir_ownership "$WOLF_DIR"
|
||||||
cd "$WOLF_DIR" || return 1
|
cd "$WOLF_DIR" || return 1
|
||||||
@@ -894,13 +877,17 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- NVIDIA_DRIVER_VOLUME_NAME=nvidia-driver-vol
|
- NVIDIA_DRIVER_VOLUME_NAME=nvidia-driver-vol
|
||||||
- HOST_APPS_STATE_FOLDER=/etc/wolf
|
# State + config live on the game drive. HOST_APPS_STATE_FOLDER must be a
|
||||||
|
# HOST path because Wolf hands it to the host Docker daemon when spawning
|
||||||
|
# app containers — so it's mounted at the SAME path inside this container.
|
||||||
|
- HOST_APPS_STATE_FOLDER=${WOLF_STATE_DIR}
|
||||||
|
- WOLF_CFG_FOLDER=${WOLF_STATE_DIR}/cfg
|
||||||
- WOLF_INTERNAL_IP=${WOLF_IP}
|
- WOLF_INTERNAL_IP=${WOLF_IP}
|
||||||
- WOLF_INTERNAL_MAC=${WOLF_MAC}
|
- WOLF_INTERNAL_MAC=${WOLF_MAC}
|
||||||
- WOLF_RENDER_NODE=${WOLF_RENDER_NODE}
|
- WOLF_RENDER_NODE=${WOLF_RENDER_NODE}
|
||||||
- LD_LIBRARY_PATH=/usr/nvidia/lib:/usr/nvidia/lib32
|
- LD_LIBRARY_PATH=/usr/nvidia/lib:/usr/nvidia/lib32
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/wolf/:/etc/wolf:rw
|
- ${WOLF_STATE_DIR}:${WOLF_STATE_DIR}:rw
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:rw
|
- /var/run/docker.sock:/var/run/docker.sock:rw
|
||||||
- /dev/:/dev/:rw
|
- /dev/:/dev/:rw
|
||||||
- /run/udev:/run/udev:rw
|
- /run/udev:/run/udev:rw
|
||||||
@@ -929,6 +916,9 @@ EOF
|
|||||||
cat > .env << EOF
|
cat > .env << EOF
|
||||||
# Wolf game/ROM storage root — edit this and run ./manage.sh update-storage to apply
|
# Wolf game/ROM storage root — edit this and run ./manage.sh update-storage to apply
|
||||||
GAME_STORAGE_DIR=${GAME_STORAGE_DIR}
|
GAME_STORAGE_DIR=${GAME_STORAGE_DIR}
|
||||||
|
# Wolf state folder (config.toml + every app session home) — lives on the game
|
||||||
|
# drive so Steam installs, games, and Proton prefixes stay off the OS drive.
|
||||||
|
WOLF_STATE_DIR=${WOLF_STATE_DIR}
|
||||||
EOF
|
EOF
|
||||||
chmod 600 .env
|
chmod 600 .env
|
||||||
chown "$ACTUAL_USER:$ACTUAL_USER" .env
|
chown "$ACTUAL_USER:$ACTUAL_USER" .env
|
||||||
@@ -950,42 +940,17 @@ EOF
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
# Make Steam's home physically live on the game drive by symlinking each Wolf
|
# Wolf's state folder (config.toml + every app session home, Steam install,
|
||||||
# Steam session home (/etc/wolf/<id>/Steam, mounted to /home/retro) at
|
# games, Proton prefixes) lives on the game drive — see WOLF_STATE_DIR in .env.
|
||||||
# $GAME_STORAGE_DIR/steam-home. Steam then installs, stores games, and keeps
|
# Nothing here needs to relocate or symlink anything; Wolf writes straight to
|
||||||
# Proton prefixes on the game drive, and its Storage screen shows that drive's
|
# the drive because docker-compose.yml mounts it as HOST_APPS_STATE_FOLDER.
|
||||||
# free space. Seeding libraryfolders.vdf does NOT work — Steam rewrites it every
|
WOLF_STATE_DIR=$(grep '^WOLF_STATE_DIR=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d= -f2-)
|
||||||
# launch. Safe to run repeatedly; skips dirs already symlinked. Needs the Steam
|
WOLF_CFG="${WOLF_STATE_DIR:-/etc/wolf}/cfg/config.toml"
|
||||||
# app to be DISCONNECTED (no running container holding the mount).
|
|
||||||
_relocate_steam_home() {
|
|
||||||
local game_dir
|
|
||||||
game_dir=$(grep '^GAME_STORAGE_DIR=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d= -f2-)
|
|
||||||
[ -n "$game_dir" ] || return 0
|
|
||||||
local target="$game_dir/steam-home"
|
|
||||||
sudo mkdir -p "$target"
|
|
||||||
local did=0 src
|
|
||||||
for src in /etc/wolf/[0-9]*/Steam; do
|
|
||||||
[ -e "$src" ] || continue
|
|
||||||
[ -L "$src" ] && continue
|
|
||||||
[ -d "$src" ] || continue
|
|
||||||
if [ -z "$(sudo ls -A "$target" 2>/dev/null)" ] && [ -n "$(sudo ls -A "$src" 2>/dev/null)" ]; then
|
|
||||||
sudo cp -a "$src/." "$target/" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
sudo rm -rf "$src"
|
|
||||||
sudo ln -s "$target" "$src"
|
|
||||||
echo " Steam home → $target"
|
|
||||||
did=1
|
|
||||||
done
|
|
||||||
sudo chown -R 1000:1000 "$target" 2>/dev/null || true
|
|
||||||
[ "$did" = 1 ] && echo "Steam now stores everything on the game drive. Reconnect Moonlight."
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
start)
|
start)
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
echo "Wolf started. Pair Moonlight to this server's IP."
|
echo "Wolf started. Pair Moonlight to this server's IP."
|
||||||
sleep 3
|
|
||||||
_relocate_steam_home
|
|
||||||
;;
|
;;
|
||||||
stop) docker compose down ;;
|
stop) docker compose down ;;
|
||||||
restart) docker compose restart ;;
|
restart) docker compose restart ;;
|
||||||
@@ -996,8 +961,7 @@ case "$1" in
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
;;
|
;;
|
||||||
apps|add-apps|update-storage)
|
apps|add-apps|update-storage)
|
||||||
WOLF_CFG=/etc/wolf/cfg/config.toml
|
# WOLF_CFG resolved at top of script from .env
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
|
|
||||||
# Resolve game storage dir: explicit arg > .env > prompt
|
# Resolve game storage dir: explicit arg > .env > prompt
|
||||||
GAME_DIR="${2:-}"
|
GAME_DIR="${2:-}"
|
||||||
@@ -1090,7 +1054,7 @@ CATALOG = {
|
|||||||
name='WolfSteam', title='Steam',
|
name='WolfSteam', title='Steam',
|
||||||
icon='https://games-on-whales.github.io/wildlife/apps/steam/assets/icon.png',
|
icon='https://games-on-whales.github.io/wildlife/apps/steam/assets/icon.png',
|
||||||
image='ghcr.io/games-on-whales/steam:edge',
|
image='ghcr.io/games-on-whales/steam:edge',
|
||||||
mounts=[], # Steam home is symlinked onto the game drive (relocate_steam_home)
|
mounts=[], # Steam home lives on the game drive via Wolf's state folder
|
||||||
env=['PROTON_LOG=1', 'RUN_SWAY=true',
|
env=['PROTON_LOG=1', 'RUN_SWAY=true',
|
||||||
'GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*'],
|
'GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*'],
|
||||||
cap_add=['SYS_ADMIN', 'SYS_NICE', 'SYS_PTRACE', 'NET_RAW', 'MKNOD', 'NET_ADMIN'],
|
cap_add=['SYS_ADMIN', 'SYS_NICE', 'SYS_PTRACE', 'NET_RAW', 'MKNOD', 'NET_ADMIN'],
|
||||||
@@ -1252,33 +1216,25 @@ PYEOF
|
|||||||
echo "Set up backups with the modular system: sudo ./setup.sh backup"
|
echo "Set up backups with the modular system: sudo ./setup.sh backup"
|
||||||
;;
|
;;
|
||||||
fix-perms)
|
fix-perms)
|
||||||
# Wolf creates each app's home directory under /etc/wolf/<session-id>/<App>
|
# Wolf creates each app's home dir under WOLF_STATE_DIR/<session-id>/<App>
|
||||||
# and mounts it as /home/retro inside the app container. If anything in
|
# and mounts it as /home/retro inside the app container. If anything in
|
||||||
# there ends up root-owned (e.g. left over from an earlier mount layout),
|
# there ends up root-owned, the in-container 'retro' user (uid 1000)
|
||||||
# the in-container 'retro' user (uid 1000) can't write to it and the app
|
# can't write to it and the app bails on startup with "Permission
|
||||||
# bails on startup with "Permission denied" creating ~/.steam, etc.
|
# denied". Re-own the whole state folder + game storage to uid/gid 1000.
|
||||||
# Re-own every Wolf app home dir to uid/gid 1000 to clear that.
|
echo "Fixing ownership of Wolf state + game storage (uid 1000)..."
|
||||||
echo "Fixing ownership of Wolf app home directories (uid 1000)..."
|
|
||||||
found=0
|
found=0
|
||||||
for d in /etc/wolf/[0-9]*/; do
|
if [ -n "$WOLF_STATE_DIR" ] && [ -d "$WOLF_STATE_DIR" ]; then
|
||||||
[ -d "$d" ] || continue
|
sudo chown -R 1000:1000 "$WOLF_STATE_DIR"
|
||||||
sudo chown -R 1000:1000 "$d"
|
echo " fixed: $WOLF_STATE_DIR"
|
||||||
echo " fixed: $d"
|
|
||||||
found=1
|
found=1
|
||||||
done
|
fi
|
||||||
# Also re-own the on-disk game storage so the retro user can write saves/installs
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
GAME_DIR=$(grep '^GAME_STORAGE_DIR=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d= -f2-)
|
GAME_DIR=$(grep '^GAME_STORAGE_DIR=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d= -f2-)
|
||||||
if [ -n "$GAME_DIR" ] && [ -d "$GAME_DIR" ]; then
|
if [ -n "$GAME_DIR" ] && [ -d "$GAME_DIR" ]; then
|
||||||
sudo chown -R 1000:1000 "$GAME_DIR"
|
sudo chown -R 1000:1000 "$GAME_DIR"
|
||||||
echo " fixed: $GAME_DIR"
|
echo " fixed: $GAME_DIR"
|
||||||
found=1
|
found=1
|
||||||
fi
|
fi
|
||||||
# Relocate Steam's home onto the game drive so games install there
|
[ "$found" = 0 ] && echo " Nothing to fix (no Wolf state dir found yet)."
|
||||||
# (and stay there). This moves an existing Steam install over once,
|
|
||||||
# then symlinks the session home — no re-download needed.
|
|
||||||
_relocate_steam_home
|
|
||||||
[ "$found" = 0 ] && echo " Nothing to fix (no Wolf app dirs found yet)."
|
|
||||||
echo "Done. Reconnect from Moonlight to relaunch the app."
|
echo "Done. Reconnect from Moonlight to relaunch the app."
|
||||||
;;
|
;;
|
||||||
pin)
|
pin)
|
||||||
@@ -1332,10 +1288,10 @@ MEOF
|
|||||||
log_info "Starting Wolf (first start pulls the image — give it a minute)..."
|
log_info "Starting Wolf (first start pulls the image — give it a minute)..."
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# Wolf writes /etc/wolf/cfg/config.toml on first start. Wait for it, then
|
# Wolf writes $WOLF_CFG (on the game drive) on first start. Wait for it,
|
||||||
# wire in game storage.
|
# then wire in game storage.
|
||||||
log_info "Waiting for Wolf to generate /etc/wolf/cfg/config.toml..."
|
log_info "Waiting for Wolf to generate config.toml at $WOLF_CFG..."
|
||||||
local WOLF_CFG=/etc/wolf/cfg/config.toml _i
|
local _i
|
||||||
for _i in $(seq 1 30); do
|
for _i in $(seq 1 30); do
|
||||||
[ -f "$WOLF_CFG" ] && break
|
[ -f "$WOLF_CFG" ] && break
|
||||||
sleep 2
|
sleep 2
|
||||||
@@ -1362,7 +1318,7 @@ CATALOG = {
|
|||||||
name='WolfSteam', title='Steam',
|
name='WolfSteam', title='Steam',
|
||||||
icon='https://games-on-whales.github.io/wildlife/apps/steam/assets/icon.png',
|
icon='https://games-on-whales.github.io/wildlife/apps/steam/assets/icon.png',
|
||||||
image='ghcr.io/games-on-whales/steam:edge',
|
image='ghcr.io/games-on-whales/steam:edge',
|
||||||
mounts=[], # Steam home is symlinked onto the game drive (relocate_steam_home)
|
mounts=[], # Steam home lives on the game drive via Wolf's state folder
|
||||||
env=['PROTON_LOG=1', 'RUN_SWAY=true',
|
env=['PROTON_LOG=1', 'RUN_SWAY=true',
|
||||||
'GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*'],
|
'GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*'],
|
||||||
cap_add=['SYS_ADMIN', 'SYS_NICE', 'SYS_PTRACE', 'NET_RAW', 'MKNOD', 'NET_ADMIN'],
|
cap_add=['SYS_ADMIN', 'SYS_NICE', 'SYS_PTRACE', 'NET_RAW', 'MKNOD', 'NET_ADMIN'],
|
||||||
@@ -1524,19 +1480,14 @@ if not added and not updated:
|
|||||||
PYEOF
|
PYEOF
|
||||||
docker compose restart wolf
|
docker compose restart wolf
|
||||||
log_success "Wolf restarted with updated config"
|
log_success "Wolf restarted with updated config"
|
||||||
|
# Nothing else to do for Steam storage: Wolf's state folder lives on the
|
||||||
# Point Steam's home at the game drive (see steam-home note above).
|
# game drive, so Steam installs, games, and Proton prefixes land there
|
||||||
# Wolf only creates the Steam session dir on first Moonlight connect,
|
# automatically the first time you connect. No fix-perms, no symlinks.
|
||||||
# so it almost certainly doesn't exist yet here — relocate any that do,
|
|
||||||
# and tell the user the one-time step for the rest.
|
|
||||||
if echo "$APP_KEYS" | grep -qw steam; then
|
if echo "$APP_KEYS" | grep -qw steam; then
|
||||||
relocate_steam_home "$GAME_STORAGE_DIR" || true
|
log_success "Steam will install games to $WOLF_STATE_DIR (on the game drive)"
|
||||||
log_info "Steam storage: after your FIRST Moonlight connection, run"
|
|
||||||
log_info " cd $WOLF_DIR && ./manage.sh fix-perms"
|
|
||||||
log_info "to move Steam onto the game drive, then reconnect. (Once only.)"
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_warning "Wolf config not generated in time. Add apps manually to /etc/wolf/cfg/config.toml"
|
log_warning "Wolf config not generated in time. Add apps manually to $WOLF_CFG"
|
||||||
log_warning "Then run: ./manage.sh apps"
|
log_warning "Then run: ./manage.sh apps"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1602,13 +1553,13 @@ PYEOF
|
|||||||
echo ""
|
echo ""
|
||||||
echo " ${GAME_STORAGE_DIR}/"
|
echo " ${GAME_STORAGE_DIR}/"
|
||||||
echo " roms/ → /ROMs (EmulationStation)"
|
echo " roms/ → /ROMs (EmulationStation)"
|
||||||
echo " steam/ → /home/retro/.steam (Steam Big Picture)"
|
|
||||||
echo " saves/ → /home/retro/.config/retroarch/saves (RetroArch saves)"
|
echo " saves/ → /home/retro/.config/retroarch/saves (RetroArch saves)"
|
||||||
echo " media/ → /media (ES-DE scraped artwork)"
|
echo " media/ → /media (ES-DE scraped artwork)"
|
||||||
|
echo " wolf-state/ → Wolf state: Steam install, games, Proton prefixes,"
|
||||||
|
echo " ES-DE settings, controller maps, config.toml"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Other app data (ES-DE settings, controller mappings, save states,"
|
echo " Wolf's entire state folder lives on the game drive, so Steam games"
|
||||||
echo " standalone-emulator saves) is persisted by Wolf under /etc/wolf and"
|
echo " and everything else stay off the OS drive automatically."
|
||||||
echo " is included when you set up backups."
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "── APPS ──────────────────────────────────────────────"
|
echo "── APPS ──────────────────────────────────────────────"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -1632,9 +1583,9 @@ PYEOF
|
|||||||
echo ""
|
echo ""
|
||||||
echo "── BACKUPS ───────────────────────────────────────────"
|
echo "── BACKUPS ───────────────────────────────────────────"
|
||||||
echo ""
|
echo ""
|
||||||
echo " Back up your saves, progress and user data (Steam user data and all of"
|
echo " Back up your saves, progress and user data (Wolf's state folder:"
|
||||||
echo " /etc/wolf: ES-DE settings, controller mappings, RetroArch saves/states,"
|
echo " ES-DE settings, controller mappings, RetroArch saves/states, emulator"
|
||||||
echo " emulator saves). ROMs and game installs are skipped."
|
echo " saves, Steam user data). ROMs and game installs are skipped."
|
||||||
echo ""
|
echo ""
|
||||||
echo " Set up automatic backups with the backup module:"
|
echo " Set up automatic backups with the backup module:"
|
||||||
echo " sudo ./setup.sh backup"
|
echo " sudo ./setup.sh backup"
|
||||||
@@ -1684,35 +1635,27 @@ cd $WOLF_DIR
|
|||||||
| 48010 | TCP | RTSP |
|
| 48010 | TCP | RTSP |
|
||||||
| 47998–48000 | UDP | RTP video/audio/control |
|
| 47998–48000 | UDP | RTP video/audio/control |
|
||||||
|
|
||||||
## Game storage: \`$GAME_STORAGE_DIR\`
|
## Storage layout
|
||||||
On-disk dirs are bind-mounted into each app container under \`/mnt/games/\`:
|
Two things live on the game drive (\`$GAME_STORAGE_DIR\`):
|
||||||
|
|
||||||
|
**1. Shared media folders** — bind-mounted into app containers:
|
||||||
- \`roms/<system>/\` → /ROMs (EmulationStation — drop ROMs here)
|
- \`roms/<system>/\` → /ROMs (EmulationStation — drop ROMs here)
|
||||||
- \`steam-home/\` → Steam's home (/home/retro) — installs, games, prefixes
|
|
||||||
- \`saves/\` → /mnt/games/saves (RetroArch saves & states)
|
- \`saves/\` → /mnt/games/saves (RetroArch saves & states)
|
||||||
- \`emulators/\` → /mnt/games/emulators (Azahar 3DS AppImage, etc.)
|
- \`emulators/\` → /mnt/games/emulators (Azahar 3DS AppImage, etc.)
|
||||||
|
|
||||||
Mounting under \`/mnt/games\` (not the container's \`/home/retro\`) avoids
|
**2. \`wolf-state/\`** — Wolf's entire state folder (\`WOLF_STATE_DIR\` in
|
||||||
clobbering the app's home directory, which Wolf manages per-session under
|
\`.env\`). This is the key to keeping games off the OS drive: it holds
|
||||||
\`/etc/wolf/<id>/<App>\`.
|
\`cfg/config.toml\` plus every app's session home, including the Steam install,
|
||||||
|
downloaded games, and Proton prefixes.
|
||||||
|
|
||||||
## Steam storage — how games land on the game drive
|
## Why games land on the game drive (no config needed)
|
||||||
Steam rewrites \`libraryfolders.vdf\` on every launch and forces its primary
|
Wolf stores all app state under \`HOST_APPS_STATE_FOLDER\`. We point that — and
|
||||||
library back to its own install dir, so seeding that file does NOT work.
|
the matching docker-compose volume — at \`wolf-state/\` on the game drive, mounted
|
||||||
Instead, this setup symlinks Steam's session home
|
at the **same path inside and outside** the wolf container so the app containers
|
||||||
(\`/etc/wolf/<id>/Steam\` → \`/home/retro\`) at \`steam-home/\` on the game
|
Wolf spawns through the Docker socket resolve it correctly on the host. Because
|
||||||
drive. Steam's one and only library is therefore the game drive — installs,
|
Steam's home is born on the game drive, its installs, games, and Proton prefixes
|
||||||
game files, and Proton prefixes all land there with nothing to configure.
|
go there with nothing to configure — no \`libraryfolders.vdf\` seeding, no
|
||||||
|
symlinks, no \`fix-perms\` dance.
|
||||||
Wolf creates the Steam session dir only on your FIRST Moonlight connection.
|
|
||||||
So the one-time sequence is:
|
|
||||||
1. Connect once and let Steam open (it lands on the OS drive this first time).
|
|
||||||
2. Disconnect, then run \`./manage.sh fix-perms\` (moves Steam to the game
|
|
||||||
drive and symlinks it — no re-download).
|
|
||||||
3. Reconnect. Steam → Settings → Storage now shows the game drive's free
|
|
||||||
space, and everything installs there from here on.
|
|
||||||
|
|
||||||
\`./manage.sh start\` re-applies this automatically, so it self-heals on
|
|
||||||
reboots.
|
|
||||||
|
|
||||||
## 3DS emulation
|
## 3DS emulation
|
||||||
Azahar (open-source Citra fork) AppImage lives in \`emulators/\` → mounted at
|
Azahar (open-source Citra fork) AppImage lives in \`emulators/\` → mounted at
|
||||||
@@ -1732,7 +1675,7 @@ Then reconnect from Moonlight.
|
|||||||
|
|
||||||
## Backup
|
## Backup
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
sudo ./setup.sh backup # covers /etc/wolf saves and ES-DE settings
|
sudo ./setup.sh backup # covers wolf-state/ saves and ES-DE settings
|
||||||
\`\`\`
|
\`\`\`
|
||||||
MD
|
MD
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user