From 37f85d6adf72d787accbd87051c11ec81a21d03b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 15:21:30 +0000 Subject: [PATCH] =?UTF-8?q?wolf:=20add=20./manage.sh=20add-web=20=E2=80=94?= =?UTF-8?q?=20URL=20shortcut=20tiles=20in=20Moonlight=20via=20Firefox=20ki?= =?UTF-8?q?osk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs --- services/wolf.sh | 162 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 1 deletion(-) diff --git a/services/wolf.sh b/services/wolf.sh index 2bbecf9..72326e3 100644 --- a/services/wolf.sh +++ b/services/wolf.sh @@ -1289,6 +1289,164 @@ PYEOF 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- + 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) echo "Set up backups with the modular system: sudo ./setup.sh backup" ;; @@ -1354,6 +1512,7 @@ PYEOF echo " ./manage.sh update - Pull latest Wolf image and restart" 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 backup - How to set up backups" ;; @@ -1657,7 +1816,7 @@ PYEOF echo " Online together → each player launches their own session," echo " all connect to the same game server" echo "" - echo "Manage: cd $WOLF_DIR && ./manage.sh {start|stop|restart|logs|status|pin|update|apps|reorder}" + echo "Manage: cd $WOLF_DIR && ./manage.sh {start|stop|restart|logs|status|pin|update|apps|reorder|add-web}" echo "" echo "── BACKUPS ───────────────────────────────────────────" echo "" @@ -1705,6 +1864,7 @@ cd $WOLF_DIR ./manage.sh update # pull latest image and restart ./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)