Merge pull request #95 from outis1one/claude/zealous-heisenberg-8ylloi

Claude/zealous heisenberg 8ylloi
This commit is contained in:
Outis
2026-06-20 11:29:02 -04:00
committed by GitHub
+246 -7
View File
@@ -866,6 +866,11 @@ UDEV
WOLF_MAC="$LAN_MAC" WOLF_MAC="$LAN_MAC"
fi fi
# Write compose using a mixed heredoc: WOLF_STATE_DIR comes from .env at
# runtime (docker compose variable substitution), so it stays correct even
# if the drive is remounted at a different path. IP/MAC/render-node are
# baked in at install time because they're hardware-specific and not stored
# in .env — use 'manage.sh update-network' to regenerate if they change.
cat > docker-compose.yml << EOF cat > docker-compose.yml << EOF
name: wolf name: wolf
@@ -877,17 +882,17 @@ services:
restart: unless-stopped restart: unless-stopped
environment: environment:
- NVIDIA_DRIVER_VOLUME_NAME=nvidia-driver-vol - NVIDIA_DRIVER_VOLUME_NAME=nvidia-driver-vol
# State + config live on the game drive. HOST_APPS_STATE_FOLDER must be a # WOLF_STATE_DIR is read from .env at runtime — edit .env to relocate.
# HOST path because Wolf hands it to the host Docker daemon when spawning # Must be the same path inside and outside the container so that app
# app containers — so it's mounted at the SAME path inside this container. # containers Wolf spawns via the Docker socket resolve it on the host.
- HOST_APPS_STATE_FOLDER=${WOLF_STATE_DIR} - HOST_APPS_STATE_FOLDER=\${WOLF_STATE_DIR}
- WOLF_CFG_FOLDER=${WOLF_STATE_DIR}/cfg - 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:
- ${WOLF_STATE_DIR}:${WOLF_STATE_DIR}: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
@@ -1212,6 +1217,236 @@ PYEOF
docker compose restart wolf docker compose restart wolf
echo "Wolf restarted. Apps will appear in Moonlight on next connection." echo "Wolf restarted. Apps will appear in Moonlight on next connection."
;; ;;
reorder)
# Interactively reorder the Moonlight tiles by reordering the
# [[profiles.apps]] blocks in Wolf's config.toml (Moonlight shows them
# in file order). config.toml is root-owned, so run Python under sudo;
# the prompt is read from /dev/tty since stdin is the heredoc script.
if [ ! -f "$WOLF_CFG" ]; then
echo "Wolf config not found at $WOLF_CFG — start Wolf once first."
exit 1
fi
sudo python3 - "$WOLF_CFG" << 'PYEOF'
import sys, re
cfg = sys.argv[1]
with open(cfg) as f:
lines = f.readlines()
prof = [i for i, l in enumerate(lines) if l.strip() == '[[profiles]]']
if not prof:
print('No [[profiles]] section found.'); sys.exit(1)
start = prof[0]
end = prof[1] if len(prof) > 1 else len(lines)
marks = [i for i in range(start, end) if lines[i].strip() == '[[profiles.apps]]']
if len(marks) < 2:
print('Fewer than two apps — nothing to reorder.'); sys.exit(2)
head = lines[:start]
preamble = lines[start:marks[0]]
tail = lines[end:]
blocks = [lines[s:(marks[k+1] if k+1 < len(marks) else end)]
for k, s in enumerate(marks)]
def title_of(b):
for l in b:
m = re.match(r"\s*title = '(.*)'\s*$", l)
if m:
return m.group(1)
return '(untitled)'
titles = [title_of(b) for b in blocks]
print('\nCurrent Moonlight order:')
for i, t in enumerate(titles, 1):
print(' %d. %s' % (i, t))
print('\nType the new order as space-separated numbers (each once).')
print('Example: 3 1 2%s — blank line cancels.'
% (''.join(' %d' % n for n in range(4, len(blocks) + 1))))
tty = open('/dev/tty')
sys.stdout.write('New order: '); sys.stdout.flush()
resp = tty.readline().strip()
if not resp:
print('Cancelled — nothing changed.'); sys.exit(2)
try:
order = [int(x) for x in resp.split()]
except ValueError:
print('Invalid input — expected numbers.'); sys.exit(2)
if sorted(order) != list(range(1, len(blocks) + 1)):
print('Must list each number 1..%d exactly once.' % len(blocks)); sys.exit(2)
out = head + preamble
for n in order:
out += blocks[n - 1]
out += tail
with open(cfg, 'w') as f:
f.writelines(out)
print('\nNew order: ' + ' -> '.join(titles[n - 1] for n in order))
sys.exit(0)
PYEOF
if [ $? -eq 0 ]; then
docker compose restart wolf
echo "Wolf restarted — the new tile order shows on next Moonlight connection."
fi
;;
add-web)
# Add a Moonlight tile that opens a URL in a Firefox kiosk streaming container.
# Usage: ./manage.sh add-web [name] [url]
if [ ! -f "$WOLF_CFG" ]; then
echo "Wolf config not found at $WOLF_CFG — start Wolf once first."
exit 1
fi
# Gather display name
WEB_TITLE="${2:-}"
if [ -z "$WEB_TITLE" ]; then
read -r -p " Display name (e.g. 'Dinosaur Game'): " WEB_TITLE
fi
WEB_TITLE="${WEB_TITLE:-Web App}"
# Gather URL
WEB_URL="${3:-}"
if [ -z "$WEB_URL" ]; then
read -r -p " URL (e.g. https://dinosaur-game.io/): " WEB_URL
fi
if [ -z "$WEB_URL" ]; then echo "No URL entered."; exit 1; fi
# Prepend https:// if no scheme given
[[ "$WEB_URL" =~ ^https?:// ]] || WEB_URL="https://$WEB_URL"
# Try to auto-detect an icon
WEB_DOMAIN=$(python3 -c "from urllib.parse import urlparse; u=urlparse('$WEB_URL'); print(u.scheme+'://'+u.netloc)" 2>/dev/null)
WEB_ICON=""
echo " Looking for icon at $WEB_DOMAIN..."
for _try_icon in \
"$WEB_DOMAIN/apple-touch-icon.png" \
"$WEB_DOMAIN/apple-touch-icon-precomposed.png" \
"$WEB_DOMAIN/icon.png" \
"$WEB_DOMAIN/favicon.png"; do
if curl -fsSL --max-time 5 -o /dev/null -w "%{http_code}" "$_try_icon" 2>/dev/null \
| grep -q "^2"; then
WEB_ICON="$_try_icon"
echo " Found icon: $WEB_ICON"
break
fi
done
if [ -z "$WEB_ICON" ]; then
echo " Could not auto-detect a PNG icon."
echo " Options:"
echo " 1) Enter an image URL (PNG)"
echo " 2) Enter a local PNG file path"
echo " 3) Use default browser icon"
read -r -p " Choice [3]: " _ICON_CHOICE
_ICON_CHOICE="${_ICON_CHOICE:-3}"
if [ "$_ICON_CHOICE" = "1" ]; then
read -r -p " Image URL: " WEB_ICON
elif [ "$_ICON_CHOICE" = "2" ]; then
read -r -p " Local PNG path: " _LOCAL_PNG
_LOCAL_PNG="${_LOCAL_PNG/#\~/$HOME}"
if [ -f "$_LOCAL_PNG" ]; then
# Copy to wolf state dir so Wolf can find it
_ICONS_DIR="${WOLF_STATE_DIR:-/etc/wolf}/cfg/icons"
sudo mkdir -p "$_ICONS_DIR"
_ICON_NAME="$(basename "$_LOCAL_PNG")"
sudo cp "$_LOCAL_PNG" "$_ICONS_DIR/$_ICON_NAME"
WEB_ICON="$_ICONS_DIR/$_ICON_NAME"
else
echo " File not found — using default icon."
fi
fi
fi
[ -z "$WEB_ICON" ] && \
WEB_ICON="https://games-on-whales.github.io/wildlife/apps/firefox/assets/icon.png"
# Sanitize title → WolfWeb-<CamelCase>
WOLF_WEB_NAME="WolfWeb-$(echo "$WEB_TITLE" | tr -s ' \t' '-' | tr -cd 'A-Za-z0-9-')"
echo ""
echo " Adding Moonlight tile:"
echo " Title : $WEB_TITLE"
echo " URL : $WEB_URL"
echo " Icon : $WEB_ICON"
echo " Name : $WOLF_WEB_NAME"
echo ""
sudo python3 - "$WOLF_CFG" "$WOLF_WEB_NAME" "$WEB_TITLE" "$WEB_ICON" "$WEB_URL" << 'PYEOF'
import sys, json, re
cfg = sys.argv[1]
wolf_name = sys.argv[2]
title = sys.argv[3]
icon = sys.argv[4]
url = sys.argv[5]
STD_RULES = ['c 13:* rmw', 'c 244:* rmw']
cap_add = ['NET_RAW', 'MKNOD', 'NET_ADMIN']
env = [
f'START_URL={url}',
'MOZ_ENABLE_WAYLAND=1',
'RUN_SWAY=1',
'GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*',
]
host_cfg = {
'IpcMode': 'host',
'CapAdd': cap_add,
'Privileged': False,
'DeviceCgroupRules': STD_RULES,
}
create_json = json.dumps({'HostConfig': host_cfg}, indent=2)
env_str = str(env).replace('"', "'")
block = (
f"\n"
f" [[profiles.apps]]\n"
f" icon_png_path = '{icon}'\n"
f" start_virtual_compositor = true\n"
f" title = '{title}'\n"
f"\n"
f" [profiles.apps.runner]\n"
f" base_create_json = '''{create_json}\n"
f"'''\n"
f" devices = []\n"
f" env = {env_str}\n"
f" image = 'ghcr.io/games-on-whales/firefox:edge'\n"
f" mounts = []\n"
f" name = '{wolf_name}'\n"
f" ports = []\n"
f" type = 'docker'\n"
)
with open(cfg) as f:
lines = f.readlines()
# Check if already present
if any(f"name = '{wolf_name}'" in l for l in lines):
print(f"'{title}' already exists in config (name={wolf_name}). Remove it first if you want to re-add.")
sys.exit(1)
# Find insert point: just before the second [[profiles]] block, or end of file
profiles_seen, insert_at = 0, len(lines)
for i, line in enumerate(lines):
if line.strip() == '[[profiles]]':
profiles_seen += 1
if profiles_seen == 2:
insert_at = i
break
block_lines = [l + '\n' if not l.endswith('\n') else l for l in block.splitlines()]
new_lines = lines[:insert_at] + block_lines + lines[insert_at:]
with open(cfg, 'w') as f:
f.writelines(new_lines)
print(f"Added '{title}' ({wolf_name}) — tile will appear in Moonlight after Wolf restarts.")
sys.exit(0)
PYEOF
if [ $? -eq 0 ]; then
docker compose restart wolf
echo "Wolf restarted — '$WEB_TITLE' will appear in Moonlight on next connection."
echo ""
echo " Controller tip: Wolf injects a virtual gamepad into the streaming container."
echo " Sites that use the browser Gamepad API (many classic arcade ports, etc.)"
echo " will respond to your controller. Keyboard-driven sites won't respond to"
echo " gamepad buttons directly, but Wolf's virtual keyboard lets you type."
fi
;;
backup) backup)
echo "Set up backups with the modular system: sudo ./setup.sh backup" echo "Set up backups with the modular system: sudo ./setup.sh backup"
;; ;;
@@ -1276,6 +1511,8 @@ PYEOF
echo " ./manage.sh pin - Show recent Moonlight pairing PIN link" echo " ./manage.sh pin - Show recent Moonlight pairing PIN link"
echo " ./manage.sh update - Pull latest Wolf image and restart" echo " ./manage.sh update - Pull latest Wolf image and restart"
echo " ./manage.sh apps - Add / update game launchers in Wolf" echo " ./manage.sh apps - Add / update game launchers in Wolf"
echo " ./manage.sh reorder - Reorder the Moonlight tile list"
echo " ./manage.sh add-web [name] [url] - Add a URL shortcut tile (Firefox kiosk)"
echo " ./manage.sh fix-perms - Fix 'Permission denied' app startup errors" echo " ./manage.sh fix-perms - Fix 'Permission denied' app startup errors"
echo " ./manage.sh backup - How to set up backups" echo " ./manage.sh backup - How to set up backups"
;; ;;
@@ -1579,7 +1816,7 @@ PYEOF
echo " Online together → each player launches their own session," echo " Online together → each player launches their own session,"
echo " all connect to the same game server" echo " all connect to the same game server"
echo "" echo ""
echo "Manage: cd $WOLF_DIR && ./manage.sh {start|stop|restart|logs|status|pin|update|apps}" echo "Manage: cd $WOLF_DIR && ./manage.sh {start|stop|restart|logs|status|pin|update|apps|reorder|add-web}"
echo "" echo ""
echo "── BACKUPS ───────────────────────────────────────────" echo "── BACKUPS ───────────────────────────────────────────"
echo "" echo ""
@@ -1626,6 +1863,8 @@ cd $WOLF_DIR
./manage.sh status # container status ./manage.sh status # container status
./manage.sh update # pull latest image and restart ./manage.sh update # pull latest image and restart
./manage.sh apps # add / update game launchers ./manage.sh apps # add / update game launchers
./manage.sh reorder # reorder the Moonlight tile list
./manage.sh add-web # add a URL shortcut tile (Firefox kiosk)
\`\`\` \`\`\`
## Ports (open on firewall / router) ## Ports (open on firewall / router)