diff --git a/infrastructure/proxmox/ansible/group_vars/pve.yml b/infrastructure/proxmox/ansible/group_vars/pve.yml index 60203c2..a07dbb1 100644 --- a/infrastructure/proxmox/ansible/group_vars/pve.yml +++ b/infrastructure/proxmox/ansible/group_vars/pve.yml @@ -1,4 +1,13 @@ --- +# Shared authentication for typed community.proxmox API modules. Secrets are +# runtime inputs; never put them in inventory or vars files. +pve_api_host: "{{ lookup('env', 'PROXMOX_HOST') }}" +pve_api_user: "{{ lookup('env', 'PROXMOX_USER') }}" +pve_api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}" +pve_api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}" +pve_api_validate_certs: true +pve_api_python_interpreter: "{{ lookup('env', 'PVE_API_PYTHON') | default(ansible_playbook_python, true) }}" + ansible_user: root ansible_python_interpreter: /usr/bin/python3 diff --git a/infrastructure/proxmox/ansible/inventory/hosts.yml b/infrastructure/proxmox/ansible/inventory/hosts.yml index b4d2841..a297c1f 100644 --- a/infrastructure/proxmox/ansible/inventory/hosts.yml +++ b/infrastructure/proxmox/ansible/inventory/hosts.yml @@ -19,20 +19,13 @@ all: pve2: ansible_host: 192.168.10.7 - # ThinkCentre, Ryzen 2400GE (Raven Ridge), 8G. THIS is the node that - # randomly froze. Kernel workaround below; also set BIOS - # "Power Supply Idle Control" -> "Typical Current Idle" (the more - # reliable half of the fix, and it can only be done physically). - pve_kernel_cmdline_extra: "idle=nomwait processor.max_cstate=1" + # ThinkCentre, Ryzen 2400GE (Raven Ridge), 8G. pve_install_disk: nvme0n1 pve_bulk_disk: sda pve3: ansible_host: 192.168.10.9 - # Same Raven Ridge silicon as pve2 but has never frozen. Deliberately - # left WITHOUT the C-state workaround: processor.max_cstate=1 blocks - # deeper idle states and costs power, so it is not applied pre-emptively. - # If pve3 ever freezes, set pve_kernel_cmdline_extra here too. + # ThinkCentre, Ryzen 2400GE (Raven Ridge), 8G. pve_install_disk: nvme0n1 pve_bulk_disk: sda diff --git a/infrastructure/proxmox/ansible/requirements.txt b/infrastructure/proxmox/ansible/requirements.txt new file mode 100644 index 0000000..93a12ca --- /dev/null +++ b/infrastructure/proxmox/ansible/requirements.txt @@ -0,0 +1,2 @@ +proxmoxer==2.3.0 +requests==2.34.2 diff --git a/infrastructure/proxmox/ansible/requirements.yml b/infrastructure/proxmox/ansible/requirements.yml index d81e9e7..8740d2c 100644 --- a/infrastructure/proxmox/ansible/requirements.yml +++ b/infrastructure/proxmox/ansible/requirements.yml @@ -13,3 +13,5 @@ collections: - name: ansible.posix # mount, authorized_key - name: community.general # proxmox_kvm (guest lifecycle). NOTE: deb822_repository # is ansible.builtin, NOT community.general — see CLAUDE.md + - name: community.proxmox + version: 2.0.0 # typed PVE API modules for HA, SDN and guest lifecycle diff --git a/infrastructure/proxmox/ansible/roles/pve_ha/defaults/main.yml b/infrastructure/proxmox/ansible/roles/pve_ha/defaults/main.yml index 7bcf283..9cc3884 100644 --- a/infrastructure/proxmox/ansible/roles/pve_ha/defaults/main.yml +++ b/infrastructure/proxmox/ansible/roles/pve_ha/defaults/main.yml @@ -14,12 +14,26 @@ pve_ha_resources: max_restart: 3 max_relocate: 2 +# pve3 is the normal home for the critical router. Non-strict preserves pve1 +# and pve2 as failover targets when pve3 is unavailable. +pve_ha_rules: + - name: prefer-vyos-pve3 + type: node-affinity + resources: ["vm:100"] + nodes: ["pve3:100", "pve1:10", "pve2:10"] + strict: false + comment: "Prefer critical VyOS router on pve3, away from CI nodes" + +pve_ha_portable_vms: + - vmid: 100 + node: pve3 + cpu: x86-64-v2-AES + # --- Watchdog --------------------------------------------------------------- # ⚠ WHY THIS MATTERS: adding ANY HA resource arms fencing cluster-wide. PVE's # default is `softdog`, a SOFTWARE watchdog — a kernel timer, which CANNOT fire if -# the kernel itself is frozen. That is precisely the failure this cluster has -# actually seen (pve2's Raven Ridge idle freeze). A hardware watchdog is -# independent silicon and fires regardless. +# the kernel itself is frozen. A hardware watchdog is independent silicon and +# fires regardless. # # Verified available 2026-07-26: # pve1 (Intel i3-6100U) -> iTCO_wdt (timeout 30s) diff --git a/infrastructure/proxmox/ansible/roles/pve_ha/handlers/main.yml b/infrastructure/proxmox/ansible/roles/pve_ha/handlers/main.yml index e6b0574..d142bb2 100644 --- a/infrastructure/proxmox/ansible/roles/pve_ha/handlers/main.yml +++ b/infrastructure/proxmox/ansible/roles/pve_ha/handlers/main.yml @@ -2,7 +2,7 @@ # Deliberately does NOT reboot. Swapping the watchdog under an armed cluster is a # manual, ordered operation (see ../../../README-ha.md); a surprise rolling reboot # of all three nodes is exactly what you do not want an idempotent play to do. -- name: reboot required for watchdog +- name: Reboot required for watchdog ansible.builtin.debug: msg: >- Watchdog config changed on {{ inventory_hostname }}. It takes effect on the diff --git a/infrastructure/proxmox/ansible/roles/pve_ha/tasks/main.yml b/infrastructure/proxmox/ansible/roles/pve_ha/tasks/main.yml index f5d4463..9c9f698 100644 --- a/infrastructure/proxmox/ansible/roles/pve_ha/tasks/main.yml +++ b/infrastructure/proxmox/ansible/roles/pve_ha/tasks/main.yml @@ -1,10 +1,19 @@ --- -# Proxmox HA. Read roles/pve_ha/defaults/main.yml first — it explains why the -# resource list is short and why the watchdog choice is not cosmetic. +- name: Validate Proxmox API inputs + ansible.builtin.assert: + that: + - pve_api_host | length > 0 + - pve_api_user | length > 0 + - pve_api_token_id | length > 0 + - pve_api_token_secret | length > 0 + fail_msg: >- + Export PROXMOX_HOST, PROXMOX_USER, PROXMOX_TOKEN_ID and + PROXMOX_TOKEN_SECRET before running ha.yml. + run_once: true + delegate_to: localhost + no_log: true + tags: [ha] -# --- Watchdog --------------------------------------------------------------- -# Done BEFORE registering resources: adding a resource arms fencing, and arming -# fencing on a software watchdog is the weakest configuration. - name: Select the hardware watchdog module ansible.builtin.lineinfile: path: /etc/default/pve-ha-manager @@ -12,13 +21,9 @@ line: "WATCHDOG_MODULE={{ pve_ha_watchdog_module }}" create: false when: pve_ha_watchdog_module | length > 0 - notify: reboot required for watchdog + notify: Reboot required for watchdog tags: [ha, watchdog] -# watchdog-mux opens /dev/watchdog, which belongs to whichever watchdog registered -# FIRST. PVE loads softdog at boot, so it wins and the hardware module ends up as -# an unused watchdog1. Blacklisting softdog is what actually makes the hardware -# one take effect — setting WATCHDOG_MODULE alone does nothing. - name: Blacklist softdog so the hardware watchdog claims /dev/watchdog ansible.builtin.copy: dest: /etc/modprobe.d/pve-ha-watchdog.conf @@ -29,48 +34,147 @@ # {{ pve_ha_watchdog_module }} register as watchdog0 and own /dev/watchdog. blacklist softdog when: pve_ha_watchdog_module | length > 0 - notify: reboot required for watchdog + notify: Reboot required for watchdog tags: [ha, watchdog] -# ⚠ NOT DONE LIVE ON PURPOSE. Swapping the watchdog on a running node means -# stopping watchdog-mux and unloading softdog while fencing is armed — get the -# order wrong and the node self-fences (reboots). The safe live procedure is -# documented in ../../README-ha.md; otherwise it simply takes effect on the next -# reboot, which is why the handler only WARNS rather than rebooting anything. - -# --- Resources --------------------------------------------------------------- -- name: Read current HA resources - ansible.builtin.command: ha-manager status - register: pve_ha_status - changed_when: false - failed_when: false +- name: Read current HA VM configurations + ansible.builtin.uri: + url: >- + https://{{ pve_api_host }}:8006/api2/json/nodes/{{ item.node }}/qemu/{{ item.vmid }}/config + headers: + Authorization: >- + PVEAPIToken={{ pve_api_user }}!{{ pve_api_token_id }}={{ pve_api_token_secret }} + ca_path: /etc/ssl/certs/ca-certificates.crt + return_content: true + loop: "{{ pve_ha_portable_vms }}" + loop_control: + label: "VM {{ item.vmid }}" + register: pve_ha_vm_config run_once: true + delegate_to: localhost + no_log: true tags: [ha] -- name: Register HA resources - ansible.builtin.command: >- - ha-manager add {{ item.sid }} - --state {{ item.state }} - --max_restart {{ item.max_restart }} - --max_relocate {{ item.max_relocate }} +- name: Reconcile portable CPU models for HA VMs + community.proxmox.proxmox_kvm: + api_host: "{{ pve_api_host }}" + api_user: "{{ pve_api_user }}" + api_token_id: "{{ pve_api_token_id }}" + api_token_secret: "{{ pve_api_token_secret }}" + validate_certs: "{{ pve_api_validate_certs }}" + ca_path: /etc/ssl/certs/ca-certificates.crt + node: "{{ item.node }}" + vmid: "{{ item.vmid }}" + cpu: "{{ item.cpu }}" + update: true + state: present + loop: "{{ pve_ha_portable_vms }}" + loop_control: + label: "VM {{ item.vmid }} -> {{ item.cpu }}" + index_var: vm_index + when: pve_ha_vm_config.results[vm_index].json.data.cpu != item.cpu + run_once: true + delegate_to: localhost + no_log: true + vars: + ansible_python_interpreter: "{{ pve_api_python_interpreter }}" + tags: [ha] + +- name: Read current HA resources + ansible.builtin.uri: + url: "https://{{ pve_api_host }}:8006/api2/json/cluster/ha/resources" + headers: + Authorization: >- + PVEAPIToken={{ pve_api_user }}!{{ pve_api_token_id }}={{ pve_api_token_secret }} + ca_path: /etc/ssl/certs/ca-certificates.crt + return_content: true + register: pve_ha_resource_state + run_once: true + delegate_to: localhost + no_log: true + tags: [ha] + +- name: Reconcile HA resources through the PVE API + community.proxmox.proxmox_cluster_ha_resources: + api_host: "{{ pve_api_host }}" + api_user: "{{ pve_api_user }}" + api_token_id: "{{ pve_api_token_id }}" + api_token_secret: "{{ pve_api_token_secret }}" + validate_certs: "{{ pve_api_validate_certs }}" + ca_path: /etc/ssl/certs/ca-certificates.crt + name: "{{ item.sid }}" + state: present + hastate: "{{ item.state }}" + max_restart: "{{ item.max_restart }}" + max_relocate: "{{ item.max_relocate }}" loop: "{{ pve_ha_resources }}" loop_control: label: "{{ item.sid }}" - # `ha-manager add` errors if the resource already exists, so gate on the status - # output. This is what keeps a second run at changed=0. - when: "'service ' ~ item.sid not in (pve_ha_status.stdout | default(''))" + when: >- + _current_resource | length == 0 or + _current_resource.state != item.state or + _current_resource.max_restart | int != item.max_restart | int or + _current_resource.max_relocate | int != item.max_relocate | int run_once: true + delegate_to: localhost + no_log: true + vars: + ansible_python_interpreter: "{{ pve_api_python_interpreter }}" + _current_resource: >- + {{ pve_ha_resource_state.json.data | + selectattr('sid', 'equalto', item.sid) | first | default({}) }} tags: [ha] -- name: Verify HA is armed and the resource is known - ansible.builtin.command: ha-manager status - register: pve_ha_verify - changed_when: false +- name: Reconcile HA placement rules through the PVE API + community.proxmox.proxmox_cluster_ha_rules: + api_host: "{{ pve_api_host }}" + api_user: "{{ pve_api_user }}" + api_token_id: "{{ pve_api_token_id }}" + api_token_secret: "{{ pve_api_token_secret }}" + validate_certs: "{{ pve_api_validate_certs }}" + ca_path: /etc/ssl/certs/ca-certificates.crt + name: "{{ item.name }}" + state: present + type: "{{ item.type }}" + resources: "{{ item.resources }}" + nodes: "{{ item.nodes }}" + strict: "{{ item.strict }}" + comment: "{{ item.comment }}" + disable: false + loop: "{{ pve_ha_rules }}" + loop_control: + label: "{{ item.name }}" run_once: true + delegate_to: localhost + no_log: true + vars: + ansible_python_interpreter: "{{ pve_api_python_interpreter }}" tags: [ha] -- name: Show HA state - ansible.builtin.debug: - msg: "{{ pve_ha_verify.stdout_lines }}" +- name: Read HA placement rules through the PVE API + community.proxmox.proxmox_cluster_ha_rules_info: + api_host: "{{ pve_api_host }}" + api_user: "{{ pve_api_user }}" + api_token_id: "{{ pve_api_token_id }}" + api_token_secret: "{{ pve_api_token_secret }}" + validate_certs: "{{ pve_api_validate_certs }}" + ca_path: /etc/ssl/certs/ca-certificates.crt + register: pve_ha_rule_state run_once: true + delegate_to: localhost + no_log: true + vars: + ansible_python_interpreter: "{{ pve_api_python_interpreter }}" + tags: [ha] + +- name: Assert declared placement rules exist + ansible.builtin.assert: + that: + - >- + (pve_ha_rules | map(attribute='name') | list | + difference(pve_ha_rule_state.rules | default([]) | + map(attribute='rule') | list) | length) == 0 + fail_msg: A declared HA placement rule is absent after reconciliation + run_once: true + delegate_to: localhost tags: [ha] diff --git a/infrastructure/proxmox/ansible/roles/pve_kernel_params/tasks/main.yml b/infrastructure/proxmox/ansible/roles/pve_kernel_params/tasks/main.yml index 36fa284..90fdaac 100644 --- a/infrastructure/proxmox/ansible/roles/pve_kernel_params/tasks/main.yml +++ b/infrastructure/proxmox/ansible/roles/pve_kernel_params/tasks/main.yml @@ -1,7 +1,6 @@ --- # Per-host kernel cmdline additions, driven by `pve_kernel_cmdline_extra` in the -# inventory. Currently only pve2 sets it (Raven Ridge random-freeze workaround); -# pve3 is identical silicon but is deliberately left alone -- see inventory. +# inventory. No node currently sets it; keep the role for future kernel parameters. # # This is a no-op on hosts that do not define the variable. diff --git a/infrastructure/proxmox/ansible/roles/pve_sdn/tasks/main.yml b/infrastructure/proxmox/ansible/roles/pve_sdn/tasks/main.yml index f41ce4c..88cd9e1 100644 --- a/infrastructure/proxmox/ansible/roles/pve_sdn/tasks/main.yml +++ b/infrastructure/proxmox/ansible/roles/pve_sdn/tasks/main.yml @@ -1,60 +1,81 @@ --- -# SDN config is cluster-wide (/etc/pve/sdn/), so everything runs once. - -- name: Read existing zones - ansible.builtin.command: - cmd: "pvesh get /cluster/sdn/zones --output-format json" - register: _zones - changed_when: false - check_mode: false +- name: Validate Proxmox API inputs + ansible.builtin.assert: + that: + - pve_api_host | length > 0 + - pve_api_user | length > 0 + - pve_api_token_id | length > 0 + - pve_api_token_secret | length > 0 + fail_msg: Missing Proxmox API environment variables run_once: true + delegate_to: localhost + no_log: true -- name: Create the VLAN zone - ansible.builtin.command: - cmd: >- - pvesh create /cluster/sdn/zones --zone {{ pve_sdn_zone }} --type vlan - --bridge {{ pve_sdn_bridge }} --mtu {{ pve_sdn_mtu }} - --nodes {{ groups['pve'] | join(',') }} - when: pve_sdn_zone not in (_zones.stdout | from_json | map(attribute='zone') | list) +- name: Read current PVE SDN zones + ansible.builtin.uri: + url: "https://{{ pve_api_host }}:8006/api2/json/cluster/sdn/zones" + headers: + Authorization: >- + PVEAPIToken={{ pve_api_user }}!{{ pve_api_token_id }}={{ pve_api_token_secret }} + ca_path: /etc/ssl/certs/ca-certificates.crt + return_content: true + register: pve_sdn_zone_state run_once: true + delegate_to: localhost + no_log: true -- name: Read existing vnets - ansible.builtin.command: - cmd: "pvesh get /cluster/sdn/vnets --output-format json" - register: _vnets - changed_when: false - check_mode: false +- name: Reconcile the PVE VLAN zone + community.proxmox.proxmox_zone: + api_host: "{{ pve_api_host }}" + api_user: "{{ pve_api_user }}" + api_token_id: "{{ pve_api_token_id }}" + api_token_secret: "{{ pve_api_token_secret }}" + validate_certs: "{{ pve_api_validate_certs }}" + ca_path: /etc/ssl/certs/ca-certificates.crt + zone: "{{ pve_sdn_zone }}" + type: vlan + bridge: "{{ pve_sdn_bridge }}" + mtu: "{{ pve_sdn_mtu }}" + nodes: "{{ groups['pve'] | join(',') }}" + update: true + state: present + when: >- + _current_zone | length == 0 or + _current_zone.type != 'vlan' or + _current_zone.bridge != pve_sdn_bridge or + _current_zone.mtu | int != pve_sdn_mtu | int or + _current_zone.nodes != (groups['pve'] | join(',')) run_once: true + delegate_to: localhost + no_log: true + vars: + ansible_python_interpreter: "{{ pve_api_python_interpreter }}" + _current_zone: >- + {{ pve_sdn_zone_state.json.data | + selectattr('zone', 'equalto', pve_sdn_zone) | first | default({}) }} -- name: Create the VNets - ansible.builtin.command: - cmd: >- - pvesh create /cluster/sdn/vnets --vnet {{ item.name }} - --zone {{ pve_sdn_zone }} --tag {{ item.tag }} - --alias '{{ item.alias }}' +# The module owns the SDN global lock and applies pending configuration before +# releasing it; a separate unconditional `pvesh set /cluster/sdn` is harmful +# because it reports changed on every run. +- name: Reconcile PVE VNets + community.proxmox.proxmox_vnet: + api_host: "{{ pve_api_host }}" + api_user: "{{ pve_api_user }}" + api_token_id: "{{ pve_api_token_id }}" + api_token_secret: "{{ pve_api_token_secret }}" + validate_certs: "{{ pve_api_validate_certs }}" + ca_path: /etc/ssl/certs/ca-certificates.crt + vnet: "{{ item.name }}" + zone: "{{ pve_sdn_zone }}" + tag: "{{ item.tag }}" + alias: "{{ item.alias }}" + update: true + state: present loop: "{{ pve_sdn_vnets }}" loop_control: - label: "{{ item.name }} (vlan {{ item.tag }})" - when: item.name not in (_vnets.stdout | from_json | map(attribute='vnet') | list) - run_once: true - -- name: Apply the SDN configuration - # SDN changes stay PENDING until applied; without this the VNet bridges are - # never actually created on the nodes. - ansible.builtin.command: - cmd: "pvesh set /cluster/sdn" - register: _apply - changed_when: true - run_once: true - -- name: Report - ansible.builtin.shell: - cmd: "pvesh get /cluster/sdn/vnets --output-format json | python3 -c \"import json,sys;[print(' ',v['vnet'],'vlan',v.get('tag'),'zone',v.get('zone')) for v in json.load(sys.stdin)]\"" - register: _rep - changed_when: false - run_once: true - -- name: Show it - ansible.builtin.debug: - msg: "{{ _rep.stdout_lines }}" + label: "{{ item.name }} (VLAN {{ item.tag }})" run_once: true + delegate_to: localhost + no_log: true + vars: + ansible_python_interpreter: "{{ pve_api_python_interpreter }}" diff --git a/infrastructure/proxmox/ansible/roles/pve_vm/defaults/main.yml b/infrastructure/proxmox/ansible/roles/pve_vm/defaults/main.yml index 10f7497..4452c5f 100644 --- a/infrastructure/proxmox/ansible/roles/pve_vm/defaults/main.yml +++ b/infrastructure/proxmox/ansible/roles/pve_vm/defaults/main.yml @@ -28,6 +28,9 @@ pve_vms: description: "VyOS router: OSPF peer to the NEC IX, gateway for the SDN VNets." cores: 2 memory: 2048 + # Portable across Intel pve1 and AMD pve2/pve3. `host` transferred memory + # but failed target-side resume during the 2026-09-17 migration. + cpu: x86-64-v2-AES disk: "pve-rg:8" # Headless by design: UEFI + serial console, NO emulated VGA. VyOS's own # kernel cmdline carries console=ttyS0, and the installer was answered with @@ -56,6 +59,7 @@ pve_vms: # are AMD. cores: 4 memory: 4096 + cpu: host disk: "pve-rg:40" bios: ovmf machine: q35 diff --git a/infrastructure/proxmox/ansible/roles/pve_vm/tasks/main.yml b/infrastructure/proxmox/ansible/roles/pve_vm/tasks/main.yml index b2ef218..f283c50 100644 --- a/infrastructure/proxmox/ansible/roles/pve_vm/tasks/main.yml +++ b/infrastructure/proxmox/ansible/roles/pve_vm/tasks/main.yml @@ -40,7 +40,7 @@ --description '{{ item.item.description }}' --machine {{ item.item.machine | default('q35') }} --bios {{ item.item.bios | default('ovmf') }} - --memory {{ item.item.memory }} --cores {{ item.item.cores }} --cpu host + --memory {{ item.item.memory }} --cores {{ item.item.cores }} --cpu {{ item.item.cpu | default('x86-64-v2-AES') }} --scsihw virtio-scsi-single --scsi0 {{ item.item.disk }},discard=on,ssd=1 --efidisk0 {{ item.item.disk.split(':')[0] }}:1,efitype=4m,pre-enrolled-keys=0 diff --git a/infrastructure/proxmox/ansible/roles/vyos_router/defaults/main.yml b/infrastructure/proxmox/ansible/roles/vyos_router/defaults/main.yml index 1438b22..1345402 100644 --- a/infrastructure/proxmox/ansible/roles/vyos_router/defaults/main.yml +++ b/infrastructure/proxmox/ansible/roles/vyos_router/defaults/main.yml @@ -71,3 +71,9 @@ vyos_ospf_router_id: "192.168.10.2" vyos_ospf_area: "0" vyos_ospf_networks: - "192.168.10.0/24" + +# Stable K3s datastore endpoint. Only the declared writable primary is present +# in the backend; PostgreSQL health checks must never be treated as promotion. +vyos_postgresql_listen_address: "10.60.0.1" +vyos_postgresql_port: 5432 +vyos_postgresql_primary_address: "10.60.0.11" diff --git a/infrastructure/proxmox/ansible/roles/vyos_router/templates/vyos.conf.j2 b/infrastructure/proxmox/ansible/roles/vyos_router/templates/vyos.conf.j2 index 76dab92..ca30c98 100644 --- a/infrastructure/proxmox/ansible/roles/vyos_router/templates/vyos.conf.j2 +++ b/infrastructure/proxmox/ansible/roles/vyos_router/templates/vyos.conf.j2 @@ -29,6 +29,17 @@ set protocols ospf area {{ vyos_ospf_area }} network {{ i.network }} set protocols ospf interface {{ i.iface }} passive {% endfor %} +{# --- K3s PostgreSQL datastore: fixed endpoint, explicitly selected primary --- #} +set load-balancing haproxy service sandbox-postgresql listen-address {{ vyos_postgresql_listen_address }} +set load-balancing haproxy service sandbox-postgresql port {{ vyos_postgresql_port }} +set load-balancing haproxy service sandbox-postgresql mode 'tcp' +set load-balancing haproxy service sandbox-postgresql backend 'sandbox-postgresql-primary' +set load-balancing haproxy backend sandbox-postgresql-primary mode 'tcp' +set load-balancing haproxy backend sandbox-postgresql-primary health-check 'pgsql' +set load-balancing haproxy backend sandbox-postgresql-primary server primary address {{ vyos_postgresql_primary_address }} +set load-balancing haproxy backend sandbox-postgresql-primary server primary port {{ vyos_postgresql_port }} +set load-balancing haproxy backend sandbox-postgresql-primary server primary check + {# --- management --- #} set service ssh port 22 diff --git a/infrastructure/samba-ad/ansible/dns-forwarder.yml b/infrastructure/samba-ad/ansible/dns-forwarder.yml new file mode 100644 index 0000000..3beb434 --- /dev/null +++ b/infrastructure/samba-ad/ansible/dns-forwarder.yml @@ -0,0 +1,10 @@ +--- +- name: 更新现有 AD 域控 DNS 上游 + hosts: samba_dc + become: true + gather_facts: false + tasks: + - name: 仅应用 DNS 转发器配置 + ansible.builtin.import_role: + name: samba_ad_dc + tasks_from: dns_forwarder diff --git a/infrastructure/samba-ad/ansible/group_vars/all/vars.yml b/infrastructure/samba-ad/ansible/group_vars/all/vars.yml index 99ad58b..42572b9 100644 --- a/infrastructure/samba-ad/ansible/group_vars/all/vars.yml +++ b/infrastructure/samba-ad/ansible/group_vars/all/vars.yml @@ -7,7 +7,7 @@ samba_ad_realm: "AD.DDUPAN.TOP" samba_ad_domain: "DDUPAN" samba_ad_dc_hostname: "dc1" samba_ad_dc_ip: "192.168.10.5" -samba_ad_dns_forwarder: "192.168.10.1" +samba_ad_dns_forwarder: "192.168.10.127" # Blocky:DN42 条件转发及公网解析 samba_ad_reverse_zone: "10.168.192.in-addr.arpa" # reverse of 192.168.10.0/24 # Static records now live in ../../dns/records.yml and are reconciled as complete diff --git a/infrastructure/samba-ad/ansible/roles/samba_ad_dc/tasks/dns_forwarder.yml b/infrastructure/samba-ad/ansible/roles/samba_ad_dc/tasks/dns_forwarder.yml new file mode 100644 index 0000000..93c3dc9 --- /dev/null +++ b/infrastructure/samba-ad/ansible/roles/samba_ad_dc/tasks/dns_forwarder.yml @@ -0,0 +1,10 @@ +--- +- name: 更新 Samba DNS 转发器并校验配置 + ansible.builtin.lineinfile: + path: /etc/samba/smb.conf + regexp: '^\s*dns forwarder\s*=' + insertafter: '^\[global\]' + line: "\tdns forwarder = {{ samba_ad_dns_forwarder }}" + validate: '/usr/bin/testparm -s %s' + backup: true + notify: restart samba-ad-dc diff --git a/infrastructure/samba-ad/ansible/roles/samba_ad_dc/tasks/main.yml b/infrastructure/samba-ad/ansible/roles/samba_ad_dc/tasks/main.yml index 4f7f779..c25755f 100644 --- a/infrastructure/samba-ad/ansible/roles/samba_ad_dc/tasks/main.yml +++ b/infrastructure/samba-ad/ansible/roles/samba_ad_dc/tasks/main.yml @@ -87,6 +87,9 @@ creates: /var/lib/samba/private/sam.ldb no_log: true # keep the admin password out of logs +- name: 持续管理已初始化域控的 DNS 上游 + ansible.builtin.import_tasks: dns_forwarder.yml + - name: Install the generated krb5.conf system-wide ansible.builtin.copy: src: /var/lib/samba/private/krb5.conf