feat: no-root deploy, remove CI deploy stage, deploy alerts

- deploy.sh installs sops/age to ./bin/ (no root/sudo needed)
- Remove CI deploy stage — supervisor auto-pulls and deploys
  (zero CI secrets: no SSH keys, no deploy credentials)
- Supervisor sends alert on deploy success/failure via webhook

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-23 18:23:19 +01:00
parent e4bd9378f5
commit 8558fd6b40
4 changed files with 22 additions and 26 deletions

4
.gitignore vendored
View File

@@ -55,6 +55,6 @@ dist/
build/ build/
*.egg-info/ *.egg-info/
# Tailwind CSS # Local binaries (tailwindcss, sops, age)
bin/tailwindcss bin/
web/src/padelnomics/static/css/output.css web/src/padelnomics/static/css/output.css

View File

@@ -1,6 +1,5 @@
stages: stages:
- test - test
- deploy
test: test:
stage: test stage: test
@@ -15,18 +14,6 @@ test:
- if: $CI_COMMIT_BRANCH == "master" - if: $CI_COMMIT_BRANCH == "master"
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
deploy: # Deployment is handled by the on-server supervisor (src/padelnomics/supervisor.py).
stage: deploy # It polls git every 60s, detects code changes, and runs deploy.sh automatically.
image: alpine:latest # No CI secrets needed — zero SSH keys, zero deploy credentials.
needs: [test]
rules:
- if: $CI_COMMIT_BRANCH == "master"
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
script:
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "cd /opt/padelnomics && git pull origin master && ./deploy.sh"

View File

@@ -2,6 +2,11 @@
set -euo pipefail set -euo pipefail
# ── Ensure sops + age are installed ─────────────────────── # ── Ensure sops + age are installed ───────────────────────
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
BIN_DIR="$APP_DIR/bin"
mkdir -p "$BIN_DIR"
export PATH="$BIN_DIR:$PATH"
ARCH=$(uname -m) ARCH=$(uname -m)
case "$ARCH" in case "$ARCH" in
x86_64) ARCH_SOPS="amd64"; ARCH_AGE="amd64" ;; x86_64) ARCH_SOPS="amd64"; ARCH_AGE="amd64" ;;
@@ -10,23 +15,23 @@ case "$ARCH" in
esac esac
if ! command -v age &>/dev/null; then if ! command -v age &>/dev/null; then
echo "==> Installing age..." echo "==> Installing age to $BIN_DIR..."
AGE_VERSION="v1.3.1" AGE_VERSION="v1.3.1"
curl -fsSL "https://dl.filippo.io/age/${AGE_VERSION}?for=linux/${ARCH_AGE}" -o /tmp/age.tar.gz curl -fsSL "https://dl.filippo.io/age/${AGE_VERSION}?for=linux/${ARCH_AGE}" -o /tmp/age.tar.gz
tar -xzf /tmp/age.tar.gz -C /usr/local/bin --strip-components=1 age/age age/age-keygen tar -xzf /tmp/age.tar.gz -C "$BIN_DIR" --strip-components=1 age/age age/age-keygen
chmod +x /usr/local/bin/age /usr/local/bin/age-keygen chmod +x "$BIN_DIR/age" "$BIN_DIR/age-keygen"
rm /tmp/age.tar.gz rm /tmp/age.tar.gz
fi fi
if ! command -v sops &>/dev/null; then if ! command -v sops &>/dev/null; then
echo "==> Installing sops..." echo "==> Installing sops to $BIN_DIR..."
SOPS_VERSION="v3.12.1" SOPS_VERSION="v3.12.1"
curl -fsSL "https://github.com/getsops/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.${ARCH_SOPS}" -o /usr/local/bin/sops curl -fsSL "https://github.com/getsops/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.${ARCH_SOPS}" -o "$BIN_DIR/sops"
chmod +x /usr/local/bin/sops chmod +x "$BIN_DIR/sops"
fi fi
# ── Ensure age keypair exists ───────────────────────────── # ── Ensure age keypair exists ─────────────────────────────
AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-/opt/padelnomics/age-key.txt}" AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-$APP_DIR/age-key.txt}"
export SOPS_AGE_KEY_FILE="$AGE_KEY_FILE" export SOPS_AGE_KEY_FILE="$AGE_KEY_FILE"
if [ ! -f "$AGE_KEY_FILE" ]; then if [ ! -f "$AGE_KEY_FILE" ]; then

View File

@@ -321,7 +321,11 @@ def tick() -> None:
# Deploy web app if code changed # Deploy web app if code changed
if os.getenv("SUPERVISOR_GIT_PULL") and web_code_changed(): if os.getenv("SUPERVISOR_GIT_PULL") and web_code_changed():
logger.info("Web code changed — deploying") logger.info("Web code changed — deploying")
run_shell("./deploy.sh") ok, _ = run_shell("./deploy.sh")
if ok:
send_alert("Deploy succeeded")
else:
send_alert("Deploy FAILED — check journalctl -u padelnomics-supervisor")
finally: finally:
conn.close() conn.close()