From e4bd9378f562746cb5fc4f4dbde4223976c2495b Mon Sep 17 00:00:00 2001 From: Deeman Date: Mon, 23 Feb 2026 18:13:06 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20self-provisioning=20deploy.sh=20?= =?UTF-8?q?=E2=80=94=20auto-installs=20sops+age,=20generates=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On first deploy to a new server, deploy.sh: 1. Installs age and sops binaries if missing 2. Generates an age keypair if missing 3. Prints the public key and exits with instructions All checks are idempotent — subsequent deploys skip to decryption. Removed duplicate sops/age setup from setup_server.sh (deploy.sh handles it). Co-Authored-By: Claude Opus 4.6 --- deploy.sh | 47 ++++++++++++++++++++++++++++++++++++++- infra/setup_server.sh | 51 ++++--------------------------------------- 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/deploy.sh b/deploy.sh index 74497c5..421133d 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,8 +1,53 @@ #!/usr/bin/env bash set -euo pipefail +# ── Ensure sops + age are installed ─────────────────────── +ARCH=$(uname -m) +case "$ARCH" in + x86_64) ARCH_SOPS="amd64"; ARCH_AGE="amd64" ;; + aarch64) ARCH_SOPS="arm64"; ARCH_AGE="arm64" ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; +esac + +if ! command -v age &>/dev/null; then + echo "==> Installing age..." + AGE_VERSION="v1.3.1" + 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 + chmod +x /usr/local/bin/age /usr/local/bin/age-keygen + rm /tmp/age.tar.gz +fi + +if ! command -v sops &>/dev/null; then + echo "==> Installing sops..." + 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 + chmod +x /usr/local/bin/sops +fi + +# ── Ensure age keypair exists ───────────────────────────── +AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-/opt/padelnomics/age-key.txt}" +export SOPS_AGE_KEY_FILE="$AGE_KEY_FILE" + +if [ ! -f "$AGE_KEY_FILE" ]; then + echo "==> Generating age keypair..." + age-keygen -o "$AGE_KEY_FILE" 2>&1 + chmod 600 "$AGE_KEY_FILE" + AGE_PUB=$(grep "public key:" "$AGE_KEY_FILE" | awk '{print $NF}') + echo "" + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "!! NEW SERVER — add this public key to .sops.yaml: !!" + echo "!! !!" + echo "!! $AGE_PUB !!" + echo "!! !!" + echo "!! Then run: sops updatekeys .env.prod.sops !!" + echo "!! Commit, push, and re-deploy. !!" + echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + echo "" + exit 1 +fi + # ── Decrypt secrets ─────────────────────────────────────── -export SOPS_AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-/opt/padelnomics/age-key.txt}" sops --input-type dotenv --output-type dotenv -d .env.prod.sops > .env chmod 600 .env diff --git a/infra/setup_server.sh b/infra/setup_server.sh index 8c7ed79..89e9f83 100644 --- a/infra/setup_server.sh +++ b/infra/setup_server.sh @@ -38,45 +38,8 @@ else echo "Deploy key already exists, skipping" fi -# Install sops + age (encrypted secrets) -AGE_KEY_FILE="$APP_DIR/age-key.txt" -ARCH=$(uname -m) -case "$ARCH" in - x86_64) ARCH_SOPS="amd64"; ARCH_AGE="amd64" ;; - aarch64) ARCH_SOPS="arm64"; ARCH_AGE="arm64" ;; - *) echo "Unsupported architecture: $ARCH"; exit 1 ;; -esac - -if ! command -v age &>/dev/null; then - echo "Installing age..." - AGE_VERSION="v1.3.1" - 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 - chmod +x /usr/local/bin/age /usr/local/bin/age-keygen - rm /tmp/age.tar.gz - echo "Installed age $(age --version)" -else - echo "age already installed, skipping" -fi - -if ! command -v sops &>/dev/null; then - echo "Installing sops..." - 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 - chmod +x /usr/local/bin/sops - echo "Installed sops $(sops --version)" -else - echo "sops already installed, skipping" -fi - -# Generate age keypair for this server (used by deploy.sh to decrypt secrets) -if [ ! -f "$AGE_KEY_FILE" ]; then - age-keygen -o "$AGE_KEY_FILE" 2>&1 - chmod 600 "$AGE_KEY_FILE" - echo "Generated age key: $AGE_KEY_FILE" -else - echo "Age key already exists: $AGE_KEY_FILE" -fi +# NOTE: sops + age installation and keypair generation is handled by deploy.sh +# (self-provisioning on first run). No need to install here. # Install rclone (landing zone backup to R2) if ! command -v rclone &>/dev/null; then @@ -110,14 +73,8 @@ echo "1. Add this deploy key to GitLab (Settings → Repository → Deploy Keys, echo "" cat "$KEY_PATH.pub" echo "" -echo "2. Add this server's age public key to .sops.yaml (comma-separated with existing keys):" -echo "" -grep "public key:" "$AGE_KEY_FILE" | awk '{print $NF}' -echo "" -echo " Then re-encrypt prod secrets: sops updatekeys .env.prod.sops" -echo "" -echo "3. Clone the repo:" +echo "2. Clone the repo:" echo " git clone git@gitlab.com:YOUR_USER/padelnomics.git $APP_DIR" echo "" -echo "4. Deploy:" +echo "3. Deploy (first run installs sops+age and generates server keypair):" echo " cd $APP_DIR && bash deploy.sh"