121 lines
5.3 KiB
Bash
Executable File
121 lines
5.3 KiB
Bash
Executable File
#!/bin/bash
|
|
# Bootstrap Materia supervisor after setup_server.sh + adding keys.
|
|
# Run once on the server after SSH deploy key is added to GitLab
|
|
# and the server age key is committed to .env.prod.sops.
|
|
#
|
|
# Usage:
|
|
# ssh root@<server_ip> 'bash -s' < infra/bootstrap_supervisor.sh
|
|
#
|
|
# Prerequisites:
|
|
# - setup_server.sh already run (beanflows_service user, SSH deploy key, age keypair, uv)
|
|
# - Deploy key added to GitLab (Settings → Repository → Deploy Keys)
|
|
# - Server age public key added to .sops.yaml + .env.prod.sops committed + pushed
|
|
|
|
set -euo pipefail
|
|
|
|
SERVICE_USER="beanflows_service"
|
|
REPO_DIR="/opt/materia"
|
|
GITEA_REPO="ssh://git@git.padelnomics.io:2222/deemanone/beanflows.git"
|
|
UV="/home/${SERVICE_USER}/.local/bin/uv"
|
|
|
|
[ "$(id -u)" = "0" ] || { echo "ERROR: Run as root"; exit 1; }
|
|
|
|
# ── Check age keypair ─────────────────────────────────────────────────────────
|
|
|
|
AGE_KEY_FILE="/home/${SERVICE_USER}/.config/sops/age/keys.txt"
|
|
if [ ! -f "${AGE_KEY_FILE}" ]; then
|
|
echo "ERROR: Age keypair not found at ${AGE_KEY_FILE}"
|
|
echo "Run infra/setup_server.sh first, then add the printed keys, then re-run."
|
|
exit 1
|
|
fi
|
|
|
|
# ── Clone or update repository ────────────────────────────────────────────────
|
|
|
|
if [ -d "${REPO_DIR}/.git" ]; then
|
|
sudo -u "${SERVICE_USER}" git -C "${REPO_DIR}" fetch --tags --prune-tags origin
|
|
else
|
|
sudo -u "${SERVICE_USER}" git clone \
|
|
"${GITEA_REPO}" "${REPO_DIR}"
|
|
fi
|
|
|
|
LATEST_TAG=$(sudo -u "${SERVICE_USER}" \
|
|
git -C "${REPO_DIR}" tag --list --sort=-version:refname "v*" | head -1)
|
|
if [ -n "${LATEST_TAG}" ]; then
|
|
sudo -u "${SERVICE_USER}" git -C "${REPO_DIR}" checkout --detach "${LATEST_TAG}"
|
|
fi
|
|
|
|
# ── Decrypt secrets ───────────────────────────────────────────────────────────
|
|
|
|
sudo -u "${SERVICE_USER}" bash -c \
|
|
"SOPS_AGE_KEY_FILE=/home/${SERVICE_USER}/.config/sops/age/keys.txt \
|
|
sops --input-type dotenv --output-type dotenv -d ${REPO_DIR}/.env.prod.sops > ${REPO_DIR}/.env"
|
|
chmod 600 "${REPO_DIR}/.env"
|
|
|
|
# ── Python dependencies ───────────────────────────────────────────────────────
|
|
|
|
sudo -u "${SERVICE_USER}" bash -c "cd ${REPO_DIR} && ${UV} sync --all-packages"
|
|
|
|
# ── Systemd supervisor service ────────────────────────────────────────────────
|
|
|
|
cp "${REPO_DIR}/infra/supervisor/materia-supervisor.service" /etc/systemd/system/
|
|
systemctl daemon-reload
|
|
systemctl enable --now materia-supervisor
|
|
|
|
# ── R2 backup timer (optional) ────────────────────────────────────────────────
|
|
# Enabled only when R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, and R2_ENDPOINT
|
|
# are present in .env. Idempotent: rclone.conf is always regenerated from env
|
|
# (so credential rotations take effect on re-run); systemd units are only copied
|
|
# when they differ from what's already installed.
|
|
|
|
_env_get() { grep -E "^${1}=" "${REPO_DIR}/.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'"'" || true; }
|
|
|
|
R2_KEY=$(_env_get R2_ACCESS_KEY_ID)
|
|
R2_SECRET=$(_env_get R2_SECRET_ACCESS_KEY)
|
|
R2_ENDPOINT=$(_env_get R2_ENDPOINT)
|
|
|
|
if [ -n "${R2_KEY}" ] && [ -n "${R2_SECRET}" ] && [ -n "${R2_ENDPOINT}" ]; then
|
|
echo "$(date '+%H:%M:%S') ==> Configuring R2 backup..."
|
|
|
|
RCLONE_CONF_DIR="/home/${SERVICE_USER}/.config/rclone"
|
|
RCLONE_CONF="${RCLONE_CONF_DIR}/rclone.conf"
|
|
|
|
sudo -u "${SERVICE_USER}" mkdir -p "${RCLONE_CONF_DIR}"
|
|
|
|
# Always write from env so credential rotations take effect automatically.
|
|
cat > "${RCLONE_CONF}" <<EOF
|
|
[r2]
|
|
type = s3
|
|
provider = Cloudflare
|
|
access_key_id = ${R2_KEY}
|
|
secret_access_key = ${R2_SECRET}
|
|
endpoint = ${R2_ENDPOINT}
|
|
acl = private
|
|
no_check_bucket = true
|
|
EOF
|
|
chown "${SERVICE_USER}:${SERVICE_USER}" "${RCLONE_CONF}"
|
|
chmod 600 "${RCLONE_CONF}"
|
|
|
|
UNITS_CHANGED=0
|
|
for unit in materia-backup.service materia-backup.timer; do
|
|
if ! diff -q "${REPO_DIR}/infra/backup/${unit}" "/etc/systemd/system/${unit}" >/dev/null 2>&1; then
|
|
cp "${REPO_DIR}/infra/backup/${unit}" /etc/systemd/system/
|
|
UNITS_CHANGED=1
|
|
fi
|
|
done
|
|
[ "${UNITS_CHANGED}" = "1" ] && systemctl daemon-reload
|
|
|
|
systemctl enable --now materia-backup.timer
|
|
echo "$(date '+%H:%M:%S') ==> R2 backup timer enabled."
|
|
else
|
|
echo "$(date '+%H:%M:%S') ==> R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY / R2_ENDPOINT not set — skipping backup timer."
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Bootstrap complete! ==="
|
|
echo ""
|
|
echo "Check status: systemctl status materia-supervisor"
|
|
echo "View logs: journalctl -u materia-supervisor -f"
|
|
echo "Workflow status: sudo -u ${SERVICE_USER} ${UV} run -p ${REPO_DIR} python src/materia/supervisor.py status"
|
|
echo "Backup timer: systemctl list-timers materia-backup.timer"
|
|
echo "Tag: $(sudo -u "${SERVICE_USER}" git -C "${REPO_DIR}" describe --tags --always)"
|