Files
project-charlie/.gitea/workflows/deploy-stack.yml
T

193 lines
6.3 KiB
YAML

name: Deploy stack
# Reusable workflow. Replaces the old per-repo `deploy.sh`.
#
# Caller pattern (from a stack repo):
#
# on:
# push:
# branches: [main]
# jobs:
# deploy:
# uses: Charlie/project-charlie/.gitea/workflows/deploy-stack.yml@main
# with:
# host: morgott
# decrypt: |
# secrets/env.sops.yaml -> .env
#
# For a multi-host stack, give it a matrix:
#
# jobs:
# deploy:
# strategy:
# matrix:
# host: [morgott, melina, mohg, miquella]
# uses: Charlie/project-charlie/.gitea/workflows/deploy-stack.yml@main
# with:
# host: ${{ matrix.host }}
#
# Per-host compose / env resolution mirrors docs/strategy.md ("Shared stacks"):
# 1. If `docker-compose.<host>.yml` exists -> use only that.
# 2. Else `docker-compose.yml` plus (if present) `overrides/<host>.yml`.
# 3. If `envs/<host>.env` exists -> loaded with `--env-file`.
on:
workflow_call:
inputs:
host:
description: Hostname this deploy targets. Must match a self-hosted runner label.
required: true
type: string
stack-path:
description: |
Absolute path on the host where the stack's working tree lives.
Defaults to `/mnt/nas/stacks/<repo-name>`.
required: false
type: string
default: ""
decrypt:
description: |
Multi-line `src -> dst` SOPS decrypt list. Empty for stacks without secrets.
See `.gitea/actions/sops-decrypt`.
required: false
type: string
default: ""
compose-args:
description: Extra args appended to `docker compose ... up -d` (advanced).
required: false
type: string
default: ""
pull:
description: Run `docker compose ... pull` before `up -d`.
required: false
type: boolean
default: true
ref:
description: Git ref to check out into the stack working tree (default = the branch this workflow ran on).
required: false
type: string
default: ""
secrets:
RUNNER_REPO_PAT:
description: |
PAT used in the absolute-URL form of `uses:` to clone composite actions
from `Charlie/project-charlie`. Set as a Charlie org-level secret;
callers pass via `secrets: inherit` (or explicitly).
required: true
jobs:
deploy:
runs-on:
- self-hosted
- ${{ inputs.host }}
steps:
- name: Resolve stack path and ref
id: resolve
shell: bash
env:
STACK_PATH_INPUT: ${{ inputs.stack-path }}
REF_INPUT: ${{ inputs.ref }}
run: |
set -euo pipefail
path="$STACK_PATH_INPUT"
if [ -z "$path" ]; then
path="/mnt/nas/stacks/${GITHUB_REPOSITORY##*/}"
fi
ref="$REF_INPUT"
if [ -z "$ref" ]; then
ref="${GITHUB_REF_NAME:-main}"
fi
if [ ! -d "$path/.git" ]; then
echo "::error::stack path '$path' is not a git working tree"
exit 1
fi
echo "path=$path" >> "$GITHUB_OUTPUT"
echo "ref=$ref" >> "$GITHUB_OUTPUT"
- name: Sync working tree
shell: bash
working-directory: ${{ steps.resolve.outputs.path }}
env:
REF: ${{ steps.resolve.outputs.ref }}
run: |
set -euo pipefail
sudo -n git fetch --prune origin
sudo -n git reset --hard "origin/$REF"
# Keep decrypted .env-style files out of the way; never touch /mnt/nas/data.
sudo -n git clean -fd --exclude='.env' --exclude='secrets/*.plain*'
- name: Decrypt SOPS secrets
if: ${{ inputs.decrypt != '' }}
# Absolute URL form (with auth) is required so act doesn't default to
# github.com. Same pattern as the caller's `uses:` for this workflow.
# See https://github.com/go-gitea/gitea/issues/25929.
uses: https://sys-gitea-runner:${{ secrets.RUNNER_REPO_PAT }}@gitea.alexandru.macocian.me/Charlie/project-charlie/.gitea/actions/sops-decrypt@main
with:
decrypt: ${{ inputs.decrypt }}
workdir: ${{ steps.resolve.outputs.path }}
- name: Resolve compose invocation
id: compose
shell: bash
working-directory: ${{ steps.resolve.outputs.path }}
env:
HOST: ${{ inputs.host }}
run: |
set -euo pipefail
host="$(printf '%s' "$HOST" | tr '[:upper:]' '[:lower:]')"
files=()
if [ -f "docker-compose.$host.yml" ]; then
files+=(-f "docker-compose.$host.yml")
else
files+=(-f "docker-compose.yml")
[ -f "overrides/$host.yml" ] && files+=(-f "overrides/$host.yml")
fi
envfile=()
[ -f "envs/$host.env" ] && envfile+=(--env-file "envs/$host.env")
# Persist as a shell-quoted string the next steps can re-eval.
printf 'compose_args=' > "$GITHUB_OUTPUT.tmp"
printf '%q ' "${envfile[@]}" "${files[@]}" >> "$GITHUB_OUTPUT.tmp"
printf '\n' >> "$GITHUB_OUTPUT.tmp"
cat "$GITHUB_OUTPUT.tmp" >> "$GITHUB_OUTPUT"
rm -f "$GITHUB_OUTPUT.tmp"
- name: Pull images
if: ${{ inputs.pull }}
shell: bash
working-directory: ${{ steps.resolve.outputs.path }}
env:
COMPOSE_ARGS: ${{ steps.compose.outputs.compose_args }}
run: |
set -euo pipefail
eval "set -- $COMPOSE_ARGS"
sudo -n docker compose "$@" pull
- name: Up
shell: bash
working-directory: ${{ steps.resolve.outputs.path }}
env:
COMPOSE_ARGS: ${{ steps.compose.outputs.compose_args }}
EXTRA_ARGS: ${{ inputs.compose-args }}
run: |
set -euo pipefail
eval "set -- $COMPOSE_ARGS"
# shellcheck disable=SC2086
sudo -n docker compose "$@" up -d $EXTRA_ARGS
- name: Cleanup decrypted plaintext
if: always()
shell: bash
working-directory: ${{ steps.resolve.outputs.path }}
run: |
set -u
list="${SOPS_DECRYPT_CLEANUP:-}"
[ -n "$list" ] && [ -f "$list" ] || exit 0
while IFS= read -r f; do
[ -z "$f" ] && continue
sudo -n rm -f -- "$f" && echo "removed: $f"
done < "$list"
rm -f "$list"