mirror of
https://github.com/RWejlgaard/pez-infra.git
synced 2026-07-04 15:46:16 +00:00
fix: cleanup deploy.yml and share workflow (#108)
* fix: cleanup deploy.yml and share workflow * lint issue
This commit is contained in:
parent
69145b3089
commit
9f84652102
4 changed files with 122 additions and 173 deletions
98
.github/workflows/_deploy-core.yml
vendored
Normal file
98
.github/workflows/_deploy-core.yml
vendored
Normal 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
|
||||||
58
.github/workflows/deploy-on-merge.yml
vendored
58
.github/workflows/deploy-on-merge.yml
vendored
|
|
@ -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 }}"
|
|
||||||
|
|
|
||||||
70
.github/workflows/deploy.yml
vendored
70
.github/workflows/deploy.yml
vendored
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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') }}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue