mirror of
https://github.com/RWejlgaard/pez-infra.git
synced 2026-10-31 19:11:03 +00:00
Neither Proxmox host had a single backup job, so the game servers, the k3s control planes and the workspace VMs had no backups at all. copenhagen-a has nowhere local to keep them (`local` is the root LV and already 82% full), so both hosts now back up to a new pve-backups Samba share on london-b, each into its own directory. VMIDs overlap between the two hosts, and retention prunes by VMID, so they can't share one. - media_stack creates a Samba-only pvebackup user on london-b, the share and the per-host directories. The password comes from SOPS and gets re-set if the login stops working. smb.conf is now checked with testparm before it's deployed. - proxmox_ve adds a backup-only london-b-backups CIFS storage, pushes the credentials again if the storage goes inactive, and fails the deploy if it still can't reach london-b. - Jobs are defined in host_vars. Each job keeps a hash of its settings in its comment, so the role only touches jobs that changed and removes ones I delete from host_vars. Jobs made by hand are left alone. london-a backs up the control plane every night. copenhagen-a backs up the control plane and game servers every night and the workspace VMs weekly, since every run is a full archive going over the WAN.
190 lines
7.3 KiB
YAML
190 lines
7.3 KiB
YAML
---
|
|
# Configure Proxmox VE apt sources for the no-subscription tier.
|
|
# Removes the enterprise repos (require a paid subscription) and enables
|
|
# the free no-subscription repo so apt update works without credentials.
|
|
|
|
# PVE 9 (trixie) uses deb822 .sources files. Clear out the enterprise repos in
|
|
# both formats, plus the old one-line no-subscription .list this role used to
|
|
# write with a hardcoded bookworm suite (mixing suites on a trixie host).
|
|
- name: Remove enterprise and legacy Proxmox apt sources
|
|
ansible.builtin.file:
|
|
path: "/etc/apt/sources.list.d/{{ item }}"
|
|
state: absent
|
|
loop:
|
|
- pve-enterprise.list
|
|
- pve-enterprise.sources
|
|
- ceph.list
|
|
- ceph.sources
|
|
- pve.sources
|
|
- pve-no-subscription.list
|
|
notify: Update apt cache
|
|
|
|
- name: Add PVE no-subscription apt source
|
|
ansible.builtin.copy:
|
|
dest: /etc/apt/sources.list.d/proxmox.sources
|
|
content: |
|
|
Types: deb
|
|
URIs: http://download.proxmox.com/debian/pve
|
|
Suites: {{ ansible_facts['distribution_release'] }}
|
|
Components: pve-no-subscription
|
|
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
notify: Update apt cache
|
|
|
|
- name: Update apt cache
|
|
ansible.builtin.apt:
|
|
update_cache: true
|
|
|
|
# Patch is re-applied automatically on each Ansible run, which handles pve-manager package updates
|
|
# reverting the file.
|
|
- name: Suppress Proxmox subscription nag dialog
|
|
ansible.builtin.replace:
|
|
path: /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
|
|
regexp: "if \\(res === null \\|\\| res === undefined \\|\\| !res \\|\\| res\\.data\\.status\\.toLowerCase\\(\\) !== 'active'\\)"
|
|
replace: "if (false)"
|
|
notify: Restart pveproxy
|
|
|
|
- name: Allow Proxmox web UI on Tailscale only
|
|
community.general.ufw:
|
|
rule: allow
|
|
port: "8006"
|
|
proto: tcp
|
|
interface: tailscale0
|
|
direction: in
|
|
comment: "Proxmox web UI - Tailscale only"
|
|
notify: Reload ufw
|
|
|
|
# --- Storage ---
|
|
# london-b's CIFS share is only mounted for hosts on the same LAN (london-a) —
|
|
# it's not worth mounting over the WAN/Tailscale for a Copenhagen host.
|
|
|
|
- name: Check if hdd CIFS storage is configured
|
|
ansible.builtin.command: pvesh get /storage/hdd
|
|
register: proxmox_ve_hdd_check
|
|
changed_when: false
|
|
failed_when: false
|
|
when: proxmox_ve_mount_cifs_storage | default(true)
|
|
|
|
- name: Add hdd CIFS storage (london-b /pve share)
|
|
ansible.builtin.command: >-
|
|
pvesm add cifs hdd
|
|
--server 100.84.65.101
|
|
--share pve
|
|
--username {{ proxmox_ve_smb_user }}
|
|
--password {{ proxmox_ve_smb_password }}
|
|
--content images,backup,import,snippets,vztmpl,rootdir,iso
|
|
no_log: true
|
|
changed_when: true
|
|
when:
|
|
- proxmox_ve_mount_cifs_storage | default(true)
|
|
- proxmox_ve_hdd_check.rc != 0
|
|
|
|
# --- VM backups (vzdump) ---
|
|
# Proxmox has nowhere local to keep backups (copenhagen-a's `local` is the root
|
|
# LV, and LVM-thin can't hold archives), so each host backs up to its own
|
|
# directory on london-b. Job definitions live in host_vars.
|
|
|
|
- name: Configure VM backups
|
|
when: proxmox_ve_backup_jobs | length > 0
|
|
block:
|
|
- name: Check if backup storage is configured
|
|
ansible.builtin.command: pvesh get /storage/{{ proxmox_ve_backup_storage }}
|
|
register: proxmox_ve_backup_storage_check
|
|
changed_when: false
|
|
failed_when: false
|
|
check_mode: false
|
|
|
|
- name: Add backup storage (london-b pve-backups share)
|
|
ansible.builtin.command: >-
|
|
pvesm add cifs {{ proxmox_ve_backup_storage }}
|
|
--server {{ proxmox_ve_backup_server }}
|
|
--share {{ proxmox_ve_backup_share }}
|
|
--subdir /{{ inventory_hostname }}
|
|
--username {{ proxmox_ve_backup_smb_user }}
|
|
--password {{ proxmox_ve_backup_smb_password }}
|
|
--content backup
|
|
--prune-backups keep-all=1
|
|
no_log: true
|
|
changed_when: true
|
|
when: proxmox_ve_backup_storage_check.rc != 0
|
|
|
|
- name: Check backup storage is active
|
|
ansible.builtin.command: pvesm status --storage {{ proxmox_ve_backup_storage }}
|
|
register: proxmox_ve_backup_storage_status
|
|
changed_when: false
|
|
failed_when: false
|
|
check_mode: false
|
|
|
|
# The add above only runs once, so re-push the credentials whenever the
|
|
# storage can't log in — e.g. after the password is rotated in SOPS.
|
|
- name: Refresh backup storage credentials
|
|
ansible.builtin.command: >-
|
|
pvesm set {{ proxmox_ve_backup_storage }}
|
|
--username {{ proxmox_ve_backup_smb_user }}
|
|
--password {{ proxmox_ve_backup_smb_password }}
|
|
no_log: true
|
|
changed_when: true
|
|
when:
|
|
- proxmox_ve_backup_storage_check.rc == 0
|
|
- "'active' not in proxmox_ve_backup_storage_status.stdout.split()"
|
|
|
|
- name: Verify backup storage is active
|
|
ansible.builtin.command: pvesm status --storage {{ proxmox_ve_backup_storage }}
|
|
register: proxmox_ve_backup_storage_verify
|
|
changed_when: false
|
|
failed_when: "'active' not in proxmox_ve_backup_storage_verify.stdout.split()"
|
|
when: not ansible_check_mode
|
|
|
|
- name: List existing backup jobs
|
|
ansible.builtin.command: pvesh get /cluster/backup --output-format json
|
|
register: proxmox_ve_backup_jobs_current
|
|
changed_when: false
|
|
check_mode: false
|
|
|
|
# Each managed job carries a hash of its desired settings in its comment,
|
|
# so drift is detected without having to normalise the API's output.
|
|
- name: Work out backup job changes
|
|
ansible.builtin.set_fact:
|
|
proxmox_ve_backup_jobs_existing: >-
|
|
{{ dict(proxmox_ve_backup_jobs_current.stdout | from_json | map(attribute='id')
|
|
| zip(proxmox_ve_backup_jobs_current.stdout | from_json | map(attribute='comment', default=''))) }}
|
|
proxmox_ve_backup_comment_prefix: "Managed by Ansible (pez-infra)"
|
|
|
|
- name: Create or update backup jobs
|
|
ansible.builtin.command: >-
|
|
pvesh {{ 'set /cluster/backup/' ~ item.id if item.id in proxmox_ve_backup_jobs_existing
|
|
else 'create /cluster/backup --id ' ~ item.id }}
|
|
--schedule {{ item.schedule | quote }}
|
|
--storage {{ proxmox_ve_backup_storage }}
|
|
--vmid {{ item.vmids | join(',') }}
|
|
--mode snapshot
|
|
--compress zstd
|
|
--enabled 1
|
|
--prune-backups {{ item.prune_backups | quote }}
|
|
--comment {{ (proxmox_ve_backup_comment_prefix ~ ' ' ~ proxmox_ve_backup_job_hash) | quote }}
|
|
vars:
|
|
# Include everything passed to pvesh, so changing a fixed flag here also
|
|
# re-applies the jobs.
|
|
proxmox_ve_backup_job_hash: >-
|
|
{{ (item | combine({'storage': proxmox_ve_backup_storage, 'mode': 'snapshot', 'compress': 'zstd'})
|
|
| to_json | hash('sha1'))[:12] }}
|
|
loop: "{{ proxmox_ve_backup_jobs }}"
|
|
loop_control:
|
|
label: "{{ item.id }}"
|
|
changed_when: true
|
|
when: >-
|
|
proxmox_ve_backup_jobs_existing.get(item.id)
|
|
!= proxmox_ve_backup_comment_prefix ~ ' ' ~ proxmox_ve_backup_job_hash
|
|
|
|
- name: Remove backup jobs no longer defined
|
|
ansible.builtin.command: pvesh delete /cluster/backup/{{ item.key }}
|
|
loop: "{{ proxmox_ve_backup_jobs_existing | dict2items }}"
|
|
loop_control:
|
|
label: "{{ item.key }}"
|
|
changed_when: true
|
|
when:
|
|
- item.value is string
|
|
- item.value.startswith(proxmox_ve_backup_comment_prefix)
|
|
- item.key not in proxmox_ve_backup_jobs | map(attribute='id')
|