/root/.ssh is mounted read-only in the management container, causing stderr
noise on every SSH connection. Routing to /dev/null avoids the write attempt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sshGet() and deploy() built SSH commands via template literal string
interpolation, which would allow shell injection if host/path values
from the app registry were tampered with. Replaced with spawnAsync()
using shell: false and explicit argv arrays. Added path validation
guard (/^\/opt\/[a-z0-9-]+$/) before both SSH calls. Also removes
the event-loop-blocking execSync in deploy().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fastify-raw-body was never registered, so HMAC ran over re-serialised
JSON and every Forgejo delivery failed with 401
- accept X-Forgejo-Signature / X-Gitea-Signature (bare hex) as well as
the GitHub-style sha256= prefix, and reject length mismatches instead
of crashing timingSafeEqual with a 500
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Without these, the updater sent Bearer <empty> to the auth registry
endpoint, causing 401 and an empty app registry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
updater now fetches app registry from auth /api/auth/internal/registry
and uses internal_host/internal_port for SSH status checks and deploys.
Removes hardcoded host map; platform infra (auth, portal, settings)
kept in INFRA_DEPLOY/INFRA_HEALTH constants.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apps without a /health route return 404 from nginx, but the service IS
running. Treat any HTTP response as up; only connection errors are down.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parallel-fetches /health on each app LXC, returns up/down + response
time. Used by the portal Uptime tab instead of embedding Uptime Kuma.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>