- 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.
273 lines
16 KiB
YAML
273 lines
16 KiB
YAML
---
|
||
# Blueforce offline hazırlık raporu -- READ-ONLY, dalga kapsamlı.
|
||
# Ne yapar: internet olmadan PROVISIONED_OFFLINE durumuna ulaşmayı sağlayan yerel ön koşulları
|
||
# kanıtlarıyla raporlar:
|
||
# 1) offline APT repo mevcut mu (BF_OFFLINE_REPO, varsayılan /opt/blueforce/offline-repo,
|
||
# zorunlu Packages/Packages.gz/packages.lock.tsv/SHA256SUMS dosyaları),
|
||
# 2) yerel paketler doğrulanmış mı (sha256sum -c SHA256SUMS, paket kilidi şeması,
|
||
# havuzda pinlenen .deb dosyaları),
|
||
# 3) firstboot tamamlanma damgası var mı (/var/lib/blueforce/firstboot-complete),
|
||
# 4) onaysız update kapısı aktif mi (20auto-upgrades değerleri 0 + apt-daily timer
|
||
# maskeleme + apt/dpkg kilidi meşgul değil).
|
||
# Sözleşme: docs/27 (offline provisioning), docs/10 (update kapısı), docs/05 modül 14.
|
||
# 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-offline-ready-check.yml -e operation_wave=lab -e operation_confirmed=true --check
|
||
# Koşu: ansible-playbook -i inventory/hosts.yml playbooks/bf-offline-ready-check.yml -e operation_wave=lab -e operation_confirmed=true
|
||
# SHA256 doğrulamasını atlamak için: -e bf_offline_verify_hashes=false
|
||
# 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: Offline hazırlık ön koşullarını raporla (read-only)
|
||
hosts: "{{ operation_wave | default('invalid-wave', true) }}"
|
||
gather_facts: false
|
||
become: true # Yalnız root sahipli offline repo/durum dosyalarını OKUMAK için; mutasyon yok.
|
||
vars:
|
||
approved_operation_waves: [lab, pilot_1, pilot_2, wave_1, wave_2, production]
|
||
production_override: false
|
||
bf_offline_repo: "{{ bf_offline_repo_path | default('/opt/blueforce/offline-repo') }}"
|
||
bf_offline_verify_hashes: true
|
||
offline_firstboot_marker: /var/lib/blueforce/firstboot-complete
|
||
offline_list_file: /var/lib/blueforce/offline.list
|
||
offline_installer: /usr/local/sbin/blueforce-install.sh
|
||
offline_dealer_id_file: /etc/blueforce/dealer-id
|
||
offline_apt_periodic_conf: /etc/apt/apt.conf.d/20auto-upgrades
|
||
offline_required_repo_files: [Packages, Packages.gz, packages.lock.tsv, SHA256SUMS]
|
||
offline_update_timers: [apt-daily.timer, apt-daily-upgrade.timer]
|
||
offline_pkg_processes: [apt-get, apt, dpkg]
|
||
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: Offline kanıt yollarını denetle (read-only)
|
||
ansible.builtin.stat:
|
||
path: "{{ item }}"
|
||
loop:
|
||
- "{{ bf_offline_repo }}"
|
||
- "{{ offline_firstboot_marker }}"
|
||
- "{{ offline_list_file }}"
|
||
- "{{ offline_installer }}"
|
||
- "{{ offline_dealer_id_file }}"
|
||
- "{{ offline_apt_periodic_conf }}"
|
||
register: offline_evidence
|
||
|
||
- name: Zorunlu offline repo dosyalarını denetle (read-only)
|
||
ansible.builtin.stat:
|
||
path: "{{ bf_offline_repo }}/{{ item }}"
|
||
loop: "{{ offline_required_repo_files }}"
|
||
register: offline_repo_file_stats
|
||
|
||
- name: Kanıt durumlarını yol ile indeksle
|
||
ansible.builtin.set_fact:
|
||
offline_evidence_by_path: "{{ offline_evidence.results | items2dict(key_name='item', value_name='stat') }}"
|
||
bf_off_repo_files: "{{ dict(offline_repo_file_stats.results | map(attribute='item') | list | zip(offline_repo_file_stats.results | map(attribute='stat') | list)) }}"
|
||
bf_off_missing_files: >-
|
||
{{ (offline_repo_file_stats.results | selectattr('stat', 'defined') | rejectattr('stat.exists') | map(attribute='item') | list)
|
||
+ (offline_repo_file_stats.results | selectattr('stat', 'defined') | selectattr('stat.exists') | selectattr('stat.size', 'equalto', 0) | map(attribute='item') | list) }}
|
||
|
||
- name: Paket kilidi satırlarını doğrula (awk, read-only)
|
||
ansible.builtin.command:
|
||
argv:
|
||
- awk
|
||
- 'NR==1 {hdr = ($1=="package" && $2=="version" && $3=="architecture" && $4=="sha256" && $5=="source") ? 1 : 0; next} /^[[:space:]]*#/ || NF==0 {next} {rows++; if (NF==5 && $1 ~ /^[a-z0-9][a-z0-9+.-]*$/ && $4 ~ /^[a-f0-9]{64}$/ && $2!="" && $3!="" && $5!="") {ok++}} END {printf "header=%d rows=%d valid=%d\n", hdr, rows, ok}'
|
||
- "{{ bf_offline_repo }}/packages.lock.tsv"
|
||
register: offline_lock_probe
|
||
changed_when: false
|
||
failed_when: false
|
||
when:
|
||
- bf_off_repo_files['packages.lock.tsv'].exists | default(false)
|
||
- bf_off_repo_files['packages.lock.tsv'].size | default(0) | int > 0
|
||
|
||
- name: Kilitli paket kaynak yollarını listele (awk, read-only)
|
||
ansible.builtin.command:
|
||
argv:
|
||
- awk
|
||
- 'NR>1 && NF>0 && $0 !~ /^[[:space:]]*#/ {print $5}'
|
||
- "{{ bf_offline_repo }}/packages.lock.tsv"
|
||
register: offline_lock_sources
|
||
changed_when: false
|
||
failed_when: false
|
||
when:
|
||
- bf_off_repo_files['packages.lock.tsv'].exists | default(false)
|
||
- bf_off_repo_files['packages.lock.tsv'].size | default(0) | int > 0
|
||
|
||
- name: Pinlenen paketlerin havuz dosyalarını denetle (read-only)
|
||
ansible.builtin.stat:
|
||
path: "{{ bf_offline_repo }}/pool/{{ item }}"
|
||
loop: "{{ offline_lock_sources.stdout_lines | default([]) }}"
|
||
register: offline_pool_files
|
||
|
||
- name: Yerel APT indekslerinin SHA256 doğrulamasını yap (read-only)
|
||
ansible.builtin.command:
|
||
argv:
|
||
- sha256sum
|
||
- -c
|
||
- --quiet
|
||
- SHA256SUMS
|
||
chdir: "{{ bf_offline_repo }}"
|
||
register: offline_checksums
|
||
changed_when: false
|
||
failed_when: false
|
||
when:
|
||
- bf_offline_verify_hashes | bool
|
||
- bf_off_repo_files['SHA256SUMS'].exists | default(false)
|
||
- bf_off_repo_files['SHA256SUMS'].size | default(0) | int > 0
|
||
|
||
- name: Update kapısı yapılandırmasını oku (read-only)
|
||
ansible.builtin.slurp:
|
||
src: "{{ offline_apt_periodic_conf }}"
|
||
register: offline_apt_periodic_raw
|
||
when:
|
||
- offline_evidence_by_path[offline_apt_periodic_conf].exists | default(false)
|
||
- offline_evidence_by_path[offline_apt_periodic_conf].size | default(0) | int > 0
|
||
|
||
- name: Otomatik güncelleme timer maskelemesini oku (read-only)
|
||
ansible.builtin.command:
|
||
argv:
|
||
- systemctl
|
||
- is-enabled
|
||
- "{{ item }}"
|
||
loop: "{{ offline_update_timers }}"
|
||
register: offline_timer_states
|
||
changed_when: false
|
||
failed_when: false
|
||
|
||
- name: Paket işlemi çalışıyor mu denetle (read-only)
|
||
ansible.builtin.command:
|
||
argv:
|
||
- pgrep
|
||
- -x
|
||
- "{{ item }}"
|
||
loop: "{{ offline_pkg_processes }}"
|
||
register: offline_pkg_procs
|
||
changed_when: false
|
||
failed_when: false
|
||
|
||
- name: Offline hazırlık ölçümlerini hesapla
|
||
ansible.builtin.set_fact:
|
||
bf_off_repo_present: "{{ offline_evidence_by_path[bf_offline_repo].exists | default(false) and offline_evidence_by_path[bf_offline_repo].isdir | default(false) }}"
|
||
bf_off_missing_count: "{{ bf_off_missing_files | length }}"
|
||
bf_off_lock_header_ok: "{{ offline_lock_probe.stdout | default('') | regex_findall('header=([0-9]+)') | first | default('0', true) == '1' }}"
|
||
bf_off_lock_rows: "{{ offline_lock_probe.stdout | default('') | regex_findall('rows=([0-9]+)') | first | default('0', true) | int }}"
|
||
bf_off_lock_valid: "{{ offline_lock_probe.stdout | default('') | regex_findall('valid=([0-9]+)') | first | default('0', true) | int }}"
|
||
bf_off_pool_checked: "{{ offline_pool_files.results | default([]) | selectattr('stat', 'defined') | list | length }}"
|
||
bf_off_pool_missing: >-
|
||
{{ offline_pool_files.results | default([]) | selectattr('stat', 'defined') | map(attribute='stat')
|
||
| selectattr('exists', 'defined') | rejectattr('exists') | list | length }}
|
||
bf_off_firstboot_marker: "{{ offline_evidence_by_path[offline_firstboot_marker].exists | default(false) }}"
|
||
bf_off_installer_present: "{{ (offline_evidence_by_path[offline_installer].exists | default(false)) and (offline_evidence_by_path[offline_installer].executable | default(false)) }}"
|
||
bf_off_dealer_id_present: "{{ offline_evidence_by_path[offline_dealer_id_file].exists | default(false) }}"
|
||
bf_off_offline_list_present: "{{ offline_evidence_by_path[offline_list_file].exists | default(false) }}"
|
||
bf_off_apt_periodic: "{{ dict(bf_off_apt_periodic_values | map('first') | map('lower') | list | zip(bf_off_apt_periodic_values | map('last') | list)) }}"
|
||
bf_off_update_timers_masked: "{{ offline_timer_states.results | default([]) | map(attribute='stdout') | map('trim') | reject('equalto', 'masked') | list | length == 0 }}"
|
||
bf_off_pkg_lock_busy: "{{ offline_pkg_procs.results | default([]) | selectattr('rc', 'equalto', 0) | list | length > 0 }}"
|
||
vars:
|
||
bf_off_apt_periodic_values: >-
|
||
{{ offline_apt_periodic_raw.content | default('', true) | b64decode
|
||
| regex_findall('(?m)^APT::Periodic::([A-Za-z-]+)[ =]+[^0-9]*([0-9]+)') }}
|
||
|
||
- name: Yerel paket doğrulama ve update kapısı durumunu etiketle
|
||
ansible.builtin.set_fact:
|
||
bf_off_checksum_state: >-
|
||
{{ 'SKIPPED' if not (bf_offline_verify_hashes | bool)
|
||
else ('VERIFIED' if offline_checksums.rc | default(1) == 0
|
||
else ('MISSING' if not (bf_off_repo_files['SHA256SUMS'].exists | default(false)) else 'FAILED')) }}
|
||
bf_off_update_gate: >-
|
||
{{ 'ACTIVE' if (bf_off_apt_periodic.get('unattended-upgrade', '1') == '0'
|
||
and bf_off_apt_periodic.get('enable', '1') == '0'
|
||
and bf_off_update_timers_masked | bool)
|
||
else 'INACTIVE' }}
|
||
|
||
- name: Offline hazırlık engellerini topla
|
||
ansible.builtin.set_fact:
|
||
bf_off_blockers: >-
|
||
{{ (['offline repo yok veya dizin değil: ' ~ bf_offline_repo] if not (bf_off_repo_present | bool) else [])
|
||
+ ([('zorunlu offline dosyası eksik/boş: ' ~ (bf_off_missing_files | join(', ')))] if (bf_off_missing_count | int) > 0 else [])
|
||
+ (['paket kilidi başlığı geçersiz (package/version/architecture/sha256/source)'] if not (bf_off_lock_header_ok | bool) else [])
|
||
+ ([('paket kilidi geçersiz veya boş satırlar: ' ~ (bf_off_lock_valid | string) ~ '/' ~ (bf_off_lock_rows | string))] if ((bf_off_lock_rows | int) == 0 or (bf_off_lock_valid | int) != (bf_off_lock_rows | int)) else [])
|
||
+ ([('havuzda eksik paket dosyası: ' ~ (bf_off_pool_missing | string))] if (bf_off_pool_missing | int) > 0 else [])
|
||
+ (['yerel APT indeksi SHA256 doğrulaması BAŞARISIZ'] if bf_off_checksum_state == 'FAILED' else [])
|
||
+ (['onaysız update kapısı kapalı (unattended-upgrades veya apt-daily timer açık)'] if bf_off_update_gate != 'ACTIVE' else [])
|
||
+ (['başka bir paket işlemi koşuyor (apt/dpkg kilidi meşgul)'] if bf_off_pkg_lock_busy | bool else []) }}
|
||
|
||
- name: Offline hazırlık kararını yaz
|
||
ansible.builtin.set_fact:
|
||
bf_off_verdict: "{{ 'PROVISIONED_OFFLINE-READY' if (bf_off_blockers | length) == 0 else 'BLOCKED' }}"
|
||
|
||
- name: Cihaz offline hazırlık satırını raporla
|
||
ansible.builtin.debug:
|
||
msg:
|
||
- "{{ inventory_hostname | upper }} offline_repo={{ 'VAR' if bf_off_repo_present | bool else 'YOK' }} eksik_dosya={{ bf_off_missing_count }} paket_kilidi={{ bf_off_lock_valid }}/{{ bf_off_lock_rows }} havuz={{ bf_off_pool_checked }} eksik_havuz={{ bf_off_pool_missing }}"
|
||
- "{{ inventory_hostname | upper }} sha256={{ bf_off_checksum_state }} firstboot_damgasi={{ 'VAR' if bf_off_firstboot_marker | bool else 'YOK' }} update_kapisi={{ bf_off_update_gate }} paket_islemi_mesgul={{ bf_off_pkg_lock_busy }}"
|
||
- "{{ inventory_hostname | upper }} kurucu={{ 'VAR' if bf_off_installer_present | bool else 'YOK' }} bayi_kimligi={{ 'VAR' if bf_off_dealer_id_present | bool else 'YOK' }} offline.list={{ 'VAR' if bf_off_offline_list_present | bool else 'YOK' }} karar={{ bf_off_verdict }}"
|
||
|
||
- name: Offline ön koşulu eksik cihaz için engelleri yaz
|
||
ansible.builtin.debug:
|
||
msg: "ENGELLER {{ inventory_hostname | upper }}: {{ bf_off_blockers }}"
|
||
when: bf_off_blockers | length > 0
|
||
|
||
- name: Offline hazırlık karar dağılımını hesapla
|
||
ansible.builtin.set_fact:
|
||
bf_off_verdict_histogram: "{{ bf_off_verdict_histogram | default({}) | combine({ item: bf_off_wave_verdicts | select('equalto', item) | list | length }) }}"
|
||
loop: "{{ bf_off_wave_verdicts | unique | sort }}"
|
||
vars:
|
||
bf_off_wave_verdicts: "{{ ansible_play_hosts_all | map('extract', hostvars, 'bf_off_verdict') | map('default', 'BLOCKED', true) | list }}"
|
||
run_once: true
|
||
|
||
- name: Dalga offline hazırlık özetini yaz
|
||
ansible.builtin.debug:
|
||
msg:
|
||
- "Dalga {{ operation_wave }} | raporlanan cihaz: {{ ansible_play_hosts_all | length }}"
|
||
- "Karar dağılımı: {{ bf_off_verdict_histogram | default({}) }}"
|
||
- "Offline ön koşulu eksik cihazlar: {{ bf_off_blocked_hosts }}"
|
||
vars:
|
||
bf_off_blocked_hosts: "{{ dict(ansible_play_hosts_all | zip(ansible_play_hosts_all | map('extract', hostvars, 'bf_off_verdict') | map('default', 'BLOCKED', true) | list)) | dict2items | selectattr('value', 'equalto', 'BLOCKED') | map(attribute='key') | list }}"
|
||
run_once: true
|