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: paths-ignore:
- "terraform/**" - "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: jobs:
discover: discover:
name: Discover hosts name: Discover hosts
@ -30,53 +24,13 @@ jobs:
deploy: deploy:
needs: discover needs: discover
name: Deploy → ${{ matrix.host }}
runs-on: ubuntu-latest
environment: production
permissions:
id-token: write
strategy: strategy:
matrix: matrix:
host: ${{ fromJson(needs.discover.outputs.hosts) }} host: ${{ fromJson(needs.discover.outputs.hosts) }}
fail-fast: false fail-fast: false
steps: uses: ./.github/workflows/_deploy-core.yml
- uses: actions/checkout@v6 with:
host: ${{ matrix.host }}
- name: Set up Tailscale playbook: deploy.yml
uses: tailscale/github-action@v4 dry_run: false
with: secrets: inherit
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 }}"

View file

@ -17,12 +17,6 @@ on:
type: boolean type: boolean
default: true 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: jobs:
prepare: prepare:
name: Prepare matrix name: Prepare matrix
@ -46,65 +40,13 @@ jobs:
deploy: deploy:
needs: prepare 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: strategy:
matrix: matrix:
host: ${{ fromJson(needs.prepare.outputs.hosts) }} host: ${{ fromJson(needs.prepare.outputs.hosts) }}
fail-fast: false fail-fast: false
steps: uses: ./.github/workflows/_deploy-core.yml
- uses: actions/checkout@v6 with:
host: ${{ matrix.host }}
- name: Set up Tailscale playbook: ${{ inputs.playbook }}
uses: tailscale/github-action@v4 dry_run: ${{ inputs.dry_run }}
with: secrets: inherit
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

View file

@ -8,37 +8,27 @@
# Full fleet: ansible-playbook deploy.yml # Full fleet: ansible-playbook deploy.yml
# Single host: ansible-playbook deploy.yml --limit helsinki-a # Single host: ansible-playbook deploy.yml --limit helsinki-a
# Dry run: ansible-playbook deploy.yml --check --diff # Dry run: ansible-playbook deploy.yml --check --diff
# Services only (skip baseline): ansible-playbook deploy.yml --tags services
# #
# Prerequisites: # Prerequisites:
# - Target host has SSH access via Tailscale # - Target host has SSH access via Tailscale
# - Target host has a base OS installed (Debian) # - Target host has a base OS installed (Debian)
# - ansible-galaxy install -r requirements.yml # - ansible-galaxy install -r requirements.yml
# ────────────────────────────────────────────── - name: Common baseline
# Stage 1: Common baseline — all hosts
# ──────────────────────────────────────────────
- name: "Stage 1: Common baseline"
hosts: all hosts: all
tags: [common, baseline] tags: [common, baseline]
roles: roles:
- role: common - role: common
- role: dotfiles - role: dotfiles
# ────────────────────────────────────────────── - name: Docker engine
# Stage 2: Docker engine — hosts that run containers
# ──────────────────────────────────────────────
- name: "Stage 2: Docker engine"
hosts: docker_hosts hosts: docker_hosts
tags: [docker] tags: [docker]
roles: roles:
- role: docker - role: docker
# ────────────────────────────────────────────── - name: Caddy + status page (helsinki-a)
# Stage 4: Per-host services
# ──────────────────────────────────────────────
# helsinki-a: Caddy reverse proxy + status page
- name: "Stage 4a: Caddy + status page (helsinki-a)"
hosts: helsinki-a hosts: helsinki-a
tags: [services, caddy, status_page] tags: [services, caddy, status_page]
roles: roles:
@ -46,75 +36,40 @@
- role: status_page - role: status_page
- role: systemd_services - role: systemd_services
# All docker hosts: deploy compose services - name: Docker services
- name: "Stage 4b: Docker services"
hosts: docker_hosts hosts: docker_hosts
tags: [services, docker_services] tags: [services, docker_services]
roles: roles:
- role: docker_services - role: docker_services
# nuremberg-a: Mail server - name: Mail — poste.io (nuremberg-a)
- name: "Stage 4c: Mail server (nuremberg-a)"
hosts: nuremberg-a hosts: nuremberg-a
tags: [services, mail] tags: [services, mail]
roles:
- role: docker_services
# london-b: Media stack + backups - name: Media stack + backups (london-b)
- name: "Stage 4d: Media stack + backups (london-b)"
hosts: london-b hosts: london-b
tags: [services, london-b] tags: [services, london-b]
roles: roles:
- role: media_stack - role: media_stack
- role: backup - role: backup
# copenhagen-a: Gaming servers - name: Gaming servers (copenhagen-a)
- name: "Stage 4e: Gaming servers (copenhagen-a)"
hosts: copenhagen-a hosts: copenhagen-a
tags: [services, gaming] tags: [services, gaming]
roles: roles:
- role: systemd_services - role: systemd_services
- role: mariadb - role: mariadb
# london-a: Proxmox VE hypervisor - name: Proxmox VE (london-a)
- name: "Stage 4f: Proxmox VE (london-a)"
hosts: london-a hosts: london-a
tags: [services, proxmox] tags: [services, proxmox]
roles: roles:
- role: proxmox_ve - role: proxmox_ve
# ────────────────────────────────────────────── - name: ZFS scrub scheduling
# Stage 4g: ZFS scrub scheduling — zfs_hosts
# ──────────────────────────────────────────────
- name: "Stage 4g: ZFS scrub scheduling"
hosts: zfs_hosts hosts: zfs_hosts
tags: [services, zfs] tags: [services, zfs]
roles: roles:
- role: zfs - 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') }}