Files
infra/ansible/playbooks/pve-gyro.yml
T
DmitryandClaude Opus 5 c676be81ec Capture current Ansible control plane state
Commit the accumulated infrastructure work that was living only in the
working tree: monitoring stack, emergency access/bot, gyro allocator,
grimmory, adguard, backup audit and the OpenCode agent definitions.

Also ignore Python bytecode, local archives and Nix/direnv artifacts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTocXkGUUazHdKKd3r9k71
2026-08-26 21:39:28 +03:00

190 lines
6.5 KiB
YAML

---
- name: Guard Gyro VMID before API updates
hosts: mini-pc
gather_facts: false
tasks:
- name: Read existing VMID 150 configuration
ansible.builtin.command: pct config 150
register: gyro_existing_vmid
check_mode: false
changed_when: false
failed_when: false
- name: Refuse to modify a foreign VMID 150
ansible.builtin.assert:
that:
- gyro_existing_vmid.rc != 0 or gyro_existing_hostname == 'gyro'
fail_msg: VMID 150 already exists and is not the Gyro container.
vars:
gyro_existing_hostname: >-
{{ gyro_existing_vmid.stdout_lines
| select('match', '^hostname: ')
| map('regex_replace', '^hostname: ', '')
| first
| default('') }}
- name: Create Gyro LXC on mini-pc
hosts: localhost
connection: local
become: false
gather_facts: false
vars:
ansible_become: false
ansible_python_interpreter: "{{ ansible_playbook_python }}"
pve_lxc_vmid: 150
pve_lxc_node: mini-pc
pve_lxc_hostname: gyro
pve_lxc_ip: 192.168.1.35/24
pve_lxc_gateway: 192.168.1.1
pve_lxc_disk: local-lvm:2
pve_lxc_cores: 1
pve_lxc_memory: 512
pve_lxc_swap: 256
pve_lxc_startup: order=80
pve_lxc_unprivileged: true
pve_lxc_update: false
pve_lxc_features: []
pve_lxc_ostemplate: local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst
roles:
- role: pve_lxc
- name: Configure Gyro LXC isolation
hosts: mini-pc
gather_facts: false
vars:
gyro_vmid: 150
tasks:
- name: Read Gyro LXC configuration
ansible.builtin.command: "pct config {{ gyro_vmid }}"
register: gyro_lxc_config
changed_when: false
- name: Require expected Gyro LXC properties
ansible.builtin.assert:
that:
- "'hostname: gyro' in gyro_lxc_config.stdout"
- "'unprivileged: 1' in gyro_lxc_config.stdout"
- "'rootfs: local-lvm:' in gyro_lxc_config.stdout"
- "'onboot: 1' in gyro_lxc_config.stdout"
- name: Check for an existing Gyro firewall file
ansible.builtin.stat:
path: "/etc/pve/firewall/{{ gyro_vmid }}.fw"
register: gyro_firewall_file
- name: Preserve the existing Gyro firewall file
ansible.builtin.slurp:
src: "/etc/pve/firewall/{{ gyro_vmid }}.fw"
register: gyro_previous_firewall
when: gyro_firewall_file.stat.exists
- name: Apply and verify Gyro firewall
block:
- name: Render Proxmox firewall for Gyro
ansible.builtin.copy:
dest: "/tmp/gyro-{{ gyro_vmid }}.fw"
owner: root
group: root
mode: "0640"
content: |
[OPTIONS]
enable: 1
policy_in: DROP
policy_out: ACCEPT
[RULES]
IN ACCEPT -source {{ homelab_lan_cidr }} -p tcp -dport 22 -log nolog
IN ACCEPT -source {{ openvpn_network_cidr }} -p tcp -dport 22 -log nolog
OUT ACCEPT -dest 192.168.1.27 -p tcp -dport 7890 -log nolog
OUT DROP -dest {{ homelab_lan_cidr }} -log nolog
register: gyro_rendered_firewall
changed_when: false
- name: Compare rendered and active Gyro firewall
ansible.builtin.command: >-
cmp -s /tmp/gyro-{{ gyro_vmid }}.fw /etc/pve/firewall/{{ gyro_vmid }}.fw
register: gyro_firewall_comparison
changed_when: false
failed_when: gyro_firewall_comparison.rc not in [0, 1]
when: gyro_firewall_file.stat.exists
- name: Install Proxmox firewall for Gyro
ansible.builtin.command: >-
cp /tmp/gyro-{{ gyro_vmid }}.fw /etc/pve/firewall/{{ gyro_vmid }}.fw
when: not gyro_firewall_file.stat.exists or gyro_firewall_comparison.rc != 0
changed_when: true
- name: Validate Proxmox firewall configuration
ansible.builtin.command: pve-firewall compile
changed_when: false
- name: Read cluster firewall options
ansible.builtin.command: pvesh get /cluster/firewall/options --output-format json
register: gyro_cluster_firewall_options
changed_when: false
- name: Report staged Proxmox firewall state
ansible.builtin.debug:
msg: >-
{{ 'Gyro Proxmox firewall is active.'
if (gyro_cluster_firewall_options.stdout | from_json).enable | default(0) | int == 1
else 'Gyro Proxmox firewall is staged but inactive because the cluster firewall is disabled; UFW remains the enforced isolation layer.' }}
- name: Verify SSH remains reachable through the firewall
ansible.builtin.wait_for_connection:
timeout: 180
delegate_to: gyro
vars:
ansible_become: false
rescue:
- name: Restore the previous Gyro firewall file
ansible.builtin.copy:
dest: "/tmp/gyro-{{ gyro_vmid }}-previous.fw"
content: "{{ gyro_previous_firewall.content | b64decode }}"
owner: root
group: root
mode: "0640"
when: gyro_firewall_file.stat.exists
- name: Reinstall the previous Gyro firewall file
ansible.builtin.command: >-
cp /tmp/gyro-{{ gyro_vmid }}-previous.fw /etc/pve/firewall/{{ gyro_vmid }}.fw
when: gyro_firewall_file.stat.exists
changed_when: true
- name: Remove the failed new Gyro firewall file
ansible.builtin.file:
path: "/etc/pve/firewall/{{ gyro_vmid }}.fw"
state: absent
when: not gyro_firewall_file.stat.exists
- name: Recompile restored Proxmox firewall configuration
ansible.builtin.command: pve-firewall compile
changed_when: false
- name: Remove temporary Gyro firewall files after rollback
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- "/tmp/gyro-{{ gyro_vmid }}.fw"
- "/tmp/gyro-{{ gyro_vmid }}-previous.fw"
- name: Stop after rolling back the Gyro firewall
ansible.builtin.fail:
msg: Gyro firewall validation or SSH reachability failed; the previous firewall state was restored.
- name: Remove temporary Gyro firewall file
ansible.builtin.file:
path: "/tmp/gyro-{{ gyro_vmid }}.fw"
state: absent
changed_when: false
- name: Wait for Gyro SSH
hosts: gyro
gather_facts: false
tasks:
- name: Wait for Gyro to accept SSH connections
ansible.builtin.wait_for_connection:
timeout: 180