- name: Site-Datei laden ansible.builtin.set_fact: knaster_pve: "{{ lookup('ansible.builtin.file', pve_site_file) | from_yaml }}" delegate_to: localhost # ---------- 4.1 Benutzer, Gruppen, Rollen, Token ---------- - name: Rollen, Gruppen, Benutzer und ACLs lesen ansible.builtin.shell: | set -euo pipefail python3 - <<'PY' import json, subprocess def get(p): return json.loads(subprocess.check_output(["pvesh", "get", p, "--output-format", "json"])) print(json.dumps({ "roles": [r["roleid"] for r in get("/access/roles")], "groups": [g["groupid"] for g in get("/access/groups")], "users": [u["userid"] for u in get("/access/users")], "acl": [[a["path"], a["ugid"], a["roleid"]] for a in get("/access/acl")], })) PY args: {executable: /bin/bash} register: access_raw check_mode: false changed_when: false - name: Zugangsdaten als Fakt ansible.builtin.set_fact: pve_access: "{{ access_raw.stdout | from_json }}" - name: Rolle für den Healer (nur Ein/Aus und Lesen der GPU-VMs) ansible.builtin.command: pveum role add {{ pve_healer_role }} --privs "{{ pve_healer_privs }}" when: pve_healer_role not in pve_access.roles changed_when: true - name: Admin-Gruppe ansible.builtin.command: pveum group add {{ knaster_pve.access.admin_group }} --comment "Administratoren (knaster), root@pam nur Notfall" when: knaster_pve.access.admin_group not in pve_access.groups changed_when: true - name: Admin-Gruppe darf alles (Administrator auf /) ansible.builtin.command: pveum acl modify / --groups {{ knaster_pve.access.admin_group }} --roles Administrator when: "['/', knaster_pve.access.admin_group, 'Administrator'] not in pve_access.acl" changed_when: true # Ohne Passwort: das setzt scripts/pve-admin-user.sh vom Laptop (zusammen mit TOTP und Recovery-Keys) - name: Persönliche Admin-Konten ansible.builtin.command: > pveum user add {{ item }}@pve --groups {{ knaster_pve.access.admin_group }} --comment "persönlicher Admin, Passwort/TOTP per pve-admin-user.sh" loop: "{{ knaster_pve.access.admin_users }}" when: (item ~ '@pve') not in pve_access.users changed_when: true - name: Healer-Benutzer ansible.builtin.command: pveum user add {{ pve_healer_user }} --comment "knaster Healer (SPEC-healer), nur Token" when: pve_healer_user not in pve_access.users changed_when: true - name: Healer darf GPU-VMs im Pool gpu schalten ansible.builtin.command: pveum acl modify /pool/gpu --users {{ pve_healer_user }} --roles {{ pve_healer_role }} when: "['/pool/gpu', pve_healer_user, pve_healer_role] not in pve_access.acl" changed_when: true - name: Healer-Token lesen (Benutzer fehlt im Probelauf noch) ansible.builtin.command: pveum user token list {{ pve_healer_user }} --output-format json register: healer_tokens check_mode: false changed_when: false failed_when: false - name: Healer-Token anlegen (Geheimnis nur einmal sichtbar) ansible.builtin.command: > pveum user token add {{ pve_healer_user }} {{ pve_healer_token }} --privsep 0 --comment "knaster Healer auf infra" --output-format json register: healer_token_new when: (healer_tokens.stdout or '[]') | from_json | selectattr('tokenid', 'equalto', pve_healer_token) | list | length == 0 no_log: true changed_when: true - name: Healer-Token auf ops ablegen (nur beim Anlegen) ansible.builtin.copy: dest: "{{ pve_healer_token_file }}" content: | PVE_USER={{ pve_healer_user }} PVE_TOKEN_NAME={{ pve_healer_token }} PVE_TOKEN_VALUE={{ (healer_token_new.stdout | from_json).value }} mode: "0600" delegate_to: localhost when: healer_token_new is not skipped no_log: true - name: Healer-Token-Datei auf ops prüfen ansible.builtin.stat: path: "{{ pve_healer_token_file }}" delegate_to: localhost register: healer_file - name: Hinweis, wenn der Token existiert, aber die Datei fehlt ansible.builtin.debug: msg: "Token {{ pve_healer_user }}!{{ pve_healer_token }} existiert, {{ pve_healer_token_file }} fehlt – Token entfernen (pveum user token remove) und Lauf wiederholen." when: not healer_file.stat.exists # ---------- 4.3 Benachrichtigungen ---------- - name: Benachrichtigungsziele und Matcher lesen ansible.builtin.shell: | set -euo pipefail python3 - <<'PY' import json, subprocess def get(p): return json.loads(subprocess.check_output(["pvesh", "get", p, "--output-format", "json"])) print(json.dumps({ "sendmail": {e["name"]: e for e in get("/cluster/notifications/endpoints/sendmail")}, "matchers": {m["name"]: m for m in get("/cluster/notifications/matchers")}, })) PY args: {executable: /bin/bash} register: notify_raw check_mode: false changed_when: false - name: Benachrichtigungen als Fakt ansible.builtin.set_fact: pve_notify: "{{ notify_raw.stdout | from_json }}" # Zustellung direkt an die MX von mygodel.de (Postfix auf dem Knoten, 13.10. getestet: 250 queued) - name: Mail-Ziel knaster-mail ansible.builtin.command: > pvesh create /cluster/notifications/endpoints/sendmail --name knaster-mail --mailto {{ knaster_pve.access.mail_to }} --comment "Proxmox-Ereignisse an das KI-Postfach" when: "'knaster-mail' not in pve_notify.sendmail" register: notify_target changed_when: true - name: Matcher knaster-alles (jedes Ereignis an knaster-mail) ansible.builtin.command: > pvesh create /cluster/notifications/matchers --name knaster-alles --target knaster-mail --comment "Backup, Fencing, Updates, Replikation" when: "'knaster-alles' not in pve_notify.matchers" changed_when: true - name: Standard-Matcher abschalten (sonst kommt jede Mail doppelt) ansible.builtin.command: pvesh set /cluster/notifications/matchers/default-matcher --disable 1 when: not (pve_notify.matchers['default-matcher'].disable | default(false)) changed_when: true - name: Testnachricht über knaster-mail ansible.builtin.command: pvesh create /cluster/notifications/targets/knaster-mail/test when: notify_target is changed changed_when: false # ---------- 4.4 Firewall ---------- - name: Firewall-Regelwerk des Clusters schreiben ansible.builtin.template: src: cluster.fw.j2 dest: /etc/pve/firewall/cluster.fw mode: "0640" unsafe_writes: true register: cluster_fw - name: Regelwerk übersetzen (Syntaxprüfung, ändert nichts) ansible.builtin.command: pve-firewall compile check_mode: false changed_when: false - name: Totmannschalter stellen (Firewall nach {{ pve_firewall_deadman_seconds }} s wieder aus) ansible.builtin.shell: | systemctl stop knaster-fw-totmann.timer 2>/dev/null || true systemd-run --on-active={{ pve_firewall_deadman_seconds }} --unit knaster-fw-totmann --description "knaster: Firewall-Rollback" sed -i 's/^enable: 1$/enable: 0/' /etc/pve/firewall/cluster.fw when: cluster_fw is changed and knaster_pve.firewall.enable changed_when: true - name: Warten, bis alle Knoten das Regelwerk geladen haben ansible.builtin.pause: seconds: 15 when: cluster_fw is changed - name: Prüfung von ops aus (SSH und 8006 zu jedem Knoten) ansible.builtin.shell: | set -euo pipefail ssh -o BatchMode=yes -o ConnectTimeout=5 root@{{ hostvars[item].ansible_host }} 'pve-firewall status' curl -sk -m 5 -o /dev/null -w '%{http_code}\n' https://{{ hostvars[item].ansible_host }}:8006/ | grep -q 200 args: {executable: /bin/bash} loop: "{{ groups['pve_all'] | sort }}" delegate_to: localhost changed_when: false when: cluster_fw is changed and knaster_pve.firewall.enable - name: Totmannschalter aufheben (Prüfung bestanden) ansible.builtin.command: systemctl stop knaster-fw-totmann.timer when: cluster_fw is changed and knaster_pve.firewall.enable changed_when: true