# ───────────────────────────────────────────────────────────────────────────── # Docker stack deploy pipeline — mirrors .gitea/workflows/deploy.yml # # Triggered on pushes to main when files under apps/*.tfvars change. # Two jobs run in sequence: # deploy-docker — loops over added/modified tfvars and applies each app # destroy-docker — loops over deleted tfvars, recovers content from git # history, destroys each app and removes its workspace # # Both jobs process all changed files sequentially (GitLab CI does not support # dynamic matrix jobs natively). A failure in one app is recorded but processing # continues for the remaining apps (fail-fast: false equivalent). # # Key difference from Gitea: deleted tfvars content is recovered via # git show $CI_COMMIT_BEFORE_SHA:$TFVARS # instead of checking out the previous commit. # ───────────────────────────────────────────────────────────────────────────── # ─── Shared template: OpenTofu + gopass setup ──────────────────────────────── .deploy_base: image: ubuntu:22.04 variables: # Full clone so git show $CI_COMMIT_BEFORE_SHA works for deleted files GIT_DEPTH: "0" before_script: - apt-get update -qq && apt-get install -y -qq curl git jq gnupg - | curl -fsSL https://get.opentofu.org/install-opentofu.sh \ | sh -s -- --install-method=standalone --opentofu-version=$TOFU_VERSION - | curl -fsSL https://github.com/gopasspw/gopass/releases/latest/download/gopass-linux-amd64.tar.gz \ | tar xz mv gopass /usr/local/bin/ - | echo "$GOPASS_GPG_KEY" | gpg --batch --import gopass clone "$GOPASS_STORE_REPO" # ─── Deploy / Update ───────────────────────────────────────────────────────── deploy-docker: extends: .deploy_base stage: deploy rules: - if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"' changes: - apps/*.tfvars script: - | ADDED_MODIFIED=$(git diff --name-only --diff-filter=ACM \ "$CI_COMMIT_BEFORE_SHA" "$CI_COMMIT_SHA" -- 'apps/*.tfvars') if [ -z "$ADDED_MODIFIED" ]; then echo "No Docker tfvars files added or modified. Nothing to deploy." exit 0 fi echo "Files to deploy: $ADDED_MODIFIED" FAILED=0 for TFVARS in $ADDED_MODIFIED; do APP=$(basename "$TFVARS" .tfvars) echo "" echo "══════════════════════════════════════════" echo " Deploying: $APP" echo "══════════════════════════════════════════" if ! ( set -e # ── Fetch SSH key from gopass ── gopass show -o "infra/ssh-keys/$APP" > /tmp/deploy_key chmod 600 /tmp/deploy_key # ── Fetch app secrets from gopass ── DB_PASSWORD=$(gopass show -o "apps/$APP/db_password") GIT_TOKEN=$(gopass show -o "apps/$APP/git_token" 2>/dev/null || echo "") # ── tofu init with SeaweedFS backend ── export AWS_ACCESS_KEY_ID="$SEAWEED_ACCESS_KEY" export AWS_SECRET_ACCESS_KEY="$SEAWEED_SECRET_KEY" tofu init \ -backend-config="bucket=tofu-state" \ -backend-config="key=apps/$APP.tfstate" \ -backend-config="endpoint=$SEAWEED_S3_ENDPOINT" \ -backend-config="region=us-east-1" \ -backend-config="force_path_style=true" \ -reconfigure # ── Workspace ── tofu workspace select "$APP" 2>/dev/null || tofu workspace new "$APP" # ── Apply ── tofu apply -auto-approve \ -var-file="$TFVARS" \ -var="ssh_key_path=/tmp/deploy_key" \ -var="db_password=$DB_PASSWORD" \ -var="openresty_git_token=$GIT_TOKEN" rm -f /tmp/deploy_key echo " Deployed: $APP" ); then echo "ERROR: Deployment of $APP failed — continuing with remaining apps" FAILED=1 rm -f /tmp/deploy_key fi done if [ $FAILED -ne 0 ]; then echo "" echo "One or more deployments failed. See logs above for details." exit 1 fi after_script: # Safety cleanup in case the job was interrupted - rm -f /tmp/deploy_key # ─── Destroy (tfvars file deleted) ─────────────────────────────────────────── destroy-docker: extends: .deploy_base stage: destroy rules: - if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"' changes: - apps/*.tfvars script: - | DELETED=$(git diff --name-only --diff-filter=D \ "$CI_COMMIT_BEFORE_SHA" "$CI_COMMIT_SHA" -- 'apps/*.tfvars') if [ -z "$DELETED" ]; then echo "No Docker tfvars files deleted. Nothing to destroy." exit 0 fi echo "Files to destroy: $DELETED" FAILED=0 for TFVARS in $DELETED; do APP=$(basename "$TFVARS" .tfvars) echo "" echo "══════════════════════════════════════════" echo " Destroying: $APP" echo "══════════════════════════════════════════" if ! ( set -e # ── Recover deleted tfvars from git history ── # No need to check out the previous commit — git show reads it directly. git show "$CI_COMMIT_BEFORE_SHA:$TFVARS" > /tmp/${APP}.tfvars # ── Fetch SSH key from gopass ── gopass show -o "infra/ssh-keys/$APP" > /tmp/deploy_key chmod 600 /tmp/deploy_key # ── Fetch app secrets from gopass ── DB_PASSWORD=$(gopass show -o "apps/$APP/db_password") GIT_TOKEN=$(gopass show -o "apps/$APP/git_token" 2>/dev/null || echo "") # ── tofu init ── export AWS_ACCESS_KEY_ID="$SEAWEED_ACCESS_KEY" export AWS_SECRET_ACCESS_KEY="$SEAWEED_SECRET_KEY" tofu init \ -backend-config="bucket=tofu-state" \ -backend-config="key=apps/$APP.tfstate" \ -backend-config="endpoint=$SEAWEED_S3_ENDPOINT" \ -backend-config="region=us-east-1" \ -backend-config="force_path_style=true" \ -reconfigure # ── Select existing workspace ── tofu workspace select "$APP" # ── Destroy ── tofu destroy -auto-approve \ -var-file="/tmp/${APP}.tfvars" \ -var="ssh_key_path=/tmp/deploy_key" \ -var="db_password=$DB_PASSWORD" \ -var="openresty_git_token=$GIT_TOKEN" # ── Remove workspace ── tofu workspace select default tofu workspace delete "$APP" rm -f /tmp/${APP}.tfvars /tmp/deploy_key echo " Destroyed: $APP" ); then echo "ERROR: Destroy of $APP failed — continuing with remaining apps" FAILED=1 rm -f /tmp/${APP}.tfvars /tmp/deploy_key fi done if [ $FAILED -ne 0 ]; then echo "" echo "One or more destroy operations failed. See logs above for details." exit 1 fi after_script: # Safety cleanup in case the job was interrupted - rm -f /tmp/deploy_key /tmp/*.tfvars