commit 9fd1839ce99c8ed8d044e6e5f5fe2e50a803a1d2 Author: Emi Griffith Date: Fri Jul 10 17:29:47 2026 -0700 Initial commit: app + VPS deploy pipeline diff --git a/deploy/Caddyfile b/deploy/Caddyfile new file mode 100644 index 0000000..6343f65 --- /dev/null +++ b/deploy/Caddyfile @@ -0,0 +1,24 @@ +# /etc/caddy/Caddyfile on the VPS. +# Replace thermograph.example.com with your real hostname. Its A/AAAA record +# must already point at this VPS — Caddy provisions a Let's Encrypt cert on +# first request and auto-renews. Nothing else to do for TLS. + +thermograph.example.com { + encode zstd gzip + + # Reverse-proxy to the loopback uvicorn (see thermograph.service). + reverse_proxy 127.0.0.1:8137 + + log { + output file /var/log/caddy/thermograph.log + } +} + +# If you keep THERMOGRAPH_BASE=/thermograph instead of serving at the root, +# use this block instead of the one above: +# +# thermograph.example.com { +# encode zstd gzip +# reverse_proxy /thermograph/* 127.0.0.1:8137 +# redir / /thermograph/ +# } diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 0000000..4a279be --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Pull the latest code and restart Thermograph. Run on the VPS — the GitHub +# Actions workflow invokes this over SSH, and you can run it by hand too. +# +# ssh deploy@vps '/opt/thermograph/deploy/deploy.sh' +set -euo pipefail + +APP_DIR="${APP_DIR:-/opt/thermograph}" +BRANCH="${BRANCH:-main}" +cd "$APP_DIR" + +echo "==> Fetching $BRANCH" +git fetch --prune origin "$BRANCH" +git reset --hard "origin/$BRANCH" + +echo "==> Installing dependencies" +if [ ! -d .venv ]; then + python3 -m venv .venv +fi +.venv/bin/pip install --upgrade pip -q +.venv/bin/pip install -r backend/requirements.txt -q + +echo "==> Restarting service" +sudo systemctl restart thermograph + +echo "==> Health check" +PORT="$(sed -n 's/^PORT=//p' /etc/thermograph.env)"; PORT="${PORT:-8137}" +BASE="$(sed -n 's/^THERMOGRAPH_BASE=//p' /etc/thermograph.env)"; BASE="${BASE:-/}" +# Normalize: no double slash, allow root. +url="http://127.0.0.1:${PORT}${BASE%/}/" +for i in $(seq 1 15); do + if curl -fsS -o /dev/null "$url"; then + echo "==> OK: $url is serving" + exit 0 + fi + sleep 1 +done +echo "!! Health check failed for $url" >&2 +sudo systemctl status thermograph --no-pager -l || true +exit 1 diff --git a/deploy/thermograph.env.example b/deploy/thermograph.env.example new file mode 100644 index 0000000..cde8d5b --- /dev/null +++ b/deploy/thermograph.env.example @@ -0,0 +1,10 @@ +# Copy to /etc/thermograph.env on the VPS and edit. +# Read by the systemd unit (EnvironmentFile). + +# Port uvicorn binds on loopback. Caddy proxies to this. Keep 8137 unless it clashes. +PORT=8137 + +# Base path the app is served under. +# / -> app at the domain root (recommended on a dedicated domain) +# /thermograph -> app under a sub-path (if sharing the host with other apps) +THERMOGRAPH_BASE=/ diff --git a/deploy/thermograph.service b/deploy/thermograph.service new file mode 100644 index 0000000..dc89889 --- /dev/null +++ b/deploy/thermograph.service @@ -0,0 +1,27 @@ +[Unit] +Description=Thermograph (FastAPI/uvicorn) +After=network-online.target +Wants=network-online.target + +[Service] +Type=exec +User=deploy +Group=deploy +# The repo checkout. uvicorn is run from backend/ so `app:app` resolves. +WorkingDirectory=/opt/thermograph/backend +# THERMOGRAPH_BASE, PORT, etc. See deploy/thermograph.env.example. +EnvironmentFile=/etc/thermograph.env +# Bind loopback only — Caddy terminates TLS and reverse-proxies to us. +ExecStart=/opt/thermograph/.venv/bin/uvicorn app:app --host 127.0.0.1 --port ${PORT} +Restart=on-failure +RestartSec=2 +# Hardening +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=full +ProtectHome=read-only +# The parquet cache must stay writable across deploys. +ReadWritePaths=/opt/thermograph/data /opt/thermograph/logs + +[Install] +WantedBy=multi-user.target