#!/bin/bash # Bootstrap script for Materia supervisor instance. # Run once on a fresh server after setup_server.sh. # # Usage: # ssh root@ 'bash -s' < infra/bootstrap_supervisor.sh # # Prerequisites: # - setup_server.sh already run (beanflows_service user, SSH deploy key, age keypair) # - 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" GITLAB_PROJECT="deemanone/materia" UV="/home/${SERVICE_USER}/.local/bin/uv" echo "=== Materia Supervisor Bootstrap ===" echo "" [ "$(id -u)" = "0" ] || { echo "ERROR: Run as root"; exit 1; } # ── System dependencies ──────────────────────────────────────────────────────── echo "--- Installing system dependencies ---" apt-get update -q apt-get install -y -q git curl ca-certificates # ── uv (installed as service user) ──────────────────────────────────────────── echo "--- Installing uv ---" if [ ! -f "${UV}" ]; then sudo -u "${SERVICE_USER}" bash -c \ 'curl -LsSf https://astral.sh/uv/install.sh | sh' fi # ── sops + age (as root, idempotent — setup_server.sh may have done this) ───── echo "--- Installing sops + age ---" ARCH=$(uname -m) case "${ARCH}" in x86_64) ARCH_TAG="amd64" ;; aarch64) ARCH_TAG="arm64" ;; *) echo "Unsupported architecture: ${ARCH}"; exit 1 ;; esac if ! command -v age >/dev/null 2>&1; then AGE_VERSION="v1.3.1" curl -fsSL "https://dl.filippo.io/age/${AGE_VERSION}?for=linux/${ARCH_TAG}" -o /tmp/age.tar.gz tar -xzf /tmp/age.tar.gz -C /usr/local/bin --strip-components=1 age/age age/age-keygen chmod +x /usr/local/bin/age /usr/local/bin/age-keygen rm /tmp/age.tar.gz fi if ! command -v sops >/dev/null 2>&1; then SOPS_VERSION="v3.12.1" curl -fsSL \ "https://github.com/getsops/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux.${ARCH_TAG}" \ -o /usr/local/bin/sops chmod +x /usr/local/bin/sops fi # ── Clone repository via SSH as service user ────────────────────────────────── echo "--- Cloning repository ---" if [ -d "${REPO_DIR}/.git" ]; then echo "Repository already exists — fetching latest tags..." sudo -u "${SERVICE_USER}" git -C "${REPO_DIR}" fetch --tags --prune-tags origin else sudo -u "${SERVICE_USER}" git clone \ "git@gitlab.com:${GITLAB_PROJECT}.git" "${REPO_DIR}" fi # Checkout latest release tag (same logic as supervisor) LATEST_TAG=$(sudo -u "${SERVICE_USER}" \ git -C "${REPO_DIR}" tag --list --sort=-version:refname "v*" | head -1) if [ -n "${LATEST_TAG}" ]; then echo "Checking out ${LATEST_TAG}..." sudo -u "${SERVICE_USER}" git -C "${REPO_DIR}" checkout --detach "${LATEST_TAG}" else echo "No release tags found — staying on current HEAD" fi # ── Check age keypair ───────────────────────────────────────────────────────── echo "--- Checking age keypair ---" AGE_KEY_FILE="/home/${SERVICE_USER}/.config/sops/age/keys.txt" if [ ! -f "${AGE_KEY_FILE}" ]; then echo "" echo "ERROR: Age keypair not found at ${AGE_KEY_FILE}" echo "" echo "Run infra/setup_server.sh first, then:" echo " 1. Add the SSH deploy key to GitLab (Settings → Repository → Deploy Keys)" echo " 2. Add the age public key to .sops.yaml on your workstation" echo " 3. Run: sops updatekeys .env.prod.sops && git push" echo " 4. Re-run this bootstrap" exit 1 fi # ── Decrypt secrets (as service user — SOPS auto-discovers age key from XDG) ── echo "--- Decrypting secrets from .env.prod.sops ---" sudo -u "${SERVICE_USER}" bash -c \ "sops --input-type dotenv --output-type dotenv -d ${REPO_DIR}/.env.prod.sops > ${REPO_DIR}/.env" chmod 600 "${REPO_DIR}/.env" echo "Secrets written to ${REPO_DIR}/.env" # ── Data directories ─────────────────────────────────────────────────────────── echo "--- Creating data directories ---" mkdir -p /data/materia/landing chown -R "${SERVICE_USER}:${SERVICE_USER}" /data/materia # ── Python dependencies (as service user) ───────────────────────────────────── echo "--- Installing Python dependencies ---" sudo -u "${SERVICE_USER}" bash -c "cd ${REPO_DIR} && ${UV} sync --all-packages" # ── Systemd service (as root) ────────────────────────────────────────────────── echo "--- Setting up systemd service ---" cp "${REPO_DIR}/infra/supervisor/materia-supervisor.service" \ /etc/systemd/system/materia-supervisor.service echo "--- Enabling and starting service ---" systemctl daemon-reload systemctl enable materia-supervisor systemctl restart materia-supervisor 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 "" echo "Repo: ${REPO_DIR}" echo "Tag: $(sudo -u "${SERVICE_USER}" git -C "${REPO_DIR}" describe --tags --always)"