homelab/.forgejo/workflows/deploy.yml
Russell Seymour 1250c9cef6
Some checks are pending
deploy / deploy (push) Waiting to run
Initial checkin of code for managing homelab
2026-08-24 20:52:36 +01:00

116 lines
4.9 KiB
YAML

name: deploy
on:
push:
branches: [main]
paths:
# No build/config/terraform/** — this workflow only deploys to Unraid,
# and `terraform apply` is run by hand. Add it if that ever changes.
- "src/**"
- "build/config/ansible/**"
jobs:
deploy:
# Requires a self-hosted Forgejo Actions runner on your LAN (labelled
# `unraid-deploy`) with: network access to Vault and to the Unraid hosts'
# SSH + Postgres ports, and Python + pip available. See README.md
# "Bootstrapping" — this workflow can only run once that runner exists,
# which is why the first deploy has to happen by hand. The SSH key
# `ansible_user` (root) authenticates with is fetched from Vault below,
# not stored as a runner or repo secret — see README.md "SSH access".
runs-on: unraid-deploy
env:
VAULT_ADDR: ${{ secrets.VAULT_ADDR }}
VAULT_AUTH_METHOD: approle
VAULT_ROLE_ID: ${{ secrets.VAULT_ROLE_ID }}
VAULT_SECRET_ID: ${{ secrets.VAULT_SECRET_ID }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # need history to diff — a shallow clone can't compute this
- name: Install Ansible and Python dependencies
run: |
python3 -m pip install --upgrade pip
python3 -m pip install ansible hvac psycopg2-binary
- name: Install Ansible collections
working-directory: build/config/ansible
run: ansible-galaxy collection install -r requirements.yml
- name: Fetch SSH deploy key from Vault
run: |
set -eu
python3 - <<'PY'
import os
import hvac
client = hvac.Client(url=os.environ["VAULT_ADDR"])
client.auth.approle.login(
role_id=os.environ["VAULT_ROLE_ID"],
secret_id=os.environ["VAULT_SECRET_ID"],
)
secret = client.secrets.kv.v2.read_secret_version(
path="homelab/ci/ssh", mount_point="kv"
)["data"]["data"]
key_path = os.path.join(os.environ["RUNNER_TEMP"], "unraid_ansible_key")
with open(key_path, "w") as f:
f.write(secret["PRIVATE_KEY"].rstrip() + "\n")
os.chmod(key_path, 0o600)
PY
echo "ANSIBLE_PRIVATE_KEY_FILE=$RUNNER_TEMP/unraid_ansible_key" >> "$GITHUB_ENV"
- name: Determine changed stacks
id: changed
run: |
set -eu
# Three possible outcomes, and they are not the same thing:
# all=true deploy every stack on every host
# only_stacks=a,b deploy just those
# skip=true nothing here affects the Unraid deployment
# An empty only_stacks must never be read as "deploy everything" —
# that is exactly what a Proxmox-only or Terraform-only push
# produces.
#
# A change under build/config/ansible/ can affect how every stack
# is deployed (the role, inventory, playbook), so don't try to
# narrow it. Same if the diff can't be computed at all (force-push,
# or a first push where `before` is all-zeros): fall back to a full
# deploy rather than silently deploying nothing.
if ! changed_files=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" 2>/dev/null); then
echo "all=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if echo "$changed_files" | grep -q '^build/config/ansible/'; then
echo "all=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Only changes that affect the Unraid deployment count, since that
# is all this job runs. A stack's common/ and ansible/unraid/ do;
# its terraform/ and ansible/proxmox/ don't.
stacks=$(echo "$changed_files" \
| { grep '^src/' || true; } \
| { grep -Ev '/(terraform|ansible/proxmox)/' || true; } \
| awk -F/ '{ print ($2 == "shared") ? $3 : $2 }' \
| sort -u | paste -sd, -)
if [ -z "$stacks" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "only_stacks=$stacks" >> "$GITHUB_OUTPUT"
fi
# Unraid only. Deploying to Proxmox from CI would need the runner to
# reach the Proxmox API and the guests, and `terraform apply` is
# deliberately kept manual — creating and destroying LXCs on a push is
# a bigger blast radius than restarting a Compose stack. Run
# playbooks/proxmox.yml by hand for now.
- name: Run deploy playbook
if: steps.changed.outputs.skip != 'true'
working-directory: build/config/ansible
run: |
if [ "${{ steps.changed.outputs.all }}" = "true" ]; then
ansible-playbook playbooks/unraid.yml
else
ansible-playbook playbooks/unraid.yml -e only_stacks="${{ steps.changed.outputs.only_stacks }}"
fi