diff --git a/.claude/plans/reflective-meandering-castle.md b/.claude/plans/reflective-meandering-castle.md index 43594477..3d94a70f 100644 --- a/.claude/plans/reflective-meandering-castle.md +++ b/.claude/plans/reflective-meandering-castle.md @@ -113,7 +113,7 @@ After getting Claude Max OAuth working on the live server, hardening the deploy - **Change**: Add security flags to default container `run_args`: `--read-only` (with tmpfs for /tmp), `--cap-drop=ALL`, `--security-opt=no-new-privileges:true`. Create per-app capability mapping for apps that need specific caps. - **Verify**: Install an app, `podman inspect` shows security constraints -### Task 20: ISO build script sync +### Task 20: ISO build script sync [DONE] - **Files**: `image-recipe/configs/nginx-archipelago.conf`, `image-recipe/configs/archipelago.service` - **Change**: Ensure all recent nginx changes (new app proxies, AIUI proxies) are in the image-recipe config. Verify systemd service is current. Check that the auto-installer includes dummy content files for Cloud sections. - **Verify**: Configs match live server state diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index 13c7cbfc..a52b8c4a 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -134,7 +134,7 @@ FROM debian:bookworm ENV DEBIAN_FRONTEND=noninteractive -# Install all packages we need including nginx and podman +# Install all packages we need including nginx, podman, and openssl (for self-signed certs) RUN apt-get update && apt-get install -y \ linux-image-amd64 \ grub-efi-amd64 \ @@ -153,6 +153,7 @@ RUN apt-get update && apt-get install -y \ htop \ vim-tiny \ ca-certificates \ + openssl \ locales \ console-setup \ keyboard-configuration \ @@ -180,6 +181,17 @@ RUN rm -f /etc/nginx/sites-enabled/default COPY nginx-archipelago.conf /etc/nginx/sites-available/archipelago RUN ln -sf /etc/nginx/sites-available/archipelago /etc/nginx/sites-enabled/archipelago +# Install nginx snippets (PWA config, HTTPS app proxies) +COPY snippets/ /etc/nginx/snippets/ + +# Generate self-signed SSL certificate for HTTPS (PWA install requires secure context) +RUN mkdir -p /etc/archipelago/ssl && \ + openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /etc/archipelago/ssl/archipelago.key \ + -out /etc/archipelago/ssl/archipelago.crt \ + -subj "/C=XX/ST=Bitcoin/L=Node/O=Archipelago/CN=archipelago" && \ + chmod 600 /etc/archipelago/ssl/archipelago.key + # Create archipelago systemd service COPY archipelago.service /etc/systemd/system/archipelago.service @@ -189,10 +201,11 @@ RUN systemctl enable NetworkManager || true && \ systemctl enable nginx || true && \ systemctl enable archipelago || true -# Create directories +# Create directories (including Cloud storage for FileBrowser) RUN mkdir -p /var/lib/archipelago/{data,config,containers} && \ mkdir -p /etc/archipelago && \ mkdir -p /opt/archipelago/{bin,scripts,web-ui} && \ + mkdir -p /var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads} && \ chown -R archipelago:archipelago /var/lib/archipelago /opt/archipelago # Clean up @@ -200,6 +213,16 @@ RUN apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* DOCKERFILE + # Copy nginx snippets for HTTPS (PWA, app proxies) + if [ -d "$SCRIPT_DIR/configs/snippets" ]; then + mkdir -p "$WORK_DIR/snippets" + cp "$SCRIPT_DIR/configs/snippets/"*.conf "$WORK_DIR/snippets/" 2>/dev/null || true + echo " Using nginx snippets from configs/snippets/" + else + mkdir -p "$WORK_DIR/snippets" + echo " ⚠ No nginx snippets found, HTTPS features may not work" + fi + # Use nginx config from configs/ (includes app proxies for Nextcloud, Vaultwarden, Immich, Penpot) if [ -f "$SCRIPT_DIR/configs/nginx-archipelago.conf" ]; then cp "$SCRIPT_DIR/configs/nginx-archipelago.conf" "$WORK_DIR/nginx-archipelago.conf" diff --git a/image-recipe/configs/snippets/archipelago-https-app-proxies.conf b/image-recipe/configs/snippets/archipelago-https-app-proxies.conf new file mode 100644 index 00000000..cffe10fe --- /dev/null +++ b/image-recipe/configs/snippets/archipelago-https-app-proxies.conf @@ -0,0 +1,180 @@ +# App proxies for HTTPS - avoids mixed content when embedding apps from HTTPS page +# Complete list for all apps that may be launched from the UI +location /app/grafana/ { + proxy_pass http://127.0.0.1:3000/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/uptime-kuma/ { + proxy_pass http://127.0.0.1:3001/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/searxng/ { + proxy_pass http://127.0.0.1:8888/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/portainer/ { + proxy_pass http://127.0.0.1:9000/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/filebrowser/ { + client_max_body_size 10G; + proxy_pass http://127.0.0.1:8083/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; + proxy_request_buffering off; +} +location /app/endurain/ { + proxy_pass http://127.0.0.1:8080/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/lnd/ { + proxy_pass http://127.0.0.1:8081/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; + proxy_read_timeout 300s; + proxy_send_timeout 300s; +} +location /app/onlyoffice/ { + proxy_pass http://127.0.0.1:9980/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/jellyfin/ { + proxy_pass http://127.0.0.1:8096/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/photoprism/ { + proxy_pass http://127.0.0.1:2342/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/mempool/ { + proxy_pass http://127.0.0.1:4080/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; + proxy_read_timeout 300s; + proxy_send_timeout 300s; +} +location /app/fedimint/ { + proxy_pass http://127.0.0.1:8175/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; + proxy_read_timeout 300s; + proxy_send_timeout 300s; +} +location /app/tailscale/ { + proxy_pass http://127.0.0.1:8240/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/ollama/ { + proxy_pass http://127.0.0.1:11434/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/bitcoin-ui/ { + proxy_pass http://127.0.0.1:8334/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/electrs/ { + proxy_pass http://127.0.0.1:50002/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} +location /app/nginx-proxy-manager/ { + proxy_pass http://127.0.0.1:81/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_hide_header X-Frame-Options; + proxy_hide_header Content-Security-Policy; +} diff --git a/image-recipe/configs/snippets/archipelago-pwa.conf b/image-recipe/configs/snippets/archipelago-pwa.conf new file mode 100644 index 00000000..66b81f1d --- /dev/null +++ b/image-recipe/configs/snippets/archipelago-pwa.conf @@ -0,0 +1,16 @@ +# PWA installability - required for Install (not just Add to Home Screen) on Android +# Manifest MUST be served with application/manifest+json - Chrome rejects otherwise +location = /manifest.webmanifest { + default_type application/manifest+json; + add_header Cache-Control "public, max-age=0, must-revalidate"; +} +# Service worker - no cache so updates apply +location ~ ^/(sw\.js|workbox-.*\.js|registerSW\.js)$ { + add_header Content-Type application/javascript; + add_header Service-Worker-Allowed /; + add_header Cache-Control "no-cache, no-store, must-revalidate"; +} +# index.html - avoid aggressive cache for PWA updates +location = /index.html { + add_header Cache-Control "public, max-age=0, must-revalidate"; +}