- Updated `bf-wireguard-restart.yml` to include dynamic host targeting based on `operation_wave`. - Added pre-tasks to assert that the operation wave is approved and confirmed, ensuring proper execution context. - Modified the service restart task to use a more structured command format. - Adjusted `operations.py` and `ui_gui.py` to reflect changes in operation scope from "simple" to "wave" for relevant operations. - Improved UI handling in `ui_gui.py` and `ui_surfaces.py` for better output management and user interaction. - Updated log rotation configuration to target specific log files. - Enhanced systemd service configurations for WireGuard to improve restart behavior. - Refined firstboot scripts to ensure proper handling of dealer ID and state management. - Improved error handling and rollback mechanisms in identity and installation scripts.
184 lines
11 KiB
YAML
184 lines
11 KiB
YAML
---
|
||
# Blueforce provisioning faz raporu -- READ-ONLY, dalga kapsamlı.
|
||
# Ne yapar: /var/lib/blueforce/state.json kanıtından cihazların provisioning fazını çıkarır
|
||
# (PROVISIONED_OFFLINE / ENROLLED / READY / PENDING / INVALID), faz dağılımını ve
|
||
# faz ile yerel kanıtlar arasındaki çelişkileri (offline cihazda wg0.conf, damgasız
|
||
# firstboot) toplu raporlar. Durum makinesi sözleşmesi: docs/27 ve docs/29.
|
||
# Hiçbir görev durum değiştirmez: yalnız stat/slurp ve read-only CLI çıktısı okunur; tüm
|
||
# command görevleri changed_when: false taşır.
|
||
# Kuru koşu: ansible-playbook -i inventory/hosts.yml playbooks/bf-provisioning-status.yml -e operation_wave=lab -e operation_confirmed=true --check
|
||
# Koşu: ansible-playbook -i inventory/hosts.yml playbooks/bf-provisioning-status.yml -e operation_wave=lab -e operation_confirmed=true
|
||
# Semaphore Pro bağımlılığı yok.
|
||
# Dalga kapısı: operation_wave + operation_confirmed (+ production_override) rapor play'inden ÖNCE,
|
||
# hiçbir hosta dokunmadan doğrulanır; --limit dalga ile kesişmezse koşu reddedilir (fail-closed).
|
||
- name: Dalga ve onay kapısı -- hiçbir hosta dokunmadan önce (mutasyon yok)
|
||
hosts: "{{ ansible_limit | default('localhost') }}"
|
||
gather_facts: false
|
||
become: false
|
||
vars:
|
||
approved_operation_waves: [lab, pilot_1, pilot_2, wave_1, wave_2, production]
|
||
production_override: false
|
||
operation_wave_gate: "{{ operation_wave | default('invalid-wave', true) }}"
|
||
tasks:
|
||
- name: Require an approved confirmed operation wave before any host is touched
|
||
ansible.builtin.assert:
|
||
that:
|
||
- operation_wave_gate in approved_operation_waves
|
||
- operation_confirmed | default(false) | bool
|
||
- operation_wave_gate in groups
|
||
- groups[operation_wave_gate] | length > 0
|
||
- (operation_wave_gate != 'production') or (production_override | default(false) | bool)
|
||
fail_msg: "operation_wave must name a non-empty approved inventory group (lab/pilot_1/pilot_2/wave_1/wave_2/production), operation_confirmed=true is required, and production additionally requires production_override=true."
|
||
run_once: true
|
||
|
||
- name: Require the --limit selection to intersect the declared wave
|
||
ansible.builtin.assert:
|
||
that:
|
||
- (((ansible_limit | default('') | split(',') | intersect(groups[operation_wave_gate]) | length) > 0)
|
||
or ((ansible_limit | default('') | split(',') | select('in', groups) | map('extract', groups) | flatten | unique | intersect(groups[operation_wave_gate]) | length) > 0))
|
||
fail_msg: "--limit selection '{{ ansible_limit }}' does not intersect the '{{ operation_wave_gate }}' wave; refusing a mis-scoped run."
|
||
when:
|
||
- (ansible_limit | default('') | length) > 0
|
||
- not (ansible_limit | default('') is search('[][*?]'))
|
||
run_once: true
|
||
|
||
- name: Provisioning faz dağılımını raporla (read-only)
|
||
hosts: "{{ operation_wave | default('invalid-wave', true) }}"
|
||
gather_facts: false
|
||
become: true # Yalnız root sahipli kanıt dosyalarını (state.json 0600) OKUMAK için; mutasyon yok.
|
||
vars:
|
||
approved_operation_waves: [lab, pilot_1, pilot_2, wave_1, wave_2, production]
|
||
production_override: false
|
||
provisioning_state_file: /var/lib/blueforce/state.json
|
||
provisioning_firstboot_marker: /var/lib/blueforce/firstboot-complete
|
||
provisioning_wg_conf: /etc/wireguard/wg0.conf
|
||
provisioning_canonical_phases: [PROVISIONED_OFFLINE, ENROLLED, READY]
|
||
pre_tasks:
|
||
- name: Require confirmed approved operation wave scope
|
||
ansible.builtin.assert:
|
||
that:
|
||
- operation_wave in approved_operation_waves
|
||
# Undefined-safe confirmation gate (static gate idiom: 'operation_confirmed | bool').
|
||
- operation_confirmed | default(false) | bool
|
||
- operation_wave in groups
|
||
- groups[operation_wave] | length > 0
|
||
- ansible_play_hosts_all | length > 0
|
||
- ansible_play_hosts_all | difference(groups[operation_wave]) | length == 0
|
||
fail_msg: "Set operation_wave to one inventory wave and operation_confirmed=true; full-fleet scope is refused."
|
||
run_once: true
|
||
|
||
- name: Require separate production override
|
||
ansible.builtin.assert:
|
||
that:
|
||
- operation_wave != 'production' or production_override | bool
|
||
fail_msg: "Production operation requires production_override=true in addition to operation_confirmed=true."
|
||
run_once: true
|
||
tasks:
|
||
- name: Provisioning kanıt yollarını denetle (read-only)
|
||
ansible.builtin.stat:
|
||
path: "{{ item }}"
|
||
loop:
|
||
- "{{ provisioning_state_file }}"
|
||
- "{{ provisioning_firstboot_marker }}"
|
||
- "{{ provisioning_wg_conf }}"
|
||
register: provisioning_evidence
|
||
|
||
- name: Kanıt durumlarını yol ile indeksle
|
||
ansible.builtin.set_fact:
|
||
provisioning_evidence_by_path: "{{ provisioning_evidence.results | items2dict(key_name='item', value_name='stat') }}"
|
||
|
||
- name: Durum dosyasını oku (read-only)
|
||
ansible.builtin.slurp:
|
||
src: "{{ provisioning_state_file }}"
|
||
register: provisioning_state_raw
|
||
when:
|
||
- provisioning_evidence_by_path[provisioning_state_file].exists | default(false)
|
||
- provisioning_evidence_by_path[provisioning_state_file].size | default(0) | int > 0
|
||
|
||
- name: Durum dosyası içeriğini çöz (bozuk dosyada rapor INVALID kalır, koşu durmaz)
|
||
ansible.builtin.set_fact:
|
||
provisioning_state_parsed: "{{ provisioning_state_raw.content | b64decode | from_json }}"
|
||
when: provisioning_state_raw.content is defined
|
||
ignore_errors: true
|
||
|
||
- name: Provisioning fazını ve yerel kanıt durumunu belirle
|
||
ansible.builtin.set_fact:
|
||
bf_state_present: "{{ provisioning_state_raw.content is defined }}"
|
||
bf_state_parsed: "{{ provisioning_state_parsed | default({}, true) if provisioning_state_parsed is defined else {} }}"
|
||
bf_firstboot_marker_exists: "{{ provisioning_evidence_by_path[provisioning_firstboot_marker].exists | default(false) }}"
|
||
bf_offline_wg_conf_exists: "{{ provisioning_evidence_by_path[provisioning_wg_conf].exists | default(false) }}"
|
||
bf_provisioning_id_present: "{{ (provisioning_state_parsed | default({}, true)).get('provisioning_id', '') | length > 0 }}"
|
||
bf_device_id: "{{ (provisioning_state_parsed | default({}, true)).get('device_id', inventory_hostname | upper) }}"
|
||
|
||
- name: Provisioning fazını sınıflandır
|
||
ansible.builtin.set_fact:
|
||
bf_phase_raw: "{{ (provisioning_state_parsed | default({}, true)).get('phase', '') | default('', true) }}"
|
||
bf_enrollment_status: "{{ (provisioning_state_parsed | default({}, true)).get('enrollment_status', 'PENDING') | default('PENDING', true) }}"
|
||
bf_fleet_status: "{{ (provisioning_state_parsed | default({}, true)).get('fleet_status', 'PENDING') | default('PENDING', true) }}"
|
||
|
||
- name: Faz etiketini ve durum dosyası sağlığını belirle
|
||
ansible.builtin.set_fact:
|
||
bf_phase: >-
|
||
{{ 'PENDING' if not bf_state_present
|
||
else (bf_phase_raw if bf_phase_raw in provisioning_canonical_phases else 'INVALID') }}
|
||
bf_state_status: >-
|
||
{{ 'MISSING' if not bf_state_present
|
||
else ('OK' if bf_phase_raw in provisioning_canonical_phases else 'INVALID') }}
|
||
|
||
- name: Yerel kanıt çelişkilerini belirle
|
||
ansible.builtin.set_fact:
|
||
bf_offline_wg_conflict: "{{ bf_phase == 'PROVISIONED_OFFLINE' and bf_offline_wg_conf_exists }}"
|
||
bf_firstboot_conflict: "{{ bf_phase == 'PENDING' and bf_firstboot_marker_exists }}"
|
||
|
||
- name: Yerel kanıt çelişkisini tek etikete indir
|
||
ansible.builtin.set_fact:
|
||
bf_phase_conflict_line: >-
|
||
{{ 'OFFLINE_WG_CONFLICT' if bf_offline_wg_conflict | bool
|
||
else ('FIRSTBOOT_CONFLICT' if bf_firstboot_conflict | bool else 'OK') }}
|
||
|
||
- name: Cihaz provisioning satırını raporla
|
||
ansible.builtin.debug:
|
||
msg: >-
|
||
{{ bf_device_id }} phase={{ bf_phase }} enrollment={{ bf_enrollment_status }} fleet={{ bf_fleet_status }}
|
||
state.json={{ bf_state_status }} provisioning_id={{ 'SET' if bf_provisioning_id_present else 'ABSENT' }}
|
||
firstboot_damgasi={{ 'VAR' if bf_firstboot_marker_exists else 'YOK' }}
|
||
|
||
- name: Henüz provision edilmemiş cihazı işaretle
|
||
ansible.builtin.debug:
|
||
msg: "UYARI {{ bf_device_id }}: provisioning fazı {{ bf_phase }}; cihaz kurulum/enrollment bekliyor."
|
||
when: bf_phase in ['PENDING', 'INVALID']
|
||
|
||
- name: PROVISIONED_OFFLINE cihazda WireGuard hub yapılandırması çelişkisini işaretle
|
||
ansible.builtin.debug:
|
||
msg: "UYARI {{ bf_device_id }}: faz PROVISIONED_OFFLINE ama /etc/wireguard/wg0.conf var; offline provisioning hub config yazmamalıdır (docs/27)."
|
||
when: bf_offline_wg_conflict | bool
|
||
|
||
- name: Damgasız firstboot çelişkisini işaretle
|
||
ansible.builtin.debug:
|
||
msg: "UYARI {{ bf_device_id }}: state.json yok ama firstboot damgası var; kurulum yarım kalmış olabilir (bkz. docs/05 modül 18)."
|
||
when: bf_firstboot_conflict | bool
|
||
|
||
- name: Faz dağılımını hesapla (faz -> cihaz sayısı)
|
||
ansible.builtin.set_fact:
|
||
bf_phase_histogram: "{{ bf_phase_histogram | default({}) | combine({ item: bf_wave_phases | select('equalto', item) | list | length }) }}"
|
||
loop: "{{ bf_wave_phases | unique | sort }}"
|
||
vars:
|
||
bf_wave_phases: "{{ ansible_play_hosts_all | map('extract', hostvars, 'bf_phase') | map('default', 'INVALID', true) | list }}"
|
||
run_once: true
|
||
|
||
- name: Dalga provisioning özetini yaz
|
||
ansible.builtin.debug:
|
||
msg:
|
||
- "Dalga {{ operation_wave }} | raporlanan cihaz: {{ ansible_play_hosts_all | length }}"
|
||
- "Faz dağılımı: {{ bf_phase_histogram | default({}) }}"
|
||
- "Kanıtsız/geçersiz cihazlar: {{ bf_unprovisioned_hosts }}"
|
||
vars:
|
||
bf_unprovisioned_hosts: "{{ dict(ansible_play_hosts_all | zip(ansible_play_hosts_all | map('extract', hostvars, 'bf_phase') | map('default', 'INVALID', true) | list)) | dict2items | rejectattr('value', 'in', provisioning_canonical_phases) | map(attribute='key') | list }}"
|
||
run_once: true
|
||
|
||
- name: Yerel kanıt çelişkisi olan cihazların dalga listesini yaz
|
||
ansible.builtin.debug:
|
||
msg: "Çelişkili cihazlar (offline wg0.conf / damgasız firstboot): {{ bf_conflict_hosts }}"
|
||
vars:
|
||
bf_conflict_hosts: "{{ dict(ansible_play_hosts_all | zip(ansible_play_hosts_all | map('extract', hostvars, 'bf_phase_conflict_line') | map('default', 'OK', true) | list)) | dict2items | rejectattr('value', 'equalto', 'OK') | map(attribute='key') | list }}"
|
||
run_once: true
|