fix: cleanup deploy.yml and share workflow (#108)

* fix: cleanup deploy.yml and share workflow

* lint issue
This commit is contained in:
Rasmus Wejlgaard 2026-05-15 20:17:28 +01:00 committed by GitHub
parent 69145b3089
commit 9f84652102
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 122 additions and 173 deletions

98
.github/workflows/_deploy-core.yml vendored Normal file
View file

@ -0,0 +1,98 @@
name: Deploy (core)
on:
workflow_call:
inputs:
host:
required: true
type: string
playbook:
required: true
type: string
dry_run:
required: false
type: boolean
default: false
secrets:
TAILSCALE_CLIENT_ID:
required: true
TAILSCALE_AUDIENCE:
required: true
SSH_PRIVATE_KEY:
required: true
AGE_SECRET_KEY:
required: true
jobs:
deploy:
name: Deploy ${{ inputs.playbook }} → ${{ inputs.host }}
runs-on: ubuntu-latest
environment: production
permissions:
id-token: write
steps:
- uses: actions/checkout@v6
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-ansible
- name: Cache Ansible collections
uses: actions/cache@v4
with:
path: ~/.ansible
key: ansible-galaxy-${{ hashFiles('ansible/requirements.yml') }}
- name: Set up Tailscale
uses: tailscale/github-action@v4
with:
oauth-client-id: ${{ secrets.TAILSCALE_CLIENT_ID }}
audience: ${{ secrets.TAILSCALE_AUDIENCE }}
tags: tag:ci
- name: Set up SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
HOST_IP=$(grep "^${{ inputs.host }}[[:space:]]" ansible/inventory/hosts.ini | grep -o 'ansible_host=[^ ]*' | cut -d= -f2)
if [ -n "$HOST_IP" ]; then
ssh-keyscan -H "$HOST_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
fi
- name: Install tools
run: |
pip install ansible
wget -qO /tmp/sops.deb https://github.com/getsops/sops/releases/download/v3.9.4/sops_3.9.4_amd64.deb
sudo dpkg -i /tmp/sops.deb
- name: Install Ansible collections
run: ansible-galaxy install -r ansible/requirements.yml
- name: Decrypt secrets
env:
SOPS_AGE_KEY: ${{ secrets.AGE_SECRET_KEY }}
run: |
find . -name '*.enc.yml' -o -name '*.enc.yaml' -o -name '*.enc.env' | while read f; do
out="${f/.enc/}"
sops -d "$f" > "$out"
done
- name: Run playbook
working-directory: ansible/
env:
ANSIBLE_HOST_KEY_CHECKING: "false"
run: |
PLAYBOOK="${{ inputs.playbook }}"
PLAYBOOK="${PLAYBOOK#playbooks/}"
PLAYBOOK="${PLAYBOOK%.yml}.yml"
if [ "$PLAYBOOK" != "deploy.yml" ]; then
PLAYBOOK="playbooks/$PLAYBOOK"
fi
ARGS="--limit ${{ inputs.host }}"
if [ "${{ inputs.dry_run }}" = "true" ]; then
ARGS="$ARGS --check --diff"
fi
ansible-playbook "$PLAYBOOK" $ARGS

View file

@ -7,12 +7,6 @@ on:
paths-ignore:
- "terraform/**"
# Requires these repository secrets:
# TAILSCALE_CLIENT_ID — Tailscale OAuth client ID (federated identity)
# TAILSCALE_AUDIENCE — Tailscale federated identity audience
# SSH_PRIVATE_KEY — SSH key authorized on target hosts
# AGE_SECRET_KEY — age private key for SOPS decryption
jobs:
discover:
name: Discover hosts
@ -30,53 +24,13 @@ jobs:
deploy:
needs: discover
name: Deploy → ${{ matrix.host }}
runs-on: ubuntu-latest
environment: production
permissions:
id-token: write
strategy:
matrix:
host: ${{ fromJson(needs.discover.outputs.hosts) }}
fail-fast: false
steps:
- uses: actions/checkout@v6
- name: Set up Tailscale
uses: tailscale/github-action@v4
uses: ./.github/workflows/_deploy-core.yml
with:
oauth-client-id: ${{ secrets.TAILSCALE_CLIENT_ID }}
audience: ${{ secrets.TAILSCALE_AUDIENCE }}
tags: tag:ci
- name: Set up SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
IPS=$(grep 'ansible_host=' ansible/inventory/hosts.ini | awk -F'ansible_host=' '{print $2}' | awk '{print $1}' | tr '\n' ' ')
ssh-keyscan -H $IPS >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Install tools
run: |
pip install ansible
wget -qO /tmp/sops.deb https://github.com/getsops/sops/releases/download/v3.9.4/sops_3.9.4_amd64.deb
sudo dpkg -i /tmp/sops.deb
- name: Install Ansible collections
run: ansible-galaxy install -r ansible/requirements.yml
- name: Decrypt secrets
env:
SOPS_AGE_KEY: ${{ secrets.AGE_SECRET_KEY }}
run: |
find . -name '*.enc.yml' -o -name '*.enc.yaml' -o -name '*.enc.env' | while read f; do
out="${f/.enc/}"
sops -d "$f" > "$out"
done
- name: Run playbook
working-directory: ansible/
env:
ANSIBLE_HOST_KEY_CHECKING: "false"
run: ansible-playbook deploy.yml --limit "${{ matrix.host }}"
host: ${{ matrix.host }}
playbook: deploy.yml
dry_run: false
secrets: inherit

View file

@ -17,12 +17,6 @@ on:
type: boolean
default: true
# Requires these repository secrets:
# TAILSCALE_CLIENT_ID — Tailscale OAuth client ID (federated identity)
# TAILSCALE_AUDIENCE — Tailscale federated identity audience
# SSH_PRIVATE_KEY — SSH key authorized on target hosts
# AGE_SECRET_KEY — age private key for SOPS decryption
jobs:
prepare:
name: Prepare matrix
@ -46,65 +40,13 @@ jobs:
deploy:
needs: prepare
name: Deploy ${{ inputs.playbook }} → ${{ matrix.host }}
runs-on: ubuntu-latest
environment: production # requires manual approval in repo settings
permissions:
id-token: write
strategy:
matrix:
host: ${{ fromJson(needs.prepare.outputs.hosts) }}
fail-fast: false
steps:
- uses: actions/checkout@v6
- name: Set up Tailscale
uses: tailscale/github-action@v4
uses: ./.github/workflows/_deploy-core.yml
with:
oauth-client-id: ${{ secrets.TAILSCALE_CLIENT_ID }}
audience: ${{ secrets.TAILSCALE_AUDIENCE }}
tags: tag:ci
- name: Set up SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
IPS=$(grep 'ansible_host=' ansible/inventory/hosts.ini | awk -F'ansible_host=' '{print $2}' | awk '{print $1}' | tr '\n' ' ')
ssh-keyscan -H $IPS >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Install tools
run: |
pip install ansible
wget -qO /tmp/sops.deb https://github.com/getsops/sops/releases/download/v3.9.4/sops_3.9.4_amd64.deb
sudo dpkg -i /tmp/sops.deb
- name: Install Ansible collections
run: ansible-galaxy install -r ansible/requirements.yml
- name: Decrypt secrets
env:
SOPS_AGE_KEY: ${{ secrets.AGE_SECRET_KEY }}
run: |
find . -name '*.enc.yml' -o -name '*.enc.yaml' -o -name '*.enc.env' | while read f; do
out="${f/.enc/}"
sops -d "$f" > "$out"
done
- name: Run playbook
working-directory: ansible/
env:
ANSIBLE_HOST_KEY_CHECKING: "false"
run: |
PLAYBOOK="${{ inputs.playbook }}"
PLAYBOOK="${PLAYBOOK#playbooks/}"
PLAYBOOK="${PLAYBOOK%.yml}.yml"
if [ "$PLAYBOOK" != "deploy.yml" ]; then
PLAYBOOK="playbooks/$PLAYBOOK"
fi
ARGS="--limit ${{ matrix.host }}"
if [ "${{ inputs.dry_run }}" = "true" ]; then
ARGS="$ARGS --check --diff"
fi
ansible-playbook "$PLAYBOOK" $ARGS
host: ${{ matrix.host }}
playbook: ${{ inputs.playbook }}
dry_run: ${{ inputs.dry_run }}
secrets: inherit

View file

@ -8,37 +8,27 @@
# Full fleet: ansible-playbook deploy.yml
# Single host: ansible-playbook deploy.yml --limit helsinki-a
# Dry run: ansible-playbook deploy.yml --check --diff
# Services only (skip baseline): ansible-playbook deploy.yml --tags services
#
# Prerequisites:
# - Target host has SSH access via Tailscale
# - Target host has a base OS installed (Debian)
# - ansible-galaxy install -r requirements.yml
# ──────────────────────────────────────────────
# Stage 1: Common baseline — all hosts
# ──────────────────────────────────────────────
- name: "Stage 1: Common baseline"
- name: Common baseline
hosts: all
tags: [common, baseline]
roles:
- role: common
- role: dotfiles
# ──────────────────────────────────────────────
# Stage 2: Docker engine — hosts that run containers
# ──────────────────────────────────────────────
- name: "Stage 2: Docker engine"
- name: Docker engine
hosts: docker_hosts
tags: [docker]
roles:
- role: docker
# ──────────────────────────────────────────────
# Stage 4: Per-host services
# ──────────────────────────────────────────────
# helsinki-a: Caddy reverse proxy + status page
- name: "Stage 4a: Caddy + status page (helsinki-a)"
- name: Caddy + status page (helsinki-a)
hosts: helsinki-a
tags: [services, caddy, status_page]
roles:
@ -46,75 +36,40 @@
- role: status_page
- role: systemd_services
# All docker hosts: deploy compose services
- name: "Stage 4b: Docker services"
- name: Docker services
hosts: docker_hosts
tags: [services, docker_services]
roles:
- role: docker_services
# nuremberg-a: Mail server
- name: "Stage 4c: Mail server (nuremberg-a)"
- name: Mail — poste.io (nuremberg-a)
hosts: nuremberg-a
tags: [services, mail]
roles:
- role: docker_services
# london-b: Media stack + backups
- name: "Stage 4d: Media stack + backups (london-b)"
- name: Media stack + backups (london-b)
hosts: london-b
tags: [services, london-b]
roles:
- role: media_stack
- role: backup
# copenhagen-a: Gaming servers
- name: "Stage 4e: Gaming servers (copenhagen-a)"
- name: Gaming servers (copenhagen-a)
hosts: copenhagen-a
tags: [services, gaming]
roles:
- role: systemd_services
- role: mariadb
# london-a: Proxmox VE hypervisor
- name: "Stage 4f: Proxmox VE (london-a)"
- name: Proxmox VE (london-a)
hosts: london-a
tags: [services, proxmox]
roles:
- role: proxmox_ve
# ──────────────────────────────────────────────
# Stage 4g: ZFS scrub scheduling — zfs_hosts
# ──────────────────────────────────────────────
- name: "Stage 4g: ZFS scrub scheduling"
- name: ZFS scrub scheduling
hosts: zfs_hosts
tags: [services, zfs]
roles:
- role: zfs
# ──────────────────────────────────────────────
# Stage 5: Verification
# ──────────────────────────────────────────────
- name: "Stage 5: Post-deploy verification"
hosts: all
tags: [verify]
tasks:
- name: Check SSH is working
ansible.builtin.ping:
- name: Gather uptime
ansible.builtin.command: uptime
changed_when: false
register: uptime_result
- name: Check Docker containers (where applicable)
ansible.builtin.command: docker ps --format "table {{ '{{' }}.Names{{ '}}' }}\t{{ '{{' }}.Status{{ '}}' }}"
changed_when: false
register: docker_status
when: "'docker_hosts' in group_names"
failed_when: false
- name: Report host status
ansible.builtin.debug:
msg: |
Host: {{ inventory_hostname }} ({{ host_description | default('no description') }})
Uptime: {{ uptime_result.stdout }}
Docker: {{ docker_status.stdout_lines | default(['N/A']) | join('\n') }}