--- name: add-web-app description: Add an external website as a web-only app to Archipelago (no container needed) disable-model-invocation: true allowed-tools: Bash, Read, Write, Edit, Glob, Grep argument-hint: "[app-id] [url]" --- Add an external website ($ARGUMENTS) as a web-only app to Archipelago. Web-only apps are external websites embedded in the Archipelago UI via iframe. They have no Docker container — they're bookmarks to public websites with full app-like detail pages. ## Architecture External websites that set `X-Frame-Options` or CSP headers blocking iframe embedding are proxied through nginx on **dedicated ports** (one port per site). This approach: - Strips X-Frame-Options so the iframe works - Serves the site at root `/` so SPA routing works correctly - Does NOT use subpath proxying (`/ext/app/`) which breaks SPAs - Optionally injects NIP-07 nostr-provider.js for Nostr login ## Steps ### 1. Choose a port Pick an unused port in the 8900-8999 range. Current allocations: - 8901: botfights.net - 8902: 484.kitchen - 8903: present.l484.com ### 2. Add nginx proxy server block Add a new `server` block to `image-recipe/configs/nginx-archipelago.conf` at the end: ```nginx server { listen {PORT}; server_name _; location / { proxy_pass https://{DOMAIN}; proxy_http_version 1.1; proxy_set_header Host {DOMAIN}; proxy_set_header Accept-Encoding ""; proxy_ssl_server_name on; proxy_hide_header X-Frame-Options; proxy_hide_header Content-Security-Policy; proxy_hide_header Cross-Origin-Embedder-Policy; proxy_hide_header Cross-Origin-Opener-Policy; proxy_hide_header Cross-Origin-Resource-Policy; sub_filter '' ''; sub_filter_once on; } location = /nostr-provider.js { alias /opt/archipelago/web-ui/nostr-provider.js; } } ``` ### 3. Add to appLauncher.ts EXTERNAL_PROXY_PORT In `neode-ui/src/stores/appLauncher.ts`, add the domain-to-port mapping: ```typescript const EXTERNAL_PROXY_PORT: Record = { // ... existing entries '{DOMAIN}': {PORT}, } ``` ### 4. Add to Apps.vue WEB_ONLY_APP_URLS and WEB_ONLY_APPS In `neode-ui/src/views/Apps.vue`: 1. Add to `WEB_ONLY_APP_URLS`: `'{app-id}': 'https://{DOMAIN}'` 2. Add to `WEB_ONLY_APPS` with a synthetic `PackageDataEntry`: - state: `'running'` - manifest with id, title, version, description - static-files with icon path ### 5. Add to dummyApps.ts In `neode-ui/src/utils/dummyApps.ts`, add a full `PackageDataEntry` with: - Long description (for detail page) - Website URL in manifest - Icon path ### 6. Add to AppDetails.vue WEB_ONLY_APP_URLS In `neode-ui/src/views/AppDetails.vue`, add to the `WEB_ONLY_APP_URLS` map. ### 7. Add app icon Place icon at `neode-ui/public/assets/img/app-icons/{app-id}.{png|webp|svg}` ### 8. Deploy ```bash # Build frontend cd neode-ui && npm run build # Deploy nginx config scp image-recipe/configs/nginx-archipelago.conf archipelago@192.168.1.228:/tmp/ ssh archipelago@192.168.1.228 "sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago && sudo nginx -t && sudo systemctl reload nginx" # Deploy frontend rsync -az --delete --exclude aiui --exclude claude-login.html web/dist/neode-ui/ archipelago@192.168.1.228:/opt/archipelago/web-ui/ ``` ### 9. Verify 1. Open Archipelago UI 2. Web-only app appears in My Apps (sorted alphabetically before container apps) 3. Click app card -> detail page with title, description, launch button, no container buttons 4. Click Launch -> iframe loads the external website correctly 5. All assets load (no 404s in Network tab) 6. `window.nostr` available in iframe console (NIP-07) ## Files Modified | File | What to add | |------|-------------| | `image-recipe/configs/nginx-archipelago.conf` | New server block with proxy | | `neode-ui/src/stores/appLauncher.ts` | EXTERNAL_PROXY_PORT entry | | `neode-ui/src/views/Apps.vue` | WEB_ONLY_APP_URLS + WEB_ONLY_APPS entries | | `neode-ui/src/views/AppDetails.vue` | WEB_ONLY_APP_URLS entry | | `neode-ui/src/utils/dummyApps.ts` | Full PackageDataEntry for detail page | | `neode-ui/public/assets/img/app-icons/` | App icon file |