//! Bootstrap host-side artifacts on every archipelago startup. //! //! The update pipeline swaps the archipelago binary but does not touch //! scripts, systemd units, or nginx configuration — those are installed //! once by the ISO builder. Without this module, changes to //! `container-doctor.sh`, the doctor service/timer, or the nginx config //! never reach boxes installed before the change. //! //! Two things are synced on startup: //! 1. Doctor artifacts (container-doctor.sh + service + timer). //! 2. Missing nginx backend proxy blocks required for frontend fetches to //! reach the backend instead of the SPA fallback. //! //! Idempotent: no-ops on boxes that are already in sync. All work is //! best-effort — failures are logged but never abort the backend. use anyhow::{Context, Result}; use std::path::{Path, PathBuf}; use tokio::fs; use tracing::{debug, info, warn}; use crate::update::host_sudo; const DOCTOR_SH: &str = include_str!("../../../scripts/container-doctor.sh"); const DOCTOR_SERVICE: &str = include_str!("../../../image-recipe/configs/archipelago-doctor.service"); const DOCTOR_TIMER: &str = include_str!("../../../image-recipe/configs/archipelago-doctor.timer"); const DOCTOR_SH_PATH: &str = "/home/archipelago/archy/scripts/container-doctor.sh"; const DOCTOR_SERVICE_PATH: &str = "/etc/systemd/system/archipelago-doctor.service"; const DOCTOR_TIMER_PATH: &str = "/etc/systemd/system/archipelago-doctor.timer"; // Kiosk hardening (#36): keep the deployed unit + launcher in sync with the // repo so the CPU/memory cap and the GPU-vs-headless flag selection reach // already-installed nodes via OTA, not just fresh ISOs. const KIOSK_SERVICE: &str = include_str!("../../../image-recipe/configs/archipelago-kiosk.service"); const KIOSK_LAUNCHER: &str = include_str!("../../../image-recipe/configs/archipelago-kiosk-launcher.sh"); const KIOSK_SERVICE_PATH: &str = "/etc/systemd/system/archipelago-kiosk.service"; const KIOSK_LAUNCHER_PATH: &str = "/usr/local/bin/archipelago-kiosk-launcher"; const NGINX_CONF_PATH: &str = "/etc/nginx/sites-available/archipelago"; const NGINX_ENABLED_CONF_PATH: &str = "/etc/nginx/sites-enabled/archipelago"; /// Per-app proxy snippet included by the HTTPS (:443) server block. Carries its /// own `/app/fedimint/` location, so it needs the same B13 asset-rewrite heal as /// the main conf — browsers reach fedimint over HTTPS via this snippet. Absent on /// HTTP-only nodes, in which case the bootstrap loop skips it. const NGINX_HTTPS_SNIPPET_PATH: &str = "/etc/nginx/snippets/archipelago-https-app-proxies.conf"; const RUNTIME_ASSETS_DIR: &str = "/opt/archipelago/web-ui/archipelago-runtime"; /// Inserted into every server block of the nginx config that lacks the /// `/api/app-catalog` proxy. Kept in sync with the canonical block in /// image-recipe/configs/nginx-archipelago.conf. const NGINX_APP_CATALOG_BLOCK: &str = "\n # App Store catalog proxy — backend fetches from configured registries\n # so the browser doesn't hit CORS/CSP. Without this block nginx falls\n # through to the SPA index.html and the frontend gets HTML back instead\n # of JSON.\n location /api/app-catalog {\n proxy_pass http://127.0.0.1:5678;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header Cookie $http_cookie;\n proxy_connect_timeout 15s;\n proxy_read_timeout 30s;\n proxy_send_timeout 15s;\n error_page 502 503 = @backend_unavailable;\n error_page 504 = @backend_timeout;\n }\n\n"; const NGINX_BITCOIN_STATUS_BLOCK: &str = "\n location /bitcoin-status {\n proxy_pass http://127.0.0.1:5678/bitcoin-status;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_connect_timeout 10s;\n proxy_read_timeout 10s;\n proxy_send_timeout 5s;\n error_page 502 503 = @backend_unavailable;\n error_page 504 = @backend_timeout;\n }\n"; /// Inserted into every server block that lacks the `/proxy/lnd/` proxy. Nodes /// flashed before 2026-04-10 shipped an nginx config without this block, so the /// browser's wallet fetches to `/proxy/lnd/*` fell through to the SPA /// index.html and got HTML back instead of JSON ("failing to fetch"). Kept in /// sync with the canonical block in image-recipe/configs/nginx-archipelago.conf. const NGINX_LND_PROXY_BLOCK: &str = "\n # LND REST proxy — backend handles auth + CORS\n location /proxy/lnd/ {\n proxy_pass http://127.0.0.1:5678;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header Cookie $http_cookie;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_connect_timeout 10s;\n proxy_read_timeout 10s;\n proxy_send_timeout 5s;\n error_page 502 503 = @backend_unavailable;\n error_page 504 = @backend_timeout;\n }\n"; /// Inserted into every server block lacking the peer-content streaming proxy. /// Without it, the browser's `