mirror of
https://github.com/RWejlgaard/pez-infra.git
synced 2026-07-04 15:46:16 +00:00
Compare commits
2 commits
69145b3089
...
7ad2766f94
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ad2766f94 | |||
| 9f84652102 |
4 changed files with 126 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:
|
||||
- "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,15 @@ 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
|
||||
permissions:
|
||||
id-token: write
|
||||
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
|
||||
|
|
|
|||
70
.github/workflows/deploy.yml
vendored
70
.github/workflows/deploy.yml
vendored
|
|
@ -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,15 @@ 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
|
||||
permissions:
|
||||
id-token: write
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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') }}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue