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:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -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"
|
|
||||||
|
|||||||
19
deploy.sh
19
deploy.sh
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user