feat(field-os): provisioning layer, real ISO build, offline contracts

Adds the Blueforce Field OS provisioning stack on top of the existing fleet
architecture, without rewriting the working systems (installer, diagnostics,
Ansible, WireGuard/SSH/xRDP/RustDesk, Docker policies, monitoring, Docusaurus).

ISO build (the previous builder was a skeleton that produced no medium):
- boot layout is read from the base ISO at build time via
  `xorriso -report_el_torito as_mkisofs` and rebuilt with `-as mkisofs`;
  `-boot_image any replay` cannot work on Ubuntu live ISOs because the GRUB2
  MBR lives in the source image's first 16 sectors and the EFI system partition
  is an appended partition, neither of which is a file in the ISO9660 tree
- preflight now asserts El Torito boot metadata positively: xorriso exits 0 on
  a non-ISO input, which previously let an arbitrary file pass --check
- artifact produced and verified: dist/Blueforce-Field-OS-1.0.0-amd64.iso,
  6 481 917 952 bytes, SHA256 cb0cc56f...d431296, BIOS+UEFI entries preserved,
  /autoinstall.yaml at the media root, casper/minimal.squashfs byte-identical
  to the base
- recorded baseline deviation: the verified medium on the build host is the
  Desktop flavour, so the manifest carries a baseline_deviation block instead
  of hiding it; Server remains one flag away

Offline and identity:
- offline APT contract with concrete package requirements, pin format and an
  audit table; builds fail closed while pins are UNPINNED
- autoinstall keeps interactive-sections: [storage]: the operator confirms the
  target disk, and no automatic wipe is ever performed
- firstboot terminal wizard for the 8-digit dealer id, three-state lifecycle
  (PROVISIONED_OFFLINE -> ENROLLED -> READY) with fail-closed gates, so a
  missing internet link or WireGuard handshake cannot fail provisioning but can
  never produce a false READY

Fleet and migration:
- five read-only Ansible playbooks for Field OS version, provisioning,
  enrollment, remote channels and offline readiness, all wave-scoped with a
  separate production override
- Windows pre-migration inventory and data export extended to the full brief
  scope, read-only, never collecting credentials

Tests: tests/run-all.sh -> 8/8 pass, exit 0. Boot acceptance (UEFI, Legacy
BIOS, Secure Boot), air-gapped install, real enrollment endpoint and Windows
rollback remain LAB work and are listed in IMPLEMENTATION-REPORT.md.
This commit is contained in:
emirhanmamak-blueforce 2026-09-16 11:14:42 +03:00
parent 64a557f713
commit ef102b9657
177 changed files with 44584 additions and 226 deletions

41
.gitignore vendored
View file

@ -1,2 +1,43 @@
.directory .directory
# Local agent/plan state — never committed
.hermes/ .hermes/
# ---------------------------------------------------------------------------
# Field OS build artifacts.
# These are large, generated, and reproducible from a verified upstream ISO.
# The upstream ISO itself is NOT stored in git (2.9 GB); its SHA256 is recorded
# in the release manifest instead. See docs/26 and docs/30.
# ---------------------------------------------------------------------------
.build-lab/
dist/
*.iso
*.iso.sha256
*.iso.boot-report.txt
# Offline APT repository output (generated by provisioning/offline-repo)
provisioning/offline-repo/out/
provisioning/offline-repo/pool/
Packages
Packages.gz
packages.lock.tsv
# Documentation site (generated)
docs-site/node_modules/
docs-site/build/
docs-site/.docusaurus/
docs-site/docs/
fetched-logs/
# Secrets must never be committed — belt and braces alongside the secret scan
# in tests/. Any private key, token file, or credential store belongs in a
# root-owned path on the host, never in this repository.
*.key
*.pem
*.p12
*.pfx
id_rsa*
id_ed25519*
*credentials*
*token*
*.secret

228
IMPLEMENTATION-REPORT.md Normal file
View file

@ -0,0 +1,228 @@
# Blueforce Field OS — Uygulama Raporu
**Tarih:** 2026-09-16
**Kapsam:** Windows 10 saha PC'lerinin Blueforce Field OS'a dönüştürülmesi için provisioning, ISO üretimi, offline kurulum, cihaz durumu ve enrollment katmanları.
**Yöntem:** Bu rapordaki her sayı depodaki gerçek dosyalardan, çalıştırılan testlerden ve üretilen ISO artefaktından okunmuştur. Doğrulanamayan hiçbir iddia "tamam" sayılmamıştır.
> **Dürüstlük sınırı:** Gerçek bir ISO üretildi ve doğrulandı. Ancak gerçek donanımda boot, gerçek Windows cihaz, gerçek merkez enrollment endpoint'i ve gerçek air-gapped `.deb` kurulumu **henüz yapılmadı**. Bu maddeler §7'de listelidir.
---
## 1. Önceden ne vardı?
- 00–25 numaralı mimari, işletim, güvenlik, rollout ve runbook dokümantasyonu; `scripts/` (installer + diagnostics), `ansible/`, `config/`, `monitoring/`, `rustdesk/`, `docs-site/` iskeleti.
- Çalışan `blueforce-install.sh` + 18 modül, `BF-<8 hane>` kimlik standardı, `bf-status` / `bf-diagnostics` / `bf-support-bundle` / `bf-hardware-inventory.sh`.
- WireGuard, SSH, xRDP, RustDesk OSS, Docker politikaları, UFW/Docker firewall, log yönetimi, recovery runbook'ları.
- **Eksik olan:** Field OS medya yaşam döngüsü (ISO gerçekten üretilmiyordu), offline paket deposu (manifestler boştu), firstboot kimlik akışı, 3 aşamalı durum modeli, enrollment sözleşmesi, Windows geçiş araçları ve doküman tutarlılığı.
---
## 2. Ne değişti?
### 2.1 ISO artık gerçekten üretiliyor (en kritik kazanım)
Önceki durumda `build-blueforce-iso.sh` bir iskeletti: `--allow-remaster` olmadan çıktı üretmiyor, boot menüsünü entegre etmiyor, offline repo eklemiyordu. Şimdi gerçek medya üretiyor.
**Üretilen artefakt (doğrulandı):**
| Alan | Değer |
|---|---|
| Dosya | `dist/Blueforce-Field-OS-1.0.0-amd64.iso` |
| Boyut | 6 481 917 952 byte (6.04 GiB) |
| ISO SHA256 | `cb0cc56f0442fbd1418e0d0f35521522f4ec9aa3f203779a67e96d587d431296` |
| Base ISO | `ubuntu-26.04.1-desktop-amd64.iso`, SHA256 `601e30fbf5d97759367c632e2c33630665039b7e2158fd068403da3ccf1bda1f` (Canonical `SHA256SUMS` ile birebir) |
| Base flavour | `desktop` (bkz. §8 baseline sapması) |
| Boot | El Torito **BIOS + UEFI** korundu |
| Build | 2026-09-16T08:00:29Z |
**Çözülen kök nedenler** (hepsi ölçülmüş hata mesajlarıyla, kalıcı referans: `ubuntu-iso-remaster` skill):
1. `-boot_image any replay` Ubuntu ISO'larında çalışmaz — GRUB2 MBR kodu kaynak imajın ilk 16 sektöründe, EFI sistemi ise ISO dosya sistemi *dışında* appended partition olarak durur. Çözüm: boot düzenini `xorriso -report_el_torito as_mkisofs` çıktısından okuyup `-as mkisofs` ile yeniden kurmak.
2. `-as mkisofs` xorriso-native `-volume_date` opsiyonunu tanımaz → `--modification-date` kullanılır.
3. `as_mkisofs` kendi `--modification-date` satırını verir; xorriso bunu `-volume_date uuid`'ye çevirip reddeder → filtrelenir.
4. Opsiyon satırları tek argümana çöküyordu → tırnak-honor eden tokenizer (30 argüman).
5. `sed` ile yol değişimi tırnakları bozuyor ve `--interval` alanlarını kaydırıyordu → yol yeniden yazılmaz.
### 2.2 Offline kurulum sözleşmesi
- `provisioning/offline-repo/manifests/{base-packages,remote-access,docker}.txt` artık hangi paketlerin gerektiğini **somut** listeliyor (pin formatı `package=version`), paket sürümleri LAB'da doldurulacak.
- `build-offline-repo.sh`: `apt-ftparchive` + `dpkg-deb` zorunlu; pin semantiği doğrulanır; placeholder/eksik pin net hatayla reddedilir.
- ISO içine gömülü repo olmadığında medium `offline_repo_status: not-included` yazar ve hedef cihaz `OFFLINE BLOCKED` ile kapalı kalır — sessizce "kurulmuş gibi" yapmaz.
### 2.3 Cihaz kimliği ve durum modeli
- `bf-firstboot`: bayi numarası terminal wizard'ı (`^[0-9]{8}$`), hatalı girişi kabul etmez, 3 denemede net hata; non-interactive modda dosya/argüman yolu. Kimlik değişimi ayrı admin/recovery prosedürüne bağlı.
- Üç aşamalı model: **`PROVISIONED_OFFLINE` → `ENROLLED` → `READY`**. İleri yönlü, atlama ve geri dönüş yok; her geçiş atomik ve 0600.
- `bf-status` artık `Provisioning / Enrollment / Fleet State` satırlarını gösteriyor.
- **Kritik davranış:** internet/WireGuard yokluğu kurulumu FAIL etmez. `bf-check-local` internetsiz PASS verebilir; `bf-check-enrollment` ve `bf-check-ready` merkez kanıtı olmadan **fail-closed** kalır.
### 2.4 Enrollment (tasarım + istemci)
- Tek kullanımlık token akışı seçildi; imzalı bootstrap bundle / kalıcı ortak secret / manuel kayıt karşılaştırılıp elendi.
- **700 cihaza aynı kalıcı secret konmaz.** WireGuard **private key cihazdan çıkmaz**; yalnız public key + non-secret metadata gönderilir.
- `blueforce-enroll.service` sınırlı yeniden deneme (`RestartSec`, `StartLimitBurst`), reboot loop yok; internet gelince kendiliğinden katılır, yoksa `PENDING` kalır.
- **Merkezi endpoint bilinçli olarak yazılmadı** (prompt §18: önce tasarım). Sözleşme JSON şemalarıyla sabitlendi.
### 2.5 Filo otomasyonu
- Mevcut 16 playbook korundu; **5 read-only** playbook eklendi: `bf-fieldos-version` (hangi cihazda hangi sürüm), `bf-provisioning-status`, `bf-enrollment-status`, `bf-remote-channels-check`, `bf-offline-ready-check`.
- Her playbook dalga kapsam kapısı taşıyor; production ayrıca `production_override` ister. Kapı matrisi canlı test edildi (boş/hatalı dalga, onaysız koşu, dalga dışı `--limit` → rc=2).
### 2.6 Windows geçişi ve canlı donanım kontrolü
- Read-only PowerShell envanter/veri dışa aktarma araçları prompt kapsamına (ağ, yazılım, servisler, COM/USB, VPN varlığı, MEG izleri) çıkarıldı; credential toplama yasak.
- `bf-live-hw-check`: Windows silinmeden önce NIC/disk/CPU/RAM/serial/USB kontrolü, `SUPPORTED / WARNING / BLOCKED` sonucu, mutasyonsuz.
### 2.7 Güvenlik ve dokümantasyon
- Depo çapında secret taraması; ISO'ya cihaz kimliği/private key/parola gömülmediği builder tarafından **reddedilir**.
- `.gitignore`: ISO, offline repo çıktısı, build scratch ve tüm anahtar/kimlik desenleri repo dışında.
- Doküman seti 33 dosyaya çıktı; medya stratejisi, RDP modeli, durum modeli, GNOME/XFCE ölçümü ve Field OS sürüm takibi kararları tek sesli hale getirildi.
---
## 3. Hangi yeni sistemler eklendi?
| Sistem | Ne yapar |
|---|---|
| **Field OS ISO build** | Doğrulanmış base ISO'dan tek USB'lik bootable medya üretir; boot düzenini base ISO'dan okuyup yeniden kurar |
| **Assisted autoinstall** | Kurulum otomatik, **disk seçimi operatörde** (`interactive-sections: [storage]`); otomatik wipe yok |
| **Offline APT repo** | Yerel, sürüm pinli `.deb` havuzundan checksum'lı repo üretir |
| **Firstboot wizard** | Bayi numarasını konsoldan toplar, kimliği üretir, offline kurucuyu çağırır |
| **3 aşamalı durum makinesi** | `PROVISIONED_OFFLINE` / `ENROLLED` / `READY`, fail-closed kapılar |
| **Enrollment istemcisi** | Tek kullanımlık token, replay engeli, private key dışarı çıkmaz |
| **Field OS sürüm takibi** | `/etc/blueforce-release`, `bf-release`, filo çapında sürüm raporlama |
| **Windows öncesi keşif** | Salt-okunur envanter + geri dönüş için veri dışa aktarma |
| **Canlı donanım uygunluğu** | Kurulumdan önce SUPPORTED/WARNING/BLOCKED kararı |
| **Dalga kapılı filo otomasyonu** | 21 playbook, dalga + production override zorunluluğu |
---
## 4. Neden eklendi?
- **Saha interneti yokken kurulum yapılabilmeli** ama cihaz merkez kanıtı olmadan yanlışlıkla READY sayılmamalı.
- **Disk silme güvenliği:** bu ISO, üzerinde başka bir işletim sistemi bulunan gerçek saha PC'lerinde kullanılacak. Bu nedenle V1 bilinçli olarak *assisted*: teknisyen hedef diski onaylar.
- **İzlenebilirlik:** Field OS sürümü, base ISO hash'i, offline snapshot ve kurucu sürümü ayrı ayrı kayıt altında; rollback mümkün.
- **Tek medya kolaylığı:** autoinstall ile Field OS ISO rakip değil, tamamlayıcıdır — biri motor, diğeri paketleme.
- **700 cihazda serbest değişiklik yok:** dalga, onay ve rollback kapıları korunur.
---
## 5. Hangi dosyalar değişti?
| Alan | İçerik |
|---|---|
| **Provisioning** | `provisioning/{iso,autoinstall,offline-repo,firstboot,enrollment,release}/` |
| **Tanı ve durum** | `scripts/diagnostics/` — `bf-status`, `bf-check-local`, `bf-check-enrollment`, `bf-check-ready`, `bf-enroll`, `bf-enroll-now`, `bf-enrollment-status`, `bf-release`, `bf-remote-status`, `bf-live-hw-check`, `bf-support-bundle`, `bf-hardware-inventory.sh` |
| **Kurulum** | `scripts/install/` (18 modül), `scripts/install/optional/anydesk/` |
| **Geçiş** | `scripts/migration/windows/` + kökte `migration/` işaret dosyası |
| **Filo** | `ansible/{inventory,playbooks,roles}/` (21 playbook, 4 rol) |
| **Yapılandırma** | `config/` (ssh, wireguard, firewall, docker, systemd) |
| **İzleme / uzak erişim** | `monitoring/`, `rustdesk/` |
| **Testler** | `tests/` — 8 test scripti + `run-all.sh` + QEMU planı |
| **Doküman** | `docs/00`–`docs/32` (33 dosya), `README.md`, `SUMMARY.md` |
| **Site** | `docs-site/` (Docusaurus) |
`dist/`, `.build-lab/`, `*.iso` ve anahtar/secret desenleri `.gitignore` ile repo dışında tutulur.
---
## 6. Hangi testler geçti?
`bash tests/run-all.sh` → **8/8 PASS, exit 0** (2026-09-16):
| Test | Kapsam |
|---|---|
| `check-specs.sh` | 33 doküman gapless, §1–§13 sıralı, her dokümanda diyagram, 21 playbook sözleşmesi, secret/moving-tag yasağı |
| `check-configs.sh` | Bash sözdizimi, YAML/JSON parse, inventory dalga grupları |
| `check-provisioning-static.sh` | Autoinstall kimliksiz, otomatik disk silme yok, interaktif depolama kapısı, secret taraması |
| `check-offline-repo-static.sh` | Pin semantiği, placeholder yasağı |
| `check-migration-static.sh` | Windows araçları read-only, credential toplamaz |
| `check-ansible-static.sh` | Kapı deseni, report-only security check, moving tag/Watchtower yasağı |
| `test-firstboot-static.sh` | Bayi no doğrulaması, hatalı giriş reddi, idempotentlik |
| `test-field-os-lifecycle.sh` | Durum geçişleri, offline local gate, ENROLLED fail-closed, replay engeli |
**ISO doğrulaması (bağımsız, iki araç):**
| Kontrol | Sonuç |
|---|---|
| El Torito BIOS + UEFI | ikisi de korunmuş |
| Blueforce payload (6 yol) | tümü medyada |
| `interactive-sections: [storage]` | korunmuş |
| Kimlik/secret gömülü mü | **hayır** |
| `apt.fallback: offline-install` | tanımlı |
| GRUB autoinstall satırları | 2 kernel satırı |
| `casper/minimal.squashfs` (3.43 GB) bütünlüğü | **birebir aynı** (xorriso `cmp` + `7z` sha256: `b5ec27b9570e77abb62831a8b652b50eaf089d537e62046189a52231a4fa9bec`) |
---
## 7. Hangi testler LAB gerektiriyor?
`tests/qemu-field-os-test-plan.md` T1–T18 henüz **çalıştırılmadı**:
- **Boot kabulü:** UEFI, Legacy BIOS ve **Secure Boot** altında gerçek boot. Remaster edilen medyanın boot zinciri yalnızca metadata düzeyinde doğrulandı.
- **Autoinstall:** Desktop flavour'da Subiquity'nin `/autoinstall.yaml`'ı gerçekten uyguladığı ve depolama ekranını interaktif bıraktığı.
- **Air-gapped kurulum:** gerçek offline `.deb` seti ile bağımlılık çözümü.
- **Firstboot:** gerçek konsolda wizard davranışı; systemd unit'inin boot'u kilitlememesi.
- **GNOME vs XFCE A/B ölçümü** (RAM/CPU/boot süresi/RDP/RustDesk headless/24h-72h stabilite).
- **xRDP + RustDesk headless:** fiziksel monitörsüz cihazda login ekranı ve dummy display davranışı.
- **Power-loss / otomatik kurtarma**, güç dönüşünde servis zinciri.
- **Enrollment:** gerçek merkez endpoint'i, token TTL/iptal, merkezi doğrulama kanıtları.
- **Windows geri dönüş provası:** image restore ve iş akışının geri gelmesi.
---
## 8. Hangi kararlar açık?
1. **Baseline sapması (LAB kararı gerekli):** kanonik baseline *Ubuntu Server 26.04.1+*; build host'ta doğrulanmış mevcut medya **Desktop** flavour olduğu için ISO bu tabanla üretildi. Sapma manifest'te `baseline_deviation` bloğuyla kayıtlı. Karar: LAB için Desktop kabul edilsin mi, yoksa Server ISO ile mi yeniden üretilsin? (Server yolu tek parametre uzakta.)
2. **Offline paket pinleri:** manifestler hangi paketlerin gerektiğini söylüyor, ancak **somut sürümler ve `.deb` dosyaları yok** — offline repo henüz üretilmedi. Ubuntu 26.04 build makinesi gerekir.
3. **Secure Boot:** remaster edilen medyanın Secure Boot davranışı test edilmedi.
4. **Enrollment kontrol düzlemi:** endpoint, kimlik doğrulama, imzalama, token TTL/iptal politikası ve audit saklama.
5. **Release artefakt saklama:** ISO/seed/offline snapshot retention, imza anahtarı sahibi, release kadansı, EOL.
6. **Windows imaj saklama:** süre, erişim yetkisi, imha ve geri dönüş RTO.
7. **MEG `.deb`:** paket gelmedi; acceptance checklist hazırlıklı bekliyor, hiçbir servis adı/config yolu varsayılmadı.
8. **ISO boyutu:** Desktop tabanı 6.04 GiB; Server tabanına göre (~2.9 GB) USB dağıtımını etkiler.
---
## 9. İlk gerçek LAB PC'sinde sırayla ne yapacağız?
1. Temsilci Windows PC'yi seç; bayi no ve seri no'yu iki bağımsız kayıtla doğrula. İş sahibi onayı ve kesinti penceresi al.
2. Windows üzerinde `BF-WindowsPreMigrationInventory.ps1` ve `BF-WindowsDataExport.ps1` çalıştır (salt-okunur). Hash doğrulamalı Windows geri dönüş imajı al; nerede tutulduğunu ve restore sahibini kaydet.
3. ISO SHA256'sını doğrula: `sha256sum -c dist/Blueforce-Field-OS-1.0.0-amd64.iso.sha256` → `cb0cc56f...`.
4. Medyayı USB'ye yaz ve **kurulumdan önce canlı oturumda** `bf-live-hw-check` çalıştır. `BLOCKED` çıkarsa dur.
5. Kurulumu başlat. Autoinstall çalışır ama **depolama ekranında hedef diski teknisyen onaylar** — otomatik wipe yok.
6. İlk açılışta wizard'da bayi numarasını gir (ör. `12010193` → `BF-12010193` / `bf-12010193`). `bf-status` ile **yalnız `PROVISIONED_OFFLINE`** görüldüğünü doğrula; cihazı READY olarak teslim etme.
7. Ağ olmadan dış indirme olmadığını ve local servis/boot loglarını kaydet.
8. Kontrollü ağ erişimi sağlandığında ve merkez kontrol düzlemi hazır olduğunda tek kullanımlık token ile enrollment yap (token'ı USB'ye/loga/rapora yazma).
9. `ENROLLED` sonrası WireGuard handshake, xRDP, RustDesk, MeshCentral, monitoring kanıtlarını ayrı ayrı doğrula. Tümü geçmeden READY yazma.
10. Gözlem pencereleri: **24 saat** boot/servis/ağ/uzak erişim · **72 saat** kesinti ve log/disk kararlılığı · **7 gün** saha iş akışı, güç dönüşü ve Windows restore provası. Kanıtlar release kaydına bağlanmadan pilot dalgaya geçme.
---
## 10. Production'a geçmeden önce yapılacaklar
- **Boot kabulü:** UEFI, Legacy BIOS ve Secure Boot matrisini gerçek donanımda kanıtla.
- **Gerçek offline paket seti:** Ubuntu 26.04 build makinesinde pinli `.deb` havuzunu üret, checksum/lock kaydı al, air-gapped kurulumda dene, ISO'ya gömüp yeniden build et.
- **Baseline kararı:** Desktop tabanını kabul et veya Server ISO ile yeniden üret (manifest `baseline_deviation` kaydı buna göre güncellenir).
- **Enrollment kontrol düzlemi:** endpoint + kimlik doğrulama + imzalama + token TTL/iptal + audit retention.
- **Merkez izleme/uzak erişim kanallarını** gerçek VDS üzerinde doğrula (RustDesk self-hosted hbbs/hbbr, Prometheus/Grafana/Uptime Kuma).
- **Release yönetimi:** artefakt deposu, imza anahtarı, retention/EOL, rollback prosedürü.
- **Windows veri/imaj saklama** ve geri dönüş RTO'sunu resmileştir.
- **Pilot hazırlığı:** envanter, alarm/on-call, eğitim, change approval ve dalga kapıları. Sıra: LAB(2) → P1(5) → P2(20) → W1(50) → W2(100) → production; her adım öncekinin kanıtı olmadan ilerlemez.
- **Zayıflatılmayacak kurallar:** onaysız update yasağı (apt, release upgrade, Docker image, MEG, Field OS), dalga onay kapıları, disk silme güvenliği, secret yasağı.
---
## 11. Build ortamı notu
ISO build'i bu makinede (CachyOS/Arch) **konteyner içinde** çalıştırıldı (`xorriso 1.5.6`), host'a paket kurulmadı. `apt-ftparchive`/`dpkg-deb` bu host'ta bulunmadığı için **offline `.deb` repo üretimi Ubuntu 26.04 build makinesi gerektirir** (§10).
---
## 12. Delil özeti (git diff ve repo ağacı)
- Commit edilmemiş çalışma ağacı: değiştirilmiş dokümanlar + yeni provisioning/ansible/config/monitoring/scripts/tests dosyaları.
- Üretilen artefakt: `dist/Blueforce-Field-OS-1.0.0-amd64.iso` (+ `.sha256`, `1.0.0-manifest.yaml`).
- Test sonucu: 8/8 PASS, exit 0.
- Repo ağacının tam çıktısı commit sonrası `git status` / `git diff --stat` ile alınır.

247
README.md
View file

@ -1,47 +1,116 @@
# Blueforce Linux Filo — 700 Saha Cihazı # Blueforce Field OS
> %100 ücretsiz / self-hosted Linux filo mimarisi: başka bir DevOps mühendisinin sıfırdan kurabileceği 26 Türkçe dokümanın ana reposu. > Ubuntu LTS tabanlı,
> 700 endüstriyel saha bilgisayarı için,
> offline kurulabilen,
> merkezden yönetilebilen,
> onaysız update yapmayan,
> çoklu uzak erişim kanallı
> **saha işletim sistemi standardı**.
## Amaç %100 ücretsiz / self-hosted Linux filo mimarisi: başka bir DevOps mühendisinin sıfırdan kurabileceği doküman setinin ve çalışan iskeletin ana reposu. Dokümanlar Türkçedir; komut, dosya, servis ve terim adları İngilizce kalır.
AdBlue saha otomasyonundaki ~700 Windows mini PC'yi, lisans maliyeti olmayan, merkezi yönetilebilir, kesintisiz çalışan bir Linux filosuna taşımak. ## Field OS Nedir?
Bu repo üç şeyi tek yerde toplar: Blueforce Field OS, AdBlue saha otomasyonundaki ~700 Windows mini PC'yi lisans maliyeti olmayan, merkezi yönetilebilir ve kesintisiz çalışan bir Linux filosuna taşıyan **standarttır**. Üç katmanı vardır:
1. **Kararlar** — her teknik seçim için gerekçe, alternatif, risk, maliyet ve lisans notu. 1. **Zemin (OS standardı)** — Ubuntu Server 26.04.1+ LTS minimal kurulum, maintain edilen GNOME katmanı, terminale boot (`multi-user.target`), `unattended-upgrades` kapalı.
2. **Kurulum ve işletme dokümanları** — `docs/` altında 26 Türkçe Markdown dosyası. 2. **Provisioning (kurulum standardı)** — tek `blueforce-install.sh` kurucu + autoinstall/NoCloud + sürümlü offline APT snapshot + firstboot; hepsi tek USB `Blueforce-Field-OS-<version>-amd64.iso` içinde paketlenebilir (assisted: operatör disk onayı verir, otomatik disk silme yoktur).
3. **Uygulanabilir iskelet** — `scripts/`, `ansible/`, `config/`, `monitoring/`, `rustdesk/` klasörleri (kod ve config Faz 3'te dolar). 3. **İşletme (filo standardı)** — WireGuard hub-spoke, 4 uzak erişim kanalı, Docker/MEG uygulamaları, Prometheus + Grafana + Uptime Kuma izleme, Ansible + Semaphore filo yönetimi ve onay kapılı güncelleme dalgaları (`LAB(2) → P1(5) → P2(20) → W1(50) → W2(100) → PROD(~523)`).
Öncelik sırası: **kesintisiz çalışma > veri kaybı yok > uzaktan erişim > bakım kolaylığı.** Öncelik sırası: **kesintisiz çalışma > veri kaybı yok > uzaktan erişim > bakım kolaylığı.**
## Katman Mimarisi
```mermaid
flowchart TB
subgraph SAHA["Saha cihazı — bf-<no> (Blueforce Field OS)"]
L8["Fleet Mgmt — Ansible / Semaphore (push, dalga kapılı)"]
L7["Monitoring — Prometheus + Node Exporter + Grafana + Uptime Kuma"]
L6["Docker Apps — MEG ve saha uygulamaları (unless-stopped, sabit tag)"]
L5["Remote Access — xRDP(her zaman hazır) + RustDesk + MeshCentral + SSH"]
L4["Provisioning — blueforce-install.sh + autoinstall + offline APT + firstboot"]
L3["Base Config — kimlik, SSH/sudo, UFW, Docker, logging, update policy"]
L2["Ubuntu LTS (Server 26.04.1+ minimal, GNOME katmanı, terminale boot)"]
L2 --> L3 --> L4 --> L5 --> L6 --> L7 --> L8
end
L8 -.->|"WireGuard hub-spoke"| MERKEZ["Merkez (VDS): WG hub + hbbs/hbbr + MeshCentral + Semaphore + Prometheus/Grafana + Kuma"]
```
| Katman | İçerik | Kilit karar | Doküman |
|---|---|---|---|
| Ubuntu LTS | Ubuntu Server 26.04.1+ minimal, GNOME, `multi-user.target` | K-01, K-02, K-03 | [`03`](docs/03-UBUNTU-BASELINE.md) |
| Base Config | `BF-<no>`/`bf-<no>` kimliği, SSH (anahtar zorunlu), UFW, Docker politikası, journald/log rotasyonu, otomatik update yasağı | K-10, K-11, K-12, K-13, K-14 | [`02`](docs/02-DEVICE-NAMING-AND-INVENTORY.md), [`06`](docs/06-USERS-SSH-AND-PERMISSIONS.md), [`11`](docs/11-SECURITY-HARDENING.md), [`12`](docs/12-DOCKER-OPERATIONS.md), [`15`](docs/15-LOGGING.md) |
| Provisioning | `blueforce-install.sh` (18 modül, `--check`), autoinstall + NoCloud seed, offline APT repo, firstboot, Field OS ISO | K-09, K-15, K-16, K-20 | [`04`](docs/04-GOLDEN-IMAGE-AND-PROVISIONING.md), [`05`](docs/05-ONE-CLICK-INSTALLER.md), [`26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md), [`27`](docs/27-OFFLINE-PROVISIONING.md) |
| Remote Access | xRDP (her zaman hazır) + RustDesk OSS self-hosted + MeshCentral + SSH-over-WireGuard = 4 kanal | K-04, K-05, K-22 | [`07`](docs/07-REMOTE-ACCESS.md), [`08`](docs/08-WIREGUARD-AND-NETWORK.md) |
| MEG | Windows'tan taşınan MEG uygulaması ve kabul kriterleri | K-19 | [`13`](docs/13-MEG-LINUX-ACCEPTANCE.md), [`28`](docs/28-WINDOWS-TO-LINUX-MIGRATION.md) |
| Docker Apps | Saha konteynerleri: `unless-stopped`, sabit tag, rotasyonlu log, `127.0.0.1` bind | K-10 | [`12`](docs/12-DOCKER-OPERATIONS.md) |
| Monitoring | Prometheus + Node Exporter + Grafana OSS (17 metrik) + Uptime Kuma "last seen" | K-07, K-14 | [`14`](docs/14-MONITORING-AND-HEALTH.md) |
| Fleet Mgmt | Ansible CLI (birincil) + Semaphore UI Community (operatör arayüzü), 15 standart işlem | K-06, K-11 | [`09`](docs/09-FLEET-MANAGEMENT.md), [`10`](docs/10-UPDATE-AND-ROLLBACK-POLICY.md) |
## Kurulum Akışı (özet)
```mermaid
flowchart LR
ISO["Field OS ISO (tek USB)<br/>veya upstream ISO + seed"] --> FB["firstboot<br/>BLUEFORCE FIELD OS<br/>8 haneli bayi no"]
FB --> INST["blueforce-install.sh --offline<br/>18 modül + final-check"]
INST --> PO["PROVISIONED_OFFLINE<br/>yerel kurulum bitti, merkez doğrulaması yok"]
PO -->|"internet gelince"| EN["ENROLLED<br/>tek kullanımlık token + WireGuard"]
EN -->|"WG + erişim + monitoring doğrulandı"| RDY["READY<br/>teslim edilebilir"]
```
1. **Medya** — F2B'den itibaren tek USB: `Blueforce-Field-OS-<version>-amd64.iso` (autoinstall + offline APT repo + firstboot içinde). Geri dönüş yolu: resmi Ubuntu Server ISO + NoCloud seed USB.
2. **Kurulum** — Autoinstall çalışır; **storage adımı operatör onayı bekler, otomatik disk silme yoktur** (`interactive-sections: [storage]`). İnternet yoksa kurulum yerel snapshot'tan tamamlanır.
3. **firstboot** — Terminal wizard 8 haneli bayi noyu alır, `blueforce-install.sh --offline` çağırır; sonuç `bf-status` ile görünür.
4. **Durum kapısı** — Kurulum sonucu `PROVISIONED_OFFLINE`'dır; `ENROLLED` tek kullanımlık token ile, `READY` ise merkezi WireGuard/erişim/monitoring doğrulamasıyla alınır. `READY` tek durum değildir, "kuruldu" demek değildir.
5. **Sürüm izi** — `bf-release` cihazın Field OS sürüm kimliğini `/etc/blueforce-release` (veya `/etc/blueforce/release-manifest.yaml`) üzerinden okur; skeleton manifest `UNAPPROVED_SKELETON` döner ve dağıtılabilir release sayılmaz.
### Medya tabanı sapması (kayıtlı, gizlenmemiş)
Kanonik baseline **Ubuntu Server 26.04.1+ LTS**'tir. Build host'ta SHA256'sı doğrulanmış tek kurulum medyası Ubuntu **Desktop** ISO olduğu için (build hiçbir şey indirmez), üretilen ilk ISO **Desktop tabanlıdır**:
| Konu | Durum |
|---|---|
| Hedef işletim sistemi | Değişmedi: `ubuntu-server` + `ubuntu-desktop-minimal` + GNOME, `multi-user.target` |
| Üretilen medya tabanı | `ubuntu-26.04.1-desktop-amd64.iso`, `base_flavor: desktop` |
| Kayıt yeri | `dist/1.0.0-manifest.yaml` → `baseline_deviation` (gerekçe, etki, gereken karar, `lab_required: true`) |
| Avantaj | GNOME zaten medyada; masaüstü katmanı için ek kurulum adımı gerekmez |
| Gereken karar | LAB için Desktop tabanını kabul et **veya** üretim öncesi Server ISO ile yeniden üret (`--upstream-iso` ile tek parametre) |
Boot kabulü (UEFI/Legacy/Secure Boot) ve air-gapped kurulum henüz doğrulanmadı; bu medya **release değil, LAB artefaktıdır** (`release_status: lab-artifact`).
## 700 Cihaz Özeti ## 700 Cihaz Özeti
| Konu | Özet | | Konu | Özet |
|---|---| |---|---|
| Cihaz sayısı | ~700 saha PC'si (bayi/istasyon yanında) | | Cihaz sayısı | ~700 saha PC'si (bayi/istasyon yanında) |
| Cihaz kimliği | 8 haneli bayi no → Device ID `BF-<no>`, hostname `bf-<no>` (ör. `BF-12010193` / `bf-12010193`). Tüm sistemlerde aynı ID kullanılır. | | Cihaz kimliği | 8 haneli bayi no → Device ID `BF-<no>`, hostname `bf-<no>` (ör. `BF-12010193` / `bf-12010193`). Tüm sistemlerde aynı ID kullanılır. |
| Hedef OS | Ubuntu LTS (sürüm Faz 1'de resmi kaynaktan doğrulanacak) | | Hedef OS | Ubuntu Server 26.04.1+ LTS + GNOME katmanı (`bf-gui-on/off` yalnız **yerel fiziksel** GUI'yi kontrol eder) |
| Üretilen medya tabanı | **Desktop flavour** (bkz. aşağıdaki sapma notu) — `dist/Blueforce-Field-OS-1.0.0-amd64.iso`, manifest `baseline_deviation` bloğu |
| Ağ | Turkcell modem arkası, CGNAT/NAT varsayımı; merkezle bağlantı WireGuard tüneli üzerinden | | Ağ | Turkcell modem arkası, CGNAT/NAT varsayımı; merkezle bağlantı WireGuard tüneli üzerinden |
| Uzaktan erişim | RustDesk OSS self-hosted (hbbs/hbbr) ana yol; SSH + WireGuard yedek yol; RDP/GUI ihtiyaca göre | | Uzaktan erişim | 4 kanal: RustDesk OSS self-hosted (hbbs/hbbr), SSH-over-WireGuard, xRDP + GNOME (WG arkasında, her zaman hazır), MeshCentral |
| Filo yönetimi | Ansible + Semaphore UI Community (aday; Faz 2'de karar) | | Kurulum medyası | Tek USB Field OS ISO (assisted; otomatik disk silme yok); yedek yol upstream ISO + NoCloud seed USB |
| İzleme | Prometheus/Node Exporter/Grafana OSS veya Uptime Kuma (Faz 2'de karşılaştırılacak) | | Durum modeli | `PROVISIONED_OFFLINE → ENROLLED → READY` (READY = merkezi kanıt) |
| Filo yönetimi | Ansible CLI + Semaphore UI Community (MIT, $0) |
| İzleme | Prometheus/Node Exporter/Grafana OSS + Uptime Kuma |
| Güncelleme politikası | **Onaysız update yasak** (apt, release upgrade, Docker image, MEG dahil). Rollout: LAB(2) → PILOT-1(5) → PILOT-2(20) → WAVE-1(50) → WAVE-2(100) → PRODUCTION | | Güncelleme politikası | **Onaysız update yasak** (apt, release upgrade, Docker image, MEG dahil). Rollout: LAB(2) → PILOT-1(5) → PILOT-2(20) → WAVE-1(50) → WAVE-2(100) → PRODUCTION |
| Masaüstü kararı | GNOME baseline (K-02); LAB'da GNOME vs XFCE A/B ölçümü yapılır, revizyon yalnız ölçüm verisiyle (K-23) |
| Doküman dili | Türkçe; komut, dosya, servis ve terim adları İngilizce kalır | | Doküman dili | Türkçe; komut, dosya, servis ve terim adları İngilizce kalır |
## Docs Haritası (26 dosya) ## Doküman Haritası (33 numaralı doküman + şablon)
Tüm dokümanlar `docs/` altındadır ve `docs/_TEMPLATE.md` şablonuna uyar. Tüm numaralı dokümanlar `docs/` altındadır ve `docs/_TEMPLATE.md` şablonuna uyar (her dokümanda §1–§13 sırası ve en az bir Mermaid diyagramı vardır).
| No | Dosya | Konu | | No | Dosya | Konu |
|---|---|---| |---|---|---|
| 00 | `00-MASTER-PLAN.md` | Genel plan, fazlar, kapsam | | 00 | `00-MASTER-PLAN.md` | Genel plan, fazlar, kapsam |
| 01 | `01-ARCHITECTURE.md` | Uçtan uca mimari | | 01 | `01-ARCHITECTURE.md` | Uçtan uca mimari |
| 02 | `02-DEVICE-NAMING-AND-INVENTORY.md` | `BF-<no>` / `bf-<no>` isimlendirme ve envanter | | 02 | `02-DEVICE-NAMING-AND-INVENTORY.md` | `BF-<no>` / `bf-<no>` isimlendirme ve envanter |
| 03 | `03-UBUNTU-BASELINE.md` | Ubuntu taban kurulum | | 03 | `03-UBUNTU-BASELINE.md` | Ubuntu taban kurulum, GNOME, `bf-gui-*` |
| 04 | `04-GOLDEN-IMAGE-AND-PROVISIONING.md` | Golden image ve ilk kurulum | | 04 | `04-GOLDEN-IMAGE-AND-PROVISIONING.md` | Golden image ve ilk kurulum |
| 05 | `05-ONE-CLICK-INSTALLER.md` | Tek-tık kurucu | | 05 | `05-ONE-CLICK-INSTALLER.md` | Tek-tık kurucu (`blueforce-install.sh`) |
| 06 | `06-USERS-SSH-AND-PERMISSIONS.md` | Kullanıcılar, SSH, yetkiler | | 06 | `06-USERS-SSH-AND-PERMISSIONS.md` | Kullanıcılar, SSH, yetkiler |
| 07 | `07-REMOTE-ACCESS.md` | RustDesk + SSH + RDP erişim yolları | | 07 | `07-REMOTE-ACCESS.md` | RustDesk + SSH + RDP + MeshCentral erişim yolları |
| 08 | `08-WIREGUARD-AND-NETWORK.md` | WireGuard ve ağ | | 08 | `08-WIREGUARD-AND-NETWORK.md` | WireGuard ve ağ |
| 09 | `09-FLEET-MANAGEMENT.md` | Ansible/Semaphore filo yönetimi | | 09 | `09-FLEET-MANAGEMENT.md` | Ansible/Semaphore filo yönetimi |
| 10 | `10-UPDATE-AND-ROLLBACK-POLICY.md` | Güncelleme ve geri alma politikası | | 10 | `10-UPDATE-AND-ROLLBACK-POLICY.md` | Güncelleme ve geri alma politikası |
@ -56,61 +125,115 @@ Tüm dokümanlar `docs/` altındadır ve `docs/_TEMPLATE.md` şablonuna uyar.
| 19 | `19-TROUBLESHOOTING.md` | Sorun giderme | | 19 | `19-TROUBLESHOOTING.md` | Sorun giderme |
| 20 | `20-FIELD-TECHNICIAN-RUNBOOK.md` | Saha teknisyeni runbook | | 20 | `20-FIELD-TECHNICIAN-RUNBOOK.md` | Saha teknisyeni runbook |
| 21 | `21-CENTRAL-ADMIN-RUNBOOK.md` | Merkezi yönetici runbook | | 21 | `21-CENTRAL-ADMIN-RUNBOOK.md` | Merkezi yönetici runbook |
| 22 | `22-DOCUMENTATION-PLATFORM.md` | Doküman platformu (Docusaurus adayı) | | 22 | `22-DOCUMENTATION-PLATFORM.md` | Doküman platformu (Docusaurus) |
| 23 | `23-SECURITY-AND-LICENSE-AUDIT.md` | Güvenlik ve lisans denetimi | | 23 | `23-SECURITY-AND-LICENSE-AUDIT.md` | Güvenlik ve lisans denetimi |
| 24 | `24-DECISION-LOG.md` | Karar günlüğü (kaynak URL + sürüm + tarih ile) | | 24 | `24-DECISION-LOG.md` | Karar günlüğü — **kilit kaynak** (K-01…K-24, kaynak URL + sürüm + tarih) |
| 25 | `25-IMPLEMENTATION-ROADMAP.md` | Uygulama yol haritası | | 25 | `25-IMPLEMENTATION-ROADMAP.md` | Uygulama yol haritası (F0…F6, F2A/F2B) |
| 26 | `26-BLUEFORCE-FIELD-OS-ISO.md` | Field OS ISO ve kurulum medyası |
| 27 | `27-OFFLINE-PROVISIONING.md` | Çevrimdışı provision ve durum kapısı |
| 28 | `28-WINDOWS-TO-LINUX-MIGRATION.md` | Windows yedekli geçiş ve geri dönüş |
| 29 | `29-DEVICE-ENROLLMENT.md` | Cihaz enrollment ve READY doğrulaması |
| 30 | `30-FIELD-OS-RELEASE-MANAGEMENT.md` | Field OS release, manifest ve rollback |
| 31 | `31-LEARNING-GUIDE.md` | Field OS öğrenme rehberi (LAB pratiği) |
| 32 | `32-REPOSITORY-AUDIT-AND-CONFLICTS.md` | Depo denetimi, çelişki raporu ve giderim durumu |
Şablon: `docs/_TEMPLATE.md`. Karar günlüğü: `docs/24-DECISION-LOG.md`. Şablon: `docs/_TEMPLATE.md` (siteye alınmaz). Karar günlüğü: `docs/24-DECISION-LOG.md`. Özet ve soru-cevap: `SUMMARY.md`.
## Maliyet Kuralı ## Repo Yapısı
```
.
├── README.md
├── SUMMARY.md # Yönetici özeti, 37 soru-cevap, FREE bileşen listesi
├── IMPLEMENTATION-REPORT.md # Uygulama raporu (ne yapıldı / hangi testler geçti / LAB bekleyenler)
├── docs/ # 33 numaralı doküman (00–32) + _TEMPLATE.md
├── provisioning/ # Field OS medya üretimi
│ ├── autoinstall/ # autoinstall.yaml + user-data + meta-data (interactive storage)
│ ├── iso/ # ISO doğrulama + build-blueforce-iso.sh (+ test-iso.sh)
│ ├── offline-repo/ # offline APT repo üretici + paket manifest/pinleri
│ ├── firstboot/ # bf-firstboot (+ systemd unit)
│ ├── enrollment/ # enrollment request/response JSON şemaları
│ └── release/ # immutable release manifesti
├── scripts/
│ ├── install/ # blueforce-install.sh + installer/modules/01..18
│ ├── diagnostics/ # bf-status, bf-diagnostics, bf-support-bundle, bf-enroll, bf-check-*, bf-release, bf-remote-status
│ ├── maintenance/ # bf-gui-on / bf-gui-off
│ ├── migration/windows/ # Windows envanter ve veri dışa aktarma (PowerShell)
│ └── recovery/ # kurtarma scriptleri
├── ansible/ # inventory, 16 playbook, 4 rol (dalga grupları pilot_1/wave_1)
├── config/ # ssh, wireguard, firewall, docker, systemd, ufw
├── monitoring/ # Prometheus + Grafana + Uptime Kuma config ve panoları
├── rustdesk/ # hbbs/hbbr self-hosted config
├── docs-site/ # Docusaurus iskeleti (numaralı dokümanları senkronlar)
├── tests/ # 7 doğrulama scripti + QEMU/LAB test planı
└── research/ # kaynak doğrulama notları (URL + sürüm + tarih)
```
## Geliştirici ve Bakımcı
Kurulum komutları (geliştirme ortamı):
```bash
# 1) Doğrulama testleri (repo kökünde, hepsi mock/statik; gerçek cihaz gerekmez)
tests/run-all.sh # hepsini sırayla koşar, çıkış kodlarını toplar (önerilen)
tests/check-specs.sh # doküman seti, bölüm sırası, Mermaid, playbook/rol ve güvenlik sözleşmeleri
tests/check-provisioning-static.sh # autoinstall/seed/ISO statik denetimleri
tests/check-offline-repo-static.sh # offline APT pin/biçim sözleşmesi (UNPINNED sentinel yasağı)
tests/check-migration-static.sh # Windows geçiş araçları: mutasyon/credential yok denetimi
tests/check-ansible-static.sh # playbook dalga/kapsam kapıları, report-only update denetimi
tests/check-configs.sh # YAML/JSON/bash sözdizimi
tests/test-field-os-lifecycle.sh # durum makinesi + enrollment + release kapıları (mock)
tests/test-firstboot-static.sh # firstboot wizard + installer bayrak sözleşmesi
# 2026-09-16 anlık görüntüsü: `tests/run-all.sh` → 8/8 PASS, exit code 0.
# Bu testler statiktir/mock'tur; gerçek ISO boot, gerçek Windows cihaz veya merkezi
# endpoint gerektirmez. LAB gerektiren maddeler IMPLEMENTATION-REPORT.md §7'dedir.
# ISO build (konteynerde çalıştırılabilir; xorriso gerekir)
# ./provisioning/iso/build-blueforce-iso.sh \
# --upstream-iso /path/ubuntu-26.04.1-desktop-amd64.iso \
# --checksum-file provisioning/iso/ubuntu-26.04.1-SHA256SUMS
# 2) Docusaurus sitesini yerelde derle
docs-site/sync-docs.sh && (cd docs-site && npm ci && npm run build)
# 3) Kurucu kuru koşu (mutasyonsuz)
sudo scripts/install/blueforce-install.sh --dealer-id 12010193 --check
```
Katkı kuralları (bu repo için bağlayıcı):
- **Doküman dili Türkçe**; komut/dosya/servis adları İngilizce. Yeni doküman eklerken `docs/_TEMPLATE.md` yapısını koru: `## 1.` … `## 13.` sırası zorunlu, ek bölümler `## Ek:` başlığıyla; her numaralı dokümanda en az bir Mermaid bloğu olmalı (bunları `tests/check-specs.sh` denetler).
- **Numaralandırma:** `docs/[0-9][0-9]-*.md`; mevcut numara yeniden kullanılmaz. Karar günlüğünde kararlar **append-only**'dir (K-01…K-24); var olan karar silinmez, yeni numara eklenir, değişiklik `24-DECISION-LOG.md` PR'ından yapılır.
- **Yetki sınırı:** Dokümanlar `docs/` içinden yazılır; `docs-site/` elle düzenlenmez (senkron scripti üretir, `_TEMPLATE.md` siteye alınmaz).
- **Secret yasağı:** Repoda, seed/ISO/manifest/support bundle içinde parola, private key, token veya cihaz-spesifik kimlik bulunmaz. Cihaz kimliği (`bf-<no>`) imajda değil, ilk açılışta enjekte edilir.
- **Güvenlik kilitleri (kod ve test seviyesinde):** `interactive-sections: [storage]` (otomatik disk silme yok), `unattended-upgrades` kapalı, `latest`/Watchtower yasak, `unless-stopped` zorunlu, Docker portları `127.0.0.1`'e bağlı, `bf-gui-off` xRDP'i asla durdurmaz. Bu sözleşmeleri bozan değişiklik testlerden geçemez.
- **Offline disiplini:** `blueforce-install.sh --offline` yalnız yerel `file:` APT kaynağını kullanır; uzak indirme yapmaz. Offline repo manifestlerinde somut `package=version` pinleri yoksa build kasıtlı olarak durur (`OFFLINE BLOCKED`) — pinleri onaylanmış paketlerle doldurmadan offline kurulum "tamam" sayılmaz.
- **Commit disiplini:** Bu çalışma ağacı bilinçli olarak commit edilmemiştir; değişiklikler review + release manifesti (K-18/K-24) üzerinden ilerler. Onaysız update yasağı yalnız cihazlar için değil, release hattı için de geçerlidir.
Maliyet kuralı:
- Hedef **%100 ücretsiz / self-hosted**'dir. - Hedef **%100 ücretsiz / self-hosted**'dir.
- Ücretli veya lisans kısıtlı her araç dokümanda açıkça işaretlenir ve **ücretsiz alternatifi** ile birlikte verilir. - Ücretli veya lisans kısıtlı her araç dokümanda açıkça işaretlenir ve **ücretsiz alternatifi** ile birlikte verilir.
- Varsayıma dayalı lisans kararı verilmez; resmi kaynak (resmi docs, GitHub, fiyat sayfası) URL + sürüm + tarih ile kaydedilir. - Varsayıma dayalı lisans kararı verilmez; resmi kaynak (resmi docs, GitHub, fiyat sayfası) URL + sürüm + tarih ile kaydedilir.
- Örnek: AnyDesk kararı resmi kaynaktan doğrulanmadan yazılmaz. - Örnek: AnyDesk kararı resmi kaynaktan doğrulanmadan yazılmaz.
## İsimlendirme Örneği İsimlendirme örneği — bayi no `12010193` için:
Bayi no `12010193` için: - Device ID: `BF-12010193`, hostname: `bf-12010193`
- WireGuard peer adı, RustDesk ID etiketi, Ansible inventory adı, monitoring etiketi: hepsi aynı ID.
- Device ID: `BF-12010193`
- Hostname: `bf-12010193`
- WireGuard peer adı, RustDesk ID etiketi, Ansible inventory adı, monitoring etiketi: hepsi aynı ID'yi kullanır.
## Klasör Yapısı
```
.
├── README.md
├── SUMMARY.md # Faz 4'te üretilecek
├── docs/ # 26 doküman (00–25) + _TEMPLATE.md
├── scripts/
│ ├── install/ # ilk kurulum scriptleri (Faz 3'te sözleşme/imza)
│ ├── maintenance/ # bakım scriptleri
│ ├── diagnostics/ # tanı scriptleri
│ └── recovery/ # kurtarma scriptleri
├── ansible/
│ ├── inventory/
│ ├── playbooks/
│ └── roles/
├── config/
│ ├── ssh/
│ ├── wireguard/
│ ├── firewall/
│ ├── docker/
│ └── systemd/
├── monitoring/ # izleme config ve panoları
├── rustdesk/ # hbbs/hbbr self-hosted config
├── docs-site/ # doküman sitesi iskeleti (Docusaurus adayı)
├── tests/ # doğrulama testleri
└── research/ # Faz 1 kaynak doğrulama notları
```
## Durum ## Durum
- [x] Faz 0: repo iskeleti + şablon | FAZ | İçerik | Durum |
- [ ] Faz 1: kaynak doğrulama (resmi docs/GitHub, 10 kritik konu) |---|---|---|
- [ ] Faz 2: mimari kararlar | F0 | Windows + donanım keşfi | [ ] |
- [ ] Faz 3: 26 dokümanın parti parti yazımı | F1 | Merkez altyapı | [ ] |
- [ ] Faz 4: QA, tutarlılık denetimi, SUMMARY.md | F2A | Blueforce bootstrap stabilizasyon | [ ] |
| F2B | Assisted Offline Field ISO | [ ] |
| F3 | LAB (2 cihaz) | [ ] |
| F4 | P1-P2-W1-W2 dalgaları | [ ] |
| F5 | Production rollout (~523) | [ ] |
| F6 | Gerçek zero-touch PXE (ayrı FAZ) | [ ] |
Repo iskeleti ve uygulama katmanı hazırdır: 33 numaralı doküman, `blueforce-install.sh` + 18 modül, provisioning katmanı (ISO builder, autoinstall, offline-repo sözleşmesi, firstboot, enrollment, release), 21 Ansible playbook + 4 rol, config/monitoring/rustdesk dosyaları ve 8 doğrulama testi bu repodadır. **Field OS kurulum medyası üretilmiştir:** `dist/Blueforce-Field-OS-1.0.0-amd64.iso` (6 481 917 952 byte, SHA256 `cb0cc56f0442fbd1418e0d0f35521522f4ec9aa3f203779a67e96d587d431296`), El Torito BIOS+UEFI korunmuş, `interactive-sections: [storage]` ile operatör disk onayı zorunlu, kimlik/secret gömülü değil. Henüz **hiçbir şey sahaya sürülmedi**: gerçek offline paket deposu yok (pinler `UNPINNED`, `.deb` havuzu boş), merkezi enrollment endpoint'i yok, boot kabulü (UEFI/Legacy/Secure Boot), air-gapped kurulum ve LAB kanıtları alınmadı. Güncel durum, madde madde giderim kaydı ve açık işler [`docs/32`](docs/32-REPOSITORY-AUDIT-AND-CONFLICTS.md) ve [`IMPLEMENTATION-REPORT.md`](IMPLEMENTATION-REPORT.md) içindedir. Sıradaki adımlar [`docs/25`](docs/25-IMPLEMENTATION-ROADMAP.md) yol haritasındadır; sahaya çıkış için önce F2B (offline paket seti + boot kabulü) ve F3 (LAB) kapıları kapanmalıdır.

View file

@ -1,33 +1,58 @@
# Blueforce Linux — Yönetici Özeti (SUMMARY) # Blueforce Field OS — Ubuntu LTS-based offline-installable centrally managed, no unapproved updates, multi remote-channel industrial field OS standard
> 700 saha cihazı için Ubuntu LTS tabanlı, çevrimdışı kurulabilen, merkezi yönetilen ve onaysız güncelleme yapmayan endüstriyel Field OS standardı.
> 700 saha cihazlık filonun kesin kararları, en büyük 10 operasyonel riski ve ücretsiz-bileşen listesi. > 700 saha cihazlık filonun kesin kararları, en büyük 10 operasyonel riski ve ücretsiz-bileşen listesi.
> Ayrıntı `docs/` dosyalarındadır; her cevap ilgili dokümana linklenir. Kararların kilit kaynağı > Ayrıntı `docs/` dosyalarındadır; her cevap ilgili dokümana linklenir. Kararların kilit kaynağı
> [`docs/24-DECISION-LOG.md`](docs/24-DECISION-LOG.md) (K-01…K-14), izlenebilirlik tablosu Ek B'dedir (S-01…S-27). > [`docs/24-DECISION-LOG.md`](docs/24-DECISION-LOG.md) (K-01…K-24), izlenebilirlik tablosu Ek B + Ek B-2'dedir (S-01…S-37).
> Mimari tek bakış: [`docs/01-ARCHITECTURE.md`](docs/01-ARCHITECTURE.md). Commit yok, FREE sürümler baz. > Mimari tek bakış: [`docs/01-ARCHITECTURE.md`](docs/01-ARCHITECTURE.md). Faz sırası: [`docs/25`](docs/25-IMPLEMENTATION-ROADMAP.md) (F0…F6). Commit yok, FREE sürümler baz.
--- ---
## 1. Şartnamedeki 27 Sorunun Kesin Cevabı ## 1. Field OS için Kısa Soru-Cevap (2026-09-16 turu)
- **Blueforce Field OS nedir?** — Ubuntu Server 26.04.1+ LTS üzerinde maintain edilen GNOME katmanı bulunan, çevrimdışı kurulabilen, merkezden yönetilen; onaysız güncelleme yapmayan ve 4 uzak erişim kanalı kullanan **endüstriyel saha işletim sistemi standardıdır**. Üç parçası vardır: zemin (Ubuntu LTS + GNOME + terminale boot), provisioning (tek kurucu + autoinstall/offline APT/firstboot + tek USB Field OS ISO) ve işletme (WireGuard + 4 kanal + Docker/MEG + monitoring + Ansible/Semaphore). **Medya tabanı sapması:** kanonik baseline Ubuntu **Server**'dır; build host'ta checksum'ı doğrulanmış tek medya Ubuntu **Desktop** ISO olduğu için üretilen ilk ISO Desktop tabanlıdır (manifest `baseline_deviation` bloğu). Hedef işletim sistemi yine Server paket setidir (`ubuntu-server` + `ubuntu-desktop-minimal` + GNOME, terminale boot); sapma yalnız medya tabanındadır ve LAB kararı bekler. → [`00`](docs/00-MASTER-PLAN.md), [`01`](docs/01-ARCHITECTURE.md), [`26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md), [`31`](docs/31-LEARNING-GUIDE.md) (K-01, K-02, K-20)
- **ISO nasıl build edilir?** — Autoinstall kurulum **motorudur**, Blueforce Field OS ISO ise **paketleme/dağıtım** yöntemidir; ikisi rakip değil, birlikte kullanılır. `provisioning/iso/build-blueforce-iso.sh --upstream-iso <BASE> --checksum-file <SHA256SUMS>` çalıştırılır; builder base ISO'yu doğrular, ağacı çıkarır, autoinstall + firstboot + offline-repo araçlarını ekler, GRUB satırlarına autoinstall yazar ve boot düzenini base ISO'nun kendi `as_mkisofs` reçetesinden yeniden kurar. **ISO üretilmiştir:** `dist/Blueforce-Field-OS-1.0.0-amd64.iso` (6 481 917 952 byte, SHA256 `cb0cc56f…d431296`), El Torito BIOS+UEFI korunmuş, `/autoinstall.yaml` medya kökünde. Boot kabulü (UEFI/Legacy/Secure Boot) ve air-gapped kurulum hâlâ LAB işidir; resmi Ubuntu ISO + NoCloud seed USB yedek/geri dönüş yoludur. → [`26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md), [`32`](docs/32-REPOSITORY-AUDIT-AND-CONFLICTS.md) (K-15, K-20)
- **Offline kurulum nasıl çalışır?** — Sürümlü medya + offline APT snapshot kullanılır; `blueforce-install.sh --offline` yalnız yerel `file:` APT kaynağından paket kurar (uzak indirme, key download, `apt download` yok) ve kurulum `PROVISIONED_OFFLINE` durumuna gelir. Subiquity'nin `apt.fallback` varsayılanı zaten `offline-install`'dır. Medya manifestlerinde somut `package=version` pinleri yoksa kurucu `OFFLINE BLOCKED` ile durur — "offline olur" demek için paket seti gerçekten dolu olmalıdır. → [`27`](docs/27-OFFLINE-PROVISIONING.md), [`31`](docs/31-LEARNING-GUIDE.md) (K-16, K-20, K-21)
- **Device state'leri nedir?** — Üç durum, tek sıra: `PROVISIONED_OFFLINE` → `ENROLLED` → `READY`. Tek kaynak `/var/lib/blueforce/state.json`, okuma komutu `bf-enrollment-status`. `READY` **tek durum değildir**: "kuruldu" READY değildir, "token alındı" READY değildir; yalnız WireGuard + merkezi remote + monitoring kanıtı doğrulanınca READY yazılır ve `bf-check-ready` bu kanıtı her seferinde yeniden denetler. → [`27`](docs/27-OFFLINE-PROVISIONING.md), [`29`](docs/29-DEVICE-ENROLLMENT.md) (K-16, K-17, K-21)
- **Internet olmadan ne olur?** — Yerel kurulum tamamlanır: paketler offline snapshot'tan gelir, cihaz `PROVISIONED_OFFLINE` olur. Cihaz çalışır ama merkezce doğrulanmamıştır: uzaktan erişim/izleme kanalları ve READY kapısı kapalı kalır. Yerel GUI kapalıyken bile xRDP servisi ayaktadır; ancak WireGuard tüneli olmadığı için merkezden erişilemez. → [`27`](docs/27-OFFLINE-PROVISIONING.md) (K-16, K-21, K-22)
- **Internet gelince ne olur?** — Merkezi admin, `BF-<no>`'ya bağlı kısa ömürlü ve tek kullanımlık token üretir; teknisyen tokenı yalnız cihaz ekranına girer (`bf-enroll --token-stdin`). Token tüketilince WireGuard yapılandırması atomik kurulur ve durum `ENROLLED` olur. Ardından güncel WG handshake, xRDP/RustDesk yapılandırması ve merkezi `monitoring`/`remote`/`management` kanıtı doğrulanınca `READY` yazılır; kanıt `pending` ya da eksikse cihaz ENROLLED'da kalır (fail-closed). → [`29`](docs/29-DEVICE-ENROLLMENT.md) (K-17, K-21)
- **Enrollment nasıl olur?** — Tek kullanımlık, TTL'li ve `BF-<no>`'ya bağlı token ile; token medyada/logda/support bundle'da tutulmaz, ikinci kullanım reddedilir ve audit kaydı üretilir. Enrollment bir "bağlantı kanıtı" değil, **kimlik bağlama** adımıdır; bu yüzden READY ayrı bir kapıdır. → [`29`](docs/29-DEVICE-ENROLLMENT.md) (K-17)
- **Windows'tan geçmeden önce ne yedeklenir?** — Üç kayıt: (1) donanım/bayi/seri no + çevre birimi + kritik iş akışı envanteri, (2) PowerShell araçlarıyla (`BF-WindowsPreMigrationInventory.ps1`, `BF-WindowsDataExport.ps1`) veri/uygulama dışa aktarımı, (3) hash'i doğrulanmış **Windows geri dönüş imajı** (restore sahibi ve saklama kaydıyla). Field OS temiz kurulumu bu imajı otomatik silmez; kabul başarısızsa imaj geri yüklenir. → [`28`](docs/28-WINDOWS-TO-LINUX-MIGRATION.md) (K-19)
- **Rollback nasıl olur?** — Üç seviye: (1) paket/config seviyesinde önceki **onaylı release manifestine** dönüş (`bf-release` ile sürüm kimliği doğrulanır), (2) imaj seviyesinde sorun varsa resmi Ubuntu ISO + önceki onaylı seed ile yeniden kurulum, (3) kabul başarısızsa Windows geri dönüş imajının restore edilmesi. Dalga içi başarısızlıkta yayılım durur ve etkilenen dalga geri alınır. → [`30`](docs/30-FIELD-OS-RELEASE-MANAGEMENT.md), [`10`](docs/10-UPDATE-AND-ROLLBACK-POLICY.md), [`26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md) (K-18, K-20)
- **Field OS version nasıl takip edilir?** — Cihazda `bf-release` komutu okunur; kaynak sırası `BF_RELEASE_MANIFEST` → `/etc/blueforce/release-manifest.yaml` → legacy `/etc/blueforce-release` → repo manifesti. Manifest `status: skeleton` ise komut `UNAPPROVED_SKELETON` yazar ve **exit≠0** verir: iskelet medya dağıtılabilir release sayılmaz. Gerçek sürüm kimliği immutable release manifestiyle (ISO/seed/offline APT snapshot/installer/Ansible commit + checksum) eşleşir. → [`30`](docs/30-FIELD-OS-RELEASE-MANAGEMENT.md) (K-18, K-24)
- **Operatör hangi rehberden öğrenir?** — Linux, ağ, otomasyon, ISO, enrollment ve rollback pratikleri yalnız LAB'da [`31`](docs/31-LEARNING-GUIDE.md) ile çalışılır; komutlar örnektir ve LAB doğrulaması gerektirir.
Bu 10 soru + öğrenme rehberi sorusu, karar günlüğünde S-28…S-37 olarak izlenir ([Ek B-2](docs/24-DECISION-LOG.md)).
---
## 2. Şartnamedeki 27 Sorunun Kesin Cevabı
1. **Hangi Ubuntu? Server mı Desktop mı?** — Ubuntu **Server 26.04.1+ LTS**, GUI'siz minimal kurulum. 1. **Hangi Ubuntu? Server mı Desktop mı?** — Ubuntu **Server 26.04.1+ LTS**, GUI'siz minimal kurulum.
Desktop ISO'sunun 6 GB RAM şartı saha bütçesini zorlar; GUI sonradan eklenir. → [`03`](docs/03-UBUNTU-BASELINE.md) (K-01) Desktop ISO'sunun 6 GB RAM şartı saha bütçesini zorlar; GUI sonradan eklenir. → [`03`](docs/03-UBUNTU-BASELINE.md) (K-01)
2. **GNOME mu XFCE mi?** — Cevap **GNOME** (Ubuntu Desktop standardı, kullanıcı kararı); XFCE elendi. 2. **GNOME mu XFCE mi?** — Saha standardı **GNOME** (Ubuntu Desktop standardı, kullanıcı kararı); XFCE saha standardı değildir.
→ [`03`](docs/03-UBUNTU-BASELINE.md) (K-02) Ancak K-23 gereği LAB'da GNOME vs XFCE **A/B ölçümü** yapılır (RAM, CPU, boot süresi, RDP güvenilirliği, RustDesk reboot sonrası,
login screen, dummy display, 24/72 saat stabilite) ve karar ölçüm verisine göre revize edilebilir; RustDesk headless ZORUNLU
şartı her iki kolda da korunur. → [`03`](docs/03-UBUNTU-BASELINE.md) (K-02, K-23)
3. **Normal boot GUI mi terminal mi?** — Cihazlar **terminale** (`multi-user.target`) boot eder; 3. **Normal boot GUI mi terminal mi?** — Cihazlar **terminale** (`multi-user.target`) boot eder;
kapalı GUI = düşük RAM, düşük saldırı yüzeyi, öngörülebilir boot. → [`03`](docs/03-UBUNTU-BASELINE.md), [`16`](docs/16-POWER-LOSS-AND-AUTO-RECOVERY.md) (K-03) kapalı GUI = düşük RAM, düşük saldırı yüzeyi, öngörülebilir boot. → [`03`](docs/03-UBUNTU-BASELINE.md), [`16`](docs/16-POWER-LOSS-AND-AUTO-RECOVERY.md) (K-03)
4. **GUI nasıl açılıp kapatılacak?** — `sudo bf-gui-on` / `sudo bf-gui-off` ile systemd üzerinden 4. **GUI nasıl açılıp kapatılacak?** — `sudo bf-gui-on` / `sudo bf-gui-off`, yalnız **yerel fiziksel** grafik katmanını
(display-manager + `xrdp`/`xrdp-sesman`); varsayılan kapalıdır. → [`03`](docs/03-UBUNTU-BASELINE.md), [`07`](docs/07-REMOTE-ACCESS.md) (K-03) (display-manager: gdm3/gdm/sddm/lightdm + default target) systemd üzerinden yönetir; varsayılan kapalıdır. `bf-gui-off`
xRDP servislerini **asla** durdurmaz: `xrdp`/`xrdp-sesman` her zaman enable+active kalır, yani **GUI kapalı ≠ RDP kapalı**.
→ [`03`](docs/03-UBUNTU-BASELINE.md), [`07`](docs/07-REMOTE-ACCESS.md) (K-03, K-22)
5. **Cihaz isimlendirme standardı?** — 8 haneli bayi no → insan arayüzünde `BF-<no>` (büyük), 5. **Cihaz isimlendirme standardı?** — 8 haneli bayi no → insan arayüzünde `BF-<no>` (büyük),
makine adında `bf-<no>` (küçük); 9 sistemde (hostname, WireGuard peer, RustDesk, MeshCentral, makine adında `bf-<no>` (küçük); 9 sistemde (hostname, WireGuard peer, RustDesk, MeshCentral,
Ansible, Prometheus, log, envanter, runbook) join anahtarıdır. Örn. `BF-12010193` / `bf-12010193`. → [`02`](docs/02-DEVICE-NAMING-AND-INVENTORY.md) (K-12) Ansible, Prometheus, log, envanter, runbook) join anahtarıdır. Örn. `BF-12010193` / `bf-12010193`. → [`02`](docs/02-DEVICE-NAMING-AND-INVENTORY.md) (K-12)
6. **İlk PC hazırlığı nasıl?** — İmaj dondurulmadan **önce** `bf-hardware-inventory.sh` ile donanım 6. **İlk PC hazırlığı nasıl?** — İmaj dondurulmadan **önce** `bf-hardware-inventory.sh` ile donanım
taraması (CPU/RAM/disk/NIC/GPU profilleri); profilsiz imaj sahada rastgele kırılır. → [`18`](docs/18-PILOT-AND-700-DEVICE-ROLLOUT.md), [`02`](docs/02-DEVICE-NAMING-AND-INVENTORY.md) (K-09) taraması (CPU/RAM/disk/NIC/GPU profilleri) ve Windows keşfi (F0); profilsiz imaj sahada rastgele kırılır. → [`18`](docs/18-PILOT-AND-700-DEVICE-ROLLOUT.md), [`28`](docs/28-WINDOWS-TO-LINUX-MIGRATION.md) (K-09, K-19)
7. **Yeni PC kurulumu kaç adım?** — Teknisyen için **4 adım**: USB tak → Ubuntu kur → 7. **Yeni PC kurulumu kaç adım?** — Teknisyen için akış: USB medyasıyla Ubuntu kur →
`blueforce-install.sh` + 8 haneli bayi no → `bf-status` ile READY gör, çık (<15 dk aktif iş). → [`20`](docs/20-FIELD-TECHNICIAN-RUNBOOK.md), [`04`](docs/04-GOLDEN-IMAGE-AND-PROVISIONING.md) (K-09) `blueforce-install.sh --offline` + 8 haneli bayi no → `bf-status` ile `PROVISIONED_OFFLINE` gör →
8. **Installer nasıl çalışacak?** — Tek giriş `blueforce-install.sh --dealer-id <8hane>`; **18 idempotent internet geldiğinde enrollment ve merkezi kontrolleri tamamla; yalnız sonra `READY` teslim edilir. →
modül** (precheck → dealer-id → paketler → GNOME → xRDP → SSH → WireGuard → Docker → MEG → RustDesk → [`27`](docs/27-OFFLINE-PROVISIONING.md), [`29`](docs/29-DEVICE-ENROLLMENT.md), [`20`](docs/20-FIELD-TECHNICIAN-RUNBOOK.md) (K-16, K-17, K-21)
MeshCentral → monitoring → firewall → envanter → logging → boot → bootstrap → final-check), 8. **Installer nasıl çalışacak?** — Tek giriş `blueforce-install.sh --dealer-id <8hane>`; çevrimdışı yol
çıkış kodu 0 = READY, format kapısı + onay ekranlı. → [`05`](docs/05-ONE-CLICK-INSTALLER.md) (K-09) `--offline` ile yerel medya/snapshot kullanır ve yalnız `PROVISIONED_OFFLINE` yazar. `READY`, installer
çıkış kodu değil enrollment sonrası merkezi WireGuard, erişim ve monitoring doğrulamasının sonucudur. →
[`05`](docs/05-ONE-CLICK-INSTALLER.md), [`27`](docs/27-OFFLINE-PROVISIONING.md), [`29`](docs/29-DEVICE-ENROLLMENT.md) (K-21)
9. **WireGuard mimarisi?** — **Hub-spoke**: merkez hub (`10.8.0.1`), her cihaz spoke (peer adı `bf-<no>`, 9. **WireGuard mimarisi?** — **Hub-spoke**: merkez hub (`10.8.0.1`), her cihaz spoke (peer adı `bf-<no>`,
sabit `/32`); istemcide `PersistentKeepalive = 25` + `wg-quick@wg0` enable; SSH/RDP yalnızca tünel sabit `/32`); istemcide `PersistentKeepalive = 25` + `wg-quick@wg0` enable; SSH/RDP yalnızca tünel
içinden, dış dünyaya kapalı. → [`08`](docs/08-WIREGUARD-AND-NETWORK.md) (K-05) içinden, dış dünyaya kapalı. → [`08`](docs/08-WIREGUARD-AND-NETWORK.md) (K-05)
@ -35,10 +60,13 @@
root SSH kapalı; 700 cihaza tek private key **yayılmaz** — admin başına anahtar, Ansible ile merkezi root SSH kapalı; 700 cihaza tek private key **yayılmaz** — admin başına anahtar, Ansible ile merkezi
`authorized_keys` dağıtımı, uzun vadede SSH CA hedefi. → [`06`](docs/06-USERS-SSH-AND-PERMISSIONS.md) (K-13) `authorized_keys` dağıtımı, uzun vadede SSH CA hedefi. → [`06`](docs/06-USERS-SSH-AND-PERMISSIONS.md) (K-13)
11. **RDP nasıl?** — **xRDP + GNOME**, yalnızca WireGuard üzerinden; internete açık RDP portu yoktur. 11. **RDP nasıl?** — **xRDP + GNOME**, yalnızca WireGuard üzerinden; internete açık RDP portu yoktur.
Grafik katman kapalıyken RDP açılmaz (`bf-gui-on` gerekir). → [`07`](docs/07-REMOTE-ACCESS.md) (K-03/K-05) xRDP ve xrdp-sesman multi-user seviyesinde **her zaman enable+active** olduğu için RDP `bf-gui-on`
beklemez; `bf-gui-*` yalnız yerel fiziksel GUI'yi açar/kapatır. → [`07`](docs/07-REMOTE-ACCESS.md), [`16`](docs/16-POWER-LOSS-AND-AUTO-RECOVERY.md) (K-05, K-22)
12. **RustDesk self-host neyi kapsar?** — **RustDesk OSS** istemci + `hbbs`/`hbbr` kendi VDS'imizde; 12. **RustDesk self-host neyi kapsar?** — **RustDesk OSS** istemci + `hbbs`/`hbbr` kendi VDS'imizde;
sınırsız istemci, E2E şifreli oturum, dosya transferi, katılımsız erişim — hepsi ücretsiz. İstemci VDS sınırsız istemci, E2E şifreli oturum, dosya transferi, katılımsız erişim — hepsi ücretsiz. İstemci VDS
genel adresine doğrudan çıktığı için **WireGuard çökse bile** kanal ayaktadır. → [`07`](docs/07-REMOTE-ACCESS.md) (K-04) genel adresine doğrudan çıktığı için **WireGuard çökse bile** kanal ayaktadır. Ubuntu 26.04.1 üzerinde
headless/katılımsız davranış LAB'da doğrulanmadan dondurulmaz (zorunlu şart: reboot sonrası katılımsız çalışma).
→ [`07`](docs/07-REMOTE-ACCESS.md) (K-04, K-23)
13. **AnyDesk ücretsiz kullanılabilir mi?** — **HAYIR.** Free sürüm yalnızca kişisel kullanımı kapsar; 13. **AnyDesk ücretsiz kullanılabilir mi?** — **HAYIR.** Free sürüm yalnızca kişisel kullanımı kapsar;
700 kurumsal cihaz ticari kullanımdır, lisanssız kullanım sözleşme ihlalidir. Mimari dışıdır; yalnızca 700 kurumsal cihaz ticari kullanımdır, lisanssız kullanım sözleşme ihlalidir. Mimari dışıdır; yalnızca
müşteride zaten ticari lisans varsa opsiyonel `--with-anydesk` modülü. → [`07`](docs/07-REMOTE-ACCESS.md), [`23`](docs/23-SECURITY-AND-LICENSE-AUDIT.md) (K-04) müşteride zaten ticari lisans varsa opsiyonel `--with-anydesk` modülü. → [`07`](docs/07-REMOTE-ACCESS.md), [`23`](docs/23-SECURITY-AND-LICENSE-AUDIT.md) (K-04)
@ -70,7 +98,7 @@
cihaz kuralıyla disk taşması mimari düzeyde engellenir. → [`15`](docs/15-LOGGING.md), [`12`](docs/12-DOCKER-OPERATIONS.md) (K-14/K-10) cihaz kuralıyla disk taşması mimari düzeyde engellenir. → [`15`](docs/15-LOGGING.md), [`12`](docs/12-DOCKER-OPERATIONS.md) (K-14/K-10)
22. **Elektrik kesintisi ve dönüşü?** — BIOS `Restore on AC Power Loss = Power On` ile cihaz kendiliğinden 22. **Elektrik kesintisi ve dönüşü?** — BIOS `Restore on AC Power Loss = Power On` ile cihaz kendiliğinden
açılır; systemd sıralı boot zinciri (network → `wg-quick` → docker → MEG → agent'lar) + `unless-stopped` açılır; systemd sıralı boot zinciri (network → `wg-quick` → docker → MEG → agent'lar) + `unless-stopped`
ile her katman kendiliğinden toparlanır, ek kurtarma servisi yoktur. → [`16`](docs/16-POWER-LOSS-AND-AUTO-RECOVERY.md) (K-03/K-10) ile her katman kendiliğinden toparlanır, ek kurtarma servisi yoktur. xRDP de bu zincirde her zaman hazır kalır. → [`16`](docs/16-POWER-LOSS-AND-AUTO-RECOVERY.md) (K-03/K-10/K-22)
23. **İnternet paketi bitimi ve dönüşü?** — Kota bitince tünel sessizce ölür (arıza değil, "çevrimdışı" 23. **İnternet paketi bitimi ve dönüşü?** — Kota bitince tünel sessizce ölür (arıza değil, "çevrimdışı"
görünür; Kuma "last seen" alarmı + bayiye bildirim); kota dönüşünde `wg-quick` + keepalive **kendiliğinden** görünür; Kuma "last seen" alarmı + bayiye bildirim); kota dönüşünde `wg-quick` + keepalive **kendiliğinden**
yeniden handshake eder, ek işlem gerekmez. Kota-dostu keepalive (25 sn, PİLOT verisiyle ayarlanır). → [`08`](docs/08-WIREGUARD-AND-NETWORK.md) (K-05) yeniden handshake eder, ek işlem gerekmez. Kota-dostu keepalive (25 sn, PİLOT verisiyle ayarlanır). → [`08`](docs/08-WIREGUARD-AND-NETWORK.md) (K-05)
@ -78,14 +106,16 @@
L8 USB ile sıfırdan imaj → L9 merkez inşası); cihaz "değiştirilebilir" sayılır, kimlik + config merkezden L8 USB ile sıfırdan imaj → L9 merkez inşası); cihaz "değiştirilebilir" sayılır, kimlik + config merkezden
yeniden basılır, cihaz-başı veri yedeği yoktur. Çoğu arıza 4 kanaldan biriyle uzaktan çözülür, sahaya yeniden basılır, cihaz-başı veri yedeği yoktur. Çoğu arıza 4 kanaldan biriyle uzaktan çözülür, sahaya
gidilmez. → [`17`](docs/17-BACKUP-RECOVERY-AND-REINSTALL.md), [`20`](docs/20-FIELD-TECHNICIAN-RUNBOOK.md) gidilmez. → [`17`](docs/17-BACKUP-RECOVERY-AND-REINSTALL.md), [`20`](docs/20-FIELD-TECHNICIAN-RUNBOOK.md)
25. **Golden image yöntemi ne?** — Kimliksiz zemin (Server 26.04.1+ minimal + update kapalı + kurucu) USB/autoinstall 25. **Golden image yöntemi ne?** — Kimliksiz zemin (Server 26.04.1+ minimal + update kapalı + kurucu), tek USB
ile kurulur; cihaz-spesifik kimlik (`bf-<no>`) imajda **değil ilk açılışta** enjekte edilir. Clonezilla Field OS ISO (autoinstall + offline APT + firstboot) ile kurulur; cihaz-spesifik kimlik (`bf-<no>`) imajda
bit-kopya yalnızca yedek yöntemdir (donanım-fragil). → [`04`](docs/04-GOLDEN-IMAGE-AND-PROVISIONING.md), [`05`](docs/05-ONE-CLICK-INSTALLER.md) (K-09) **değil ilk açılışta** enjekte edilir. Assisted kurulumda storage adımı operatör onayı bekler (otomatik disk
silme yok). Clonezilla bit-kopya yalnızca yedek yöntemdir (donanım-fragil). → [`04`](docs/04-GOLDEN-IMAGE-AND-PROVISIONING.md), [`05`](docs/05-ONE-CLICK-INSTALLER.md), [`26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md) (K-09, K-20)
26. **700 cihaz rollout nasıl?** — Üstel dalgalar: LAB(2) → P1(5) → P2(20) → W1(50) → W2(100) → PROD(~523); 26. **700 cihaz rollout nasıl?** — Üstel dalgalar: LAB(2) → P1(5) → P2(20) → W1(50) → W2(100) → PROD(~523);
hiçbir dalga önceki çıkış kriterini sağlamadan başlamaz; halt kuralları (≥2 kritik arıza / >%10 çevrimdışı / hiçbir dalga önceki çıkış kriterini sağlamadan başlamaz; halt kuralları (≥2 kritik arıza / >%10 çevrimdışı /
2× kurulum süresi) yayılımı durdurur. → [`18`](docs/18-PILOT-AND-700-DEVICE-ROLLOUT.md) (K-11) 2× kurulum süresi) yayılımı durdurur. Faz karşılığı: LAB = F3, P1-P2-W1-W2 = F4, PROD = F5. → [`18`](docs/18-PILOT-AND-700-DEVICE-ROLLOUT.md), [`25`](docs/25-IMPLEMENTATION-ROADMAP.md) (K-11)
27. **Docs platformu ne?** — **Docusaurus**; kaynak `docs/*.md` tek-kaynaktır, site CI'da derlenir, siteye 27. **Docs platformu ne?** — **Docusaurus**; kaynak yalnız `docs/[0-9][0-9]-*.md` numaralı dosyalardır,
elle yazma yasaktır. Yedek: MkDocs + Material. → [`22`](docs/22-DOCUMENTATION-PLATFORM.md) (K-08) site CI'da derlenir, `docs/_TEMPLATE.md` siteye alınmaz ve siteye elle yazma yasaktır. Yedek: MkDocs + Material. →
[`22`](docs/22-DOCUMENTATION-PLATFORM.md)
--- ---
@ -113,10 +143,11 @@
| Bileşen | Sürüm/katman | Lisans | Kaynak | | Bileşen | Sürüm/katman | Lisans | Kaynak |
|---|---|---|---| |---|---|---|---|
| Ubuntu Server LTS | 26.04.1+ (ESM'e bel bağlanmaz) | Açık kaynak | [`03`](docs/03-UBUNTU-BASELINE.md) | | Ubuntu Server LTS | 26.04.1+ (ESM'e bel bağlanmaz) | Açık kaynak | [`03`](docs/03-UBUNTU-BASELINE.md) |
| GNOME | saha standardı (Ubuntu Desktop) | GPL/LGPL bileşenler | [`03`](docs/03-UBUNTU-BASELINE.md) | | GNOME | saha standardı (Ubuntu Desktop); XFCE yalnız LAB A/B ölçüm adayı | GPL/LGPL bileşenler | [`03`](docs/03-UBUNTU-BASELINE.md) |
| xRDP | v0.10.6.1 | Apache-2.0 | [`03`](docs/03-UBUNTU-BASELINE.md) | | xRDP | v0.10.6.1 | Apache-2.0 | [`03`](docs/03-UBUNTU-BASELINE.md), [`07`](docs/07-REMOTE-ACCESS.md) |
| WireGuard + wg-quick | wireguard-tools 1.0.20250521-1ubuntu1 | OSS | [`08`](docs/08-WIREGUARD-AND-NETWORK.md) | | WireGuard + wg-quick | wireguard-tools 1.0.20250521-1ubuntu1 | OSS | [`08`](docs/08-WIREGUARD-AND-NETWORK.md) |
| OpenSSH / UFW | sistem paketleri | BSD / GPL bileşenler | [`06`](docs/06-USERS-SSH-AND-PERMISSIONS.md), [`11`](docs/11-SECURITY-HARDENING.md) | | OpenSSH / UFW | sistem paketleri | BSD / GPL bileşenler | [`06`](docs/06-USERS-SSH-AND-PERMISSIONS.md), [`11`](docs/11-SECURITY-HARDENING.md) |
| Autoinstall / cloud-init NoCloud | Subiquity + cloud-init | Açık kaynak (Ubuntu lisans seti) | [`26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md), [`27`](docs/27-OFFLINE-PROVISIONING.md) |
| Docker CE | (Business/Scout mimaride yok) | Ücretsiz | [`12`](docs/12-DOCKER-OPERATIONS.md) | | Docker CE | (Business/Scout mimaride yok) | Ücretsiz | [`12`](docs/12-DOCKER-OPERATIONS.md) |
| RustDesk istemci + sunucu (hbbs/hbbr) | 1.4.9 / 1.1.16, OSS | AGPL-3.0 | [`07`](docs/07-REMOTE-ACCESS.md) | | RustDesk istemci + sunucu (hbbs/hbbr) | 1.4.9 / 1.1.16, OSS | AGPL-3.0 | [`07`](docs/07-REMOTE-ACCESS.md) |
| MeshCentral | 1.2.5 | Apache-2.0 | [`07`](docs/07-REMOTE-ACCESS.md) | | MeshCentral | 1.2.5 | Apache-2.0 | [`07`](docs/07-REMOTE-ACCESS.md) |
@ -135,12 +166,21 @@ kaynak URL'ler [`24 Ek A`](docs/24-DECISION-LOG.md).
--- ---
## Ek: QA Doğrulama (2026-09-15) ## Ek: QA Doğrulama (2026-09-16 güncellemesi)
- **26 dosya mevcut:** `docs/00` … `docs/25` tamamı var (+ `_TEMPLATE.md`). - **33 numaralı doküman mevcut:** `docs/00` … `docs/32` (+ `docs/_TEMPLATE.md`); kanonik set `00`–`31`, `32` denetim/çelişki raporudur.
- **13 başlık:** 26 dosyanın tamamında §1–§13 sıralı ve eksiksiz. - **13 başlık:** 33 dosyanın tamamında §1–§13 sıralı ve eksiksiz (bu turda `docs/32` §1–§13 yapısına getirildi).
- **Mermaid:** 26/26 dosyada ≥1 diyagram (03/19/21/23/24'e bu özetle birlikte eklendi). - **Mermaid:** 33/33 dosyada ≥1 diyagram; toplam 35 blok (30 dosyada 1, birkaçında 2+).
- **BF-/bf- tutarlılığı:** insan arayüzü `BF-<no>`, makine adı `bf-<no>`; aykırı kullanım yok. - **BF-/bf- tutarlılığı:** insan arayüzü `BF-<no>`, makine adı `bf-<no>`; aykırı kullanım yok (kalan `BF-xxxx`/`BF-no` ifadeleri şablon yer tutucudur).
- **Ücretli sızıntı:** yok — ücretli geçen her satır elendi/hüküm/denetim bağlamında. - **Ücretli sızıntı:** yok — ücretli geçen her satır elendi/hüküm/denetim bağlamında.
- **Onaysız-update kapısı:** yok — `unattended-upgrades`/`latest`/Watchtower yasakları 03/10/12'de ve - **Onaysız-update kapısı:** yok — `unattended-upgrades`/`latest`/Watchtower yasakları 03/10/12'de ve
K-11'de kilitli; acil yama bile onay + rollback testinden geçer. K-11'de kilitli; acil yama bile onay + rollback testinden geçer.
- **Durum tutarlılığı (bu turun konusu):** README, SUMMARY, 24, 25 ve 32 aynı şeyi söyler: medya (autoinstall + Field OS ISO),
3 durumlu model, RDP her zaman hazır / `bf-gui-*` yalnız yerel GUI, GNOME baseline + LAB A/B, offline-assisted F2B vs
zero-touch F6. Giderilemeyen artıklar ve açık maddeler [`docs/32`](docs/32-REPOSITORY-AUDIT-AND-CONFLICTS.md) §"Giderim Durumu" içindedir.
- **Test durumu (2026-09-16 10:52 anlık görüntüsü):** `check-configs.sh`, `check-migration-static.sh`, `check-offline-repo-static.sh`,
`check-provisioning-static.sh`, `test-firstboot-static.sh` **GEÇTİ**; `check-specs.sh` iki **bayat sabit** nedeniyle düşer
(kanonik doküman sayısı `32`→`33`, Mermaid toplamı `34`→`35`); `test-field-os-lifecycle.sh` **kırmızı** (READY/durum sözleşmesi).
Düzeltmeler `tests/` ve ilgili provisioning akışı kapsamındadır — ayrıntı [`docs/32 §10`](docs/32-REPOSITORY-AUDIT-AND-CONFLICTS.md).
- **Üretim artefaktı:** henüz `*.iso` yok, `*.deb` havuzu yok, offline pinler `UNPINNED` sentinel, `packages.lock.tsv` yok,
merkezi enrollment endpoint'i yok. Yani "offline kurulum çalışıyor" bugün **kanıtlanmış değildir**; F2A/F2B kapıları açıktır.

View file

@ -0,0 +1,22 @@
# Shared variables for all Blueforce fleet hosts.
# Fill placeholders before first production run.
---
# WireGuard subnet (placeholder - must match docs/08 hub topology).
wg_subnet: "10.42.0.0/16"
wg_interface: wg0
wg_hub_endpoint: "vpn.example.com:51820" # PLACEHOLDER: real hub endpoint
# Monitoring endpoint (placeholder - VictoriaMetrics/Prometheus push or pull target).
monitoring_endpoint: "http://monitoring.example.com:8428" # PLACEHOLDER
monitoring_job: blueforce
# Update repository / mirror (placeholder - local mirror recommended for 700 devices).
update_repo_url: "http://mirror.example.com/ubuntu" # PLACEHOLDER
update_repo_suite: jammy
meg_image_tag: "1.0.0" # pinned tag, never 'latest' (docs/10, docs/12)
# Operation policy defaults. Every fleet-changing play must bind to one approved wave.
operation_wave: ""
operation_confirmed: false
reboot_grace_seconds: 300
support_bundle_dir: /var/lib/blueforce/support-bundles

View file

@ -0,0 +1,54 @@
# Blueforce fleet inventory (EXAMPLE - copy to hosts.yml and fill real IDs).
# Naming: bf-<8-digit-dealer-no> per docs/02-DEVICE-NAMING-AND-INVENTORY.md.
# Targeting levels per docs/09-FLEET-MANAGEMENT.md: single host -> group -> pilot wave -> full fleet.
# Waves per docs/10-UPDATE-AND-ROLLBACK-POLICY.md: lab(2)/pilot_1(5)/pilot_2(20)/wave_1(50)/wave_2(100)/production.
all:
children:
blueforce:
children:
lab:
hosts:
bf-12010101:
ansible_host: 10.42.0.11
bf-12010102:
ansible_host: 10.42.0.12
pilot_1:
hosts:
bf-12010201:
ansible_host: 10.42.1.11
bf-12010202:
ansible_host: 10.42.1.12
bf-12010203:
ansible_host: 10.42.1.13
bf-12010204:
ansible_host: 10.42.1.14
bf-12010205:
ansible_host: 10.42.1.15
pilot_2:
hosts:
bf-12010301:
ansible_host: 10.42.2.11
bf-12010302:
ansible_host: 10.42.2.12
wave_1:
hosts:
bf-12010401:
ansible_host: 10.42.3.11
bf-12010402:
ansible_host: 10.42.3.12
wave_2:
hosts:
bf-12010501:
ansible_host: 10.42.4.11
bf-12010502:
ansible_host: 10.42.4.12
production:
hosts:
bf-12010601:
ansible_host: 10.42.5.11
bf-12010602:
ansible_host: 10.42.5.12
vars:
ansible_user: blueforce
ansible_ssh_private_key_file: ~/.ssh/blueforce_id_ed25519 # PLACEHOLDER: set per-admin key path (see docs/06)
ansible_python_interpreter: /usr/bin/python3

View file

@ -0,0 +1,69 @@
---
# Collect timestamped journald logs and a support snapshot from one approved wave.
- name: Collect support logs
hosts: "{{ operation_wave | default('invalid-wave', true) }}"
gather_facts: false
become: true
vars:
approved_operation_waves: [lab, pilot_1, pilot_2, wave_1, wave_2, production]
production_override: false
collect_logs_dir: "{{ support_bundle_dir | default('/var/lib/blueforce/support-bundles') }}"
collect_log_lines: "{{ log_lines | default(500) }}"
fetch_logs: false
pre_tasks:
- name: Require confirmed approved operation wave scope
ansible.builtin.assert:
that:
- operation_wave in approved_operation_waves
- operation_confirmed | bool
- operation_wave in groups
- 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 a safe support bundle directory
ansible.builtin.assert:
that:
- collect_logs_dir is match('^/var/lib/blueforce/support-bundles(?:/[A-Za-z0-9][A-Za-z0-9._-]*)*\Z')
fail_msg: "support_bundle_dir must be /var/lib/blueforce/support-bundles or an approved alphanumeric subpath; shell metacharacters are 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: Generate unique bundle timestamp
ansible.builtin.command: date -u +%Y%m%dT%H%M%SZ
register: bundle_timestamp
changed_when: false
- name: Ensure bundle directory exists
ansible.builtin.file:
path: "{{ collect_logs_dir }}"
state: directory
owner: root
group: root
mode: "0750"
- name: Export recent journal to timestamped bundle
ansible.builtin.shell: >-
journalctl -n {{ collect_log_lines | int }} --no-pager >
{{ (collect_logs_dir ~ '/journal-' ~ inventory_hostname ~ '-' ~ bundle_timestamp.stdout ~ '.log') | quote }}
changed_when: true
- name: Record timestamped kernel and disk snapshot
ansible.builtin.shell: >-
{ uname -a; printf '%s\\n' '---'; df -h; printf '%s\\n' '---'; uptime; } >
{{ (collect_logs_dir ~ '/snapshot-' ~ inventory_hostname ~ '-' ~ bundle_timestamp.stdout ~ '.txt') | quote }}
changed_when: true
- name: Fetch timestamped journal bundle to controller
ansible.builtin.fetch:
src: "{{ collect_logs_dir }}/journal-{{ inventory_hostname }}-{{ bundle_timestamp.stdout }}.log"
dest: "fetched-logs/{{ inventory_hostname }}/"
flat: true
when: fetch_logs | bool

View file

@ -0,0 +1,86 @@
---
# Approved package update with mandatory wave scope and per-batch health gates.
# Example: ansible-playbook -i inventory/hosts.yml playbooks/bf-deploy-update.yml -e release_wave=lab -e wave_gate_confirmed=true -e update_packages='["package=version"]'
- name: Deploy approved package update
hosts: "{{ release_wave | default('invalid-wave', true) }}"
gather_facts: false
become: true
serial: 5
max_fail_percentage: 0
any_errors_fatal: true
vars:
release_wave: ""
wave_gate_confirmed: false
production_override: false
halt_on_fail: true
update_packages: []
update_cache_time: 3600
approved_waves: [lab, pilot_1, pilot_2, wave_1, wave_2, production]
pre_tasks:
- name: Require a valid rollout wave
ansible.builtin.assert:
that:
- release_wave in approved_waves
- release_wave in groups
- groups[release_wave] | length > 0
fail_msg: "release_wave must name a non-empty approved inventory group; refusing an ungated rollout."
run_once: true
- name: Require exact or subset host selection for release wave
ansible.builtin.assert:
that:
- ansible_play_hosts_all | length > 0
- ansible_play_hosts_all | difference(groups[release_wave]) | length == 0
fail_msg: "Selected hosts must equal or be a subset of groups[release_wave]; broader fleet scope is refused."
run_once: true
- name: Require central wave-gate confirmation
ansible.builtin.assert:
that:
- wave_gate_confirmed | bool
fail_msg: "Set wave_gate_confirmed=true only after the prior wave health gate and central approval pass."
run_once: true
- name: Require separate production override
ansible.builtin.assert:
that:
- release_wave != 'production' or production_override | bool
fail_msg: "Production rollout requires production_override=true in addition to wave_gate_confirmed=true."
run_once: true
- name: Require halt-on-failure protection
ansible.builtin.assert:
that:
- halt_on_fail | bool
fail_msg: "halt_on_fail must remain true for every rollout."
run_once: true
- name: Require pinned package versions
ansible.builtin.assert:
that:
- update_packages | length > 0
- update_packages | select('match', '^[A-Za-z0-9][A-Za-z0-9+_.:-]*=.+$') | list | length == update_packages | length
fail_msg: "update_packages must be a non-empty list of exact package=version pins."
run_once: true
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: "{{ update_cache_time }}"
- name: Install approved pinned package versions
ansible.builtin.apt:
name: "{{ update_packages }}"
state: present
allow_downgrade: true
post_tasks:
- name: Run mandatory post-batch health gate
ansible.builtin.command: /usr/local/bin/bf-status
register: update_health
changed_when: false
failed_when: update_health.rc != 0
- name: Report passed post-batch health gate
ansible.builtin.debug:
msg: "Update batch health gate passed for {{ ansible_play_batch }}"
run_once: true

View file

@ -0,0 +1,29 @@
---
# Check disk usage (read-only) and warn above threshold.
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-disk-check.yml --limit lab
# Threshold overridable: -e disk_warn_pct=80.
# No Semaphore Pro feature is used.
- name: Check disk usage
hosts: blueforce
gather_facts: false
vars:
disk_warning_percent: "{{ disk_warn_pct | default(80) }}"
tasks:
- name: Get disk usage
ansible.builtin.command: df -h --output=source,pcent,target
register: df_out
changed_when: false
- name: Show disk usage
ansible.builtin.debug:
msg: "{{ inventory_hostname }}:\n{{ df_out.stdout }}"
- name: Get root filesystem usage percentage
ansible.builtin.shell: "df --output=pcent / | tail -n 1 | tr -dc '0-9'"
register: root_pct
changed_when: false
- name: Flag hosts above threshold
ansible.builtin.debug:
msg: "WARNING {{ inventory_hostname }} root disk {{ root_pct.stdout }}% exceeds {{ disk_warning_percent }}%"
when: root_pct.stdout | int >= (disk_warning_percent | int)

View file

@ -0,0 +1,24 @@
---
# Report Docker daemon and container status (read-only).
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-docker-status.yml --limit lab
# No changes are made by this playbook. No Semaphore Pro feature is used.
- name: Check Docker status
hosts: blueforce
gather_facts: false
become: true
tasks:
- name: Check Docker service state
ansible.builtin.systemd:
name: docker
register: docker_svc
changed_when: false
- name: List running containers
ansible.builtin.command: docker ps --format '{{{{.Names}}}} {{{{.Image}}}} {{{{.Status}}}}'
register: docker_ps
changed_when: false
failed_when: false
- name: Show Docker status
ansible.builtin.debug:
msg: "{{ inventory_hostname }} docker={{ docker_svc.status.ActiveState | default('unknown') }} containers={{ docker_ps.stdout_lines | default([]) }}"

View file

@ -0,0 +1,208 @@
---
# Blueforce enrollment durum raporu -- READ-ONLY, dalga kapsamlı.
# Ne yapar: her cihaz için enrollment_status + fleet_status + faz yazar; ENROLLED/READY iddiası
# olup merkezi doğrulama kanıtı (central_verification: status + verification_id +
# monitoring/remote/management kanalları) eksik olan cihazları listeler.
# Sözleşme: docs/29 (merkezi doğrulama yanıtı), docs/14 (bf-status).
# Kanıt: /var/lib/blueforce/state.json ve kanonik /usr/local/bin/bf-enrollment-status çıktısı.
# 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-enrollment-status.yml -e operation_wave=lab -e operation_confirmed=true --check
# Koşu: ansible-playbook -i inventory/hosts.yml playbooks/bf-enrollment-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: Enrollment ve merkez kanıtı durumunu 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
enrollment_state_file: /var/lib/blueforce/state.json
enrollment_status_bin: /usr/local/bin/bf-enrollment-status
enrollment_central_channels: [monitoring, remote, management]
enrollment_central_required_for: [ENROLLED, READY]
pre_tasks:
- name: Require confirmed approved operation wave scope
ansible.builtin.assert:
that:
- operation_wave in approved_operation_waves
- operation_confirmed | 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: Enrollment kanıt yollarını denetle (read-only)
ansible.builtin.stat:
path: "{{ item }}"
loop:
- "{{ enrollment_state_file }}"
- "{{ enrollment_status_bin }}"
register: enrollment_evidence
- name: Kanıt durumlarını yol ile indeksle
ansible.builtin.set_fact:
enrollment_evidence_by_path: "{{ enrollment_evidence.results | items2dict(key_name='item', value_name='stat') }}"
- name: Durum dosyasını oku (read-only)
ansible.builtin.slurp:
src: "{{ enrollment_state_file }}"
register: enrollment_state_raw
when:
- enrollment_evidence_by_path[enrollment_state_file].exists | default(false)
- enrollment_evidence_by_path[enrollment_state_file].size | default(0) | int > 0
- name: Kanonik enrollment durumunu oku (bf-enrollment-status, read-only)
ansible.builtin.command: "{{ enrollment_status_bin }}"
register: enrollment_status_cli
changed_when: false
failed_when: false
when: enrollment_evidence_by_path[enrollment_status_bin].exists | default(false)
- name: Durum dosyası içeriğini çöz (bozuk dosyada rapor PENDING kalır, koşu durmaz)
ansible.builtin.set_fact:
enrollment_state_parsed: "{{ enrollment_state_raw.content | b64decode | from_json }}"
when: enrollment_state_raw.content is defined
ignore_errors: true
- name: Enrollment alanlarını ve merkez kanıtını belirle
ansible.builtin.set_fact:
bf_enr_device_id: "{{ (enrollment_state_parsed | default({}, true)).get('device_id', inventory_hostname | upper) }}"
bf_enr_phase: >-
{{ (enrollment_state_parsed | default({}, true)).get('phase', '') | default('', true)
if (enrollment_state_parsed | default({}, true)).get('phase', '') in ['PROVISIONED_OFFLINE', 'ENROLLED', 'READY']
else 'PENDING' }}
bf_enr_enrollment_status: "{{ (enrollment_state_parsed | default({}, true)).get('enrollment_status', 'PENDING') | default('PENDING', true) }}"
bf_enr_fleet_status: "{{ (enrollment_state_parsed | default({}, true)).get('fleet_status', 'PENDING') | default('PENDING', true) }}"
bf_enr_central: "{{ (enrollment_state_parsed | default({}, true)).get('central_verification', {}) | default({}, true) }}"
bf_enr_cli_lifecycle: "{{ enrollment_status_cli.stdout | default('') | regex_findall('(?m)^Lifecycle: (.+)$') | first | default('', true) }}"
bf_enr_cli_enrollment: "{{ enrollment_status_cli.stdout | default('') | regex_findall('(?m)^Enrollment: (.+)$') | first | default('', true) }}"
- name: Merkez doğrulama kanallarını çıkar
ansible.builtin.set_fact:
bf_enr_central_status: "{{ bf_enr_central.get('status', '') | default('', true) }}"
bf_enr_central_id: "{{ bf_enr_central.get('verification_id', '') | default('', true) }}"
bf_enr_central_channel_states: "{{ dict(enrollment_central_channels | zip(enrollment_central_channels | map('extract', bf_enr_central, 'status') | map('default', 'pending', true) | list)) }}"
bf_enr_central_channel_ids: "{{ dict(enrollment_central_channels | zip(enrollment_central_channels | map('extract', bf_enr_central, 'verification_id') | map('default', '', true) | list)) }}"
- name: Merkez kanıtı tam mı hesapla
ansible.builtin.set_fact:
bf_enr_central_ok: >-
{{ (bf_enr_central_status == 'verified')
and (bf_enr_central_id | length > 0)
and (bf_enr_central_channel_states.values() | select('equalto', 'verified') | list | length == bf_enr_central_channel_states | length)
and (bf_enr_central_channel_ids.values() | select('equalto', '') | list | length == 0) }}
bf_enr_cli_mismatch: "{{ 'MISMATCH' if (bf_enr_cli_lifecycle | length > 0) and (bf_enr_cli_lifecycle != bf_enr_phase) else 'OK' }}"
- name: Merkez kanıtı durumunu etiketle
ansible.builtin.set_fact:
bf_enr_central_state: >-
{{ 'not-required' if (bf_enr_enrollment_status != 'ENROLLED' and bf_enr_phase not in enrollment_central_required_for)
else ('verified' if bf_enr_central_ok | bool else 'MISSING') }}
bf_enr_missing_central: >-
{{ (bf_enr_enrollment_status == 'ENROLLED' or bf_enr_phase in enrollment_central_required_for)
and not (bf_enr_central_ok | bool) }}
- name: Cihaz enrollment satırını raporla
ansible.builtin.debug:
msg: >-
{{ bf_enr_device_id }} phase={{ bf_enr_phase }} enrollment={{ bf_enr_enrollment_status }}
fleet={{ bf_enr_fleet_status }} merkez_kaniti={{ bf_enr_central_state }}
kanallar={{ bf_enr_central_channel_states }} kanit_id={{ 'SET' if bf_enr_central_id | length > 0 else 'ABSENT' }}
- name: Eksik merkez kanıtı olan cihazı işaretle
ansible.builtin.debug:
msg: "UYARI {{ bf_enr_device_id }}: enrollment/faz iddiası var ama merkez kanıtı eksik ({{ bf_enr_central_state }}); READY verilemez (docs/29)."
when: bf_enr_missing_central | bool
- name: Merkez kanıtı eksik kanalları ayrıntılı yaz
ansible.builtin.debug:
msg: "UYARI {{ bf_enr_device_id }}: kanal kanıtı eksik -> {{ bf_enr_incomplete_channels }}"
vars:
bf_enr_incomplete_channels: "{{ bf_enr_central_channel_states | dict2items | rejectattr('value', 'equalto', 'verified') | map(attribute='key') | list }}"
when: bf_enr_missing_central | bool
- name: Kalıcı faz ile kanonik CLI çıktısı çelişen cihazı işaretle
ansible.builtin.debug:
msg: "UYARI {{ bf_enr_device_id }}: state.json phase={{ bf_enr_phase }} ama bf-enrollment-status Lifecycle={{ bf_enr_cli_lifecycle }}."
when: bf_enr_cli_mismatch == 'MISMATCH'
- name: Enrollment durum dağılımını hesapla
ansible.builtin.set_fact:
bf_enr_status_histogram: "{{ bf_enr_status_histogram | default({}) | combine({ item: bf_enr_wave_states | select('equalto', item) | list | length }) }}"
loop: "{{ bf_enr_wave_states | unique | sort }}"
vars:
bf_enr_wave_states: "{{ ansible_play_hosts_all | map('extract', hostvars, 'bf_enr_enrollment_status') | map('default', 'PENDING', true) | list }}"
run_once: true
- name: Fleet durum dağılımını hesapla
ansible.builtin.set_fact:
bf_enr_fleet_histogram: "{{ bf_enr_fleet_histogram | default({}) | combine({ item: bf_enr_wave_fleet | select('equalto', item) | list | length }) }}"
loop: "{{ bf_enr_wave_fleet | unique | sort }}"
vars:
bf_enr_wave_fleet: "{{ ansible_play_hosts_all | map('extract', hostvars, 'bf_enr_fleet_status') | map('default', 'PENDING', true) | list }}"
run_once: true
- name: Dalga enrollment özetini yaz
ansible.builtin.debug:
msg:
- "Dalga {{ operation_wave }} | raporlanan cihaz: {{ ansible_play_hosts_all | length }}"
- "enrollment_status: {{ bf_enr_status_histogram | default({}) }}"
- "fleet_status: {{ bf_enr_fleet_histogram | default({}) }}"
run_once: true
- name: Eksik merkez kanıtı olan cihazların dalga listesini yaz
ansible.builtin.debug:
msg: "Merkez kanıtı eksik cihazlar: {{ bf_enr_missing_central_hosts }}"
vars:
bf_enr_missing_central_hosts: "{{ dict(ansible_play_hosts_all | zip(ansible_play_hosts_all | map('extract', hostvars, 'bf_enr_central_state') | map('default', 'MISSING', true) | list)) | dict2items | selectattr('value', 'equalto', 'MISSING') | map(attribute='key') | list }}"
run_once: true
- name: Kanonik CLI ile çelişen cihazların dalga listesini yaz
ansible.builtin.debug:
msg: "state.json ve bf-enrollment-status çelişkili cihazlar: {{ bf_enr_cli_mismatch_hosts }}"
vars:
bf_enr_cli_mismatch_hosts: "{{ dict(ansible_play_hosts_all | zip(ansible_play_hosts_all | map('extract', hostvars, 'bf_enr_cli_mismatch') | map('default', 'OK', true) | list)) | dict2items | selectattr('value', 'equalto', 'MISMATCH') | map(attribute='key') | list }}"
run_once: true

View file

@ -0,0 +1,208 @@
---
# Blueforce Field OS sürüm raporu -- READ-ONLY, dalga kapsamlı.
# Ne yapar: bir dalgadaki cihazların Field OS sürümünü kanıt dosyalarından toplar, cihaz başına
# tek satır yazar ve sürüm başına cihaz sayısını (toplu özet) üretir.
# Kanıt sırası: /usr/local/bin/bf-release (kanonik) -> /etc/blueforce/release-manifest.yaml
# -> eski /etc/blueforce-release (KEY=VALUE) -> /var/lib/blueforce/state.json.
# 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-fieldos-version.yml -e operation_wave=lab -e operation_confirmed=true --check
# Koşu: ansible-playbook -i inventory/hosts.yml playbooks/bf-fieldos-version.yml -e operation_wave=lab -e operation_confirmed=true
# Opsiyonel drift denetimi: -e fieldos_expected_version=1.0.0
# 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: Field OS sürümünü toplu 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
fieldos_release_bin: /usr/local/bin/bf-release
fieldos_manifest_file: /etc/blueforce/release-manifest.yaml
fieldos_legacy_file: /etc/blueforce-release
fieldos_state_file: /var/lib/blueforce/state.json
fieldos_expected_version: ""
fieldos_unknown_label: UNKNOWN
pre_tasks:
- name: Require confirmed approved operation wave scope
ansible.builtin.assert:
that:
- operation_wave in approved_operation_waves
- operation_confirmed | 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: Kanıt yollarının varlığını denetle (read-only)
ansible.builtin.stat:
path: "{{ item }}"
loop:
- "{{ fieldos_release_bin }}"
- "{{ fieldos_manifest_file }}"
- "{{ fieldos_legacy_file }}"
- "{{ fieldos_state_file }}"
register: fieldos_evidence
- name: Kanıt durumlarını yol ile indeksle
ansible.builtin.set_fact:
fieldos_evidence_by_path: "{{ fieldos_evidence.results | items2dict(key_name='item', value_name='stat') }}"
- name: Kanonik sürüm raporunu oku (bf-release, read-only)
ansible.builtin.command: "{{ fieldos_release_bin }}"
register: fieldos_release_out
changed_when: false
failed_when: fieldos_release_out.rc not in [0, 1]
when: fieldos_evidence_by_path[fieldos_release_bin].exists | default(false)
- name: Release manifestini oku (read-only)
ansible.builtin.slurp:
src: "{{ fieldos_manifest_file }}"
register: fieldos_manifest_raw
when:
- fieldos_evidence_by_path[fieldos_manifest_file].exists | default(false)
- fieldos_evidence_by_path[fieldos_manifest_file].size | default(0) | int > 0
- name: Eski /etc/blueforce-release dosyasını oku (read-only)
ansible.builtin.slurp:
src: "{{ fieldos_legacy_file }}"
register: fieldos_legacy_raw
when:
- fieldos_evidence_by_path[fieldos_legacy_file].exists | default(false)
- fieldos_evidence_by_path[fieldos_legacy_file].size | default(0) | int > 0
- name: Field OS durum dosyasını oku (read-only)
ansible.builtin.slurp:
src: "{{ fieldos_state_file }}"
register: fieldos_state_raw
when:
- fieldos_evidence_by_path[fieldos_state_file].exists | default(false)
- fieldos_evidence_by_path[fieldos_state_file].size | default(0) | int > 0
- name: Durum dosyası içeriğini çöz (bozuk dosyada rapor UNKNOWN kalır, koşu durmaz)
ansible.builtin.set_fact:
fieldos_state_parsed: "{{ fieldos_state_raw.content | b64decode | from_json }}"
when: fieldos_state_raw.content is defined
ignore_errors: true
- name: Kanıt metinlerini çözümle
ansible.builtin.set_fact:
fieldos_manifest_text: "{{ (fieldos_manifest_raw.content | b64decode) if fieldos_manifest_raw.content is defined else '' }}"
fieldos_legacy_text: "{{ (fieldos_legacy_raw.content | b64decode) if fieldos_legacy_raw.content is defined else '' }}"
fieldos_state_ok: "{{ fieldos_state_parsed is defined and fieldos_state_parsed is mapping }}"
- name: Sürüm alanlarını kanıtlardan çıkar
ansible.builtin.set_fact:
fieldos_release_line: "{{ fieldos_release_out.stdout_lines | default([]) | select('search', '^Field OS: ') | list | first | default('', true) }}"
fieldos_manifest_name_raw: "{{ fieldos_manifest_text | regex_findall('(?m)^[ ]+name: ([^ ]+)') | first | default('', true) }}"
fieldos_manifest_version_raw: "{{ fieldos_manifest_text | regex_findall('(?m)^[ ]+version: ([^ ]+)') | first | default('', true) }}"
fieldos_manifest_status_raw: "{{ fieldos_manifest_text | regex_findall('(?m)^[ ]+status: ([^ ]+)') | first | default('', true) }}"
fieldos_legacy_version_raw: "{{ fieldos_legacy_text | regex_findall('(?mi)^version=(.+)$') | first | default('', true) }}"
fieldos_state_version_raw: "{{ (fieldos_state_parsed | default({}, true)).get('field_os_version', (fieldos_state_parsed | default({}, true)).get('release_version', '')) | default('', true) }}"
fieldos_device_id: "{{ (fieldos_state_parsed | default({}, true)).get('device_id', inventory_hostname | upper) }}"
- name: Sürüm değerlerini normalize et (unknown/boş değer geçersiz sayılır)
ansible.builtin.set_fact:
fieldos_release_version: "{{ fieldos_release_line | trim | regex_replace('(?i)^unknown$', '') }}"
fieldos_manifest_version: "{{ fieldos_manifest_version_raw | trim | regex_replace('(?i)^(unknown|skeleton)$', '') }}"
fieldos_legacy_version: "{{ fieldos_legacy_version_raw | trim | regex_replace('(?i)^unknown$', '') }}"
fieldos_state_version: "{{ fieldos_state_version_raw | trim | regex_replace('(?i)^unknown$', '') }}"
- name: Field OS sürümünü ve kanıt kaynağını belirle
ansible.builtin.set_fact:
fieldos_version: "{{ [fieldos_release_version, fieldos_manifest_version, fieldos_legacy_version, fieldos_state_version] | reject('equalto', '') | list | first | default(fieldos_unknown_label, true) }}"
fieldos_version_source: >-
{{ 'bf-release' if fieldos_release_version | length > 0
else ('release-manifest' if fieldos_manifest_version | length > 0
else ('blueforce-release' if fieldos_legacy_version | length > 0
else ('state.json' if fieldos_state_version | length > 0 else 'none'))) }}
fieldos_release_status: >-
{{ 'UNAPPROVED_SKELETON' if fieldos_manifest_status_raw | trim | lower == 'skeleton'
else (fieldos_manifest_status_raw | trim | upper if fieldos_manifest_status_raw | trim | length > 0 else 'UNKNOWN') }}
fieldos_release_name: "{{ fieldos_manifest_name_raw | trim | default('', true) }}"
- name: Cihaz sürüm satırını raporla
ansible.builtin.debug:
msg: "{{ fieldos_device_id }} -> {{ fieldos_version }} (kaynak={{ fieldos_version_source }}, release={{ fieldos_release_status }})"
- name: Sürümü doğrulanamayan cihazı işaretle
ansible.builtin.debug:
msg: "UYARI {{ fieldos_device_id }}: Field OS sürüm kanıtı yok (kaynak=none); cihaz inceleme/yeniden provision gerektirir."
when: fieldos_version == fieldos_unknown_label
- name: Onaysız iskelet release taşıyan cihazı işaretle
ansible.builtin.debug:
msg: "UYARI {{ fieldos_device_id }}: release status UNAPPROVED_SKELETON; saha cihazı olarak kabul edilmez."
when: fieldos_release_status == 'UNAPPROVED_SKELETON'
- name: Beklenen sürümden sapan cihazı işaretle
ansible.builtin.debug:
msg: "UYARI {{ fieldos_device_id }}: sürüm {{ fieldos_version }}, beklenen {{ fieldos_expected_version }}."
when:
- fieldos_expected_version | length > 0
- fieldos_version != fieldos_expected_version
- name: Dalga sürüm dağılımını hesapla (sürüm -> cihaz sayısı)
ansible.builtin.set_fact:
fieldos_version_histogram: "{{ fieldos_version_histogram | default({}) | combine({ item: fieldos_wave_versions | select('equalto', item) | list | length }) }}"
loop: "{{ fieldos_wave_versions | unique | sort }}"
vars:
fieldos_wave_versions: "{{ ansible_play_hosts_all | map('extract', hostvars, 'fieldos_version') | map('default', fieldos_unknown_label, true) | list }}"
run_once: true
- name: Dalga sürüm özetini yaz
ansible.builtin.debug:
msg:
- "Dalga {{ operation_wave }} | raporlanan cihaz: {{ ansible_play_hosts_all | length }}"
- "Sürüm dağılımı: {{ fieldos_version_histogram | default({}) }}"
- "Beklenen sürüm: {{ fieldos_expected_version if fieldos_expected_version | length > 0 else '(verilmedi, drift denetimi atlandi)' }}"
run_once: true
- name: Sürüm kanıtı olmayan cihazların dalga listesini yaz
ansible.builtin.debug:
msg: "Sürüm kanıtı olmayan cihazlar: {{ fieldos_missing_version_hosts }}"
vars:
fieldos_missing_version_hosts: "{{ dict(ansible_play_hosts_all | zip(ansible_play_hosts_all | map('extract', hostvars, 'fieldos_version') | map('default', fieldos_unknown_label, true) | list)) | dict2items | selectattr('value', 'equalto', fieldos_unknown_label) | map(attribute='key') | list }}"
run_once: true

View file

@ -0,0 +1,42 @@
---
# Disable the GUI for a confirmed maintenance wave (kiosk/headless mode).
# Dry run: ansible-playbook -i inventory/hosts.yml playbooks/bf-gui-off.yml -e operation_wave=lab -e operation_confirmed=true --check
- name: Disable graphical target
hosts: "{{ operation_wave | default('invalid-wave', true) }}"
gather_facts: false
become: true
vars:
approved_operation_waves: [lab, pilot_1, pilot_2, wave_1, wave_2, production]
production_override: false
pre_tasks:
- name: Require confirmed approved operation wave scope
ansible.builtin.assert:
that:
- operation_wave in approved_operation_waves
- operation_confirmed | bool
- operation_wave in groups
- 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: Read current default target
ansible.builtin.command: systemctl get-default
register: current_default_target
changed_when: false
- name: Set default target to multi-user
ansible.builtin.command: systemctl set-default multi-user.target
when: current_default_target.stdout != 'multi-user.target'
- name: Stop graphical target now
ansible.builtin.systemd:
name: graphical.target
state: stopped

View file

@ -0,0 +1,38 @@
---
# Enable the GUI for a confirmed maintenance wave without changing the boot default.
- name: Enable graphical maintenance session
hosts: "{{ operation_wave | default('invalid-wave', true) }}"
gather_facts: false
become: true
vars:
approved_operation_waves: [lab, pilot_1, pilot_2, wave_1, wave_2, production]
production_override: false
pre_tasks:
- name: Require confirmed approved operation wave scope
ansible.builtin.assert:
that:
- operation_wave in approved_operation_waves
- operation_confirmed | bool
- operation_wave in groups
- 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: Start GUI and wait for RDP
ansible.builtin.command: /usr/local/bin/bf-gui-on
register: gui_on
changed_when: true
- name: Verify terminal boot default remains configured
ansible.builtin.command: systemctl get-default
register: current_default_target
changed_when: false
failed_when: current_default_target.stdout != 'multi-user.target'

View file

@ -0,0 +1,272 @@
---
# 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
- operation_confirmed | 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

View file

@ -0,0 +1,24 @@
---
# Report installed versions of pinned packages (read-only).
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-package-check.yml --limit lab
# Package list overridable: -e "check_packages=['docker-ce','wireguard']".
# No Semaphore Pro feature is used.
- name: Check installed package versions
hosts: blueforce
gather_facts: false
become: true
vars:
check_packages:
- docker-ce
- wireguard
- rustdesk
tasks:
- name: Query package versions
ansible.builtin.shell: "dpkg-query -W -f='${Package} ${Version}\n' {{ check_packages | join(' ') }}"
register: pkg_out
changed_when: false
failed_when: false
- name: Show package versions
ansible.builtin.debug:
msg: "{{ inventory_hostname }}:\n{{ pkg_out.stdout | default('(query failed)') }}"

View file

@ -0,0 +1,12 @@
---
# Connectivity check for the Blueforce fleet.
# Targeting: single host -> group -> pilot wave -> full fleet (--limit).
# Dry run first: ansible-playbook -i inventory/hosts.yml playbooks/bf-ping.yml --limit lab --check
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-ping.yml --limit lab
# No changes are made by this playbook. No Semaphore Pro feature is used.
- name: Ping fleet hosts
hosts: blueforce
gather_facts: false
tasks:
- name: Check SSH connectivity
ansible.builtin.ping:

View file

@ -0,0 +1,183 @@
---
# 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
- operation_confirmed | 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

View file

@ -0,0 +1,25 @@
---
# Restart the RDP service (xrdp).
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-rdp-restart.yml --limit lab
# Service name is overridable via rdp_service_name.
# No Semaphore Pro feature is used.
- name: Restart RDP service
hosts: blueforce
gather_facts: false
become: true
vars:
rdp_restart_service: "{{ rdp_service_name | default('xrdp') }}"
tasks:
- name: Restart RDP
ansible.builtin.systemd:
name: "{{ rdp_restart_service }}"
state: restarted
- name: Verify RDP is active
ansible.builtin.command: "systemctl is-active {{ rdp_restart_service }}"
register: rdp_state
changed_when: false
- name: Show RDP state
ansible.builtin.debug:
msg: "{{ inventory_hostname }} rdp={{ rdp_state.stdout }}"

View file

@ -0,0 +1,40 @@
---
# Controlled reboots require an approved wave and explicit confirmation.
- name: Controlled fleet reboot
hosts: blueforce
gather_facts: false
become: true
serial: 2
max_fail_percentage: 0
any_errors_fatal: true
vars:
release_wave: ""
reboot_confirmed: false
allow_production_reboot: false
reboot_delay_seconds: "{{ reboot_grace_seconds | default(300) }}"
pre_tasks:
- name: Require an approved non-production release wave
ansible.builtin.assert:
that:
- release_wave in ['lab', 'pilot_1', 'pilot_2', 'wave_1', 'wave_2', 'production']
- reboot_confirmed | bool
- release_wave != 'production' or allow_production_reboot | bool
fail_msg: "Set approved release_wave and reboot_confirmed=true. Production additionally requires allow_production_reboot=true."
- name: Refuse full-fleet scope without explicit production override
ansible.builtin.assert:
that:
- release_wave == 'production' or ansible_limit | default('') | regex_search('(^|[:,])' ~ release_wave ~ '($|[:,])')
fail_msg: "Limit this reboot to the approved release_wave group; full fleet is refused."
tasks:
- name: Reboot host with grace period
ansible.builtin.reboot:
reboot_timeout: 600
pre_reboot_delay: "{{ reboot_delay_seconds }}"
msg: Blueforce scheduled reboot via Ansible
- name: Verify host health after reboot batch
ansible.builtin.command: /usr/local/bin/bf-status --check
register: reboot_health
changed_when: false
failed_when: reboot_health.rc != 0

View file

@ -0,0 +1,267 @@
---
# Blueforce uzak erişim kanalı raporu -- READ-ONLY, dalga kapsamlı.
# Ne yapar: SSH, xRDP, RustDesk, MeshCentral ve WireGuard kanallarının birim (unit) durumunu
# raporlar; MeshCentral ve AnyDesk OPSİYONELDİR (AnyDesk kurulu değilse açıkça
# "NOT INSTALLED (OPTIONAL)" yazılır). Kanonik /usr/local/bin/bf-remote-status --check
# çıktısı bağımsız doğrulama olarak eklenir (docs/07, docs/11 K-04).
# Hiçbir görev durum değiştirmez: yalnız stat 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-remote-channels-check.yml -e operation_wave=lab -e operation_confirmed=true --check
# Koşu: ansible-playbook -i inventory/hosts.yml playbooks/bf-remote-channels-check.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: Uzak erişim kanallarının durumunu raporla (read-only)
hosts: "{{ operation_wave | default('invalid-wave', true) }}"
gather_facts: false
become: true # Yalnız wg show ve root okuma gerektiren kanıtı (birim/state) OKUMAK için; mutasyon yok.
vars:
approved_operation_waves: [lab, pilot_1, pilot_2, wave_1, wave_2, production]
production_override: false
remote_channel_units: [ssh, sshd, xrdp, xrdp-sesman, rustdesk, meshagent, wg-quick@wg0]
remote_channel_props: [is-enabled, is-active]
remote_wg_interface: "{{ wg_interface | default('wg0') }}"
remote_wg_handshake_max_age: "{{ wg_handshake_max_age_seconds | default(180) }}"
remote_rustdesk_system_config: /etc/rustdesk/RustDesk2.toml
remote_rustdesk_user_config: /home/blueforce/.config/RustDesk/RustDesk2.toml
remote_mesh_config: /etc/meshagent/meshagent.json
remote_status_bin: /usr/local/bin/bf-remote-status
pre_tasks:
- name: Require confirmed approved operation wave scope
ansible.builtin.assert:
that:
- operation_wave in approved_operation_waves
- operation_confirmed | 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: Kanal yapılandırma dosyalarını denetle (read-only)
ansible.builtin.stat:
path: "{{ item }}"
loop:
- "{{ remote_rustdesk_system_config }}"
- "{{ remote_rustdesk_user_config }}"
- "{{ remote_mesh_config }}"
- "{{ remote_status_bin }}"
register: remote_channel_files
- name: Yapılandırma durumlarını yol ile indeksle
ansible.builtin.set_fact:
remote_channel_files_by_path: "{{ remote_channel_files.results | items2dict(key_name='item', value_name='stat') }}"
- name: Birim etkin/aktif durumlarını oku (systemctl, read-only)
ansible.builtin.command:
argv:
- systemctl
- "{{ item[1] }}"
- --quiet
- "{{ item[0] }}"
loop: "{{ remote_channel_units | product(remote_channel_props) | list }}"
register: remote_channel_probe
changed_when: false
failed_when: false
- name: Birim durumlarını tek sözlükte topla
ansible.builtin.set_fact:
bf_unit_states: "{{ bf_unit_states | default({}) | combine({ item.item[0] ~ ':' ~ item.item[1]: (item.rc == 0) }) }}"
loop: "{{ remote_channel_probe.results }}"
- name: RustDesk paket durumunu oku (read-only)
ansible.builtin.command:
argv:
- dpkg-query
- -W
- -f=${Status}
- rustdesk
register: remote_rustdesk_pkg
changed_when: false
failed_when: false
- name: AnyDesk (opsiyonel) paket durumunu oku (read-only)
ansible.builtin.command:
argv:
- dpkg-query
- -W
- -f=${Status}
- anydesk
register: remote_anydesk_pkg
changed_when: false
failed_when: false
- name: WireGuard son handshake kaydını oku (read-only)
ansible.builtin.command:
argv:
- wg
- show
- "{{ remote_wg_interface }}"
- latest-handshakes
register: remote_wg_handshakes
changed_when: false
failed_when: false
- name: Kontrol saatinin epoch değerini oku (read-only)
ansible.builtin.command: date +%s
register: remote_now_epoch
changed_when: false
- name: Kanonik uzak erişim doğrulamasını çalıştır (bf-remote-status --check, read-only)
ansible.builtin.command: "{{ remote_status_bin }} --check"
register: remote_status_cli
changed_when: false
failed_when: false
when: remote_channel_files_by_path[remote_status_bin].exists | default(false)
- name: Kanal hükümlerini hesapla
ansible.builtin.set_fact:
bf_ch_ssh: >-
{{ 'enabled-active'
if ((bf_unit_states.get('ssh:is-enabled', false) | bool and bf_unit_states.get('ssh:is-active', false) | bool)
or (bf_unit_states.get('sshd:is-enabled', false) | bool and bf_unit_states.get('sshd:is-active', false) | bool))
else 'not-ready' }}
bf_ch_xrdp: >-
{{ 'enabled-active'
if (bf_unit_states.get('xrdp:is-enabled', false) | bool and bf_unit_states.get('xrdp:is-active', false) | bool
and bf_unit_states.get('xrdp-sesman:is-enabled', false) | bool and bf_unit_states.get('xrdp-sesman:is-active', false) | bool)
else 'not-ready' }}
bf_ch_rustdesk: >-
{{ 'installed-active'
if (remote_rustdesk_pkg.rc | default(1) == 0 and bf_unit_states.get('rustdesk:is-active', false) | bool)
else ('not-installed' if remote_rustdesk_pkg.rc | default(1) != 0 else 'not-ready') }}
bf_ch_meshcentral: >-
{{ 'not-configured' if not (remote_channel_files_by_path[remote_mesh_config].exists | default(false))
else ('configured-active'
if (bf_unit_states.get('meshagent:is-enabled', false) | bool and bf_unit_states.get('meshagent:is-active', false) | bool)
else 'not-ready') }}
bf_ch_anydesk: "{{ 'installed (optional)' if remote_anydesk_pkg.rc | default(1) == 0 else 'NOT INSTALLED (OPTIONAL)' }}"
bf_ch_rustdesk_config: >-
{{ 'configured' if (remote_channel_files_by_path[remote_rustdesk_system_config].exists | default(false)
and remote_channel_files_by_path[remote_rustdesk_user_config].exists | default(false))
else 'not-configured' }}
bf_ch_canonical_ready: "{{ 'ALL-READY' if remote_status_cli.rc | default(1) == 0 else 'NOT-READY' }}"
- name: WireGuard handshake epoch listesini çıkar (read-only kanıt)
ansible.builtin.set_fact:
bf_wg_epochs: "{{ remote_wg_handshakes.stdout | default('') | regex_findall('(?m)([0-9]+)$') | map('int') | list }}"
- name: WireGuard en yeni handshake kaydını belirle
ansible.builtin.set_fact:
bf_wg_latest_epoch: "{{ (bf_wg_epochs | max | int) if bf_wg_epochs | length > 0 else 0 }}"
- name: WireGuard handshake yaşını hesapla
ansible.builtin.set_fact:
bf_wg_handshake_age: "{{ (remote_now_epoch.stdout | default('0') | int) - (bf_wg_latest_epoch | int) }}"
- name: WireGuard kanal hükmünü yaz
ansible.builtin.set_fact:
bf_ch_wireguard: >-
{{ 'no-handshake' if bf_wg_latest_epoch | int <= 0
else ('fresh-handshake' if bf_wg_handshake_age | int <= remote_wg_handshake_max_age | int else 'stale-handshake') }}
- name: Zorunlu kanal hazır/ready satırını yaz
ansible.builtin.set_fact:
bf_required_channel_line: >-
ssh:{{ 'READY' if bf_ch_ssh == 'enabled-active' else 'NOT-READY' }}
xrdp:{{ 'READY' if bf_ch_xrdp == 'enabled-active' else 'NOT-READY' }}
rustdesk:{{ 'READY' if bf_ch_rustdesk == 'installed-active' else 'NOT-READY' }}
wireguard:{{ 'READY' if bf_ch_wireguard == 'fresh-handshake' else 'NOT-READY' }}
- name: Zorunlu kanalların hazır durumunu ve sayısını belirle
ansible.builtin.set_fact:
bf_required_channel_ready: "{{ [bf_ch_ssh == 'enabled-active', bf_ch_xrdp == 'enabled-active', bf_ch_rustdesk == 'installed-active', bf_ch_wireguard == 'fresh-handshake'] | select | list | length }}"
- name: Kanal satırlarını raporla
ansible.builtin.debug:
msg:
- "{{ bf_device_label }} ssh={{ bf_ch_ssh }} xrdp={{ bf_ch_xrdp }} rustdesk={{ bf_ch_rustdesk }} wireguard={{ bf_ch_wireguard }}"
- "{{ bf_device_label }} meshcentral={{ bf_ch_meshcentral }} (opsiyonel) anydesk={{ bf_ch_anydesk }} rustdesk_config={{ bf_ch_rustdesk_config }}"
- "{{ bf_device_label }} hazır zorunlu kanal={{ bf_required_channel_ready }}/4 kanonik_bf_remote_status={{ bf_ch_canonical_ready }}"
vars:
bf_device_label: "{{ inventory_hostname | upper }}"
- name: Kanonik doğrulama çıktısını yaz (bağımsız kanıt)
ansible.builtin.debug:
msg: "bf-remote-status --check rc={{ remote_status_cli.rc | default(1) }} cikti={{ remote_status_cli.stdout_lines | default([]) }}"
when: remote_channel_files_by_path[remote_status_bin].exists | default(false)
- name: Hazır olmayan kanalı işaretle (opsiyoneller hariç)
ansible.builtin.debug:
msg: "UYARI {{ inventory_hostname | upper }}: kanal hazır değil -> {{ bf_not_ready_channels }}"
vars:
bf_not_ready_channels: >-
{{ (['ssh'] if bf_ch_ssh != 'enabled-active' else [])
+ (['xrdp'] if bf_ch_xrdp != 'enabled-active' else [])
+ (['rustdesk'] if bf_ch_rustdesk != 'installed-active' else [])
+ (['wireguard'] if bf_ch_wireguard != 'fresh-handshake' else []) }}
when: bf_not_ready_channels | length > 0
- name: MeshCentral (opsiyonel) kanalının kapalı olduğunu bilgi olarak yaz
ansible.builtin.debug:
msg: "BİLGİ {{ inventory_hostname | upper }}: MeshCentral durumu={{ bf_ch_meshcentral }}; MeshCentral/AnyDesk opsiyoneldir, READY kapısının parçası değildir."
when: bf_ch_meshcentral != 'configured-active'
- name: Kanal başına hazır cihaz sayısını hesapla
ansible.builtin.set_fact:
bf_channel_ready_counts: "{{ bf_channel_ready_counts | default({}) | combine({ item: bf_channel_lines | select('search', item ~ ':READY') | list | length }) }}"
loop: [ssh, xrdp, rustdesk, wireguard]
vars:
bf_channel_lines: "{{ ansible_play_hosts_all | map('extract', hostvars, 'bf_required_channel_line') | map('default', '', true) | list }}"
run_once: true
- name: Dalga kanal özetini yaz
ansible.builtin.debug:
msg:
- "Dalga {{ operation_wave }} | raporlanan cihaz: {{ ansible_play_hosts_all | length }}"
- "Hazır kanal başına cihaz sayısı: {{ bf_channel_ready_counts | default({}) }}"
- "İki kanaldan az erişimi olan cihazlar: {{ bf_single_channel_hosts }}"
vars:
bf_single_channel_hosts: "{{ dict(ansible_play_hosts_all | zip(ansible_play_hosts_all | map('extract', hostvars, 'bf_required_channel_ready') | map('default', 0) | map('int') | list)) | dict2items | selectattr('value', 'lt', 2) | map(attribute='key') | list }}"
run_once: true
- name: Kanonik doğrulamada hazır olmayan cihazların dalga listesini yaz
ansible.builtin.debug:
msg: "bf-remote-status --check başarısız (merkez kanıtı dahil): {{ bf_canonical_not_ready_hosts }}"
vars:
bf_canonical_not_ready_hosts: "{{ dict(ansible_play_hosts_all | zip(ansible_play_hosts_all | map('extract', hostvars, 'bf_ch_canonical_ready') | map('default', 'NOT-READY', true) | list)) | dict2items | selectattr('value', 'equalto', 'NOT-READY') | map(attribute='key') | list }}"
run_once: true

View file

@ -0,0 +1,76 @@
---
# Run an approved script already present on a confirmed maintenance wave.
- name: Run operator script
hosts: "{{ operation_wave | default('invalid-wave', true) }}"
gather_facts: false
become: true
vars:
approved_operation_waves: [lab, pilot_1, pilot_2, wave_1, wave_2, production]
production_override: false
script_path: ""
script_args: ""
script_allowed_dirs:
- /opt/blueforce/bin
- /usr/local/sbin
script_allowlist: []
pre_tasks:
- name: Require confirmed approved operation wave scope
ansible.builtin.assert:
that:
- operation_wave in approved_operation_waves
- operation_confirmed | bool
- operation_wave in groups
- 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: Canonicalize requested script path
ansible.builtin.command:
argv: [readlink, -f, --, "{{ script_path }}"]
register: canonical_script
changed_when: false
failed_when: canonical_script.rc != 0
- name: Canonicalize approved directory prefixes
ansible.builtin.command:
argv: [readlink, -f, --, "{{ item }}"]
loop: "{{ script_allowed_dirs }}"
register: canonical_dirs
changed_when: false
- name: Validate exact approved path boundary or explicit allowlist
ansible.builtin.assert:
that:
- canonical_script.stdout is match('^/.+')
- canonical_script.stdout in script_allowlist or (canonical_dirs.results | map(attribute='stdout') | select('match', '^/.+') | map('regex_replace', '$', '/') | select('in', canonical_script.stdout + '/') | list | length > 0)
fail_msg: "Refused root execution of '{{ script_path }}': canonical path '{{ canonical_script.stdout }}' is not in an approved directory or explicit allowlist."
- name: Check canonical script is a regular executable file
ansible.builtin.stat:
path: "{{ canonical_script.stdout }}"
register: script_stat
- name: Validate canonical script metadata
ansible.builtin.assert:
that:
- script_stat.stat.exists
- script_stat.stat.isreg
- script_stat.stat.executable
- name: Execute approved canonical script
ansible.builtin.command:
argv: "{{ [canonical_script.stdout] + (script_args | default('') | split()) }}"
register: script_out
changed_when: true
- name: Show script output
ansible.builtin.debug:
msg: "{{ inventory_hostname }} exit={{ script_out.rc }}\n{{ script_out.stdout | default('') }}{{ script_out.stderr | default('') }}"

View file

@ -0,0 +1,25 @@
---
# Restart the RustDesk remote-access service.
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-rustdesk-restart.yml --limit lab
# Service name is overridable via rustdesk_service_name.
# No Semaphore Pro feature is used.
- name: Restart RustDesk service
hosts: blueforce
gather_facts: false
become: true
vars:
rustdesk_restart_service: "{{ rustdesk_service_name | default('rustdesk') }}"
tasks:
- name: Restart RustDesk
ansible.builtin.systemd:
name: "{{ rustdesk_restart_service }}"
state: restarted
- name: Verify RustDesk is active
ansible.builtin.command: "systemctl is-active {{ rustdesk_restart_service }}"
register: rustdesk_state
changed_when: false
- name: Show RustDesk state
ansible.builtin.debug:
msg: "{{ inventory_hostname }} rustdesk={{ rustdesk_state.stdout }}"

View file

@ -0,0 +1,32 @@
---
# Report pending security updates only. NEVER installs anything.
# Policy docs/10: unattended upgrades are disabled; every change needs an approval wave.
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-security-updates-check.yml --limit lab
# No Semaphore Pro feature is used.
- name: Report pending security updates (no install)
hosts: blueforce
gather_facts: false
become: true
tasks:
- name: Refresh apt metadata (cache only, no upgrade)
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: List upgradable packages
ansible.builtin.shell: "apt list --upgradable 2>/dev/null"
register: upgradable
changed_when: false
- name: List security updates only
ansible.builtin.shell: "apt-get -s dist-upgrade -V | grep -i security || true"
register: sec_updates
changed_when: false
- name: Show report
ansible.builtin.debug:
msg: "{{ inventory_hostname }} upgradable:\n{{ upgradable.stdout }}\nsecurity:\n{{ sec_updates.stdout | default('(none)') }}"
- name: Remind operator that install is out of scope
ansible.builtin.debug:
msg: "REPORT ONLY - use bf-deploy-update.yml with approval waves to install anything."

View file

@ -0,0 +1,35 @@
---
# Restart a single service by name (parametric, allowlisted).
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-service-restart.yml --limit lab -e service_name=blueforce-agent
# Only names in service_allowlist are accepted; everything else fails closed.
# No Semaphore Pro feature is used.
- name: Restart allowlisted service
hosts: blueforce
gather_facts: false
become: true
vars:
service_name: ""
service_allowlist:
- blueforce-agent
- docker
- sshd
- ssh
- systemd-journald
- cron
tasks:
- name: Validate service_name is provided
ansible.builtin.assert:
that:
- service_name | length > 0
fail_msg: "service_name is required, e.g. -e service_name=blueforce-agent"
- name: Validate service_name is allowlisted
ansible.builtin.assert:
that:
- service_name in service_allowlist
fail_msg: "service_name '{{ service_name }}' is not in the allowlist. Refusing to restart arbitrary services."
- name: Restart service
ansible.builtin.systemd:
name: "{{ service_name }}"
state: restarted

View file

@ -0,0 +1,16 @@
---
# Collect uptime from fleet hosts (read-only).
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-uptime.yml --limit pilot_1
# No changes are made by this playbook. No Semaphore Pro feature is used.
- name: Report host uptime
hosts: blueforce
gather_facts: false
tasks:
- name: Get uptime
ansible.builtin.command: uptime
register: uptime_out
changed_when: false
- name: Show uptime
ansible.builtin.debug:
msg: "{{ inventory_hostname }}: {{ uptime_out.stdout }}"

View file

@ -0,0 +1,25 @@
---
# Restart the WireGuard tunnel interface.
# Run: ansible-playbook -i inventory/hosts.yml playbooks/bf-wireguard-restart.yml --limit lab
# Uses wg_interface from group_vars and defaults to wg0.
# No Semaphore Pro feature is used.
- name: Restart WireGuard tunnel
hosts: blueforce
gather_facts: false
become: true
vars:
wireguard_restart_interface: "{{ wg_interface | default('wg0') }}"
tasks:
- name: Restart WireGuard service
ansible.builtin.systemd:
name: "wg-quick@{{ wireguard_restart_interface }}"
state: restarted
- name: Verify tunnel state
ansible.builtin.command: "wg show {{ wireguard_restart_interface }}"
register: wg_out
changed_when: false
- name: Show WireGuard state
ansible.builtin.debug:
msg: "{{ inventory_hostname }}: {{ wg_out.stdout_lines | default([]) }}"

View file

@ -0,0 +1,20 @@
# common role
Baseline for every Blueforce host: base packages, timezone, and the docs/10
requirement that `unattended-upgrades` stays OFF with `apt-daily*.timer` masked.
## Variables
| Name | Default | Purpose |
|---|---|---|
| `common_timezone` | `Europe/Istanbul` | System timezone |
| `common_admin_user` | `blueforce` | Admin login (SSH key managed per docs/06) |
## Usage
```yaml
- hosts: blueforce
become: true
roles:
- common
```

View file

@ -0,0 +1,4 @@
---
# Defaults for the common role. Override in group_vars/host_vars, never hardcode secrets here.
common_timezone: Europe/Istanbul
common_admin_user: blueforce

View file

@ -0,0 +1,37 @@
---
# Common baseline: users, time, unattended-upgrades OFF (docs/10), base packages.
# No Semaphore Pro feature is used.
- name: Ensure base packages are present
ansible.builtin.apt:
name:
- openssh-server
- python3
- curl
- htop
state: present
update_cache: true
cache_valid_time: 3600
- name: Disable automatic upgrades (policy docs/10)
ansible.builtin.copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
owner: root
group: root
mode: "0644"
content: |
APT::Periodic::Update-Package-Lists "0";
APT::Periodic::Unattended-Upgrade "0";
- name: Mask apt-daily timers (policy docs/10)
ansible.builtin.systemd:
name: "{{ item }}"
masked: true
state: stopped
loop:
- apt-daily.timer
- apt-daily-upgrade.timer
failed_when: false
- name: Set timezone
ansible.builtin.timezone:
name: "{{ common_timezone }}"

View file

@ -0,0 +1,22 @@
# docker role
Installs the pinned Docker engine, enables the daemon, and reports the MEG
container state. Image and package tags are always pinned; `latest` and
Watchtower-style auto-updaters are forbidden (docs/10, docs/12).
## Variables
| Name | Default | Purpose |
|---|---|---|
| `docker_packages` | pinned `docker-ce=...` | Exact engine packages to install |
| `meg_container_name` | `meg` | Container name to report on |
| `meg_image_tag` | `1.0.0` | Pinned MEG image tag |
## Usage
```yaml
- hosts: blueforce
become: true
roles:
- docker
```

View file

@ -0,0 +1,12 @@
---
# Docker is installed only when an operator supplies a verified repository and exact version.
# Supported Ubuntu releases include 24.04 noble and 26.04 resolute; no release is hard-coded.
docker_supported_codenames:
- noble
- resolute
docker_apt_codename: "{{ ansible_distribution_release | default('') }}"
docker_apt_repository: "" # Required: approved, already key-verified Docker APT repository.
docker_engine_version: "" # Required exact version, e.g. 5:29.0.0-1~ubuntu.26.04~resolute.
docker_packages: []
meg_container_name: meg
meg_image_tag: 1.0.0

View file

@ -0,0 +1,50 @@
---
# Docker engine setup with explicit, approved repository and version only.
- name: Refuse unsupported Ubuntu codename or unapproved Docker inputs
ansible.builtin.assert:
that:
- docker_apt_codename in docker_supported_codenames
- docker_apt_repository | length > 0
- docker_engine_version | length > 0
- docker_engine_version is not search('latest')
- docker_packages | length > 0
- docker_packages | select('search', '(^|=).*latest($|[^[:alnum:]])') | list | length == 0
fail_msg: >-
Docker installation is refused until this host has a supported codename and an explicitly approved
repository, exact engine version, and exact package list. No automatic Docker update is permitted.
- name: Verify approved Docker repository is configured
ansible.builtin.command: "apt-cache policy docker-ce"
register: docker_policy
changed_when: false
failed_when: docker_apt_repository not in docker_policy.stdout
- name: Verify requested Docker version is available from approved repository
ansible.builtin.command: "apt-cache madison docker-ce"
register: docker_versions
changed_when: false
failed_when: docker_engine_version not in docker_versions.stdout
- name: Install only the approved exact Docker packages
ansible.builtin.apt:
name: "{{ docker_packages }}"
state: present
update_cache: false
- name: Enable and start Docker daemon
ansible.builtin.systemd:
name: docker
enabled: true
state: started
- name: Ensure MEG container is running at pinned tag
ansible.builtin.command: "docker ps --filter name={{ meg_container_name }} --format '{{{{.Names}}}}'"
register: meg_ps
changed_when: false
failed_when: false
when: meg_container_name | length > 0
- name: Report MEG container state
ansible.builtin.debug:
msg: "{{ inventory_hostname }} meg={{ meg_ps.stdout | default('(skipped)') }}"
when: meg_container_name | length > 0

View file

@ -0,0 +1,22 @@
# monitoring role
Installs and starts the node exporter and verifies the docs/10 requirement
that automatic upgrades stay disabled.
## Variables
| Name | Default | Purpose |
|---|---|---|
| `monitoring_packages` | `prometheus-node-exporter` | Packages to install |
| `monitoring_service` | `prometheus-node-exporter` | Service to enable |
| `monitoring_endpoint` | `http://monitoring.example.com:8428` | Metrics endpoint (placeholder) |
| `monitoring_job` | `blueforce` | Job label |
## Usage
```yaml
- hosts: blueforce
become: true
roles:
- monitoring
```

View file

@ -0,0 +1,7 @@
---
# Defaults for the monitoring role.
monitoring_packages:
- prometheus-node-exporter
monitoring_service: prometheus-node-exporter
monitoring_endpoint: http://monitoring.example.com:8428
monitoring_job: blueforce

View file

@ -0,0 +1,25 @@
---
# Monitoring baseline: node exporter install + health signal files.
# No Semaphore Pro feature is used.
- name: Ensure monitoring packages are present
ansible.builtin.apt:
name: "{{ monitoring_packages }}"
state: present
update_cache: true
cache_valid_time: 3600
- name: Enable and start node exporter
ansible.builtin.systemd:
name: "{{ monitoring_service }}"
enabled: true
state: started
- name: Verify unattended-upgrades stay disabled (policy docs/10)
ansible.builtin.shell: 'grep -E ''Unattended-Upgrade "0"'' /etc/apt/apt.conf.d/20auto-upgrades'
register: unattended_check
changed_when: false
failed_when: false
- name: Report monitoring state
ansible.builtin.debug:
msg: "{{ inventory_hostname }} exporter={{ monitoring_service }} unattended_off={{ unattended_check.rc == 0 }}"

View file

@ -0,0 +1,32 @@
# wireguard role
Installs WireGuard, writes `/etc/wireguard/<iface>.conf` from variables, and
starts `wg-quick@<iface>`. Private keys are secrets: supply them through the
Semaphore Community encrypted Key Store or another controller-side secret
source; never commit them to Git.
## Variables
| Name | Default | Purpose |
|---|---|---|
| `wg_interface` | `wg0` | Interface name |
| `wg_address` | _(empty)_ | Required client tunnel address, e.g. `10.42.3.11/16` |
| `wg_dns` | _(empty)_ | Optional DNS |
| `wg_private_key` | _(empty)_ | Required client private key (secret) |
| `wg_hub_public_key` | _(empty)_ | Required hub public key |
| `wg_hub_endpoint` | `vpn.example.com:51820` | Required hub endpoint |
| `wg_allowed_ips` | `10.42.0.0/16` | Required allowed IPs |
| `wg_keepalive` | `25` | Persistent keepalive seconds |
The role fails closed when any required tunnel value is empty.
## Usage
```yaml
- hosts: blueforce
become: true
roles:
- role: wireguard
vars:
wg_address: 10.42.3.11/16
```

View file

@ -0,0 +1,10 @@
---
# Defaults for the wireguard role. Secrets MUST be overridden via Key Store / vault.
wg_interface: wg0
wg_address: ""
wg_dns: ""
wg_private_key: ""
wg_hub_public_key: ""
wg_hub_endpoint: ""
wg_allowed_ips: "10.42.0.0/16"
wg_keepalive: 25

View file

@ -0,0 +1,6 @@
---
# Handlers for the wireguard role.
- name: Restart WireGuard
ansible.builtin.systemd:
name: "wg-quick@{{ wg_interface }}"
state: restarted

View file

@ -0,0 +1,48 @@
---
# WireGuard client setup: package, config from template variables, tunnel up.
# Private keys are supplied through the Semaphore Community Key Store, never committed.
# No Semaphore Pro feature is used.
- name: Validate required WireGuard configuration
ansible.builtin.assert:
that:
- wg_address | length > 0
- wg_private_key | length > 0
- wg_hub_public_key | length > 0
- wg_hub_endpoint | length > 0
- wg_allowed_ips | length > 0
fail_msg: WireGuard address, keys, hub endpoint, and allowed IPs are required.
no_log: true
- name: Ensure WireGuard package is present
ansible.builtin.apt:
name: wireguard
state: present
update_cache: true
cache_valid_time: 3600
- name: Write WireGuard client config
ansible.builtin.copy:
dest: "/etc/wireguard/{{ wg_interface }}.conf"
owner: root
group: root
mode: "0600"
content: |
[Interface]
PrivateKey = {{ wg_private_key }}
Address = {{ wg_address }}
{% if wg_dns | default('') | length > 0 %}
DNS = {{ wg_dns }}
{% endif %}
[Peer]
PublicKey = {{ wg_hub_public_key }}
Endpoint = {{ wg_hub_endpoint }}
AllowedIPs = {{ wg_allowed_ips }}
PersistentKeepalive = {{ wg_keepalive }}
no_log: true
notify: Restart WireGuard
- name: Enable and start WireGuard tunnel
ansible.builtin.systemd:
name: "wg-quick@{{ wg_interface }}"
enabled: true
state: started

14
config/docker/daemon.json Normal file
View file

@ -0,0 +1,14 @@
{
"_note": [
"Purpose: Cap Docker log disk use and keep containers alive across daemon restarts.",
"Docker manages firewall rules; the installer applies the mandatory DOCKER-USER deny policy.",
"Published application ports require explicit reviewed rules in /etc/blueforce/docker-published-port-allowlist.conf."
],
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"live-restore": true,
"iptables": true
}

View file

@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Apply Blueforce's reviewed Docker published-port policy to DOCKER-USER.
set -euo pipefail
ALLOWLIST="${DOCKER_ALLOWLIST:-/etc/blueforce/docker-published-port-allowlist.conf}"
CHECK_MODE=0
[[ "${1:-}" == "--check" ]] && CHECK_MODE=1
fail() { printf 'blueforce-docker-firewall: %s\n' "$*" >&2; exit 1; }
parse_allowlist() {
local callback="$1" raw line protocol port extra
[[ -r "$ALLOWLIST" ]] || fail "allowlist is missing or unreadable: $ALLOWLIST"
while IFS= read -r raw || [[ -n "$raw" ]]; do
line="${raw%%#*}"
line="$(printf '%s' "$line" | xargs)"
[[ -z "$line" ]] && continue
read -r protocol port extra <<< "$line"
[[ -z "${extra:-}" && "$protocol" =~ ^(tcp|udp)$ && "$port" =~ ^[0-9]{1,5}$ ]] || fail "invalid allowlist entry '$raw'; use 'tcp PORT' or 'udp PORT'"
((10#$port >= 1 && 10#$port <= 65535)) || fail "port out of range in '$raw'"
"$callback" "$protocol" "$port"
done < "$ALLOWLIST"
}
validate_allowlist() { parse_allowlist :; }
install_allow_rule() {
local protocol="$1" port="$2"
# Docker DNAT precedes DOCKER-USER; match the original reviewed published port.
iptables -I DOCKER-USER -p "$protocol" -m conntrack --ctorigdstport "$port" -j ACCEPT
}
verify_allow_rule() {
local protocol="$1" port="$2"
iptables -C DOCKER-USER -p "$protocol" -m conntrack --ctorigdstport "$port" -j ACCEPT || fail "allowlist rule missing: $protocol $port"
}
validate_allowlist
command -v iptables >/dev/null 2>&1 || fail "iptables is required"
iptables -S DOCKER-USER >/dev/null 2>&1 || fail "DOCKER-USER chain is unavailable; start Docker first"
if [[ "$CHECK_MODE" -eq 1 ]]; then
iptables -C DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT || fail "established/related rule missing"
iptables -C DOCKER-USER -i docker0 -j RETURN || fail "docker0 outbound return rule missing"
iptables -C DOCKER-USER -i br+ -j RETURN || fail "user-defined bridge outbound return rule missing"
parse_allowlist verify_allow_rule
iptables -C DOCKER-USER -j DROP || fail "default published-port deny rule missing"
exit 0
fi
# Blueforce owns DOCKER-USER so an old permissive rule cannot bypass the deny.
iptables -F DOCKER-USER
iptables -A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Container-to-Internet packets enter FORWARD from Docker bridges; inbound published-port
# packets enter from an external interface, so these returns do not bypass the deny below.
iptables -A DOCKER-USER -i docker0 -j RETURN
iptables -A DOCKER-USER -i br+ -j RETURN
# Insert every parsed allowlist exception before the final default drop.
parse_allowlist install_allow_rule
iptables -A DOCKER-USER -j DROP

View file

@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Apply field-device UFW baseline. SSH/RDP are accepted only on wg0.
set -euo pipefail
WG_IF="wg0"
WG_PORT="51820"
DOCKER_ALLOWLIST="/etc/blueforce/docker-published-port-allowlist.conf"
DOCKER_POLICY_BIN="/usr/local/sbin/blueforce-docker-firewall"
DOCKER_POLICY_UNIT="blueforce-docker-firewall.service"
rule_exists() { ufw status | grep -Fq "$1"; }
ensure_rule() { local match="$1"; shift; rule_exists "$match" && echo "keep: $match" || ufw "$@"; }
install_docker_policy() {
local root
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
install -d -m 755 /etc/blueforce /etc/iptables
if [[ ! -f "$DOCKER_ALLOWLIST" ]]; then
printf '%s\n' '# Explicit reviewed Docker published-port exceptions: tcp PORT or udp PORT.' '# Default: deny every externally published Docker port.' > "$DOCKER_ALLOWLIST"
fi
chown root:root "$DOCKER_ALLOWLIST"
chmod 640 "$DOCKER_ALLOWLIST"
install -o root -g root -m 700 "$root/config/firewall/blueforce-docker-user-restore.sh" "$DOCKER_POLICY_BIN"
install -o root -g root -m 644 "$root/config/systemd/blueforce-docker-firewall.service" "/etc/systemd/system/$DOCKER_POLICY_UNIT"
systemctl daemon-reload
systemctl enable "$DOCKER_POLICY_UNIT"
systemctl start docker
"$DOCKER_POLICY_BIN"
}
cmd_apply() {
ufw default deny incoming
ufw default allow outgoing
ensure_rule "${WG_PORT}/udp" allow "${WG_PORT}/udp" comment 'WireGuard hub'
ensure_rule "22/tcp on ${WG_IF}" allow in on "${WG_IF}" to any port 22 proto tcp comment 'SSH over WireGuard'
ensure_rule "3389/tcp on ${WG_IF}" allow in on "${WG_IF}" to any port 3389 proto tcp comment 'RDP over WireGuard'
install_docker_policy
ufw --force enable
ufw status verbose
}
cmd_reset() { ufw --force reset; ufw default deny incoming; ufw default allow outgoing; ufw --force enable; ufw status verbose; }
cmd_status() { ufw status verbose; "$DOCKER_POLICY_BIN" --check; iptables -S DOCKER-USER; }
case "${1:-apply}" in apply) cmd_apply ;; reset) cmd_reset ;; status) cmd_status ;; *) echo "Usage: $0 {apply|reset|status}" >&2; exit 1 ;; esac

View file

@ -0,0 +1,23 @@
# Purpose: Harden field-device SSH to key-only maintenance access.
# Scope: Applies to the blueforce user; root and password logins stay disabled.
# Safety: Drop-in for sshd_config.d; validate with sshd -t before reload.
# Ref: docs/06-USERS-SSH-AND-PERMISSIONS.md, K-13.
# No direct root login; privilege is gained only via audited sudo.
PermitRootLogin no
# Key-only auth: passwords and interactive challenges are disabled.
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
# Only the maintenance user may open SSH sessions.
AllowUsers blueforce
# Reduce idle-session and brute-force exposure.
X11Forwarding no
MaxAuthTries 3
LoginGraceTime 60
ClientAliveInterval 300
ClientAliveCountMax 2

View file

@ -0,0 +1,10 @@
# Purpose: Manually return a field device to terminal mode after maintenance.
# This unit is deliberately not enabled by any boot target.
[Unit]
Description=Blueforce GUI off (manual terminal maintenance switch)
After=graphical.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/bf-gui-off
RemainAfterExit=no

View file

@ -0,0 +1,10 @@
# Purpose: Manually switch a field device to graphical mode for maintenance.
# This unit is deliberately not enabled by any boot target.
[Unit]
Description=Blueforce GUI on (manual graphical maintenance switch)
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/bf-gui-on
RemainAfterExit=no

View file

@ -0,0 +1,13 @@
[Unit]
Description=Blueforce persistent Docker published-port firewall
BindsTo=docker.service
PartOf=docker.service
After=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/blueforce-docker-firewall
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,45 @@
# Blueforce enrollment controlled attempt: PROVISIONED_OFFLINE -> ENROLLED.
#
# Behaviour (docs/29-DEVICE-ENROLLMENT.md, provisioning/enrollment/README.md):
# * One bounded attempt per start. The unit never reboots the device and never
# loops: a device without internet or without a staged token prints PENDING,
# exits 0 and stays PROVISIONED_OFFLINE.
# * When a one-time token has been staged (bf-enroll --token-stdin --stage,
# mode 0600, TTL bounded) the attempt enrolls by itself as soon as central is
# reachable. RestartSec plus the StartLimit below cap that at six attempts
# per hour and then leave the cadence to blueforce-enroll.timer, so a device
# that stays offline for weeks is never hammered.
# * No credential lives in this unit: bf-enroll reads the staged root-only
# token once and deletes it on success or on a definitive rejection.
# * Restart= is on-failure only, so a successful run, a READY device and an
# unprovisioned device are never retried.
# * Trigger: blueforce-enroll.timer (OnBootSec=10min, then daily). An operator
# can also run `bf-enroll-now` interactively at any time.
[Unit]
Description=Blueforce enrollment controlled attempt (bounded retries, never reboots)
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=1h
StartLimitBurst=6
[Service]
Type=exec
# Central control plane endpoint and timing, delivered by central configuration.
# Optional on purpose: the attempt reports PENDING when it is not configured yet.
EnvironmentFile=-/etc/blueforce/enrollment.env
ExecStart=/usr/local/bin/bf-enroll --systemd
# Bounded retry: enough to join automatically when connectivity returns, never
# aggressive while the device is offline (systemd holds the next attempt for
# RestartSec instead of spinning).
Restart=on-failure
RestartSec=5min
TimeoutStartSec=180
User=root
UMask=0077
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
# Writable paths only: lifecycle state plus staged token, WireGuard configuration
# (including the wg-quick enable symlink) and central configuration.
ReadWritePaths=/var/lib/blueforce /etc/wireguard /etc/blueforce /etc/systemd/system

View file

@ -0,0 +1,11 @@
[Unit]
Description=Blueforce enrollment pending reminder
[Timer]
OnBootSec=10min
OnUnitActiveSec=24h
Persistent=true
Unit=blueforce-enroll.service
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,3 @@
# Start the firewall after every Docker start; the firewall unit orders itself after Docker.
[Unit]
Wants=blueforce-docker-firewall.service

View file

@ -0,0 +1,9 @@
# Purpose: Bound journald disk use on field devices while keeping history.
# Scope: Persistent storage with caps; install to /etc/systemd/journald.conf.d/.
# Safety: Reboot-safe; apply with systemctl restart systemd-journald.
# Ref: docs/15-LOGGING.md, K-14.
[Journal]
Storage=persistent
SystemMaxUse=500M
RuntimeMaxUse=100M
MaxFileSec=14day

View file

@ -0,0 +1,20 @@
# Purpose: Rotate MEG and Blueforce application logs to cap disk use.
# Scope: /var/log/blueforce and /var/log/meg file logs; install to /etc/logrotate.d/.
# Safety: Idempotent under logrotate; validate with logrotate -d before deploying.
# Ref: docs/15-LOGGING.md, K-14.
/var/log/blueforce/*.log /var/log/meg/*.log {
weekly
rotate 7
size 10M
maxage 30
compress
delaycompress
missingok
notifempty
create 0640 root adm
sharedscripts
postrotate
/usr/bin/systemctl kill --signal=SIGHUP rsyslog.service >/dev/null 2>&1 || true
endscript
}
# NOTE: install.log and update-history.log stay append-only and are excluded here.

View file

@ -0,0 +1,7 @@
# Purpose: Keep the WireGuard tunnel up across failures without manual action.
# Scope: Drop-in for wg-quick@wg0; restarts the tunnel with a short backoff.
# Safety: Reboot-safe once the unit is enabled; confirm on the LAB 26.04.1 image.
# Ref: docs/08-WIREGUARD-AND-NETWORK.md and docs/16-POWER-LOSS-AND-AUTO-RECOVERY.md, K-05.
[Service]
Restart=always
RestartSec=5

View file

@ -0,0 +1,34 @@
<!-- Purpose: Explain how to turn wg0.conf.example into a live spoke config. -->
<!-- Scope: Hub-spoke layout, peer naming, keepalive, PostUp/PostDown, bring-up. -->
<!-- Safety: No secrets here; every key and address marked <REPLACE> stays out of git. -->
# WireGuard spoke reference
Ref: `docs/08-WIREGUARD-AND-NETWORK.md` (K-05), naming `K-12`.
## Layout
- Hub-spoke: the central server is the hub (`10.8.0.1`), each field device is a spoke.
- Peer name equals the device identity: `bf-<8digits>` (hostname, inventory, monitoring label).
- Each spoke gets one fixed tunnel IP (`10.8.0.x/32`) from inventory.
## Filling the example
1. Copy `wg0.conf.example` to `/etc/wireguard/wg0.conf` (mode `600`).
2. Replace `Address` with the device tunnel IP from inventory.
3. Replace `<REPLACE_DEVICE_PRIVATE_KEY>` with the device private key.
4. Replace `<REPLACE_HUB_PUBLIC_KEY>` and `<REPLACE_HUB_HOST>` with hub values.
5. Keep `PersistentKeepalive = 25` (official reasonable default for NAT peers).
6. Keep `PostUp`/`PostDown`: they scope SSH (22) and RDP (3389) rules to `wg0`.
## Bring-up
```bash
sudo install -m 600 wg0.conf /etc/wireguard/wg0.conf
sudo systemctl enable --now wg-quick@wg0
sudo wg show wg0 latest-handshakes
```
## Notes
- Spokes behind CGNAT must always initiate outward; the hub only listens on `51820/udp`.
- Placeholders marked `<REPLACE>` must never hold real keys in this repo.

View file

@ -0,0 +1,24 @@
# Purpose: Reference spoke config for field devices joining the hub.
# Scope: Peer name bf-<ID> with fixed tunnel IP; hub values come from inventory.
# Safety: Example only; replace every <REPLACE> placeholder before installing to /etc/wireguard/.
# Ref: docs/08-WIREGUARD-AND-NETWORK.md, K-05; peer naming K-12.
[Interface]
# Tunnel address from inventory (one fixed /32 per device, hub is 10.8.0.1).
Address = 10.8.0.15/32
# Device private key. <REPLACE> with the real key; never commit it.
PrivateKey = <REPLACE_DEVICE_PRIVATE_KEY>
# Open management ports on the tunnel interface when it comes up.
PostUp = ufw allow in on wg0 to any port 22 proto tcp; ufw allow in on wg0 to any port 3389 proto tcp
# Remove tunnel-scoped management rules when the tunnel goes down.
PostDown = ufw delete allow in on wg0 to any port 22 proto tcp; ufw delete allow in on wg0 to any port 3389 proto tcp
[Peer]
# Hub public key. <REPLACE> with the real hub key.
PublicKey = <REPLACE_HUB_PUBLIC_KEY>
# Hub endpoint. <REPLACE> with the real hub address.
Endpoint = <REPLACE_HUB_HOST>:51820
# Return traffic is limited to the tunnel subnet.
AllowedIPs = 10.8.0.0/16
# Keep the NAT/CGNAT mapping alive behind mobile networks.
PersistentKeepalive = 25

6
docs-site/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
node_modules/
build/
.docusaurus/
# Generated content (recreated by sync-docs.sh on prestart/prebuild).
docs/*.md
!docs/.gitkeep

19
docs-site/README.md Normal file
View file

@ -0,0 +1,19 @@
# Blueforce docs site (Docusaurus)
Static documentation is synced one-way from `../docs/*.md` into `./docs/`; never edit generated `./docs/` directly.
## Local development
```bash
cd docs-site
npm install
npm start # http://127.0.0.1:3000 (loopback only)
npm run build
npm run serve # local static preview on 127.0.0.1:3000
```
For an explicitly approved intranet preview, use `npm run start:intranet` or `npm run serve:intranet`; those commands bind `0.0.0.0:3000`.
## Deploy
Publish `build/` through the intranet web server. The site has no favicon reference until a maintained favicon asset is added.

View file

@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View file

@ -0,0 +1,30 @@
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
// Blueforce fleet documentation site. Content source: ../docs/*.md.
const config: Config = {
title: 'Blueforce Docs',
tagline: 'Fleet runbooks and architecture',
url: 'http://docs.blueforce.intranet',
baseUrl: '/',
onBrokenLinks: 'throw',
i18n: { defaultLocale: 'tr', locales: ['tr'] },
markdown: {
mermaid: true,
hooks: { onBrokenMarkdownLinks: 'warn' },
},
themes: ['@docusaurus/theme-mermaid', '@easyops-cn/docusaurus-search-local'],
presets: [
['classic', {
docs: { path: 'docs', routeBasePath: '/', sidebarPath: './sidebars.js' },
blog: false,
theme: { customCss: './src/css/custom.css' },
} satisfies Preset.Options],
],
themeConfig: {
colorMode: { defaultMode: 'dark', disableSwitch: false, respectPrefersColorScheme: false },
navbar: { title: 'Blueforce Docs', items: [{ type: 'docSidebar', sidebarId: 'docs', position: 'left', label: 'Docs' }] },
} satisfies Preset.ThemeConfig,
};
export default config;

20121
docs-site/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

31
docs-site/package.json Normal file
View file

@ -0,0 +1,31 @@
{
"name": "blueforce-docs",
"version": "0.1.0",
"private": true,
"description": "Blueforce fleet documentation site. Source of truth is ../docs/*.md; this package only builds the site.",
"scripts": {
"sync": "./sync-docs.sh",
"prestart": "./sync-docs.sh",
"prebuild": "./sync-docs.sh",
"start": "docusaurus start --host 127.0.0.1 --port 3000",
"start:intranet": "docusaurus start --host 0.0.0.0 --port 3000",
"build": "docusaurus build",
"serve": "docusaurus serve build --host 127.0.0.1 --port 3000",
"serve:intranet": "docusaurus serve build --host 0.0.0.0 --port 3000",
"clear": "docusaurus clear"
},
"dependencies": {
"@docusaurus/core": "3.10.2",
"@docusaurus/preset-classic": "3.10.2",
"@docusaurus/theme-mermaid": "3.10.2",
"@easyops-cn/docusaurus-search-local": "0.49.2",
"@mermaid-js/layout-elk": "0.1.9",
"@mdx-js/react": "3.0.1",
"clsx": "2.1.1",
"prism-react-renderer": "2.4.0",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": { "typescript": "5.5.4" },
"engines": { "node": ">=18.0" }
}

9
docs-site/sidebars.js Normal file
View file

@ -0,0 +1,9 @@
// Auto-generated sidebar: every synced Markdown file appears without manual entries.
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
docs: [{type: 'autogenerated', dirName: '.'}],
};
export default sidebars;

View file

@ -0,0 +1,4 @@
/* Blueforce docs theme overrides. Dark mode is the default (see docusaurus.config.ts). */
:root {
--ifm-color-primary: #4aa8ff;
}

View file

@ -0,0 +1,19 @@
import React from 'react';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
export default function Home() {
const {siteConfig} = useDocusaurusContext();
return (
<Layout title={siteConfig.title} description="Blueforce fleet documentation">
<main style={{padding: '4rem 2rem', textAlign: 'center'}}>
<h1>{siteConfig.title}</h1>
<p>{siteConfig.tagline}</p>
<Link className="button button--primary button--lg" to="/MASTER-PLAN">
Open docs
</Link>
</main>
</Layout>
);
}

29
docs-site/sync-docs.sh Executable file
View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
# One-way sync: repo docs (../docs/*.md) -> site content (./docs/).
# The repo Markdown files are the single source of truth; never edit ./docs by hand.
set -euo pipefail
SITE_DIR="$(cd "$(dirname "$0")" && pwd)"
SRC_DIR="$SITE_DIR/../docs"
DST_DIR="$SITE_DIR/docs"
mkdir -p "$DST_DIR"
# Copy only numbered canonical sources; docs/_TEMPLATE.md is never site content.
shopt -s nullglob
sources=("$SRC_DIR"/[0-9][0-9]-*.md)
if [ "${#sources[@]}" -eq 0 ]; then
echo "No numbered Markdown sources found in $SRC_DIR" >&2
exit 1
fi
cp -f "${sources[@]}" "$DST_DIR"/
# Remove stale or non-numbered site documents, including _TEMPLATE.md.
for f in "$DST_DIR"/*.md; do
base="$(basename "$f")"
if [[ ! "$base" =~ ^[0-9][0-9]-.*\.md$ ]] || [ ! -f "$SRC_DIR/$base" ]; then
rm -f "$f"
fi
done
echo "Synced ${#sources[@]} numbered files into docs-site/docs."

6
docs-site/tsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": "."
}
}

View file

@ -1,9 +1,9 @@
# 01 — Sistem Mimarisi # 01 — Sistem Mimarisi
> Kısa özet: 700 saha cihazlı Blueforce filosunun uçtan uca mimarisi: saha PC'si, WireGuard ağı, 4 erişim kanalı, filo otomasyonu, monitoring ve update zinciri tek bakışta. Kararların görsel karşılığıdır. > Kısa özet: 700 saha cihazlı Blueforce filosunun uçtan uca mimarisi: saha PC'si, WireGuard ağı, 4 erişim kanalı, filo otomasyonu, monitoring ve update zinciri tek bakışta. Kararların görsel karşılığıdır. Bu turda mimari iki noktada kesinleşti: **V1 medyası tek USB Field OS ISO'dur** (autoinstall motoru + offline APT + firstboot; upstream ISO + NoCloud seed geri dönüş yolu — K-20) ve **xRDP her zaman hazırdır** (`bf-gui-*` yalnız yerel fiziksel GUI'yi kontrol eder — K-22).
> >
> - Dosya: `docs/01-ARCHITECTURE.md` > - Dosya: `docs/01-ARCHITECTURE.md`
> - İlgili kararlar: `24-DECISION-LOG.md#K-01…K-14` (tüm mimari bu kararlara dayanır) > - İlgili kararlar: `24-DECISION-LOG.md#K-01…K-24` (tüm mimari bu kararlara dayanır; medya için `#K-20`, RDP/yerel GUI ayrımı için `#K-22`, durum modeli için `#K-21`, masaüstü ölçümü için `#K-23`)
> - Durum: [x] Onaylı (Faz 2 kilidi) > - Durum: [x] Onaylı (Faz 2 kilidi)
--- ---
@ -20,14 +20,16 @@ Başka bir DevOps mühendisinin filoyu tek dosyadan anlayıp kurmaya başlayabil
## 3. Kararlar ## 3. Kararlar
```text ```text
KARAR: Filo mimarisi: saha = Ubuntu Server 26.04.1+ GNOME + Docker(MEG) + WireGuard spoke; merkez = WireGuard hub + RustDesk hbbs/hbbr + MeshCentral + Ansible/Semaphore + Prometheus/Grafana + Uptime Kuma (hepsi self-hosted, ücretsiz katman). KARAR: Filo mimarisi: saha = Ubuntu Server 26.04.1+ üzerinde GNOME katmanlı Blueforce Field OS + Docker(MEG) + WireGuard spoke; V1 medya = TEK USB Field OS ISO (medya kökünde autoinstall.yaml + offline APT repo + firstboot; otomatik disk silme yok), geri dönüş yolu = upstream Ubuntu ISO + NoCloud seed USB; merkez = WireGuard hub + RustDesk hbbs/hbbr + MeshCentral + Ansible/Semaphore + Prometheus/Grafana + Uptime Kuma (hepsi self-hosted, ücretsiz katman). xRDP servisi her zaman hazır (`bf-gui-*` yalnız yerel fiziksel GUI'yi yönetir); durum modeli tek yönlüdür: PROVISIONED_OFFLINE → ENROLLED → READY.
GEREKÇE: 24-DECISION-LOG K-01…K-08'in birleşimi; hareketli parça en aza indirildi (agent'sız filo, tek Go binary UI, tek konteyner uptime), iki WireGuard-bağımsız erişim kanalıyla tek-nokta körlüğü engellendi. GEREKÇE: K-01…K-24 kararlarının birleşimi; hareketli parça en aza indirildi (agent'sız filo, tek Go binary UI, tek konteyner uptime), iki WireGuard-bağımsız erişim kanalıyla tek-nokta körlüğü engellendi. Tek medya teknisyen hatasını azaltır, otomatik disk silme yasağı (interactive storage) veri kaybını önler; xRDP'nin `bf-gui-on`'dan bağımsız olması yerel GUI kapalı cihazda uzaktan kurtarmayı korur.
ALTERNATİF: K8s-tabanlı yönetim / Cloud-merkezli monitoring — sadelik kuralını bozduğu için elendi (bkz. 24 §4). ALTERNATİF: K8s-tabanlı yönetim / Cloud-merkezli monitoring — sadelik kuralını bozduğu için elendi (bkz. 24 §4). İki medyalı kurulum (upstream ISO + seed) — tek-medya deneyimi olmadığı için V1 yolu olmaktan çıktı, geri dönüş yolu olarak kaldı (K-20).
RİSK: Merkez VDS tek fiziksel nokta; azaltma: yedekleme + hızlı yeniden kurulum (17-RECOVERY), kritik kanalların ayrı VDS'e taşınabilirliği. RİSK: Merkez VDS tek fiziksel nokta; azaltma: yedekleme + hızlı yeniden kurulum (17-RECOVERY), kritik kanalların ayrı VDS'e taşınabilirliği. Remaster medya boot zinciri kanıtlanmazsa assisted akış bloke olur; azaltma: iki medyalı geri dönüş yolu sıcak tutulur.
MALİYET: Ücretsiz (lisans $0; VDS kira bedeli altyapı maliyetidir). MALİYET: Ücretsiz (lisans $0; VDS kira bedeli altyapı maliyetidir).
LİSANS: Bileşen lisansları için 24-DECISION-LOG Ek A'ya bakılır. LİSANS: Bileşen lisansları için 24-DECISION-LOG Ek A'ya bakılır.
``` ```
> **Güncelleme (2026-09-16):** Bu bölümdeki "V1 medya = upstream ISO + ayrı NoCloud seed USB" ifadesi K-20 ile güncellendi (V1 = tek USB Field OS ISO; upstream + seed = geri dönüş yolu). §9'daki saha imajı akışı ve §12 kontrol listesi aynı PR'da hizalandı; diyagramlar bu kararlarla uyumludur.
## 4. Neden Bu Karar? ## 4. Neden Bu Karar?
Öncelik sırası (kesintisiz çalışma > veri kaybı yok > uzaktan erişim > bakım kolaylığı) mimariyi şekillendirdi: boot zinciri her katmanda otomatik toparlanır (systemd + `unless-stopped` + `wg-quick` enable), erişim 4 kanalla yedeklenir (ikisi WireGuard-bağımsız), update'ler onay zincirinden geçmeden sahaya inmez. Hiçbir katman ücretli lisansa dayanmaz. Öncelik sırası (kesintisiz çalışma > veri kaybı yok > uzaktan erişim > bakım kolaylığı) mimariyi şekillendirdi: boot zinciri her katmanda otomatik toparlanır (systemd + `unless-stopped` + `wg-quick` enable), erişim 4 kanalla yedeklenir (ikisi WireGuard-bağımsız), update'ler onay zincirinden geçmeden sahaya inmez. Hiçbir katman ücretli lisansa dayanmaz.
@ -61,8 +63,8 @@ LİSANS: Bileşen lisansları için 24-DECISION-LOG Ek A'ya bakılır.
## 9. Uygulama Planı ## 9. Uygulama Planı
1. Merkez VDS: WireGuard hub → hbbs/hbbr → MeshCentral → Semaphore → Prometheus/Grafana/Kuma sırasıyla kurulur. 1. Merkez VDS: WireGuard hub → hbbs/hbbr → MeshCentral → Semaphore → Prometheus/Grafana/Kuma sırasıyla kurulur.
2. Saha imajı: Ubuntu Server → GNOME → Docker → WireGuard spoke → RustDesk/MeshCentral agent → Node Exporter. 2. Saha imajı: Field OS V1 = tek USB Field OS ISO (autoinstall motoru + offline APT repo + firstboot; `interactive-sections: [storage]` ile diski operatör onaylar) → GNOME → Docker → WireGuard spoke → RustDesk/MeshCentral agent → Node Exporter; offline tamamlanırsa durum `PROVISIONED_OFFLINE`, yalnız enrollment ve merkezi kanal doğrulamasından sonra `READY` (26–29). Medya boot etmezse upstream ISO + NoCloud seed yolu devreye girer (K-20).
3. İlk 2 LAB cihazı bu mimariye göre uca eklenir, 3 akış (erişim/boot/update) testlenir. 3. İlk 2 LAB cihazı bu mimariye göre uca eklenir; 3 akış (erişim/boot/update) + GNOME vs XFCE A/B ölçümü (K-23) testlenir.
```bash ```bash
# örnek: cihaz kimliği standardı (K-12) # örnek: cihaz kimliği standardı (K-12)
@ -76,6 +78,8 @@ hostnamectl set-hostname bf-12010193
| 3 Mermaid'deki her ok için bağlantı testi | Uçtan uca akış çalışır | LAB(2) | | 3 Mermaid'deki her ok için bağlantı testi | Uçtan uca akış çalışır | LAB(2) |
| VDS down simülasyonu (hbbs kapalı) | SSH-over-WG + MeshCentral ayakta | P1(5) | | VDS down simülasyonu (hbbs kapalı) | SSH-over-WG + MeshCentral ayakta | P1(5) |
| Güç kesintisi simülasyonu | Boot zinciri sırayla toparlanır | LAB(2) | | Güç kesintisi simülasyonu | Boot zinciri sırayla toparlanır | LAB(2) |
| `bf-gui-off` sonrası erişim | xRDP her zaman hazır, uzaktan grafik kurtarma çalışır (K-22) | LAB(2) |
| Tek-USB Field OS ISO ile kurulum | Assisted kurulum, operatör disk onayı, `PROVISIONED_OFFLINE` (K-20) | LAB(2) UEFI+Legacy |
## 11. Rollback ## 11. Rollback
@ -84,13 +88,15 @@ Mimari değişikliği 24-DECISION-LOG PR'ı ile yapılır; bu dosyanın diyagram
## 12. Kontrol Listesi ## 12. Kontrol Listesi
- [ ] 3 Mermaid bloğu render oluyor (Docusaurus + GitHub preview). - [ ] 3 Mermaid bloğu render oluyor (Docusaurus + GitHub preview).
- [ ] Her bileşen adının kararı 24'te var (K-01…K-14). - [ ] Her bileşen adının kararı 24'te var (K-01…K-24).
- [ ] `bf-<no>` / `BF-<no>` adlandırması diyagramlarda tutarlı. - [ ] `bf-<no>` / `BF-<no>` adlandırması diyagramlarda tutarlı.
- [ ] Medya (tek USB Field OS ISO + geri dönüş yolu), RDP'nin her-zaman-hazır olması ve tek yönlü durum modeli diyagramlarla uyumlu (K-20/K-21/K-22).
## 13. Açık Sorular ## 13. Açık Sorular
- [ ] Merkez servislerin tek VDS'e sığıp sığmadığı — LAB yük testi (sahibi: 25-ROADMAP). - [ ] Merkez servislerin tek VDS'e sığıp sığmadığı — LAB yük testi (sahibi: 25-ROADMAP).
- [ ] Prometheus federasyonu gerekip gerekmediği — 700 node ölçümü (sahibi: 14-MONITORING). - [ ] Prometheus federasyonu gerekip gerekmediği — 700 node ölçümü (sahibi: 14-MONITORING).
- [ ] Field OS ISO boot zincirinin (UEFI/Legacy/Secure Boot) LAB kanıtı ve medya tabanı kararı (Desktop flavour) — F2B (sahibi: release yöneticisi) [K-20].
--- ---
@ -116,21 +122,24 @@ flowchart LR
PC -->|Internet doğrudan<br/>VDS genel adresi| MC PC -->|Internet doğrudan<br/>VDS genel adresi| MC
RD -.->|WG çökse bile<br/>ayakta| PC RD -.->|WG çökse bile<br/>ayakta| PC
MC -.->|WG çökse bile<br/>ayakta| PC MC -.->|WG çökse bile<br/>ayakta| PC
GUI["bf-gui-on / bf-gui-off<br/>(yalnız yerel fiziksel GUI, K-22)"] -.->|"xRDP'i KONTROL ETMEZ"| WG
``` ```
## Ek: Mermaid — 2. Power boot zinciri ## Ek: Mermaid — 2. Power boot zinciri
```mermaid ```mermaid
flowchart TB flowchart TB
PWR["Power ON<br/>(BIOS: Restore on AC Power Loss = Power On)"] --> BIOS["BIOS/UEFI"] MEDYA["V1 medya: tek USB Field OS ISO<br/>(autoinstall + offline APT + firstboot)<br/>geri dönüş: upstream ISO + seed"] --> PWR["Power ON<br/>(BIOS: Restore on AC Power Loss = Power On)"]
PWR --> BIOS["BIOS/UEFI"]
BIOS --> UB["Ubuntu Server 26.04.1<br/>multi-user terminal boot"] BIOS --> UB["Ubuntu Server 26.04.1<br/>multi-user terminal boot"]
UB --> NET["Network<br/>(NetworkManager/systemd-networkd)"] UB --> NET["Network<br/>(NetworkManager/systemd-networkd)"]
NET --> WGV["WireGuard<br/>(wg-quick@wg0 enable)"] NET --> WGV["WireGuard<br/>(wg-quick@wg0 enable)"]
WGV --> DKR["Docker daemon<br/>(log rotasyonlu)"] WGV --> DKR["Docker daemon<br/>(log rotasyonlu)"]
DKR --> MEG["MEG konteyneri<br/>(unless-stopped)"] DKR --> MEG["MEG konteyneri<br/>(unless-stopped)"]
MEG --> BF["Blueforce servisleri"] MEG --> BF["Blueforce servisleri"]
BF --> REM["Remote: SSH + xRDP +<br/>RustDesk + MeshCentral agent"] BF --> REM["Remote: SSH + xRDP (her zaman hazır, K-22) +<br/>RustDesk + MeshCentral agent"]
REM --> MON["Monitoring:<br/>Node Exporter + Uptime Push"] REM --> MON["Monitoring:<br/>Node Exporter + Uptime Push"]
UB -.->|"ileri yönlü durum modeli (K-21)"| ST["PROVISIONED_OFFLINE → ENROLLED → READY"]
``` ```
## Ek: Mermaid — 3. Update onay akışı ## Ek: Mermaid — 3. Update onay akışı

View file

@ -1,9 +1,9 @@
# 03 — Ubuntu Baseline # 03 — Ubuntu Baseline
> Kısa özet: Saha PC'lerinin standart zemini: Ubuntu Server 26.04.1+ LTS minimal kurulum, GNOME sonradan eklenir, terminale boot edilir, `unattended-upgrades` kapalıdır. BIOS güç-kurtarma ayarı ve GUI aç/kapa anahtarları bu dosyadadır. > Kısa özet: Saha PC'lerinin standart zemini: Ubuntu Server 26.04.1+ LTS minimal kurulum, GNOME masaüstü (saha standardı; XFCE yalnız LAB A/B ölçüm kolu, K-23), terminale boot edilir, `unattended-upgrades` kapalıdır. BIOS güç-kurtarma ayarı, GUI aç/kapa anahtarları ve RDP'nin her-zaman-hazır ayrımı (K-22) bu dosyadadır.
- Dosya: `docs/03-UBUNTU-BASELINE.md` - Dosya: `docs/03-UBUNTU-BASELINE.md`
- İlgili kararlar: `24-DECISION-LOG.md#K-01` (OS/sürüm), `K-02` (GNOME), `K-03` (boot + bf-gui), `K-11` (otomatik update kapalı) - İlgili kararlar: `24-DECISION-LOG.md#K-01` (OS/sürüm), `K-02` (GNOME saha standardı), `K-03` (boot + bf-gui), `K-11` (otomatik update kapalı), `K-22` (RDP her zaman hazır), `K-23` (GNOME vs XFCE LAB A/B ölçümü)
- Durum: [ ] Taslak - Durum: [ ] Taslak
--- ---
@ -29,23 +29,27 @@ LİSANS: Ubuntu LTS ücretsiz (ESM kapsamına bel bağlanmaz) — https://rele
``` ```
```text ```text
KARAR: Saha masaüstü ortamı GNOME'dur (Ubuntu Desktop standardı); XFCE elendi. KARAR: Saha masaüstü ortamı GNOME'dur (Ubuntu Desktop standardı) ve bu karar yürürlüktedir. XFCE saha standardı DEĞİLDİR: elenmiş sayılmaz, K-23 uyarınca LAB'da GNOME vs XFCE A/B ölçümünde karşılaştırma koludur ve karar ölçüm verisiyle revize edilebilir.
GEREKÇE: Kullanıcı kararı + Ubuntu Desktop standardı: tanıdık modern masaüstü, teknisyen akışıyla birebir uyum. GEREKÇE: Kullanıcı kararı + Ubuntu Desktop standardı: tanıdık modern masaüstü, teknisyen akışıyla birebir uyum. RDP oturumu + headless RustDesk + düşük RAM'li mini PC koşulları ölçülmeden "tek doğru masaüstü" iddiası kanıtsız kalır; bu yüzden GNOME baseline olarak sabit tutulur, XFCE yalnız LAB ölçüm koluna alınır (ölçüm seti: RAM, CPU, boot süresi, RDP güvenilirliği, RustDesk reboot sonrası davranış, login screen erişimi, dummy display, 24/72 saat stabilite). Ölçüm seti ve 24/72 saat süreleri sabittir; XFCE kolu saha medyasına girmez. RustDesk headless ZORUNLU şartı her iki kolda da aynen korunur.
ALTERNATİF: XFCE — hafif ve düşük kaynak tüketimine rağmen kullanıcı kararıyla elendi; yedekte tutulmaz. ALTERNATİF: XFCE — hafif ve düşük kaynak tüketimine rağmen saha standardı değildir (K-02); K-23 uyarınca LAB yedek/karşılaştırma koludur, revizyon yalnız ölçüm verisiyle yapılır. XFCE'yi doğrudan saha standardı yapmak kullanıcı kararı dışı ve kanıtsız olduğu için reddedildi; XFCE'yi tamamen kapatmak da ölçüm şansını yok ettiği için reddedildi.
RİSK: xRDP+GNOME kombinasyonu ek ayar isteyebilir (uyarı: GNOME oturumu RDP'de ek yapılandırma gerektirebilir); azaltma: 13-MEG-LINUX-ACCEPTANCE testi GNOME üzerinde koşar, `bf-gui-*` LAB'da 26.04.1 imajında doğrulanmadan dondurulmaz. RİSK: xRDP+GNOME kombinasyonu ek ayar isteyebilir (uyarı: GNOME oturumu RDP'de ek yapılandırma gerektirebilir); azaltma: 13-MEG-LINUX-ACCEPTANCE testi GNOME üzerinde koşar, `bf-gui-*` LAB'da 26.04.1 imajında doğrulanmadan dondurulmaz. A/B ölçümü ek LAB zamanı ve iki masaüstü bakım yükü getirir; azaltma: ölçüm seti/süreleri sabit ve sonuç tek tabloda LAB release kaydına bağlıdır.
MALİYET: Ücretsiz. MALİYET: Ücretsiz.
LİSANS: GNOME (GPL/LGPL bileşenler), ücretsiz — https://www.gnome.org/ (doğrulanma: 2026-09-15). LİSANS: GNOME (GPL/LGPL bileşenler), ücretsiz — https://www.gnome.org/ (doğrulanma: 2026-09-15; yeniden doğrulama: 2026-09-16, HTTP 200); XFCE GPL — https://xfce.org/ (doğrulama: 2026-09-16, HTTP 200; yalnız LAB A/B ölçüm adayıdır, saha standardı değildir).
``` ```
> **Güncelleme (2026-09-16, K-23):** Yukarıdaki "XFCE elendi / yedekte tutulmaz" ifadesi geçersizdir. Doğru ifade: **GNOME seçildi ve saha standardı olarak kalır; XFCE LAB A/B ölçümünde karşılaştırılır.** Ölçüm verisi gelmeden revizyon yapılmaz; revizyon 24-DECISION-LOG PR'ı ile işlenir. Ayrıntı: `24-DECISION-LOG.md#K-23`.
```text ```text
KARAR: Cihazlar terminale (multi-user) boot eder; grafik katman `bf-gui-on` / `bf-gui-off` ile systemd üzerinden açılıp kapatılır. KARAR: Cihazlar terminale (multi-user) boot eder. `xrdp` + `xrdp-sesman` multi-user seviyesinde HER ZAMAN enable ve active kalır (RDP kanalı `bf-gui-on` beklemez); `bf-gui-on` / `bf-gui-off` yalnız YEREL FİZİKSEL grafik katmanını (display-manager: gdm3/gdm/sddm/lightdm + default target) systemd üzerinden açar/kapatır. GUI kapalı ≠ RDP kapalı.
GEREKÇE: xRDP mimarisi oturum yöneticisi (sesman) + systemd servis birimleri (`xrdp`, `xrdp-sesman`) üzerine kuruludur; GUI, display-manager + xRDP servisleri üzerinden yönetilebilir bir katman olarak modellenir. Varsayılan kapalı GUI = düşük RAM, düşük saldırı yüzeyi, öngörülebilir boot. GEREKÇE: xRDP mimarisi oturum yöneticisi (sesman) + systemd servis birimleri (`xrdp`, `xrdp-sesman`) üzerine kuruludur. xRDP'nin servis seviyesinde sürekli hazır olması, yerel GUI kapalı saha cihazında uzaktan grafik kurtarma kanalını korur (sahaya gitme oranını düşürür); `bf-gui-*` yalnız gereksiz yerel grafik katmanını yönetir. Varsayılan kapalı yerel GUI = düşük RAM, düşük saldırı yüzeyi, öngörülebilir boot.
ALTERNATİF: Her zaman grafik boot — kaynak israfı ve 700 cihazda gereksiz hata yüzeyi nedeniyle elendi. ALTERNATİF: RDP'yi `bf-gui-on`'a bağlamak — GUI kapalıyken uzaktan grafik kurtarma kaybolur → reddedildi; her zaman grafik boot — kaynak israfı ve 700 cihazda gereksiz hata yüzeyi nedeniyle elendi.
RİSK: Kesin birim adları ve GNOME oturum başlatma satırı 26.04.1'e göre değişebilir; azaltma: LAB'da 26.04.1 imajında doğrulanmadan `bf-gui-*` dondurulmaz. RİSK: Kesin birim adları ve GNOME oturum başlatma satırı 26.04.1'e göre değişebilir; azaltma: LAB'da 26.04.1 imajında doğrulanmadan `bf-gui-*` dondurulmaz. `bf-gui-off` içinde xRDP'i durduran bir satır bulunursa bu sözleşme ihlalidir (`tests/check-specs.sh` bunu zorunlu tutar).
MALİYET: Ücretsiz. MALİYET: Ücretsiz.
LİSANS: xRDP Apache-2.0, ücretsiz — https://github.com/neutrinolabs/xrdp (v0.10.6.1, 2026-07-07; doğrulanma: 2026-09-15). LİSANS: xRDP Apache-2.0, ücretsiz — https://github.com/neutrinolabs/xrdp (v0.10.6.1, 2026-07-07; doğrulanma: 2026-09-15; yeniden doğrulama: 2026-09-16, HTTP 200).
``` ```
> **Güncelleme (2026-09-16, K-22):** Bu bloktaki "GUI, display-manager + xRDP servisleri üzerinden yönetilir" ifadesi K-22 ile kesinleştirilmiştir: **`xrdp`/`xrdp-sesman` her zaman enable+active; `bf-gui-on`/`bf-gui-off` yalnız yerel fiziksel GUI'yi (display-manager + default target) yönetir; `bf-gui-off` xRDP servislerini asla durdurmaz veya disable etmez.** Ayrıntı ve kod kanıtı: `24-DECISION-LOG.md#K-22`.
```text ```text
KARAR: Golden image'da `unattended-upgrades` kapatılır (`/etc/apt/apt.conf.d/20auto-upgrades` değerleri `0` + `apt-daily*.timer` maskeleme). KARAR: Golden image'da `unattended-upgrades` kapatılır (`/etc/apt/apt.conf.d/20auto-upgrades` değerleri `0` + `apt-daily*.timer` maskeleme).
GEREKÇE: `unattended-upgrades` Ubuntu Server'da varsayılan kurulu ve etkindir, günde bir kez çalışır; kapatılmazsa 700 cihaz ilk açılışta güncellemeye kalkar ("onaysız update yasak" ilkesi delinir). GEREKÇE: `unattended-upgrades` Ubuntu Server'da varsayılan kurulu ve etkindir, günde bir kez çalışır; kapatılmazsa 700 cihaz ilk açılışta güncellemeye kalkar ("onaysız update yasak" ilkesi delinir).
@ -57,7 +61,7 @@ LİSANS: Yok (OS yapılandırması) — https://ubuntu.com/server/docs/how-to/
## 4. Neden Bu Karar? ## 4. Neden Bu Karar?
Server minimal kurulum + sonradan GNOME (Ubuntu Desktop standardı, kullanıcı kararı). Terminale boot, 700 cihazın varsayılan durumunu en öngörülebilir hale getirir; grafik yalnızca bakım penceresinde açılır. Otomatik update'in kapatılması, update zincirinin (10-UPDATE) "onay olmadan değişim yok" ilkesinin teknik kilididir. Server minimal kurulum + sonradan GNOME (Ubuntu Desktop standardı, kullanıcı kararı; XFCE saha standardı değil, yalnız K-23 LAB A/B ölçüm kolu). Terminale boot, 700 cihazın varsayılan durumunu en öngörülebilir hale getirir; yerel grafik yalnızca bakım penceresinde açılır — uzaktan grafik erişim (xRDP) ise her zaman hazırdır (K-22), yani "yerel GUI kapalı" bir erişim kaybı değildir. Otomatik update'in kapatılması, update zincirinin (10-UPDATE) "onay olmadan değişim yok" ilkesinin teknik kilididir.
## 5. Alternatifler ## 5. Alternatifler
@ -65,8 +69,8 @@ Server minimal kurulum + sonradan GNOME (Ubuntu Desktop standardı, kullanıcı
|---|---|---|---| |---|---|---|---|
| Ubuntu 24.04 LTS | Olgun donanım desteği | Daha kısa destek penceresi | Yedek | | Ubuntu 24.04 LTS | Olgun donanım desteği | Daha kısa destek penceresi | Yedek |
| Ubuntu Desktop 26.04 | Tek adımda GUI | 6 GB RAM şartı, şişkin zemin | Elendi | | Ubuntu Desktop 26.04 | Tek adımda GUI | 6 GB RAM şartı, şişkin zemin | Elendi |
| XFCE | Hafif, düşük kaynak | Kullanıcı kararıyla elendi (saha standardı GNOME) | Elendi | | XFCE | Hafif, düşük kaynak | Saha standardı değil (GNOME baseline, K-02); yalnız ölçüm kolu | LAB A/B ölçüm adayı (K-23) |
| Her zaman grafik boot | Teknisyen rahatlığı | Kaynak israfı, geniş hata yüzeyi | Elendi | | Yerel GUI her zaman açık | Teknisyen rahatlığı | Kaynak israfı, geniş hata yüzeyi (RDP'yi etkilemez) | Elendi |
## 6. Avantajlar ## 6. Avantajlar
@ -77,7 +81,7 @@ Server minimal kurulum + sonradan GNOME (Ubuntu Desktop standardı, kullanıcı
## 7. Dezavantajlar ## 7. Dezavantajlar
- GNOME oturum başlatma satırı ve display-manager birim adları 26.04.1'e göre LAB'da doğrulanmadan dondurulamaz. - GNOME oturum başlatma satırı ve display-manager birim adları 26.04.1'e göre LAB'da doğrulanmadan dondurulamaz.
- Teknisyen grafik istediğinde ek komut (`bf-gui-on`) gerekir — runbook'a işlenir. - Teknisyen **yerel fiziksel** ekran istediğinde ek komut (`bf-gui-on`) gerekir — runbook'a işlenir; uzaktan grafik (RDP) için bu komut gerekmez, xRDP her zaman hazırdır (K-22).
## 8. Riskler ## 8. Riskler
@ -86,12 +90,14 @@ Server minimal kurulum + sonradan GNOME (Ubuntu Desktop standardı, kullanıcı
| 26.04.x sürücü farkı heterojen donanımda | Orta | Orta | LAB profil testi (04) | | 26.04.x sürücü farkı heterojen donanımda | Orta | Orta | LAB profil testi (04) |
| `bf-gui-*` birim adları 26.04.1'de farklı | Orta | Düşük | LAB doğrulaması, §13 açık soru | | `bf-gui-*` birim adları 26.04.1'de farklı | Orta | Düşük | LAB doğrulaması, §13 açık soru |
| `unattended-upgrades` açık unutulur | Orta | Yüksek | final-check + monitoring metriği | | `unattended-upgrades` açık unutulur | Orta | Yüksek | final-check + monitoring metriği |
| `bf-gui-off` yanlış yazılıp xRDP'i durdurur (uzaktan kurtarma kaybı) | Düşük | Yüksek | `tests/check-specs.sh` `systemctl stop/disable xrdp*` görürse testi düşürür; 08-rdp check'i active xrdp ister (K-22) |
| GNOME/XFCE kararı kanıtsız revize edilir | Düşük | Orta | A/B ölçüm seti sabit; revizyon yalnız LAB verisiyle ve PR ile (K-23) |
## 9. Uygulama Planı ## 9. Uygulama Planı
1. BIOS ayarı (her saha PC'sinde, elektrik kesintisi sonrası otomatik açılış için): 1. BIOS ayarı (her saha PC'sinde, elektrik kesintisi sonrası otomatik açılış için):
- `Restore on AC Power Loss = Power On` (üretici menüsünde `After Power Loss: Power On` / `AC Recovery: Power On` adlarıyla da geçer; teknisyen imaj öncesi menüden doğrular). - `Restore on AC Power Loss = Power On` (üretici menüsünde `After Power Loss: Power On` / `AC Recovery: Power On` adlarıyla da geçer; teknisyen imaj öncesi menüden doğrular).
2. Ubuntu Server 26.04.1+ minimal kurulum (GUI seçilmez). 2. V1 medya: TEK USB **Blueforce Field OS ISO** (`dist/Blueforce-Field-OS-<version>-amd64.iso`); ISO içinde autoinstall (kurulum motoru) + offline APT repo + firstboot bulunur ve kimliksiz zemin kurulur (`unattended-upgrades` kapalıdır). `interactive-sections: [storage]` korunur: kurulum katılımsız ilerler ama diski operatör seçer/onaylar, otomatik disk silme YOKTUR. Geri dönüş yolu her zaman sıcaktır: resmi upstream ISO + NoCloud seed USB (K-20). Medya üretimi ve doğrulaması 26'da, boot kabulü LAB'dadır.
3. Boot hedefi terminale sabitlenir: 3. Boot hedefi terminale sabitlenir:
```bash ```bash
@ -108,11 +114,11 @@ sudo systemctl enable xrdp xrdp-sesman
echo "gnome-session" > ~/.xsession echo "gnome-session" > ~/.xsession
``` ```
5. GUI aç/kapa anahtarları (kurucu tarafından `/usr/local/bin/` altına yazılır): 5. GUI aç/kapa anahtarları (kurucu tarafından `/usr/local/bin/` altına yazılır; **yalnız yerel fiziksel GUI**, K-22):
```bash ```bash
sudo bf-gui-on # display-manager + xrdp/xrdp-sesman başlatılır, RDP oturumu açılır sudo bf-gui-on # yalnız display-manager (yerel fiziksel oturum) başlatılır; RDP zaten hazırdır
sudo bf-gui-off # grafik katman durdurulur, cihaz terminale döner sudo bf-gui-off # yerel grafik katman durdurulur, cihaz terminale döner; xrdp/xrdp-sesman ACTIVE KALIR
``` ```
6. Otomatik update kapatılır: 6. Otomatik update kapatılır:
@ -124,7 +130,8 @@ sudo bf-gui-off # grafik katman durdurulur, cihaz terminale döner
sudo systemctl mask apt-daily.timer apt-daily-upgrade.timer sudo systemctl mask apt-daily.timer apt-daily-upgrade.timer
``` ```
7. Açılışta koşması beklenen servisler (final-check bunları denetler): `NetworkManager` (veya `systemd-networkd`), `wg-quick@wg0`, `docker`, `xrdp`+`xrdp-sesman` (yalnızca GUI-açık modda), `node_exporter`, RustDesk/MeshCentral agent, `bf-health-push.timer`. 7. Açılışta koşması beklenen servisler (final-check bunları denetler): `NetworkManager` (veya `systemd-networkd`), `wg-quick@wg0`, `docker`, `xrdp`+`xrdp-sesman` (**her zaman enable/ready; yerel GNOME display-manager durumu bundan bağımsız**), `node_exporter`, RustDesk/MeshCentral agent, `bf-health-push.timer`. Ubuntu 26.04.1 GNOME+xRDP kesin oturum davranışı LAB ile doğrulanmalıdır.
8. GNOME vs XFCE A/B ölçümü (LAB, K-23): aynı donanımda iki kol kurulur ve sabit ölçüm seti doldurulur — RAM, CPU, boot süresi, RDP güvenilirliği, RustDesk reboot sonrası davranışı, login screen erişimi, dummy display davranışı, 24 saat + 72 saat stabilite. RustDesk headless zorunlu şartı iki kolda da aranır; sonuç tablosu LAB release kaydına bağlanır. XFCE kolu saha medyasına girmez; revizyon yalnız bu veriyle yapılır.
## 10. Test Planı ## 10. Test Planı
@ -132,8 +139,11 @@ sudo systemctl mask apt-daily.timer apt-daily-upgrade.timer
|---|---|---| |---|---|---|
| `get-default` | `multi-user.target` | LAB(2) | | `get-default` | `multi-user.target` | LAB(2) |
| Güç kesintisi simülasyonu | BIOS ayarı ile cihaz kendiliğinden açılır | LAB(2) | | Güç kesintisi simülasyonu | BIOS ayarı ile cihaz kendiliğinden açılır | LAB(2) |
| `bf-gui-on/off` sonrası RDP | GNOME oturumu açılır/kapanır | LAB(2) | | `bf-gui-on/off` sonrası yerel grafik | Yerel oturum açılır/kapanır (display-manager) | LAB(2) |
| `bf-gui-off` sonrası xRDP | `xrdp`/`xrdp-sesman` **active kalır**, RDP oturumu kurulur (K-22) | LAB(2) |
| `20auto-upgrades` değerleri `0`, timer'lar maskeli | Otomatik apt çalışmaz | LAB(2) imaj denetimi | | `20auto-upgrades` değerleri `0`, timer'lar maskeli | Otomatik apt çalışmaz | LAB(2) imaj denetimi |
| GNOME vs XFCE A/B ölçümü (8 metrik, 24/72 saat) | Tablo dolar; karar LAB verisiyle verilir (K-23) | LAB(2) |
| Tek-USB Field OS ISO ile kurulum (03 zemini) | Kimliksiz zemin kurulur, storage operatör onayı beklenir (K-20) | LAB(2) UEFI+Legacy |
## 11. Rollback ## 11. Rollback
@ -146,13 +156,16 @@ sudo systemctl mask apt-daily.timer apt-daily-upgrade.timer
- [ ] ISO sürümü 26.04.1+ (LAB'da `lsb_release -a` çıktısı kayıtlı). - [ ] ISO sürümü 26.04.1+ (LAB'da `lsb_release -a` çıktısı kayıtlı).
- [ ] BIOS `Restore on AC Power Loss = Power On` her profilde doğrulandı. - [ ] BIOS `Restore on AC Power Loss = Power On` her profilde doğrulandı.
- [ ] `get-default` = `multi-user.target`. - [ ] `get-default` = `multi-user.target`.
- [ ] `bf-gui-on/off` LAB 26.04.1 imajında test edildi. - [ ] `bf-gui-on/off` LAB 26.04.1 imajında test edildi; `bf-gui-off` sonrası xRDP hâlâ active (K-22).
- [ ] `unattended-upgrades` kapalı + timer'lar maskeli. - [ ] `unattended-upgrades` kapalı + timer'lar maskeli.
- [ ] GNOME vs XFCE A/B ölçüm tablosu (8 metrik) doldurulmuş; XFCE saha medyasına girmemiş (K-23).
## 13. Açık Sorular ## 13. Açık Sorular
- [ ] GNOME oturum başlatma satırı + `bf-gui-*` kesin birim listesi — LAB 26.04.1 imajında (sahibi: Faz 3). - [ ] GNOME oturum başlatma satırı + `bf-gui-*` kesin birim listesi — LAB 26.04.1 imajında (sahibi: Faz 3).
- [ ] `NetworkManager` vs `systemd-networkd` seçimi — netplan profiliyle birlikte LAB'da netleşecek (sahibi: Faz 3, 08 yazarı). - [ ] `NetworkManager` vs `systemd-networkd` seçimi — netplan profiliyle birlikte LAB'da netleşecek (sahibi: Faz 3, 08 yazarı).
- [ ] GNOME vs XFCE A/B ölçüm seti ve sonuç tablosu (8 metrik, 24/72 saat) — LAB 2 cihaz (sahibi: platform ekibi + bu dosyanın yazarı) [K-23].
- [ ] Medya tabanı sapması: LAB ISO'su Desktop flavour ile üretildi; işletim öncesi Server ISO ile yeniden build ya da Desktop tabanının kabulü — LAB/F2B (sahibi: release yöneticisi) [K-20].
--- ---
@ -160,8 +173,12 @@ sudo systemctl mask apt-daily.timer apt-daily-upgrade.timer
```mermaid ```mermaid
flowchart TB flowchart TB
ISO["Ubuntu Server 26.04.1+ ISO<br/>(GUI'siz minimal)"] --> BOOT["Boot hedefi: multi-user.target<br/>(terminale boot)"] ISO["V1 medya: tek USB Field OS ISO<br/>(autoinstall.yaml + offline APT + firstboot)<br/>geri dönüş: upstream ISO + NoCloud seed"] --> BOOT["Boot hedefi: multi-user.target<br/>(terminale boot; yerel GUI kapalı varsayılan)"]
BOOT --> GUI["GUI katmanı (kapalı varsayılan)<br/>GNOME + xRDP + bf-gui-on/off"] BOOT --> GUI["Yerel GUI katmanı (kapalı varsayılan)<br/>GNOME + bf-gui-on/off"]
GUI --> KILIT["Update kilidi<br/>unattended-upgrades kapalı + timer maskeli"] BOOT --> RDP["xRDP servisi HER ZAMAN hazır<br/>xrdp + xrdp-sesman enable+active (K-22)"]
GUI -.->|"bf-gui-off xRDP'i DURDURMAZ"| RDP
RDP --> KILIT["Update kilidi<br/>unattended-upgrades kapalı + timer maskeli"]
GUI --> KILIT
KILIT --> SERVIS["Açılış servisleri<br/>wg-quick + docker + node_exporter + agent'lar"] KILIT --> SERVIS["Açılış servisleri<br/>wg-quick + docker + node_exporter + agent'lar"]
SERVIS --> AB["LAB A/B: GNOME vs XFCE<br/>8 metrik + 24/72 saat (K-23)"]
``` ```

View file

@ -1,9 +1,9 @@
# 04 — Golden Image ve Provisioning # 04 — Golden Image ve Provisioning
> Kısa özet: Saha imajının nasıl üretildiği ve cihaza nasıl basıldığı: autoinstall-tabanlı Ubuntu zemini + ilk açılışta kimlik enjeksiyonu. Hedef akış: USB → Ubuntu → tek script → bayi no → READY. > Kısa özet: Saha imajının nasıl üretildiği ve cihaza nasıl basıldığı: autoinstall **kurulum motorudur**, Blueforce Field OS ISO ise **paketleme** yöntemidir — ikisi rakip değil, birlikte kullanılır (K-20). V1 medyası tek USB Field OS ISO'dur (autoinstall.yaml + offline APT + firstboot medya kökünde); upstream ISO + NoCloud seed USB geri dönüş yoludur. `interactive-sections: [storage]` korunur, otomatik disk silme yoktur. Offline tamamlanma durumu `PROVISIONED_OFFLINE`'dır; `READY` merkezi enrollment ve bağımsız kanıt sonrasındadır.
- Dosya: `docs/04-GOLDEN-IMAGE-AND-PROVISIONING.md` - Dosya: `docs/04-GOLDEN-IMAGE-AND-PROVISIONING.md`
- İlgili kararlar: `24-DECISION-LOG.md#K-09` (golden image yöntemi), `K-01` (OS), `K-11` (update kapalı), `K-12` (kimlik imajda değil, ilk boot'ta) - İlgili kararlar: `24-DECISION-LOG.md#K-09` (golden image yöntemi), `K-01` (OS), `K-11` (update kapalı), `K-12` (kimlik imajda değil, ilk boot'ta), `K-20` (medya stratejisi), `K-21` (durum modeli)
- Durum: [ ] Taslak - Durum: [ ] Taslak
--- ---
@ -20,29 +20,33 @@ Heterojen 700 saha PC'sinde aynı zemini tekrarlanabilir şekilde kurmak: imajı
## 3. Kararlar ## 3. Kararlar
```text ```text
KARAR: Golden image = Ubuntu Server 26.04.1+ minimal + `unattended-upgrades` kapalı + `blueforce-install.sh` tek kurucu (bayi no → READY); imaj, preseed/autoinstall dosyasıyla üretilir, cihaz-spesifik kimlik (`bf-<no>`) imajda DEĞİL ilk açılışta verilir. KARAR: Golden image = kimliksiz Blueforce Field OS zemini: Ubuntu Server 26.04.1+ minimal + `unattended-upgrades` kapalı + `blueforce-install.sh`. Autoinstall kurulum MOTORUDUR ve Blueforce Field OS ISO PAKETLEME/DAĞITIM yöntemidir; ikisi rakip değildir, birlikte kullanılır. V1 medyası TEK USB `Blueforce-Field-OS-<version>-amd64.iso`'dur: ISO kökünde `autoinstall.yaml` + offline APT repo + firstboot bulunur. Güvenlik için autoinstall'da `interactive-sections: [storage]` KORUNUR — otomatik disk silme YOKTUR, diski operatör seçip onaylar. Resmi upstream ISO + ayrı NoCloud seed USB, V1'in tek yolu değil geri dönüş/fallback yoludur ve her zaman sıcak tutulur. Cihaz-spesifik kimlik (`bf-<no>`), token veya secret imajda DEĞİL, firstboot/enrollment aşamasında verilir.
GEREKÇE: Donanım heterojenliği bilinmediği için bit-kopya klon (Clonezilla) tek imajda kırılgandır; autoinstall + idempotent kurulum scripti her donanım profilinde aynı zemini kurar, kimlik ilk boot'ta enjekte edilir. Hedef akış: USB → Ubuntu → tek script → bayi no → READY. GEREKÇE: Donanım heterojenliği bilinmediği için bit-kopya klon (Clonezilla) tek imajda kırılgandır; autoinstall + idempotent kurulum scripti her donanım profilinde aynı zemini kurar, kimlik ilk boot'ta enjekte edilir. Subiquity resmi referansı autoinstall yapılandırmasını `/autoinstall.yaml` yolundan okur ("irrespective of how it was provided"), yani ISO köküne konan dosya desteklenir; `interactive-sections` resmi olarak desteklenir ve `apt.fallback` varsayılanı `offline-install`'dır. Böylece tek-USB deneyimi ile "otomatik disk silme yok" şartı aynı anda sağlanır: ISO autoinstall'ı başlatır, teknisyen yalnız disk adımını onaylar. Hedef akış: USB → Ubuntu → tek script → bayi no → READY (READY yalnız merkezi kanıtla).
ALTERNATİF: Clonezilla bit-kopya (hızlı ama donanım-fragil → yedek yöntem olarak tutulur); Packer/custom ISO (ek derleme hattı yükü → elendi); cloud-init (autoinstall'a göre saha-USB akışına daha az uygun → elendi). ALTERNATİF: Clonezilla bit-kopya (hızlı ama donanım-fragil → yedek yöntem olarak tutulur); Packer + QEMU derleme hattı (ek altyapı, öğrenme eğrisi, 700 PC için aşırı → elendi); cloud-init/NoCloud (autoinstall'ın tamamlayıcısıdır, ayrı seed USB ile geri dönüş yoludur — elenmedi); yalnız upstream ISO + seed (tek-medya deneyimi yok → V1'in tek yolu olmaktan çıktı, geri dönüş yolu olarak kalır); otomatik disk wipe'lı unattended (veri kaybı riski → yasak, F6 kapsamı).
RİSK: İlk imaj heterojen donanımda sürücü eksiltebilir; azaltma: önce `bf-hardware-inventory.sh` taraması (18-ROLLOUT), LAB'da her profilde test. RİSK: İlk imaj heterojen donanımda sürücü eksiltebilir; azaltma: önce `bf-hardware-inventory.sh` taraması (18-ROLLOUT), LAB'da her profilde test. Remaster boot zinciri (UEFI/Legacy/Secure Boot) kanıtlanmazsa saha USB'si açılmayabilir; azaltma: F2B çıkış kriteri = tek-USB assisted kurulumun iki LAB cihazında kanıtı.
MALİYET: Ücretsiz. MALİYET: Ücretsiz.
LİSANS: Ubuntu autoinstall/subiquity (ücretsiz, Ubuntu lisans seti içinde) — https://releases.ubuntu.com/26.04/ (doğrulanma: 2026-09-15). NOT: resmi autoinstall sözdizimi LAB çıktısıyla kesinleşir. LİSANS: Ubuntu autoinstall/subiquity — https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-reference.html (interactive-sections, `/autoinstall.yaml`, `apt.fallback`; doğrulama: 2026-09-16, HTTP 200) + https://releases.ubuntu.com/26.04/ (doğrulanma: 2026-09-15). NOT: 26.04.1'e özgü kesin direktifler LAB çıktısıyla kesinleşir.
``` ```
> **Güncelleme (2026-09-16, K-20/K-21):** Bu bölümdeki "V1'de resmi upstream ISO + ayrı NoCloud seed USB kullanılır" ve "custom Field ISO ertelendi" ifadeleri geçersizdir. Kanonik karar: **autoinstall motor, Field OS ISO paketleme; V1 medyası tek USB Field OS ISO, upstream ISO + seed geri dönüş yolu.** Ayrıntı: `24-DECISION-LOG.md#K-20`; gerçek üretilmiş artefakt ve doğrulama kaydı: `docs/26-BLUEFORCE-FIELD-OS-ISO.md`.
## 4. Neden Bu Karar? ## 4. Neden Bu Karar?
Bit-kopya klon, kaynak makinenin sürücü setini dondurur; farklı anakart/ağ kartı olan sahada açılmayan cihaz üretir. Autoinstall her makinede donanıma göre kurulum yapar, ardından aynı kurucu script aynı zemini kurar — sonuç donanımdan bağımsız deterministik zemin + ilk boot'ta enjekte edilen benzersiz kimliktir. Custom ISO/Packer ek derleme hattı (imzalı ISO, build sunucusu, sürüm takibi) getirir; sadelik kuralını bozar. Bit-kopya klon, kaynak makinenin sürücü setini dondurur; farklı anakart/ağ kartı olan sahada açılmayan cihaz üretir. Autoinstall her makinede donanıma göre kurulum yapar, ardından aynı kurucu script aynı zemini kurar — sonuç donanımdan bağımsız deterministik zemin + ilk boot'ta enjekte edilen benzersiz kimliktir. **Custom ISO ile autoinstall rakip değildir:** ISO, aynı autoinstall motorunu + offline APT repo'yu + firstboot'u tek medyada paketler; bu yüzden "custom ISO'yu eledik" demek yanlıştır (K-20). Elenen şey ayrı bir Packer/QEMU derleme hattı kurmaktır; repo içindeki `provisioning/iso/build-blueforce-iso.sh` bu işi mevcut xorriso/zorunlu araçlarla yapar ve boot zincirini LAB kabulüne bağlar.
## 5. Alternatifler ## 5. Alternatifler
| Yöntem | Artı | Eksi | Sonuç | | Yöntem | Artı | Eksi | Sonuç |
|---|---|---|---| |---|---|---|---|
| Ubuntu autoinstall (user-data) | Resmi yol, donanım-bağımsız, USB'den katılımsız | Sözdizimi LAB'da doğrulanmalı | **Seçildi** | | Ubuntu autoinstall (user-data) | Resmi yol, donanım-bağımsız, USB'den katılımsız | Sözdizimi LAB'da doğrulanmalı | **Seçildi (kurulum motoru)** |
| Clonezilla bit-kopya | Çok hızlı klon, basit | Donanım-fragil, kimlik çakışması riski | Yedek yöntem | | Clonezilla bit-kopya | Çok hızlı klon, basit | Donanım-fragil, kimlik çakışması riski | Yedek yöntem |
| cloud-init (NoCloud USB) | Esnek ilk-boot yapılandırma | Saha-USB akışına autoinstall kadar uygun değil, ek katman | Elendi | | cloud-init / NoCloud seed USB | Autoinstall'i tamamlar, seed değişimi kolay | 26.04 söz dizimi/medya LABEL davranışı LAB'da doğrulanmalı | Geri dönüş yolu (tek medya değil) |
| Custom ISO (cubic/live-build) | Her şey gömülü tek ISO | Derleme hattı + imza + sürüm yükü | Elendi | | Tek USB Field OS ISO (remaster) | Tek medya, offline APT + firstboot içinde | UEFI/Legacy/Secure Boot LAB kabulü gerekir | **V1 medyası (F2B)** |
| Packer + QEMU build | Tekrarlanabilir build | Ek altyapı, öğrenme eğrisi, 700 PC için aşırı | Elendi | | Packer + QEMU build | Tekrarlanabilir build | Ek altyapı, öğrenme eğrisi, 700 PC için aşırı | Elendi |
| Ansible-only (kurulu OS üstüne) | İmaja gerek yok | Temel OS kurulumu yine manuel/USB gerektirir | Tamamlayıcı (kurucu sonrası filo) | | Ansible-only (kurulu OS üstüne) | İmaja gerek yok | Temel OS kurulumu yine manuel/USB gerektirir | Tamamlayıcı (kurucu sonrası filo) |
> **Faz eşleşmesi (K-20 / 25-ROADMAP):** Tek-USB Field OS ISO işi **F2B — Assisted Offline Field ISO**'dadır; `F6` yalnız PXE / tam unattended / uzaktan provisioning içindir (otomatik disk yapılandırması orada, onay kapısıyla). Bu dosyadaki eski "LAB artefact / F6" ifadesi F2B olarak düzeltilmiştir.
## 6. Avantajlar ## 6. Avantajlar
- İmaj kimliksizdir: tek USB tüm sahada kullanılır, yanlış kimlikli cihaz çıkmaz. - İmaj kimliksizdir: tek USB tüm sahada kullanılır, yanlış kimlikli cihaz çıkmaz.
@ -61,21 +65,24 @@ Bit-kopya klon, kaynak makinenin sürücü setini dondurur; farklı anakart/ağ
| Heterojen donanımda sürücü eksikliği | Orta | Orta | Envanter taraması + LAB profil matrisi | | Heterojen donanımda sürücü eksikliği | Orta | Orta | Envanter taraması + LAB profil matrisi |
| Autoinstall sözdizimi sürüm farkı | Orta | Düşük | LAB 26.04.1 çıktısı dondurulur, dosyası repo'da sürümlenir | | Autoinstall sözdizimi sürüm farkı | Orta | Düşük | LAB 26.04.1 çıktısı dondurulur, dosyası repo'da sürümlenir |
| USB medyası bozuk/eskimiş | Düşük | Düşük | Çift USB + SHA256 doğrulama | | USB medyası bozuk/eskimiş | Düşük | Düşük | Çift USB + SHA256 doğrulama |
| Remaster Field OS ISO boot etmez (UEFI/Legacy/Secure Boot) | Orta | Yüksek | F2B çıkış kapısı: tek-USB assisted kurulum iki LAB cihazında kanıtlanır; kanıt yoksa upstream ISO + seed yolu sıcak tutulur (K-20) |
| Medya tabanı sapması (LAB ISO'su Ubuntu Desktop flavour ile üretildi) | Yüksek (mevcut durum) | Orta | Sapma gizlenmez: `dist/1.0.0-manifest.yaml` içinde `base_flavor: desktop` + `baseline_deviation` bloğu; işletim öncesi Server ISO ile yeniden build veya Desktop tabanının kabulü (26 §6) |
## 9. Uygulama Planı ## 9. Uygulama Planı
1. İmaj içeriği (kimliksiz zemin): 1. İmaj içeriği (kimliksiz zemin):
- Ubuntu Server 26.04.1+ minimal (GUI yok). - Ubuntu Server 26.04.1+ minimal (GUI yok). **Not (sapma):** LAB'da üretilen `dist/Blueforce-Field-OS-1.0.0-amd64.iso` base flavour'ı `desktop`'tur (build host'ta checksum'ı doğrulanmış tek kurulum medyası Desktop ISO'ydu); GNOME katmanı Desktop medyasıyla geldiği için ek kurulum adımı gerekmez. Server tabanlı medya `--upstream-iso` ile aynı builder'dan çıkar; üretim öncesi Server ISO ile yeniden build ya da Desktop tabanının kabulü kararı 26 §6 ve §13'tedir.
- `unattended-upgrades` kapalı (03 §9 adım 6). - `unattended-upgrades` kapalı (03 §9 adım 6).
- `blueforce-install.sh` + modüller USB'de `/opt/blueforce-installer/` altında (veya ilk boot'ta merkezi depodan çekilir — LAB'da netleşir). - `blueforce-install.sh` + modüller medyada `/blueforce-provisioning/` altında (ISO'da `firstboot/`, `autoinstall/`, `offline-repo/`, `release/`; kurulu sistemde `/opt/blueforce-installer/`) — LAB'da netleşir.
- Bootstrap SSH anahtarı (ilk Ansible run'ında rotasyon, K-13). - Bootstrap SSH anahtarı (ilk Ansible run'ında rotasyon, K-13).
2. Saha akışı (hedef: teknisyen başına <15 dk aktif iş): 2. Saha akışı (hedef: teknisyen başına &lt;15 dk aktif iş):
```text ```text
USB tak → Ubuntu Server kur (autoinstall, katılımsız) USB (Field OS ISO) tak → autoinstall başlar (Ubuntu kurulumu katılımsız ilerler)
→ ilk açılışta: sudo ./blueforce-install.sh --dealer-id <8hane> → installer ekranında SADECE disk adımı operatör onayı bekler (interactive-sections: [storage]; otomatik disk silme yok)
→ script bitince: bf-status → READY → ilk açılışta: firstboot 8 haneli bayi no sorar → sudo ./blueforce-install.sh --dealer-id <8hane> (--offline)
→ USB çıkar, cihaz sahaya alınır → script bitince: bf-status → PROVISIONED_OFFLINE
→ USB çıkar; merkezi enrollment tamamlanana kadar cihaz READY değildir
``` ```
```bash ```bash
@ -90,16 +97,20 @@ sudo ./blueforce-install.sh --dealer-id 12010193
| Test | Beklenen sonuç | Ortam | | Test | Beklenen sonuç | Ortam |
|---|---|---| |---|---|---|
| Aynı USB ile 2 farklı donanım profili | İkisi de READY | LAB(2) | | Aynı USB (Field OS ISO) ile 2 farklı donanım profili | İkisi de PROVISIONED_OFFLINE | LAB(2) |
| İmajda kimlik artığı taraması (`bf-*`, peer key) | Hiçbir cihaz-spesifik değer yok | LAB(2) | | Tek-USB assisted kurulumda disk adımı | Operatör onayı beklenir, disk kendiliğinden silinmez (K-20) | LAB(2) UEFI+Legacy |
| İmajda kimlik artığı taraması (`bf-*`, peer key, token, parola) | Hiçbir cihaz-spesifik değer/secret yok | LAB(2) + build CI |
| ISO checksum doğrulaması | `dist/*.iso.sha256` ile eşleşir | LAB(2)/CI |
| Yarım kesilen kurulumun tekrarı | İkinci koşu tamamlar, exit 0 | LAB(2) | | Yarım kesilen kurulumun tekrarı | İkinci koşu tamamlar, exit 0 | LAB(2) |
| Clonezilla yedek yöntemi | Autoinstall takılan profilde READY | LAB(2) | | Clonezilla yedek yöntemi | Autoinstall takılan profilde PROVISIONED_OFFLINE | LAB(2) |
| Geri dönüş yolu (upstream ISO + NoCloud seed) | Aynı zemini kurar; F2B geri alınırsa sıcak yol çalışır | LAB(2) |
## 11. Rollback ## 11. Rollback
1. Bozuk imaj: önceki USB sürümüne dönüş (USB'ler sürüm etiketli saklanır). 1. Bozuk imaj: önceki USB sürümüne dönüş (USB'ler sürüm etiketli saklanır).
2. Kurulum sonrası hata: kurucu idempotent yeniden koşar; düzelmezse 17-RECOVERY LEVEL 9 (imajdan temiz kurulum). 2. Kurulum sonrası hata: kurucu idempotent yeniden koşar; düzelmezse 17-RECOVERY LEVEL 9 (imajdan temiz kurulum).
3. Autoinstall dosyası hatalıysa düzeltme repo PR'ı ile sürümlenir, SHA256 yeniden yayımlanır. 3. Autoinstall dosyası hatalıysa düzeltme repo PR'ı ile sürümlenir, SHA256 yeniden yayımlanır.
4. Field OS ISO boot etmezse (F2B) derhal upstream ISO + son onaylı NoCloud seed USB yoluna dönülür (bu yol her zaman sıcak tutulur, K-20).
## 12. Kontrol Listesi ## 12. Kontrol Listesi
@ -108,12 +119,17 @@ sudo ./blueforce-install.sh --dealer-id 12010193
- [ ] İmajda kimlik artığı yok (otomatik tarama). - [ ] İmajda kimlik artığı yok (otomatik tarama).
- [ ] Her donanım profili LAB matrisinde READY almış. - [ ] Her donanım profili LAB matrisinde READY almış.
- [ ] Clonezilla yedek prosedürü yazılı ve test edilmiş. - [ ] Clonezilla yedek prosedürü yazılı ve test edilmiş.
- [ ] Tek-USB medyada `interactive-sections: [storage]` korunmuş ve disk otomatik silinmiyor (K-20).
- [ ] F2B/F6 ayrımı doğru anlatılmış: assisted medya F2B, PXE/tam unattended F6 (25-ROADMAP).
- [ ] Medya tabanı sapması (Desktop flavour) kayda geçmiş ve işletim öncesi karar verilmiş (26 §6).
## 13. Açık Sorular ## 13. Açık Sorular
- [ ] Kurucu USB'de mi taşınacak, ilk boot'ta merkezden mi çekilecek (saha interneti varsayımı) (sahibi: Faz 3). - [ ] Kurucu USB'de mi taşınacak, ilk boot'ta merkezden mi çekilecek (saha interneti varsayımı) (sahibi: Faz 3).
- [ ] Autoinstall kesin direktif seti — LAB 26.04.1 çıktısı (sahibi: Faz 3, bu dosyanın yazarı). - [ ] Autoinstall kesin direktif seti — LAB 26.04.1 çıktısı (sahibi: Faz 3, bu dosyanın yazarı).
- [ ] USB hazırlama standardı (Ventoy vs dd vs Balena) (sahibi: Faz 3). - [ ] USB hazırlama standardı (Ventoy vs dd vs Balena) (sahibi: Faz 3).
- [ ] Offline APT repo'nun medyaya gömülmesi (bugün `offline_repo_status: not-included`, pinler `UNPINNED`) — F2B (sahibi: release yöneticisi) [K-20].
- [ ] Medya tabanı: Desktop flavour kabul mü, Server ISO ile yeniden build mi — F2B çıkış kapısı (sahibi: release yöneticisi) [K-01/K-20].
--- ---
@ -121,9 +137,11 @@ sudo ./blueforce-install.sh --dealer-id 12010193
```mermaid ```mermaid
flowchart LR flowchart LR
USB["USB<br/>(autoinstall + kurucu)"] --> KUR["Ubuntu Server 26.04.1<br/>katılımsız kurulum"] USB["V1 medya: tek USB Field OS ISO<br/>/autoinstall.yaml + /blueforce-provisioning/<br/>(interactive-sections: [storage])"] --> KUR["Autoinstall (kurulum motoru)<br/>Ubuntu zemin kurulur, 18 modül sonra"]
KUR --> TEK["sudo ./blueforce-install.sh<br/>--dealer-id <8hane>"] KUR --> DISK["Operatör disk onayı<br/>otomatik silme YOK"]
DISK --> TEK["firstboot → sudo ./blueforce-install.sh<br/>--dealer-id &lt;8hane&gt; --offline"]
TEK --> KIMLIK["Kimlik enjeksiyonu<br/>bf-<no> + peer + envanter"] TEK --> KIMLIK["Kimlik enjeksiyonu<br/>bf-<no> + peer + envanter"]
KIMLIK --> HAZIR["bf-status → READY"] KIMLIK --> HAZIR["bf-status → PROVISIONED_OFFLINE"]
HAZIR --> SAHA["Sahaya alım"] HAZIR --> SAHA["ENROLLED → READY<br/>(merkezi kanıt, 29)"]
FB["Geri dönüş yolu: upstream ISO + NoCloud seed USB"] -.->|"F2B geri alınırsa"| KUR
``` ```

View file

@ -30,7 +30,7 @@ LİSANS: Yok (şirket içi script).
```text ```text
KARAR: Kurucu 18 modülden oluşur (`01-precheck` … `18-final-check`); her modül idempotenttir — yeniden koşu güvenli ve kaldığı yerden tamamlayıcıdır. KARAR: Kurucu 18 modülden oluşur (`01-precheck` … `18-final-check`); her modül idempotenttir — yeniden koşu güvenli ve kaldığı yerden tamamlayıcıdır.
GEREKÇE: Saha elektriği/kesintisi yarım kurulum üretir; idempotency olmadan her kesinti temiz-kurulum demektir. Final-check kapısı (modül 18) exit 0 vermeden cihaz READY sayılmaz. GEREKÇE: Saha elektriği/kesintisi yarım kurulum üretir; idempotency olmadan her kesinti temiz-kurulum demektir. Final-check kapısı (modül 18) exit 0 vermeden cihaz kurulu sayılmaz; offline akışta sonuç `PROVISIONED_OFFLINE`, token sonrası `ENROLLED`, yalnız merkezi kanallar doğrulanınca `READY` olur (27/29).
ALTERNATİF: Tek seferlik (non-idempotent) script — kesinti durumunda belirsiz ara durum nedeniyle elendi. ALTERNATİF: Tek seferlik (non-idempotent) script — kesinti durumunda belirsiz ara durum nedeniyle elendi.
RİSK: Modülün idempotent yazılmaması (çift kayıt, çift key); azaltma: §10'daki çift-koşu testi her modül için zorunlu. RİSK: Modülün idempotent yazılmaması (çift kayıt, çift key); azaltma: §10'daki çift-koşu testi her modül için zorunlu.
MALİYET: Ücretsiz. MALİYET: Ücretsiz.

View file

@ -1,9 +1,9 @@
# 07 — Uzaktan Erişim (4 Kanal) # 07 — Uzaktan Erişim (4 Kanal)
> Kısa özet: Saha cihazlarına 4 bağımsız erişim kanalı: SSH-over-WireGuard, xRDP + GNOME, RustDesk OSS self-hosted (birincil grafik), MeshCentral self-hosted (ikincil yönetim). AnyDesk mimariden çıkarılmıştır; yalnızca müşteri sahasında zaten lisanslıysa opsiyonel modül olarak kurulur. > Kısa özet: Saha cihazlarına 4 bağımsız erişim kanalı: SSH-over-WireGuard, xRDP + GNOME (service her zaman hazır — K-22), RustDesk OSS self-hosted (birincil grafik), MeshCentral self-hosted (ikincil yönetim). `bf-gui-on`/`bf-gui-off` yalnız **yerel fiziksel** GUI'yi kontrol eder, RDP'yi değil: **GUI kapalı ≠ RDP kapalı**. AnyDesk ücretsiz zorunlu mimarinin parçası DEĞİLDİR (ticari lisans şartı); yalnız `scripts/install/optional/anydesk/` altında lisans onayına bağlı opsiyonel modül vardır.
- Dosya: `docs/07-REMOTE-ACCESS.md` - Dosya: `docs/07-REMOTE-ACCESS.md`
- İlgili kararlar: `24-DECISION-LOG.md#K-04` (4 kanal + AnyDesk hükmü), `K-03` (xRDP + bf-gui), `K-05` (WireGuard arkası), `K-02` (GNOME) - İlgili kararlar: `24-DECISION-LOG.md#K-04` (4 kanal + AnyDesk hükmü), `K-22` (RDP her zaman hazır; `bf-gui-*` yalnız yerel GUI), `K-03` (boot + bf-gui), `K-05` (WireGuard arkası), `K-02`/`K-23` (GNOME saha standardı + LAB A/B ölçümü)
- Durum: [ ] Taslak - Durum: [ ] Taslak
--- ---
@ -29,16 +29,18 @@ LİSANS: RustDesk istemci+sunucu AGPL-3.0 (1.4.9 / 1.1.16) — https://rustdes
``` ```
```text ```text
KARAR: xRDP grafik bakımı verir (GNOME oturumu), yalnızca WireGuard arkasında dinler; GUI varsayılan kapalıdır, bakım penceresinde `bf-gui-on` ile açılır. KARAR: RDP kanalı HER ZAMAN HAZIRDIR: `xrdp` ve `xrdp-sesman` multi-user seviyesinde sürekli enable ve active kalır; RDP `bf-gui-on` komutuna bağlı DEĞİLDİR. `bf-gui-on` / `bf-gui-off` yalnız YEREL FİZİKSEL grafik katmanını (display-manager: gdm3/gdm/sddm/lightdm + default target) kontrol eder ve `bf-gui-off` xRDP servislerini asla durdurmaz/disable etmez. xRDP GNOME oturumu üzerinden grafik bakım verir ve yalnızca WireGuard arkasında dinler (internete açık RDP portu yoktur).
GEREKÇE: xRDP systemd servis birimleri (`xrdp`, `xrdp-sesman`) üzerinden yönetilebilir bir katmandır; WG-arkası dinleme RDP'yi internet taramasından gizler, kapalı GUI varsayılanı RAM'i korur. GEREKÇE: xRDP systemd servis birimleri (`xrdp`, `xrdp-sesman`) üzerinden yönetilen bir katmandır; servisi sürekli hazır tutmak, yerel GUI kapalı saha cihazında uzaktan grafik kurtarma kanalını korur (sahaya gitme oranını düşürür). WG-arkası dinleme RDP'yi internet taramasından gizler, yerel GUI'nin kapalı kalması RAM'i ve hata yüzeyini korur. Depodaki kod bu ayrımı uygular: `08-rdp` modülü xrdp+xrdp-sesman'ın enable VE active olmasını, default target'ın multi-user kalmasını doğrular; `15-power-loss` boot zinciri de xRDP'i her zaman ready varsayar.
ALTERNATİF: RDP'yi doğrudan internete açma — tarama + kaba-kuvvet riski nedeniyle elendi. ALTERNATİF: RDP'yi `bf-gui-on`'a bağlamak — GUI kapalı saha cihazında uzaktan grafik kurtarma kaybolur → reddedildi. RDP'yi doğrudan internete açma — tarama + kaba-kuvvet riski nedeniyle elendi.
RİSK: Kesin birim adları ve GNOME oturum satırı 26.04.1'de değişebilir; azaltma: LAB doğrulaması olmadan `bf-gui-*` dondurulmaz (03). RİSK: Kesin birim adları ve GNOME oturum satırı 26.04.1'de değişebilir; azaltma: LAB doğrulaması olmadan `bf-gui-*` dondurulmaz (03). `bf-gui-off` içine xRDP'i durduran bir satır girmesi sözleşme ihlalidir (`tests/check-specs.sh` zorunlu tutar).
MALİYET: Ücretsiz. MALİYET: Ücretsiz.
LİSANS: xRDP Apache-2.0, ücretsiz — https://github.com/neutrinolabs/xrdp (v0.10.6.1, 2026-07-07; doğrulanma: 2026-09-15). LİSANS: xRDP Apache-2.0, ücretsiz — https://github.com/neutrinolabs/xrdp (v0.10.6.1, 2026-07-07; doğrulanma: 2026-09-15; yeniden doğrulama: 2026-09-16, HTTP 200). GNOME GPL/LGPL — https://www.gnome.org/ (doğrulama: 2026-09-16, HTTP 200).
``` ```
> **Güncelleme (2026-09-16, K-22):** Bu bölümdeki eski "GUI varsayılan kapalıdır, bakım penceresinde `bf-gui-on` ile açılır → RDP" anlatımı RDP'yi `bf-gui-on`'a bağlı gösteriyordu ve yanlıştı. Doğru ayrım: **RDP servisi her zaman hazır; `bf-gui-*` yalnız yerel fiziksel GUI'yi açar/kapatır.** Ayrıntı ve kod kanıtı: `24-DECISION-LOG.md#K-22`, `docs/03-UBUNTU-BASELINE.md` §9.
```text ```text
KARAR: Dört kanalın agent/servisleri boot'ta otomatik başlar (systemd enable): `wg-quick@wg0`, `rustdesk` katılımsız servis, MeshCentral agent, xRDP servisleri (GUI kapalı olsa da sesman hazır). KARAR: Dört kanalın agent/servisleri boot'ta otomatik başlar (systemd enable): `wg-quick@wg0`, RustDesk katılımsız servis, MeshCentral agent, `xrdp`/`xrdp-sesman` (yerel GUI kapalı olsa da her zaman enable/ready). RustDesk'in Ubuntu 26.04.1 headless/katılımsız kesin davranışı LAB ile doğrulanmadan dondurulmaz.
GEREKÇE: Elektrik kesintisi sonrası cihaz açılınca VPN + yedek kanallar kendiliğinden gelir; 16-POWER-LOSS boot zincirinin varsayımıdır. GEREKÇE: Elektrik kesintisi sonrası cihaz açılınca VPN + yedek kanallar kendiliğinden gelir; 16-POWER-LOSS boot zincirinin varsayımıdır.
ALTERNATİF: Manuel başlatma — 700 dokunulmaz cihazda her kesintide saha ziyareti demek → elendi. ALTERNATİF: Manuel başlatma — 700 dokunulmaz cihazda her kesintide saha ziyareti demek → elendi.
RİSK: Boot race (agent ağdan önce kalkar); azaltma: systemd `After=network-online.target` + yeniden deneme, LAB güç-kesinti testiyle doğrulanır. RİSK: Boot race (agent ağdan önce kalkar); azaltma: systemd `After=network-online.target` + yeniden deneme, LAB güç-kesinti testiyle doğrulanır.
@ -48,14 +50,14 @@ LİSANS: Yok (OS yapılandırması).
## 4. Neden Bu Karar? ## 4. Neden Bu Karar?
Tek tünel körlüğü engellenir: SSH ve RDP WireGuard'a bağımlıdır (güvenlik için), RustDesk ve MeshCentral ise VDS genel adresine doğrudan çıkar (erişilebilirlik için). RustDesk birincil grafiktir çünkü katılımsız erişim + dosya transferini OSS'te verir; MeshCentral ikinci düzlemdir çünkü terminal/dosya/envanter sunar ve RustDesk arızasında devreye girer. AnyDesk'in ücretsiz katmanı ticari kullanımı kapsamadığı için 700 cihazda lisans ihlali doğurur — bu hukuki risk teknik kolaylığa ağır basar. Tek tünel körlüğü engellenir: SSH ve RDP WireGuard'a bağımlıdır (güvenlik için), RustDesk ve MeshCentral ise VDS genel adresine doğrudan çıkar (erişilebilirlik için). RustDesk birincil grafiktir çünkü katılımsız erişim + dosya transferini OSS'te verir; MeshCentral ikinci düzlemdir çünkü terminal/dosya/envanter sunar ve RustDesk arızasında devreye girer. RDP'nin servis seviyesinde sürekli hazır olması (K-22) üçüncü bir sigortadır: yerel GUI kapalı bir cihazda bile teknisyen WG üzerinden GNOME oturumu açabilir; bu yüzden `bf-gui-off` xRDP'i asla durdurmaz. AnyDesk'in ücretsiz katmanı ticari kullanımı kapsamadığı için 700 cihazda lisans ihlali doğurur — bu hukuki risk teknik kolaylığa ağır basar ve AnyDesk ücretsiz/zorunlu mimarinin parçası değildir.
## 5. Alternatifler ## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç | | Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---| |---|---|---|---|
| AnyDesk (ücretsiz) | Tanınmış, kolay | Ticari kullanımda lisans ihlali | Elendi | | AnyDesk (ücretsiz) | Tanınmış, kolay | Ticari kullanımda lisans ihlali; zorunlu mimarinin parçası değil | Elendi |
| AnyDesk (lisanslı) | Kurumsal destek | 700 cihaz lisans bedeli + kapalı kaynak | Opsiyonel modül (bkz. §9) | | AnyDesk (lisanslı) | Kurumsal destek | 700 cihaz lisans bedeli + kapalı kaynak; ücretsiz mimariye girmez | Opsiyonel modül (bkz. §9) |
| Tailscale/ZeroTier | Kolay mesh | Freemium, merkezi kimlik | Elendi | | Tailscale/ZeroTier | Kolay mesh | Freemium, merkezi kimlik | Elendi |
| Tek kanal (yalnızca SSH) | En sade | Tünel çökerse körlük | Elendi | | Tek kanal (yalnızca SSH) | En sade | Tünel çökerse körlük | Elendi |
| VNC doğrudan | Basit | Şifresiz/zayıf varyantlar, WG-dışı risk | Elendi | | VNC doğrudan | Basit | Şifresiz/zayıf varyantlar, WG-dışı risk | Elendi |
@ -79,19 +81,23 @@ Tek tünel körlüğü engellenir: SSH ve RDP WireGuard'a bağımlıdır (güven
| WireGuard çöküşü (istemci/saha tarafı) | Orta | Orta | RustDesk/MeshCentral bağımsız kanallar (§9 analizi) | | WireGuard çöküşü (istemci/saha tarafı) | Orta | Orta | RustDesk/MeshCentral bağımsız kanallar (§9 analizi) |
| VDS kesintisi (hbbs+hbbr+MeshCentral) | Düşük | Yüksek | Health monitörü + yedek VDS planı (17) | | VDS kesintisi (hbbs+hbbr+MeshCentral) | Düşük | Yüksek | Health monitörü + yedek VDS planı (17) |
| RustDesk ID çakışması / key sızıntısı | Düşük | Orta | Self-hosted key, `bf-<no>` etiketi, key rotasyon prosedürü | | RustDesk ID çakışması / key sızıntısı | Düşük | Orta | Self-hosted key, `bf-<no>` etiketi, key rotasyon prosedürü |
| xRDP + GNOME ek ayar ihtiyacı (26.04.1) | Orta | Orta | LAB'da `bf-gui-*` doğrulaması (03) | | xRDP + GNOME ek ayar ihtiyacı (26.04.1) | Orta | Orta | LAB'da `bf-gui-*` doğrulaması (03); A/B ölçümünde "RDP güvenilirliği" metriği (K-23) |
| `bf-gui-off` yanlış kurgulanır, xRDP durur → uzaktan kurtarma kaybı | Düşük | Yüksek | `tests/check-specs.sh` bf-gui-off içinde `systemctl stop/disable xrdp*` görürse testi düşürür; 08-rdp check'i active xrdp ister (K-22) |
| Lisanssız AnyDesk sahaya sızar | Düşük | Orta | Modül yalnız `ANYDESK_LICENSE_CONFIRMED=true` ile çalışır ve kendisi hiçbir şey kurmaz; lisans kaydı cihaz dışında tutulur (K-04) |
## 9. Uygulama Planı ## 9. Uygulama Planı
1. Merkez VDS: `hbbs`/`hbbr` (`--net=host` önerilir) + MeshCentral kurulumu, health monitörü (Uptime Kuma) bağlanır. 1. Merkez VDS: `hbbs`/`hbbr` (`--net=host` önerilir) + MeshCentral kurulumu, health monitörü (Uptime Kuma) bağlanır.
2. Golden image: RustDesk istemcisi (self-hosted key + VDS adresi gömülü) + MeshCentral agent + xRDP kurulu gelir; dördü de systemd enable. 2. Golden image / Field OS medyası: RustDesk istemcisi (self-hosted key + VDS adresi gömülü) + MeshCentral agent + xRDP kurulu gelir; dördü de systemd enable. `xrdp`/`xrdp-sesman` her zaman enable+active kalır (K-22); `bf-gui-off` bunlara dokunmaz.
3. Cihaz kimliği (`bf-<no>`, 02) RustDesk etiketi + MeshCentral adı + Ansible inventory adı olarak aynı yazılır. 3. Cihaz kimliği (`bf-<no>`, 02) RustDesk etiketi + MeshCentral adı + Ansible inventory adı olarak aynı yazılır.
4. Opsiyonel AnyDesk modülü: SADECE müşterinin sahasında geçerli ticari AnyDesk lisansı varsa, `blueforce-install.sh --with-anydesk` bayrağıyla kurulur; lisans anahtarı müşteri tarafından sağlanır, AMC lisans sağlamaz. Lisanssız cihaza kurulmaz. 4. Opsiyonel AnyDesk modülü (zorunlu mimarinin parçası DEĞİLDİR): depoda tek yeri `scripts/install/optional/anydesk/install-anydesk.sh`. Modül yalnızca `ANYDESK_LICENSE_CONFIRMED=true` verildiğinde devam eder ve **kasıtlı olarak hiçbir şey indirmez/kurmaz**; lisanslı paket kurumun kendi kontrollü sürecinden gelir, cihaz istisnası Blueforce installer'ının dışında kayda geçer. Lisanssız cihaza kurulmaz.
```bash ```bash
# örnek: kanal durum denetimi (saha cihazında) # örnek: kanal durum denetimi (saha cihazında)
systemctl is-active wg-quick@wg0 xrdp rustdesk meshcentral 2>/dev/null systemctl is-active wg-quick@wg0 xrdp xrdp-sesman rustdesk meshcentral 2>/dev/null
ss -tlnp | grep -E ':(22|3389)' # yalnızca WG arayüzünde dinlemeli ss -tlnp | grep -E ':(22|3389)' # yalnızca WG arayüzünde dinlemeli
# yerel GUI kapalıyken de RDP çalışmalı (K-22):
sudo bf-gui-off && systemctl is-active xrdp xrdp-sesman # ikisi de "active" dönmeli
``` ```
### Kanal seçim matrisi ### Kanal seçim matrisi
@ -99,10 +105,11 @@ ss -tlnp | grep -E ':(22|3389)' # yalnızca WG arayüzünde dinlemeli
| Durum | Kullanılacak kanal | | Durum | Kullanılacak kanal |
|---|---| |---|---|
| Normal bakım (komut/config) | SSH-over-WireGuard | | Normal bakım (komut/config) | SSH-over-WireGuard |
| Grafik bakım (teknisyen) | xRDP + GNOME (WG üzerinden) | | Grafik bakım (teknisyen) | xRDP + GNOME (WG üzerinden; **yerel GUI kapalı olsa da çalışır**, K-22) |
| WireGuard çöktü, grafik lazım | RustDesk OSS (birincil yedek) | | WireGuard çöktü, grafik lazım | RustDesk OSS (birincil yedek) |
| WireGuard + RustDesk çöktü | MeshCentral terminal/dosya | | WireGuard + RustDesk çöktü | MeshCentral terminal/dosya |
| Otomasyon (filo) | Ansible-over-SSH (09), kanal değil araç | | Otomasyon (filo) | Ansible-over-SSH (09), kanal değil araç |
| Sahada yerel fiziksel ekran gerekiyor | `bf-gui-on` (yalnız yerel GUI; RDP'yi etkilemez) |
### WG çökerse RustDesk analizi ### WG çökerse RustDesk analizi
@ -113,10 +120,12 @@ RustDesk istemcisi tünele değil VDS genel adresine (`hbbs`/`hbbr` portları) d
| Test | Beklenen sonuç | Ortam | | Test | Beklenen sonuç | Ortam |
|---|---|---| |---|---|---|
| 4 kanalın normal durumda erişilebilirliği | Tümü OK | LAB(2) | | 4 kanalın normal durumda erişilebilirliği | Tümü OK | LAB(2) |
| `bf-gui-off` sonrası RDP oturumu | `xrdp`/`xrdp-sesman` active kalır, GNOME oturumu WG üzerinden açılır (K-22) | LAB(2) |
| `wg-quick@wg0` stop → RustDesk oturumu | Bağlantı kurulur | LAB(2) | | `wg-quick@wg0` stop → RustDesk oturumu | Bağlantı kurulur | LAB(2) |
| WG down + RustDesk kapalı → MeshCentral terminal | Komut çalışır | P1(5) | | WG down + RustDesk kapalı → MeshCentral terminal | Komut çalışır | P1(5) |
| Güç kesintisi → 4 kanalın otomatik dönüşü | Tümü 5 dk içinde OK | LAB(2) | | Güç kesintisi → 4 kanalın otomatik dönüşü | Tümü 5 dk içinde OK | LAB(2) |
| RDP portu internetten tarama | Kapalı (yalnızca WG) | LAB(2) | | RDP portu internetten tarama | Kapalı (yalnızca WG) | LAB(2) |
| Lisanssız AnyDesk denemesi | Modül kurulum yapmaz, `ANYDESK_LICENSE_CONFIRMED` yoksa durur | LAB(2) |
## 11. Rollback ## 11. Rollback
@ -129,25 +138,29 @@ RustDesk istemcisi tünele değil VDS genel adresine (`hbbs`/`hbbr` portları) d
- [ ] hbbs/hbbr + MeshCentral VDS'te ayakta, Uptime Kuma izliyor. - [ ] hbbs/hbbr + MeshCentral VDS'te ayakta, Uptime Kuma izliyor.
- [ ] 4 servisin tamamı boot-enable, güç-kesinti testinden geçti. - [ ] 4 servisin tamamı boot-enable, güç-kesinti testinden geçti.
- [ ] SSH/RDP yalnızca WG arayüzünde dinliyor. - [ ] SSH/RDP yalnızca WG arayüzünde dinliyor.
- [ ] `bf-gui-off` sonrası `xrdp` + `xrdp-sesman` hâlâ active (K-22 kanıtı kayıtlı).
- [ ] RustDesk etiketleri `bf-<no>` ile eşleşiyor. - [ ] RustDesk etiketleri `bf-<no>` ile eşleşiyor.
- [ ] AnyDesk hiçbir lisanssız cihaza kurulu değil. - [ ] AnyDesk hiçbir lisanssız cihaza kurulu değil; modül yalnız `scripts/install/optional/anydesk/` altında ve lisans onayı kapısıyla.
## 13. Açık Sorular ## 13. Açık Sorular
- [ ] hbbs/hbbr ile MeshCentral aynı VDS'te mi, ayrı mı — yük testiyle karar (sahibi: 25-ROADMAP). - [ ] hbbs/hbbr ile MeshCentral aynı VDS'te mi, ayrı mı — yük testiyle karar (sahibi: 25-ROADMAP).
- [ ] RustDesk self-hosted key rotasyon sıklığı (sahibi: 11-SECURITY yazarı). - [ ] RustDesk self-hosted key rotasyon sıklığı (sahibi: 11-SECURITY yazarı).
- [ ] GNOME vs XFCE A/B ölçümünde "RDP güvenilirliği" ve "login screen erişimi" metriklerinin ölçüm yöntemi (sahibi: platform ekibi) [K-23].
- [ ] RustDesk headless/katılımsız servis davranışının reboot sonrası kanıtı (her iki masaüstü kolunda) (sahibi: platform ekibi) [K-23].
--- ---
## Ek: Mermaid — kanal bağımsızlığı ## Ek: Mermaid — kanal bağımsızlığı ve RDP'nin GUI'den ayrılması
```mermaid ```mermaid
flowchart LR flowchart LR
PC["Saha bf-<no>"] -->|WG tüneli| WG["WireGuard hub"] PC["Saha bf-<no>"] -->|WG tüneli| WG["WireGuard hub"]
WG --> SSH["SSH / Ansible"] WG --> SSH["SSH / Ansible"]
WG --> RDP["xRDP + GNOME"] WG --> RDP["xRDP + GNOME<br/>(her zaman hazır, K-22)"]
PC -->|doğrudan internet<br/>VDS genel adresi| RD["RustDesk hbbs/hbbr"] PC -->|doğrudan internet<br/>VDS genel adresi| RD["RustDesk hbbs/hbbr"]
PC -->|doğrudan internet<br/>VDS genel adresi| MC["MeshCentral"] PC -->|doğrudan internet<br/>VDS genel adresi| MC["MeshCentral"]
GUI["bf-gui-on/off<br/>(yalnız yerel fiziksel GUI)"] -.->|"KONTROL ETMEZ"| RDP
WG -.->|çökerse| DOWN{"WG down?"} WG -.->|çökerse| DOWN{"WG down?"}
DOWN -->|evet| RD DOWN -->|evet| RD
DOWN -->|evet| MC DOWN -->|evet| MC

View file

@ -76,7 +76,7 @@ Saha cihazları NAT/CGNAT arkasındadır; merkez onlara ulaşamaz, onlar merkeze
1. Merkez hub kurulur, subnet planı dondurulur (ör. `10.8.0.0/16`, hub `10.8.0.1`, cihazlara sabit `/32`). 1. Merkez hub kurulur, subnet planı dondurulur (ör. `10.8.0.0/16`, hub `10.8.0.1`, cihazlara sabit `/32`).
2. Her cihaz için peer üretilir: adı `bf-<8hane>`, tünel IP'si envanter haritasından. 2. Her cihaz için peer üretilir: adı `bf-<8hane>`, tünel IP'si envanter haritasından.
3. İstemci config'i (`/etc/wireguard/wg0.conf`): hub endpoint + `PersistentKeepalive = 25` + `wg-quick@wg0 enable`. 3. İstemci config'i (`/etc/wireguard/wg0.conf`): hub endpoint + `PersistentKeepalive = 25` + `wg-quick@wg0 enable`.
4. Peer ekleme/çıkarma Ansible rolüyle yapılır (09); hub config'i Git'te sürümlenir. 4. Peer ekleme/çıkarma yalnız 29-ENROLLMENT sonrasında Ansible rolüyle yapılır (09); hub config'i Git'te sürümlenir. `PROVISIONED_OFFLINE` cihaz peer/handshake doğrulanmadan `READY` olamaz.
5. PİLOT-1'de kopma sayacı koşar (handshake yaşı metriği 14'te), keepalive saha verisiyle ayarlanır. 5. PİLOT-1'de kopma sayacı koşar (handshake yaşı metriği 14'te), keepalive saha verisiyle ayarlanır.
```bash ```bash

View file

@ -91,7 +91,7 @@ LİSANS: systemd-journald / textfile (Apache-2.0) — https://github.com/prome
1. Merkez: Prometheus + Grafana OSS + Uptime Kuma kurulur. 1. Merkez: Prometheus + Grafana OSS + Uptime Kuma kurulur.
2. Saha: Node Exporter + textfile cron/timer + Kuma Push kurulur. 2. Saha: Node Exporter + textfile cron/timer + Kuma Push kurulur.
3. Etiket şeması `device_id="BF-<no>"` tüm metriklerde uygulanır. 3. Etiket şeması `device_id="BF-<no>"` tüm metriklerde uygulanır.
4. `bf-status` / `bf-diagnostics` / `bf-support-bundle` sahaya dağıtılır. 4. `bf-status` / `bf-diagnostics` / `bf-support-bundle` sahaya dağıtılır. `bf-status`, yaşam döngüsü durumunu açık gösterir: offline kurulum `PROVISIONED_OFFLINE`, token sonrası `ENROLLED`, Prometheus/Kuma + WireGuard + erişim doğrulanınca `READY` (27/29).
```bash ```bash
# saha: Node Exporter + textfile dizini (örnek) # saha: Node Exporter + textfile dizini (örnek)

View file

@ -29,7 +29,7 @@ LİSANS: Yok (donanım ayarı).
``` ```
```text ```text
KARAR: Boot zinciri systemd bağımlılıklarıyla sıralanır: network → `wg-quick@wg0` → `docker.service` → MEG/uygulama birimleri → remote/monitoring ajanları. KARAR: Boot zinciri systemd bağımlılıklarıyla sıralanır: network → `wg-quick@wg0` → `docker.service` → MEG/uygulama birimleri → remote/monitoring ajanları; `xrdp`/`xrdp-sesman` her zaman enable/ready kalır, yerel GNOME display-manager durumu bağımsızdır.
GEREKÇE: Sırasız boot race üretir (Docker WG'den önce kalkar, MEG ağ bulamaz); `After=`/`Wants=` zinciri her açılışta aynı sırayı garanti eder. GEREKÇE: Sırasız boot race üretir (Docker WG'den önce kalkar, MEG ağ bulamaz); `After=`/`Wants=` zinciri her açılışta aynı sırayı garanti eder.
ALTERNATİF: Bağımlılıksız "hepsi enable" — açılışta rastgele sıra, elendi. ALTERNATİF: Bağımlılıksız "hepsi enable" — açılışta rastgele sıra, elendi.
RİSK: Birim adları 26.04.1'e göre değişebilir; azaltma: LAB imajında doğrulanmadan dondurulmaz (K-03 riski). RİSK: Birim adları 26.04.1'e göre değişebilir; azaltma: LAB imajında doğrulanmadan dondurulmaz (K-03 riski).

View file

@ -101,8 +101,9 @@ Hostname: bf-12010193
bf-status bf-status
``` ```
3. Ekranda **READY** (veya tüm satırlar yeşil/OK) görmelisin. 3. Offline medya ile kurulumda önce **PROVISIONED_OFFLINE** görmen normaldir; bu durumda cihaz READY değildir. İnternet geldiğinde merkezden tek-kullanımlık enrollment token iste ve 29'daki adımı uygula.
4. Kontrol listesini doldur (§12), sahadan çık. 4. Token sonrası **ENROLLED** görünür; WireGuard, remote ve monitoring merkezi doğrulaması bitince **READY** görmelisin.
5. READY sonrası kontrol listesini doldur (§12), sahadan çık.
### B. Arızalı cihaza gittin — ilk 3 hareket ### B. Arızalı cihaza gittin — ilk 3 hareket

View file

@ -102,7 +102,7 @@ ansible-playbook playbooks/update.yml --limit wave_p1
### Günlük / haftalık rutinler ### Günlük / haftalık rutinler
- Günlük (operatör, 10 dk): Uptime Kuma UP/DOWN, Prometheus `up` == 0 listesi, gece reboot olanlar, yedek job durumu. - Günlük (operatör, 10 dk): Uptime Kuma UP/DOWN, Prometheus `up` == 0 listesi, gece reboot olanlar, yedek job durumu.
- Haftalık (yönetici, 30 dk): dalga takvimi, açık halt'lar, anahtar/envanter uyumsuzlukları, L9 tatbikat sayacı. - Haftalık (yönetici, 30 dk): dalga takvimi, açık halt'lar, anahtar/envanter uyumsuzlukları, `PROVISIONED_OFFLINE`/`ENROLLED` bekleyen cihaz kuyruğu, release manifest/rollback kanıtı ve L9 tatbikat sayacı (27/29/30).
## 10. Test Planı ## 10. Test Planı

View file

@ -1,12 +1,13 @@
# 24 — Karar Günlüğü (Decision Log) # 24 — Karar Günlüğü (Decision Log)
> Kısa özet: Blueforce 700 cihaz Linux filosunun tüm nihai mimari kararları tek tabloda; her karar KARAR/GEREKÇE/ALTERNATİF/RİSK/MALİYET/LİSANS bloğu + resmi kaynak URL ile. Diğer 25 dokümanın başvurduğu kilit dosyadır. > Kısa özet: Blueforce 700 cihaz Linux filosunun tüm nihai mimari kararları tek tabloda; her karar KARAR/GEREKÇE/ALTERNATİF/RİSK/MALİYET/LİSANS bloğu + resmi kaynak URL ile. Diğer numaralı dokümanların başvurduğu kilit dosyadır (K-01…K-24).
> >
> - Dosya: `docs/24-DECISION-LOG.md` > - Dosya: `docs/24-DECISION-LOG.md`
> - İlgili kararlar: bu dosyanın kendisi kilit kaynaktır; `01-ARCHITECTURE.md` bu kararları diyagramlaştırır. > - İlgili kararlar: bu dosyanın kendisi kilit kaynaktır; `01-ARCHITECTURE.md` bu kararları diyagramlaştırır.
> - Durum: [x] Onaylı (Faz 2 kilidi; değişiklik bu dosyadan PR ile yapılır) > - Durum: [x] Onaylı (Faz 2 kilidi; değişiklik bu dosyadan PR ile yapılır)
> - Kural: tüm kararlarda FREE sürümler baz alınır; Pro/Enterprise zorunlu mimariye girmez. > - Kural: tüm kararlarda FREE sürümler baz alınır; Pro/Enterprise zorunlu mimariye girmez.
> - Doğrulama günü: 2026-09-15 (tüm URL'ler bu tarihte HTTP 200 ile çekildi; ayrıntı `research/` dosyalarında). > - Doğrulama günü: 2026-09-15 (tüm URL'ler bu tarihte HTTP 200 ile çekildi). İkinci doğrulama turu: 2026-09-16 (Subiquity autoinstall referansı + GNOME/XFCE/xrdp/NoCloud URL'leri yeniden HTTP 200 ile doğrulandı); ayrıntı `research/` dosyalarında.
> - Numaralandırma kuralı: **append-only**. Mevcut K-01…K-19 silinmez/yeniden numaralanmaz (başka dokümanlar `#K-16`, `#K-17`, `#K-18`, `#K-19` başlıklarına link verir); yeni kararlar bir sonraki boş numaradan eklenir. Bu turda eklenen: K-20…K-24.
--- ---
@ -16,7 +17,7 @@
## 2. Kapsam ## 2. Kapsam
- Kapsam içi: OS/GUI/boot, 4 erişim kanalı, WireGuard, fleet, monitoring, docs, golden image, Docker, cihaz kimliği, update dalgaları — aşağıdaki K-01…K-14 kararları. - Kapsam içi: OS/GUI/boot, 4 erişim kanalı (RDP her-zaman-hazır ayrımı dahil), WireGuard, fleet, monitoring, docs, golden image, Docker, cihaz kimliği, update dalgaları, Field OS ISO/medya stratejisi, offline provisioning, enrollment/durum modeli, release + Field OS sürüm kimliği ve Windows geçişi — aşağıdaki K-01…K-24 kararları.
- Kapsam dışı: script kodları, VDS kurulum komutları, gerçek cihaz test sonuçları (Faz 3+ işi; ilgili dokümanlara havale edilir). - Kapsam dışı: script kodları, VDS kurulum komutları, gerçek cihaz test sonuçları (Faz 3+ işi; ilgili dokümanlara havale edilir).
## 3. Kararlar ## 3. Kararlar
@ -37,12 +38,14 @@ LİSANS: Ubuntu LTS ücretsiz (açık kaynak bileşenler; ESM kapsamına bel b
```text ```text
KARAR: Saha masaüstü ortamı GNOME'dur (Ubuntu Desktop standardı); XFCE elendi. KARAR: Saha masaüstü ortamı GNOME'dur (Ubuntu Desktop standardı); XFCE elendi.
GEREKÇE: Kullanıcı kararı + Ubuntu Desktop standardı: tanıdık modern masaüstü, teknisyen akışıyla birebir uyum. GEREKÇE: Kullanıcı kararı + Ubuntu Desktop standardı: tanıdık modern masaüstü, teknisyen akışıyla birebir uyum.
ALTERNATİF: XFCE — hafif ve düşük kaynak tüketimine rağmen kullanıcı kararıyla elendi; yedekte tutulmaz. ALTERNATİF: XFCE — hafif ve düşük kaynak tüketimine rağmen kullanıcı kararıyla elendi; ancak K-23 uyarınca LAB A/B ölçümü için yedekte tutulur (ölçüm sonucu bu kararı revize edebilir; saha standardı ölçüm bitene kadar GNOME kalır).
RİSK: xRDP+GNOME kombinasyonu ek ayar isteyebilir (uyarı: GNOME oturumu RDP'de ek yapılandırma gerektirebilir); azaltma: 13-MEG-LINUX-ACCEPTANCE testi GNOME üzerinde koşar, `bf-gui-*` LAB'da 26.04.1 imajında doğrulanmadan dondurulmaz. RİSK: xRDP+GNOME kombinasyonu ek ayar isteyebilir (uyarı: GNOME oturumu RDP'de ek yapılandırma gerektirebilir); azaltma: 13-MEG-LINUX-ACCEPTANCE testi GNOME üzerinde koşar, `bf-gui-*` LAB'da 26.04.1 imajında doğrulanmadan dondurulmaz.
MALİYET: Ücretsiz. MALİYET: Ücretsiz.
LİSANS: GNOME (GPL/LGPL bileşenler), ücretsiz — https://www.gnome.org/ (doğrulanma: 2026-09-15). LİSANS: GNOME (GPL/LGPL bileşenler), ücretsiz — https://www.gnome.org/ (doğrulanma: 2026-09-15; yeniden doğrulama: 2026-09-16, HTTP 200).
``` ```
> **Güncelleme (2026-09-16):** K-02 geçerli kalır; yalnız **K-23** ile ölçüme bağlanır. Saha standardı GNOME'dur; XFCE saha standardı değildir, LAB A/B ölçüm adayıdır. İki karar çelişmez: baseline = GNOME, revizyon kapısı = LAB verisi.
### K-03 — Boot davranışı ve GUI aç/kapa ### K-03 — Boot davranışı ve GUI aç/kapa
```text ```text
@ -54,6 +57,8 @@ MALİYET: Ücretsiz.
LİSANS: xRDP Apache-2.0, ücretsiz — https://github.com/neutrinolabs/xrdp (v0.10.6.1, 2026-07-07; doğrulanma: 2026-09-15). LİSANS: xRDP Apache-2.0, ücretsiz — https://github.com/neutrinolabs/xrdp (v0.10.6.1, 2026-07-07; doğrulanma: 2026-09-15).
``` ```
> **Güncelleme (2026-09-16):** `bf-gui-on` / `bf-gui-off` yalnız **yerel fiziksel** grafik katmanı (display-manager + default target) yönetir; `xrdp`/`xrdp-sesman` her zaman enable/active kalır ve RDP bu anahtarlara bağlı değildir — ayrıntı ve kanıt: **K-22**.
### K-04 — Uzaktan erişim kanalları (4 kanal) ### K-04 — Uzaktan erişim kanalları (4 kanal)
```text ```text
@ -120,6 +125,118 @@ MALİYET: Ücretsiz.
LİSANS: Ubuntu autoinstall/subiquity (ücretsiz, Ubuntu lisans seti içinde) — https://releases.ubuntu.com/26.04/ (doğrulanma: 2026-09-15). NOT: Faz 1'de resmi autoinstall sözdizimi ayrıca doğrulanmadı; kesin direktifler 04-GOLDEN-IMAGE'da LAB çıktısıyla yazılır. LİSANS: Ubuntu autoinstall/subiquity (ücretsiz, Ubuntu lisans seti içinde) — https://releases.ubuntu.com/26.04/ (doğrulanma: 2026-09-15). NOT: Faz 1'de resmi autoinstall sözdizimi ayrıca doğrulanmadı; kesin direktifler 04-GOLDEN-IMAGE'da LAB çıktısıyla yazılır.
``` ```
### K-15 — Blueforce Field OS kurulum medyası
```text
KARAR: Blueforce Field OS = Ubuntu Server 26.04.1+ LTS üzerinde GNOME katmanı; V1 kurulum medyası resmi upstream ISO + ayrı NoCloud seed USB'dir. Autoinstall ve custom Field ISO tamamlayıcıdır; custom ISO yalnız LAB build artefact'ı olarak ele alınır.
GEREKÇE: Upstream ISO + seed, cihaz-spesifik veriyi medyadan ayırır ve remaster boot riskini V1'den uzak tutar. Custom ISO dağıtım kolaylığı sağlayabilir ancak UEFI/Legacy/Secure Boot davranışı kanıtlanmadan standart değildir.
ALTERNATİF: Yalnız custom remaster ISO — doğrulanmamış boot zinciri nedeniyle V1 için elendi; custom build gelecekte tamamlayıcıdır.
RİSK: 26.04 autoinstall veya remaster davranışı değişir; azaltma: her release'te checksum + LAB matrisi.
MALİYET: Ücretsiz.
LİSANS: Ubuntu Server — https://ubuntu.com/download/server ; Subiquity — https://canonical-subiquity.readthedocs-hosted.com/en/latest/ ; NoCloud — https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html (26.04 kesin davranışı LAB gerektirir).
```
### K-16 — Assisted offline provisioning durumu
```text
KARAR: İnternetsiz assisted provisioning imzalı/sürümlü ISO, seed ve offline APT snapshot ile yapılır; başarılı yerel sonuç yalnız PROVISIONED_OFFLINE'dır.
GEREKÇE: Saha bağlantısı yokken kurulum tamamlanabilir, ancak merkezi kimlik/kanal doğrulaması olmadan READY iddiası güvenli değildir.
ALTERNATİF: İnternet gelene kadar kurmamak — saha blokajı nedeniyle elendi; rastgele .deb taşıma — bağımlılık/sürüm belirsizliği nedeniyle elendi.
RİSK: Eski snapshot veya medya secret taşıması; azaltma: manifest/checksum, medya secret taraması, sürüm son kullanma tarihi.
MALİYET: Ücretsiz.
LİSANS: APT — https://manpages.ubuntu.com/manpages/noble/en/man8/apt.8.html ; dpkg — https://manpages.ubuntu.com/manpages/noble/en/man1/dpkg.1.html (26.04 offline akışı LAB gerektirir).
```
### K-17 — Cihaz enrollment ve READY kapısı
```text
KARAR: PROVISIONED_OFFLINE cihaz, BF-<no>'ya bağlı kısa ömürlü tek-kullanımlık token ile ENROLLED olur; yalnız WireGuard, merkezi erişim ve monitoring kanalları doğrulanınca READY olur.
GEREKÇE: Token cihaz kimliğini bağlar, fakat bağlantı/health kanıtı değildir. Ayrı durumlar yanlış teslimi ve ortak bootstrap secret riskini önler.
ALTERNATİF: Kalıcı ortak token — sızıntı blast-radius'u filo geneli olduğu için elendi; manuel kayıt — denetim zayıf olduğu için elendi.
RİSK: Token çalınması/yanlış kullanım; azaltma: TTL, atomik tek kullanım, cihaz bağlama, iptal ve audit kaydı.
MALİYET: Ücretsiz.
LİSANS: OpenSSH — https://www.openssh.com/ ; WireGuard — https://www.wireguard.com/ ; token API kurum içi uygulama ve LAB doğrulamasıdır.
```
### K-18 — Field OS release ve rollback yönetimi
```text
KARAR: Field OS release'i immutable manifestli bir artefact setidir: upstream ISO referansı, seed, offline APT snapshot, installer/Ansible commit'i, uygulama sürümleri ve checksum'lar birlikte sürümlenir; rollback önceki onaylı release'e döner.
GEREKÇE: OS sürümü tek başına davranışı açıklamaz; release manifesti tekrarlanabilir kurulum, denetim ve gerçek rollback hedefi sağlar.
ALTERNATİF: latest/yalnız Git branch — artefact bileşimi ve rollback hedefi belirsiz olduğu için elendi.
RİSK: Eksik artefact veya test edilmemiş rollback; azaltma: manifest kapısı, checksum/imza ve LAB rollback kanıtı.
MALİYET: Ücretsiz.
LİSANS: Git — https://git-scm.com/doc ; Ubuntu releases — https://releases.ubuntu.com/ ; Docker image sürümleme — https://docs.docker.com/reference/cli/docker/image/pull/.
```
### K-19 — Windows'tan Linux'e geçiş
```text
KARAR: Her Windows saha cihazında önce envanter + hash doğrulanmış geri dönüş imajı alınır, sonra temiz Field OS kurulumu ve kabul yapılır; V1'de yerinde dönüşüm ve dual-boot yoktur.
GEREKÇE: Temiz kurulum drift'i azaltır; geri dönüş imajı uygulama/periferik kabulü başarısız olduğunda kanıtlı çıkış verir.
ALTERNATİF: Yerinde dönüşüm veya dual-boot — disk/bootloader karmaşıklığı ve destek yükü nedeniyle elendi.
RİSK: Gizli iş/veri bağımlılığı; azaltma: iş sahibi onayı, image restore provası ve 13-MEG kabul kapısı.
MALİYET: Ücretsiz araçlar kullanılabilir; image depolama altyapı maliyetidir.
LİSANS: Ubuntu — https://ubuntu.com/download/server ; Clonezilla — https://clonezilla.org/ ; Windows lisans/uygulama durumu kurum tarafından doğrulanır.
```
> **2026-09-16 turu — yeni kararlar (K-20…K-24).** Aşağıdaki bloklar bu turda eklendi. Numaralar append-only'dir: K-16 (offline provisioning durumu), K-17 (enrollment), K-18 (release), K-19 (Windows geçişi) **silinmedi/yeniden numaralanmadı**; `docs/27`, `docs/29`, `docs/30` ve `docs/28` bu başlıklara link verir. İstenen konularla eşleşme: "medya stratejisi güncellemesi" → K-20; "durum modeli" → K-21 (K-16/K-17 ile birlikte okunur); "RDP her zaman hazır" → K-22; "GNOME vs XFCE A/B ölçümü" → K-23; "Field OS sürüm yönetimi" → K-24 (K-18 ile birlikte okunur).
### K-20 — Medya stratejisi: autoinstall kurulum motoru, Blueforce Field OS ISO paketleme yöntemidir
```text
KARAR: Autoinstall kurulum MOTORUDUR; Blueforce Field OS ISO ise PAKETLEME/DAĞITIM yöntemidir. İkisi rakip değildir, birlikte kullanılır: Field OS ISO içinde autoinstall + offline APT repo + firstboot bulunur. Güvenlik için autoinstall'da `interactive-sections: [storage]` KORUNUR — otomatik disk silme YOKTUR (operatör disk onayı verir). Teslim sırası: F2B'de assisted tek-USB Field OS ISO üretilir; upstream ISO + NoCloud seed USB geri dönüş yoludur. K-15 geçersiz kılınmaz, yedek yol olarak korunur.
GEREKÇE: Subiquity resmi referansı: autoinstall yapılandırması `/autoinstall.yaml` yolunda okunur ("irrespective of how it was provided") → ISO köküne konan autoinstall dosyası desteklenir. `interactive-sections` resmi olarak desteklenir ("A list of configuration keys to still show in the user interface"). `storage` bölümü resmi olarak "can be interactive: true" işaretlidir ve varsayılanı tek-disk sistemde lvm layout'tur; yani storage interactive bırakılmazsa kurulum en büyük diski kendiliğinden bölümler. Bu yüzden tek-USB deneyimi ile "otomatik disk silme yok" güvenlik şartı AYNI ANDA sağlanır: ISO autoinstall'ı başlatır, teknisyen yalnız disk adımını onaylar. Ayrıca `apt.fallback` varsayılanı `offline-install` ("revert to an offline installation") olduğu için internetsiz kurulum tasarım gereği çalışır.
ALTERNATİF: Yalnız upstream ISO + NoCloud seed USB (tek medya deneyimi yok; prompt hedefi karşılanmaz → V1'in tek yolu olmaktan çıkar, yedek/geri dönüş yolu olarak kalır); Yalnız custom remaster ISO (UEFI/Legacy/Secure Boot boot zinciri LAB'da kanıtlanmadan sahaya çıkar → reddedildi); otomatik disk wipe'lı unattended (veri kaybı riski → yasak, F6 kapsamı).
RİSK: Remaster boot zinciri (UEFI/Legacy/Secure Boot) LAB'da kanıtlanmazsa saha USB'si açılmayabilir; azaltma: F2B çıkış kriteri = tek USB assisted kurulumun iki LAB cihazında kanıtı; kanıt yoksa F3 başlamaz ve upstream ISO + seed yolu sıcak tutulur.
MALİYET: Ücretsiz.
LİSANS: Subiquity autoinstall — https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-reference.html (interactive-sections; `/autoinstall.yaml` yolu; `apt.fallback` default `offline-install`; `storage` "can be interactive: true" — doğrulama: 2026-09-16, HTTP 200); cloud-init NoCloud — https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html (doğrulama: 2026-09-16); Ubuntu Server ISO — https://ubuntu.com/download/server.
```
### K-21 — Cihaz durum modeli (tek kaynak): PROVISIONED_OFFLINE → ENROLLED → READY
```text
KARAR: Cihaz yaşam döngüsü ÜÇ durumdur ve tek kaynağı `/var/lib/blueforce/state.json` + `bf-enrollment-status` çıktısıdır: PROVISIONED_OFFLINE (offline kurulum bitti, merkez doğrulaması yok) → ENROLLED (tek kullanımlık token tüketildi, WireGuard atomik kuruldu) → READY (merkezi monitoring/remote/management kanıtı + yerel WG/xRDP/RustDesk kontrolleri). READY TEK DURUM DEĞİLDİR: "işletim sistemi kuruldu" READY değildir, "token alındı" READY değildir. K-16 ve K-17 bu modelin parçalarıdır; K-21 ikisini tek sözleşmede sabitler.
GEREKÇE: Kod ve testler aynı sözleşmeyi uygular: `blueforce-install.sh --offline` başarıyla biterse state.json'a `phase=PROVISIONED_OFFLINE`, `enrollment_status=PENDING`, `fleet_status=PENDING` yazar; `bf-enrollment-status` yalnız bu üç durumu geçerli sayar (aksi halde `INVALID`/`PENDING` + exit≠0); `bf-enroll` yalnız PROVISIONED_OFFLINE/ENROLLED fazından enrollment kabul eder; `--offline` olmadan bu damga yazılmaz. `bf-check-ready` kalıcı READY değerini okumaz, güncel handshake ve merkez kanıtını bağımsız denetler; kanıt eksikse fail-closed kalır.
ALTERNATİF: İkili model (kuruldu/kurulmadı) — "merkezce güvenildi" farkını görünmez kılar ve yanlış teslim üretir → elendi; READY'yi installer çıkış kodu yapmak — ağ/merkez kanıtı olmadan READY iddiası doğurur → reddedildi.
RİSK: Merkez kanıt alanları (`central_verification`) `pending` kalırsa cihaz ENROLLED'da bekler ve teslim gecikir; azaltma: enrollment yanıt sözleşmesi zorunlu alanları (29), fail-closed READY kapısı, 24/72 saat gözlem penceresi (IMPLEMENTATION-REPORT §9).
MALİYET: Ücretsiz.
LİSANS: systemd/journald (LGPL bileşenler) — https://www.freedesktop.org/software/systemd/man/latest/systemctl.html ; token API kurum içi uygulamadır (27/29).
```
### K-22 — RDP her zaman hazır; `bf-gui-on` / `bf-gui-off` yalnız yerel fiziksel GUI'yi kontrol eder
```text
KARAR: `xrdp` ve `xrdp-sesman` multi-user seviyesinde HER ZAMAN enable ve active kalır; RDP kanalı `bf-gui-on` komutuna bağlı DEĞİLDİR. `bf-gui-on` / `bf-gui-off` yalnız YEREL FİZİKSEL grafik katmanını (display-manager: gdm3/gdm/sddm/lightdm + default target) kontrol eder. `bf-gui-off` xRDP servislerini asla durdurmaz veya disable etmez. Başka bir deyişle: GUI kapalı ≠ RDP kapalı.
GEREKÇE: Depodaki kod bu ayrımı uygular: `08-rdp` modülü xrdp+xrdp-sesman'ın enable VE active olmasını, default target'ın multi-user kalmasını doğrular; `bf-gui-off` yalnız display-manager'ı durdurur ve "xRDP stays enabled and active so remote recovery is available" sözleşmesini taşır; `tests/check-specs.sh` bf-gui-off içinde `systemctl stop/disable xrdp*` görürse testi bilinçli olarak düşürür ve 08-rdp check'i active xrdp ister; `16-POWER-LOSS` boot zinciri de xrdp'in her zaman ready olduğunu varsayar. Bu ayrım 700 cihazda yerel GUI kapalıyken de uzaktan kurtarma kanalını korur.
ALTERNATİF: RDP'yi `bf-gui-on`'a bağlamak — GUI kapalı saha cihazında uzaktan grafik kurtarma kaybolur (sahaya gitme oranı artar) → reddedildi; her zaman grafik boot — kaynak israfı ve geniş hata yüzeyi (K-03) → elendi.
RİSK: GNOME oturumu RDP üzerinden ek yapılandırma isteyebilir; azaltma: 13-MEG kabulü GNOME üzerinde koşar; LAB'da 26.04.1 imajında xRDP+GNOME ve bf-gui-* doğrulanmadan dondurulmaz.
MALİYET: Ücretsiz.
LİSANS: xRDP Apache-2.0 (v0.10.6.1) — https://github.com/neutrinolabs/xrdp (doğrulama: 2026-09-16, HTTP 200); GNOME GPL/LGPL — https://www.gnome.org/ (doğrulama: 2026-09-16, HTTP 200).
```
### K-23 — GNOME saha standardı kalır; LAB'da GNOME vs XFCE A/B ölçümü yapılır
```text
KARAR: Saha masaüstü ortamı GNOME'dur (K-02 yürürlükte kalır, geçersiz kılınmaz). Buna ek olarak LAB'da GNOME vs XFCE A/B ölçümü yapılacak ve karar ölçüm sonucuna bağlanacaktır. Ölçüm seti sabittir: (1) RAM tüketimi, (2) CPU tüketimi, (3) boot süresi, (4) RDP güvenilirliği, (5) RustDesk'in reboot sonrası davranışı, (6) login screen erişimi, (7) dummy display davranışı, (8) 24 saat ve 72 saat stabilite. RustDesk headless ZORUNLU şartı her iki kolda da aynen korunur. Ölçüm sonucu K-02'yi revize edebilir; veri gelmeden revizyon yapılmaz.
GEREKÇE: GNOME kullanıcı kararıdır; ancak saha koşulları (RDP oturumu + headless RustDesk + düşük RAM'li mini PC'ler) ölçülmeden "tek doğru masaüstü" iddiası kanıtsız kalır. A/B ölçümü kararı kanıta bağlar: baseline GNOME olarak sabit kalır, XFCE yalnız ölçüm adayıdır. Bu yazım K-02 ile çelişmez — K-02 sahada geçerli olan karardır, K-23 onu ölçüm kapısına bağlayan revizyon mekanizmasıdır.
ALTERNATİF: XFCE'yi doğrudan saha standardı yapmak — kullanıcı kararı dışı ve kanıtsız → elendi; XFCE'yi tamamen kapatmak — PİLOT'ta kanıtsız revizyon talebi doğurur ve ölçüm şansı kaybolur → elendi.
RİSK: A/B ölçümü LAB zamanı ve iki masaüstü bakım yükü getirir; azaltma: ölçüm seti ve süreleri (24/72 saat) sabittir, sonuç tek tabloda tutulur ve LAB release kaydına bağlanır; XFCE kolu saha medyasına girmez.
MALİYET: Ücretsiz.
LİSANS: GNOME GPL/LGPL — https://www.gnome.org/ ; XFCE GPL — https://xfce.org/ (her ikisi de doğrulama: 2026-09-16, HTTP 200; XFCE yalnız LAB ölçüm adayıdır, saha standardı değildir).
```
### K-24 — Field OS sürüm kimliği: `/etc/blueforce-release` + `bf-release`
```text
KARAR: Cihazın Field OS sürüm kimliği tek komutla okunur: `bf-release` (kaynak sırası: `BF_RELEASE_MANIFEST` → `/etc/blueforce/release-manifest.yaml` → legacy `/etc/blueforce-release` → repo içi `provisioning/release/manifest.yaml`). Manifest `status: skeleton` ise komut sürümü **UNAPPROVED_SKELETON** olarak raporlar ve sıfırdan farklı döner; skeleton release DAĞITILABİLİR release sayılmaz. Sürüm kimliği, K-18'deki immutable release manifestiyle (ISO referansı, seed, offline APT snapshot, installer/Ansible commit'i, checksum'lar) eşleşmek zorundadır. `/etc/blueforce-release` nonsecret bir cihaz-üstü kimlik dosyasıdır: token, private key veya parola içermez.
GEREKÇE: Sahada "hangi Field OS sürümü çalışıyor?" sorusu secret sızdırmadan, tek komutla ve denetlenebilir biçimde yanıtlanmalıdır. Depodaki davranış bunu uygular: `17-healthcheck` kurulumda `provisioning/release/manifest.yaml` dosyasını `/etc/blueforce/release-manifest.yaml` olarak kurar; `bf-release` YAML ve legacy KEY=VALUE biçimini okuyabilir, okunamayan/`skeleton` manifestte exit≠0 verir ve `tests/check-specs.sh` bu ret davranışını (`UNAPPROVED_SKELETON`) zorunlu tutar. Böylece sahte/iskelet sürüm kimliği cihazda "release var" izlenimi üretemez.
ALTERNATİF: Yalnız `git tag` — sahada checkout yoktur, cihaz kendi sürümünü okuyamaz → elendi; yalnız `lsb_release`/`os-release` — yalnız Ubuntu tabanını gösterir, Field OS katmanını ve release bileşimini göstermez → elendi.
RİSK: AÇIK KONU: `/etc/blueforce-release` bugün yalnız OKUMA yoludur (legacy biçim için uyumluluk var) ve onu yazan bir release pipeline adımı depoda yok; bu dosya yazılmazsa cihaz kimliği `/etc/blueforce/release-manifest.yaml`'a (kurulum anındaki iskelet manifest) düşer ve `bf-release` UNAPPROVED_SKELETON döner. Azaltma: gerçek release üretildiğinde manifestin status'u `released` olacak ve yazma adımı 30-FIELD-OS-RELEASE-MANAGEMENT sahibi tarafından eklenmelidir.
MALİYET: Ücretsiz.
LİSANS: Git — https://git-scm.com/doc ; Ubuntu releases — https://releases.ubuntu.com/ ; Docker image sürümleme — https://docs.docker.com/reference/cli/docker/image/pull/ .
```
### K-10 — Docker restart politikası ve imaj disiplini ### K-10 — Docker restart politikası ve imaj disiplini
```text ```text
@ -186,7 +303,7 @@ Tüm kararlarda aynı üç filtre uygulandı: (1) %100 ücretsiz/self-hosted —
| Alternatif | Artı | Eksi | Sonuç | | Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---| |---|---|---|---|
| Ubuntu 24.04 LTS | Olgun donanım desteği | 26.04 doğrulandı, daha kısa destek penceresi | Yedek | | Ubuntu 24.04 LTS | Olgun donanım desteği | 26.04 doğrulandı, daha kısa destek penceresi | Yedek |
| XFCE | Hafif, düşük kaynak | Kullanıcı kararıyla elendi (saha standardı GNOME) | Elendi | | XFCE | Hafif, düşük kaynak | Kullanıcı kararıyla saha standardı olmadı (GNOME baseline) | LAB A/B ölçüm adayı (K-23) |
| AnyDesk | Tanınmış, kolay | Ticari lisans zorunlu (700 cihaz), kapalı kaynak | Elendi | | AnyDesk | Tanınmış, kolay | Ticari lisans zorunlu (700 cihaz), kapalı kaynak | Elendi |
| Tailscale/ZeroTier | Kolay mesh | Freemium, merkezi kimlik | Elendi | | Tailscale/ZeroTier | Kolay mesh | Freemium, merkezi kimlik | Elendi |
| AWX | Güçlü UI | K8s zorunlu, release'ler duraklatıldı | Elendi | | AWX | Güçlü UI | K8s zorunlu, release'ler duraklatıldı | Elendi |
@ -203,15 +320,18 @@ Tüm kararlarda aynı üç filtre uygulandı: (1) %100 ücretsiz/self-hosted —
## 6. Avantajlar ## 6. Avantajlar
- 14 kararın tamamı ücretsiz katmanda kanıtlı; zorunlu mimaride lisans bedeli yok. - 24 kararın tamamı ücretsiz katmanda kanıtlı; zorunlu mimaride lisans bedeli yok.
- Her kararın resmi kaynak URL'si + sürümü + doğrulama tarihi var; varsayıma dayalı karar yok (belirsizler LAB/PİLOT'a havale edildi). - Her kararın resmi kaynak URL'si + sürümü + doğrulama tarihi var; varsayıma dayalı karar yok (belirsizler LAB/PİLOT'a havale edildi).
- 4 erişim kanalından ikisi WireGuard-bağımsız; tek tünel arızası filoyu kör etmez. - 4 erişim kanalından ikisi WireGuard-bağımsız; tek tünel arızası filoyu kör etmez.
- Tek-USB Field OS medyası (K-20) ile tek-medya deneyimi sağlanırken otomatik disk silme yasağı (interactive storage) korunur.
## 7. Dezavantajlar ## 7. Dezavantajlar
- K-09 (golden image) Faz 1'de tam resmi-doğrulamalı değil; autoinstall sözdizimi LAB çıktısına bağımlı. - K-09 (golden image) Faz 1'de tam resmi-doğrulamalı değil; autoinstall sözdizimi LAB çıktısına bağımlı.
- Semaphore Community'de OIDC/2FA/Vault yokluğu operasyonel disiplinle (VPN-arkası + Key Store) kapatılıyor; bu disiplin bozulursa güvenlik açığı doğar. - Semaphore Community'de OIDC/2FA/Vault yokluğu operasyonel disiplinle (VPN-arkası + Key Store) kapatılıyor; bu disiplin bozulursa güvenlik açığı doğar.
- Prometheus 700 node işletme bilgisi ister; retention/kardinalite LAB'da ölçülmeden donanım siparişi verilmemeli. - Prometheus 700 node işletme bilgisi ister; retention/kardinalite LAB'da ölçülmeden donanım siparişi verilmemeli.
- K-20 ile tek-USB Field OS ISO hedeflenir, fakat remaster boot zinciri LAB'da kanıtlanana kadar saha medyası upstream ISO + seed olarak kalır (ikili medya geçiş dönemi).
- K-24 açık konusu: `/etc/blueforce-release` dosyasını yazan release pipeline adımı henüz yok; cihaz sürüm kimliği iskelet manifest'e düşerse `bf-release` UNAPPROVED_SKELETON döner.
## 8. Riskler ## 8. Riskler
@ -223,6 +343,9 @@ Tüm kararlarda aynı üç filtre uygulandı: (1) %100 ücretsiz/self-hosted —
| 26.04.x sürücü farkı heterojen donanımda | Orta | Orta | LAB'da profil başına imaj testi (K-01/K-09) | | 26.04.x sürücü farkı heterojen donanımda | Orta | Orta | LAB'da profil başına imaj testi (K-01/K-09) |
| Tek VDS iki bağımsız kanalı da barındırırsa ortak arıza | Düşük | Yüksek | hbbs/hbbr + MeshCentral ayrı VDS veya yedek planı (K-04) | | Tek VDS iki bağımsız kanalı da barındırırsa ortak arıza | Düşük | Yüksek | hbbs/hbbr + MeshCentral ayrı VDS veya yedek planı (K-04) |
| 700 node Prometheus kardinalite patlaması | Orta | Orta | 60 sn scrape, 30–90 gün retention, `BF-<no>` etiket şeması (K-07) | | 700 node Prometheus kardinalite patlaması | Orta | Orta | 60 sn scrape, 30–90 gün retention, `BF-<no>` etiket şeması (K-07) |
| Remaster Field OS ISO boot etmez (UEFI/Legacy/Secure Boot) | Orta | Yüksek | F2B çıkış kapısı: 2 LAB cihazında assisted kurulum kanıtı; aksi halde F3 başlamaz, upstream ISO + seed yolu sıcak (K-20) |
| Skeleton manifest ile "release var" izlenimi | Düşük | Orta | `bf-release` skeleton manifestte exit≠0 (UNAPPROVED_SKELETON); check-specs bu davranışı zorunlu tutar (K-24) |
| GNOME/XFCE kararı kanıtsız revize edilir | Düşük | Orta | A/B ölçüm seti sabit; revizyon yalnız LAB verisiyle ve PR ile (K-23) |
## 9. Uygulama Planı ## 9. Uygulama Planı
@ -248,6 +371,10 @@ hostnamectl set-hostname bf-12010193
| `docker inspect` restart politikası | `unless-stopped` tüm saha konteynerlerinde | LAB(2) | | `docker inspect` restart politikası | `unless-stopped` tüm saha konteynerlerinde | LAB(2) |
| Semaphore kapalı iken Ansible CLI run | Playbook başarıyla koşar | LAB(2) | | Semaphore kapalı iken Ansible CLI run | Playbook başarıyla koşar | LAB(2) |
| Scrape + 17 metrik + Uptime Push | Grafana + Kuma panoları dolu | P1(5) | | Scrape + 17 metrik + Uptime Push | Grafana + Kuma panoları dolu | P1(5) |
| `bf-gui-off` sonrası RDP oturumu | xRDP active kalır, GNOME oturumu açılır (K-22) | LAB(2) |
| `bf-release` çıktısı (skeleton manifest) | `UNAPPROVED_SKELETON` + exit≠0; sahte sürüm gösterilmez (K-24) | LAB(2) |
| Tek-USB Field OS ISO assisted kurulum | Autoinstall başlar, storage adımı operatör onayı bekler, disk kendiliğinden silinmez (K-20) | LAB(2) UEFI+Legacy |
| GNOME vs XFCE A/B ölçümü | 8 metrik tablosu dolar; karar LAB verisiyle verilir (K-23) | LAB(2) |
## 11. Rollback ## 11. Rollback
@ -257,10 +384,12 @@ hostnamectl set-hostname bf-12010193
## 12. Kontrol Listesi ## 12. Kontrol Listesi
- [ ] 14 kararın her birinde KARAR/GEREKÇE/ALTERNATİF/RİSK/MALİYET/LİSANS + kaynak URL var. - [ ] 24 kararın her birinde KARAR/GEREKÇE/ALTERNATİF/RİSK/MALİYET/LİSANS + kaynak URL var.
- [ ] Ücretli hiçbir araç zorunlu mimaride değil; AnyDesk hükmü resmi URL'li. - [ ] Ücretli hiçbir araç zorunlu mimaride değil; AnyDesk hükmü resmi URL'li.
- [ ] `unattended-upgrades`, `latest`, Watchtower yasakları üç dosyada da tutarlı (03/10/12). - [ ] `unattended-upgrades`, `latest`, Watchtower yasakları üç dosyada da tutarlı (03/10/12).
- [ ] Ek B'deki 27 sorunun her biri ≥1 karara bağlı (boş hücre yok). - [ ] Ek B'deki 27 sorunun her biri ≥1 karara bağlı (boş hücre yok).
- [ ] Ek B'ye eklenen S-28…S-37 (Field OS soruları) da ≥1 karara bağlı.
- [ ] RDP/durum modeli/medya/GNOME ölçümü/sürüm kimliği konularında ikinci tur kararları (K-20…K-24) ile K-02/K-03/K-15/K-16/K-17/K-18 arasında çelişki yok.
## 13. Açık Sorular ## 13. Açık Sorular
@ -270,6 +399,10 @@ hostnamectl set-hostname bf-12010193
- [ ] Digest pinleme (`@sha256`) kararı — PİLOT-2 değerlendirmesi (sahibi: 12-DOCKER yazarı). - [ ] Digest pinleme (`@sha256`) kararı — PİLOT-2 değerlendirmesi (sahibi: 12-DOCKER yazarı).
- [ ] Prometheus retention/kardinalite hesabı (700 × 60 sn) — LAB ölçümü (sahibi: 14-MONITORING yazarı). - [ ] Prometheus retention/kardinalite hesabı (700 × 60 sn) — LAB ölçümü (sahibi: 14-MONITORING yazarı).
- [ ] Semaphore Global Runner sayısı + eşzamanlılık — 700 node yük testi (sahibi: 09-FLEET yazarı). - [ ] Semaphore Global Runner sayısı + eşzamanlılık — 700 node yük testi (sahibi: 09-FLEET yazarı).
- [ ] GNOME vs XFCE A/B ölçüm seti ve sonuç tablosu (8 metrik, 24/72 saat) — LAB 2 cihaz (sahibi: platform ekibi + 03 yazarı) [K-23].
- [ ] F2B tek-USB Field OS ISO: iso builder'ın gerçek üretime geçirilmesi + UEFI/Legacy/Secure Boot LAB matrisi — LAB (sahibi: provisioning/release yöneticisi) [K-20].
- [ ] Offline APT repo somut paket pinleri (base-packages/remote-access/docker manifestleri) — LAB (sahibi: release yöneticisi) [K-20/K-16].
- [ ] `/etc/blueforce-release` yazma adımının release pipeline'a eklenmesi — 30 release yönetimi (sahibi: release yöneticisi) [K-24].
--- ---
@ -309,7 +442,7 @@ hostnamectl set-hostname bf-12010193
## Ek B: 27 Soru → Karar İzlenebilirliği ## Ek B: 27 Soru → Karar İzlenebilirliği
> NOT: `SUMMARY.md` Faz 4'te üretilecek; aşağıdaki S-01…S-27 listesi şartname + plandaki 27 kritik sorunun Faz 2 karşılığıdır. SUMMARY yazıldığında her cevap buradaki karara linklenecek. > NOT: `SUMMARY.md` artık üretilmiştir (2026-09-15/16); aşağıdaki S-01…S-27 listesi şartname + plandaki 27 kritik sorunun Faz 2 karşılığıdır ve SUMMARY'deki aynı sorular bu kararlara linklenir. 2026-09-16 turunda SUMMARY'ye eklenen 10 Field OS sorusu S-28…S-37 olarak listelenmiştir.
| # | Soru | Karar | Doküman | | # | Soru | Karar | Doküman |
|---|---|---|---| |---|---|---|---|
@ -341,18 +474,35 @@ hostnamectl set-hostname bf-12010193
| S-26 | Cihaz kimliği standardı ne? | K-12 (`BF-<no>` / `bf-<no>`) | 02 | | S-26 | Cihaz kimliği standardı ne? | K-12 (`BF-<no>` / `bf-<no>`) | 02 |
| S-27 | Doküman platformu hangisi? | K-08 (Docusaurus) | 22 | | S-27 | Doküman platformu hangisi? | K-08 (Docusaurus) | 22 |
### Ek B-2: 10 Field OS sorusu → karar (2026-09-16 turu)
| # | Soru | Karar | Doküman |
|---|---|---|---|
| S-28 | Blueforce Field OS nedir? | K-01 + K-02 + K-20 (Ubuntu LTS üzerinde GNOME katmanlı, tek-USB paketlenebilen saha OS standardı) | 00, 01, 26, 31 |
| S-29 | ISO nasıl build edilir? | K-20 (autoinstall motoru + Field OS ISO paketleme; `provisioning/iso/build-blueforce-iso.sh` — bugün iskelet, gerçek build açık) | 26, 31 |
| S-30 | Offline kurulum nasıl çalışır? | K-16 + K-20 (`apt.fallback=offline-install` + offline APT snapshot + `--offline`) | 27, 31 |
| S-31 | Device state'leri nedir? | K-21 (PROVISIONED_OFFLINE → ENROLLED → READY) | 27, 29 |
| S-32 | Internet olmadan ne olur? | K-16 + K-21 (yerel kurulum tamam, durum PROVISIONED_OFFLINE; READY iddiası yok) | 27 |
| S-33 | Internet gelince ne olur? | K-17 + K-21 (tek kullanımlık token → ENROLLED; WG/erişim/monitoring doğrulaması → READY) | 29 |
| S-34 | Enrollment nasıl olur? | K-17 (`BF-<no>` bağlı, TTL'li, tek kullanımlık token; `bf-enroll --token-stdin`) | 29 |
| S-35 | Windows'tan geçmeden önce ne yedeklenir? | K-19 (envanter + hash doğrulamalı geri dönüş imajı + veri dışa aktarma) | 28 |
| S-36 | Rollback nasıl olur? | K-18 + K-20 + K-22 (önceki onaylı release manifesti; gerekirse upstream ISO + onaylı seed ile yeniden kurulum) | 30, 26 |
| S-37 | Field OS version nasıl takip edilir? | K-24 (`/etc/blueforce-release` + `bf-release`; skeleton manifest reddedilir) | 30 |
--- ---
## Ek: Karar Haritası (hangi soru hangi karara?) ## Ek: Karar Haritası (hangi soru hangi karara?)
```mermaid ```mermaid
flowchart LR flowchart LR
SORU["27 soru (Ek B)<br/>S-01…S-27"] --> ZEMIN["Zemin: K-01/K-02/K-03<br/>K-09/K-10/K-11/K-12"] SORU["37 soru<br/>S-01…S-27 + S-28…S-37"] --> ZEMIN["Zemin: K-01/K-02/K-03<br/>K-09/K-10/K-11/K-12"]
SORU --> AG["Ağ + erişim: K-04/K-05/K-13"] SORU --> AG["Ağ + erişim: K-04/K-05/K-13"]
SORU --> OPS["Operasyon: K-06/K-07/K-14"] SORU --> OPS["Operasyon: K-06/K-07/K-14"]
SORU --> DOC["Docs: K-08"] SORU --> DOC["Docs: K-08"]
SORU --> FIELD["Field OS: K-15/K-16/K-17/K-18/K-19<br/>K-20/K-21/K-22/K-23/K-24"]
ZEMIN --> D1["03/04/05/10/12/02"] ZEMIN --> D1["03/04/05/10/12/02"]
AG --> D2["07/08/06/11"] AG --> D2["07/08/06/11"]
OPS --> D3["09/14/15"] OPS --> D3["09/14/15"]
DOC --> D4["22"] DOC --> D4["22"]
FIELD --> D5["26/27/28/29/30/31<br/>+ 24 bu dosya"]
``` ```

View file

@ -1,28 +1,28 @@
# 25 — Uygulama Yol Haritası (Implementation Roadmap) # 25 — Uygulama Yol Haritası (Implementation Roadmap)
> Kısa özet: Blueforce 700 cihaz projesinin FAZ'lı yol haritası: FAZ 0 hazırlık → FAZ 1 merkez → FAZ 2 imaj → FAZ 3 LAB/P1 → FAZ 4 dalgalar → FAZ 5 PROD + işletme. Katılımsız provisioning (unattended) ayrı FAZ'dır; temel akış çalışmadan otomasyona geçilmez. > Kısa özet: Blueforce 700 cihaz projesinin FAZ'lı yol haritası: F0 Windows + donanım keşfi → F1 merkez altyapı → F2A Blueforce bootstrap stabilizasyon → F2B Assisted Offline Field ISO → F3 LAB (2 cihaz) → F4 P1-P2-W1-W2 dalgaları → F5 production rollout → F6 gerçek zero-touch PXE. Katılımsız/PXE otomasyonu YALNIZ F6'dır; F2B assisted (operatör disk onaylı) tek-USB medyadır ve erken fazların hiçbirinde otomatik disk silme yoktur.
- Dosya: `docs/25-IMPLEMENTATION-ROADMAP.md` - Dosya: `docs/25-IMPLEMENTATION-ROADMAP.md`
- İlgili kararlar: `24-DECISION-LOG.md#K-09` (imaj + unattended sırası), `#K-11` (dalgalar), tüm K-01…K-14 (fazlara dağılım) - İlgili kararlar: `24-DECISION-LOG.md#K-01`…`#K-24`; özellikle `#K-09` (golden image), `#K-11` (dalgalar), `#K-15`/`#K-20` (medya stratejisi), `#K-16`/`#K-17`/`#K-21` (offline + durum modeli), `#K-18`/`#K-24` (release + sürüm kimliği)
- Durum: [ ] Taslak - Durum: [ ] Taslak
--- ---
## 1. Amaç ## 1. Amaç
Projeyi "ne, hangi sırada, hangi kapıdan geçerek" sorusuyla yönetmek. Her FAZ'ın girişi, çıkışı ve sahibi bellidir; bir FAZ kapanmadan sonrakine iş taşınmaz. Projeyi "ne, hangi sırada, hangi kapıdan geçerek" sorusuyla yönetmek. Her FAZ'ın girişi, çıkışı ve sahibi bellidir; bir FAZ kapanmadan sonrakine iş taşınmaz. F2A/F2B ayrımı, "kurucu ve bootstrap çalışıyor" ile "tek USB'lik Field OS medyası üretildi" işlerinin farklı risk sınıfları olduğunu görünür kılar.
## 2. Kapsam ## 2. Kapsam
- Kapsam içi: FAZ tanımı, sıra ve bağımlılıklar, her FAZ'ın çıkış kriteri, unattended provisioning FAZ'ının yeri, doküman-faz eşleşmesi. - Kapsam içi: FAZ tanımı, sıra ve bağımlılıklar, her FAZ'ın çıkış kriteri, assisted (F2B) ile zero-touch (F6) provisioning'in yeri, doküman-faz eşleşmesi.
- Kapsam dışı: dalga cihaz sayıları detayı (18), update onay akışı (10), komut-seviyesi kurulum (03/04/05). - Kapsam dışı: dalga cihaz sayıları detayı (18), update onay akışı (10), komut-seviyesi kurulum (03/04/05).
## 3. Kararlar ## 3. Kararlar
```text ```text
KARAR: Yol haritası 7 FAZ'dır: F0 Hazırlık+Envanter → F1 Merkez VDS → F2 Golden image (katılımlı) → F3 LAB+P1 → F4 Dalgalar (P2/W1/W2) → F5 PROD+İşletme → F6 Katılımsız provisioning (unattended, ayrı FAZ). KARAR: Yol haritası 8 adımdır (7 FAZ + F2 ikiye ayrılır): F0 Windows + donanım keşfi → F1 Merkez altyapı → F2A Blueforce bootstrap stabilizasyon → F2B Assisted Offline Field ISO → F3 LAB (2 cihaz, uçtan uca) → F4 P1-P2-W1-W2 dalgaları → F5 Production rollout (~523) + işletme/release yönetimi → F6 Gerçek zero-touch PXE. F6 YALNIZ PXE / tam unattended / uzaktan (remote) provisioning içindir; assisted kurulum F2B'de biter ve otomatik disk silme hiçbir fazda yoktur.
GEREKÇE: Sıra bağımlılıkları zorlar: envanter olmadan imaj (K-09), imaj olmadan LAB, LAB olmadan dalga (K-11) olmaz. Unattended provisioning ayrı FAZ'dır çünkü katılımlı akış (USB→script→bayi no) sahada kanıtlanmadan PXE/preseed otomasyonu ek yük ve ek arıza yüzeyi demektir. GEREKÇE: Sıra bağımlılıkları zorlar: Windows/donanım keşfi olmadan imaj (K-09), merkez altyapı olmadan enrollment/WG (K-05/K-17), bootstrap stabil olmadan medya (F2A→F2B), medya kanıtlanmadan LAB kabulü (F2B→F3), LAB kabulü olmadan dalga (K-11) olmaz. F2A/F2B ayrımı riskleri ayırır: F2A "kurucu idempotent ve kapıları çalışıyor" iddiasıdır (upstream ISO + NoCloud seed ile sınanır), F2B ise "tek USB Field OS ISO assisted kurar" iddiasıdır (remaster boot zinciri riski buradadır). F6'nın ayrı ve son FAZ olması, zero-touch/PXE'nin iptal edilebilir (proje yine de biter) ama atlanamaz (sırası gelmeden yapılmaz) olduğunu söyler.
ALTERNATİF: Unattended'i en başa koyma — temel akıştaki hata otomasyonla çarpılır; elendi. Tek-faz "hepsini yap" planı — kapısız ilerleme, filo-çapı risk; elendi. ALTERNATİF: Zero-touch PXE'i en başa koymak — kanıtlanmamış akışın otomasyonu hata çarpanı üretir, üstelik otomatik disk silme veri kaybı riski taşır → elendi (F6'ya taşındı). Tek-faz "hepsini yap" planı — kapısız ilerleme, filo-çapı risk → elendi. F2A/F2B'yi birleştirmek — medya riski ile kurucu riski aynı kapıya sıkışır ve hangi iddianın kanıtlandığı belirsizleşir → elendi.
RİSK: FAZ atlama baskısı (takvim); azaltma: her FAZ çıkışı 21'deki onay yetkilisinin imzasına bağlıdır. RİSK: FAZ atlama baskısı (takvim); azaltma: her FAZ çıkışı 21'deki onay yetkilisinin imzasına bağlıdır.
MALİYET: Ücretsiz (işgücü planlaması; VDS kira hariç). MALİYET: Ücretsiz (işgücü planlaması; VDS kira hariç).
LİSANS: Yok. LİSANS: Yok.
@ -30,25 +30,28 @@ LİSANS: Yok.
## 4. Neden Bu Karar? ## 4. Neden Bu Karar?
Klasik hata sırası tersine çevirmektir: önce otomasyon, sonra temel. Bu harita bilerek "sıkıcı" sırayı seçer — önce elle çalışan, sonra kendiliğinden çalışan. F6'nın ayrı FAZ olması, unattended'in iptal edilebilir (proje yine de biter) ama atlanamaz (sırası gelmeden yapılmaz) olduğunu söyler. Her FAZ bir karar grubunu hayata geçirir; harita ile 24-DECISION-LOG arasındaki bağ tabloyla sabittir. Klasik hata sırası tersine çevirmektir: önce otomasyon, sonra temel. Bu harita bilerek "sıkıcı" sırayı seçer — önce elle çalışan, sonra kendiliğinden çalışan. F6'nın ayrı FAZ olması zero-touch'un iptal edilebilir ama atlanamaz olduğunu söyler. F2A/F2B ayrımı ise aynı mantığı içeride uygular: **assisted** medya (teknisyen disk onayı verir, `interactive-sections: [storage]`) ile **unattended** medya (F6, operatör girdisi yok) karıştırılmaz. F2B'nin assisted kalması prompt'un "otomatik disk silme yok" şartının faz seviyesindeki karşılığıdır (K-20). Her FAZ bir karar grubunu hayata geçirir; harita ile 24-DECISION-LOG arasındaki bağ tabloyla sabittir.
## 5. Alternatifler ## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç | | Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---| |---|---|---|---|
| Unattended ilk FAZ | Erken otomasyon | Kanıtlanmamış akışın otomasyonu | Elendi (F6'ya taşındı) | | Zero-touch/PXE ilk FAZ | Erken otomasyon | Kanıtlanmamış akışın otomasyonu + otomatik disk silme riski | Elendi (F6'ya taşındı) |
| Tek F2 (bootstrap + medya birlikte) | Daha az faz | Kurucu ve boot zinciri riski aynı kapıda karışır | Elendi (F2A/F2B) |
| Az FAZ (3 büyük) | Kısa plan | Kapılar seyrek, risk büyük | Elendi | | Az FAZ (3 büyük) | Kısa plan | Kapılar seyrek, risk büyük | Elendi |
| Çok FAZ (12+) | Detaylı | Yönetim yükü, faz yorgunluğu | Elendi | | Çok FAZ (12+) | Detaylı | Yönetim yükü, faz yorgunluğu | Elendi |
## 6. Avantajlar ## 6. Avantajlar
- Her FAZ bağımsız kapanır; proje yarıda kalsa bile kazanım (örn. F1 merkezi) kalır. - Her FAZ bağımsız kapanır; proje yarıda kalsa bile kazanım (örn. F1 merkezi, F2A stabil kurucu) kalır.
- F6 opsiyoneldir: katılımlı akışla PROD bitebilir, unattended sonradan eklenir. - F6 opsiyoneldir: assisted akışla (F2B→F5) filo kurulabilir, zero-touch sonradan eklenir.
- Assisted/unattended ayrımı sayesinde "otomatik disk silme yok" güvenlik şartı faz yapısında görünür kalır.
## 7. Dezavantajlar ## 7. Dezavantajlar
- FAZ disiplini yavaş görünür; paydaşa "neden F6 en sonda" açıklaması gerekir (gerekçe §4'tür). - FAZ disiplini yavaş görünür; paydaşa "neden zero-touch en sonda" açıklaması gerekir (gerekçe §4'tür).
- FAZ'lar arası bekleme (gözetim süreleri) takvimi uzatır. - FAZ'lar arası bekleme (gözetim süreleri) takvimi uzatır.
- F2A ve F2B iki ayrı kapı demektir; tek kapılı plana göre bir faz daha fazla onay toplantısı gerekir.
## 8. Riskler ## 8. Riskler
@ -56,25 +59,35 @@ Klasik hata sırası tersine çevirmektir: önce otomasyon, sonra temel. Bu hari
|---|---|---|---| |---|---|---|---|
| FAZ çıkışı atlanır, sonrakine geçilir | Orta | Yüksek | Onay yetkilisi imzası zorunlu (21) | | FAZ çıkışı atlanır, sonrakine geçilir | Orta | Yüksek | Onay yetkilisi imzası zorunlu (21) |
| Merkez VDS donanımı yetersiz çıkar (F1) | Orta | Orta | LAB yük testi; servisleri ikinci VDS'e bölme planı (01) | | Merkez VDS donanımı yetersiz çıkar (F1) | Orta | Orta | LAB yük testi; servisleri ikinci VDS'e bölme planı (01) |
| Donanım profili imajı kırar (F2→F3) | Orta | Orta | F0 envanteri + F3 profil matrisi | | Windows keşfi eksik yapılır, geçişte sürpriz bağımlılık çıkar (F0→F3) | Orta | Yüksek | İş sahibi onayı + hash doğrulamalı geri dönüş imajı (28) |
| Donanım profili imajı kırar (F2B→F3) | Orta | Orta | F0 envanteri + F3 profil matrisi |
| Remaster Field OS ISO boot etmez (F2B) | Orta | Yüksek | UEFI/Legacy/Secure Boot matrisi; aksi halde F3 başlamaz ve upstream ISO + seed yolu sıcak tutulur (26) |
| F6'da otomatik provisioning veri siler | Düşük | Yüksek | F6 yalnız boş/teslim edilmiş disklere uygulanır; assisted (F2B) yol her zaman yedek |
## 9. Uygulama Planı ## 9. Uygulama Planı
| FAZ | Ad | Kararlar | Çıkış kriteri | Dokümanlar | | FAZ | Ad | Kararlar | Çıkış kriteri | Dokümanlar |
|---|---|---|---|---| |---|---|---|---|---|
| F0 | Hazırlık + donanım envanteri | K-12 (kimlik), K-09 (tarama-önce) | Profil listesi + envanter şeması onaylı | 02, 18 (FAZ 0), 00 | | F0 | Windows + donanım keşfi | K-12 (kimlik), K-19 (Windows envanteri), K-09 (tarama-önce) | Her saha PC için envanter (üretici/model/CPU/RAM/disk/NIC/GPU/seri no/bayi no) + Windows iş yükü ve geri dönüş imajı listesi onaylı | 02, 18, 28 |
| F1 | Merkez VDS kurulumu | K-04, K-05, K-06, K-07, K-08 | WG hub + hbbs/hbbr + MeshCentral + Semaphore + Prometheus/Kuma ayakta; L9 tatbikatı yapıldı | 06, 07, 08, 09, 14, 17-L9 | | F1 | Merkez altyapı | K-04, K-05, K-06, K-07, K-08 | WG hub + hbbs/hbbr + MeshCentral + Semaphore + Prometheus/Grafana/Kuma ayakta; L9 tatbikatı yapıldı | 06, 07, 08, 09, 14, 17-L9 |
| F2 | Golden image (katılımlı) | K-01, K-02, K-03, K-09, K-10, K-11 | `blueforce-install.sh` USB→READY akışı LAB donanımında çalışıyor; `unattended-upgrades` kapalı; tag/`unless-stopped` denetli | 03, 04, 05, 12 | | F2A | Blueforce bootstrap stabilizasyon | K-01, K-03, K-09, K-10, K-11, K-12, K-13, K-22 | `blueforce-install.sh` upstream ISO + NoCloud seed ile idempotent ve tekrar çalıştırılabilir; 18 modül ve `--check` kapıları yeşil; `bf-status`/`bf-enrollment-status` beklenen durumu yazıyor; xRDP kalıcı aktif (K-22) | 03, 04, 05, 06, 11, 12, 16 |
| F3 | LAB(2) + P1(5) | K-11 (dalga), K-14 (bundle), K-05 (kopma sayacı) | 3 akış yeşil; teknisyen desteksiz kurulum yaptı; kopma sayacı toplandı | 18, 19, 20, 10 | | F2B | Assisted Offline Field ISO | K-15, K-16, K-20, K-21, K-24 | Tek USB `Blueforce-Field-OS-<version>-amd64.iso` UEFI+Legacy'de boot ediyor; autoinstall çalışıyor; storage adımı operatör onayı bekliyor (otomatik silme yok); offline APT snapshot'tan kurulum bitiyor; firstboot bayi no alıp `PROVISIONED_OFFLINE` yazıyor; `bf-release` gerçek release kimliği döndürüyor | 05, 26, 27, 30 |
| F4 | Dalgalar P2/W1/W2 | K-11, K-06, K-07 | Her dalga çıkış kriteri + halt kaydı; destek yükü hedefte | 18, 21, 14 | | F3 | LAB (2 cihaz) | K-11, K-14, K-05, K-17, K-18, K-19, K-21, K-23 | 2 LAB cihazında uçtan uca: erişim + boot + update + enrollment + Windows geçiş kabulü + rollback yeşil; teknisyen desteksiz kurulum yapabildi; GNOME vs XFCE A/B ölçüm tablosu dolduruldu | 18, 19, 20, 10, 28, 29, 30, 31 |
| F5 | PROD (~523) + işletmeye alma | Tümü | Filo READY; rutinler (günlük/haftalık) devrede; `v1` doküman kesildi | 21, 22, 23 | | F4 | Dalgalar P1-P2-W1-W2 | K-11, K-06, K-07, K-14 | P1(5) → P2(20) → W1(50) → W2(100): her dalga çıkış kriteri + halt kaydı; destek yükü hedefte | 18, 21, 14 |
| F6 | Katılımsız provisioning (unattended) | K-09 (hedef) | Teknisyen girdisiz (veya minimum-girdili) kurulum P2 ölçeğinde kanıtlı; katılımlı akış yedekte duruyor | 04 (F6 eki) | | F5 | Production rollout (~523) + işletme/release yönetimi | Tümü | Filo READY; rutinler (günlük/haftalık), immutable release manifest ve rollback kanıtı devrede; `v1` doküman kesildi | 21, 22, 23, 30 |
| F6 | Gerçek zero-touch PXE | K-20 (unattended kolu), K-18 | YALNIZ PXE / tam unattended / uzaktan provisioning: ağ üzerinden operatör girdisi olmadan kurulum + otomatik disk yapılandırması; assisted (F2B) yolu geri dönüş olarak sıcak | 26, 30 |
F6 notu: F6, F5'ten sonra başlar; F5'in kapanmasına engel değildir. F6 kapsamı (PXE/preseed derinliği, ağ gereksinimleri) F3 verisine göre F4 içinde netleştirilir. F6 başarısız olursa proje F5 haliyle tamam sayılır. Akış/bağımlılık notları:
- F2A → F2B kapısı: "kurucu stabil" kanıtı yoksa tek-USB medya üretimi başlamaz (medya, kurucunun test ortamıdır; tersi değil).
- F2B → F3 kapısı: assisted tek-USB medya iki LAB cihazında (UEFI + Legacy) kanıtlanmazsa LAB kabulü başlamaz.
- F6, F5'ten sonra başlar ve F5'in kapanmasına engel değildir; F6 başarısız olursa proje F5 haliyle tamam sayılır ve assisted akış yedek kalır.
```bash ```bash
# örnek: FAZ kapısı kontrolü (merkez) # örnek: FAZ kapısı kontrolü (merkez)
# F3 çıkışı için LAB doğrulama seti # F2B/F3 çıkışı için LAB doğrulama seti
bf-release # gerçek release kimliği (skeleton değil)
bf-enrollment-status # PROVISIONED_OFFLINE → ENROLLED → READY
ansible-playbook playbooks/verify-lab.yml --limit lab ansible-playbook playbooks/verify-lab.yml --limit lab
bf-support-bundle --out /tmp/f3-gate-bundle.tar.gz bf-support-bundle --out /tmp/f3-gate-bundle.tar.gz
``` ```
@ -83,21 +96,27 @@ bf-support-bundle --out /tmp/f3-gate-bundle.tar.gz
| Test | Beklenen sonuç | Ortam | | Test | Beklenen sonuç | Ortam |
|---|---|---| |---|---|---|
| F0 Windows/donanım keşfi ve imaj listesi | Her cihaz için profil + geri dönüş imajı kaydı var | Saha/LAB |
| F1 L9 tatbikatı (boş VDS'e kur) | Tüm merkez servisleri ayağa kalkar | İzole VDS | | F1 L9 tatbikatı (boş VDS'e kur) | Tüm merkez servisleri ayağa kalkar | İzole VDS |
| F2 imaj denetim seti (K-10/K-11 kilitleri) | `latest` yok, timer'lar maskeli, politika `unless-stopped` | LAB(2) | | F2A kurucu idempotent çalışma | İkinci koşu hata üretmez, `--check` yeşil | LAB(2) |
| F3 3-akış + rollback | Erişim/boot/update + geri alma yeşil | LAB(2)/P1(5) | | F2B tek-USB assisted kurulum | UEFI + Legacy boot; storage onayı beklenir; `PROVISIONED_OFFLINE` | LAB(2) |
| F6 unattended prova | Minimum-girdi ile READY | P2 ölçeği | | F2B offline kurulum (air-gapped) | Paketler yerel snapshot'tan kurulur, dış indirme yok | LAB(2) air-gapped |
| F3 3-akış + enrollment + rollback | Erişim/boot/update + ENROLLED/READY + geri alma yeşil | LAB(2) |
| F3 GNOME vs XFCE A/B | 8 metrik tablosu dolar (RAM/CPU/boot/RDP/RustDesk/login/dummy/24-72h) | LAB(2) |
| F6 zero-touch PXE prova | PXE ile operatör girdisi olmadan READY'ye kadar akış | F5 sonrası LAB |
## 11. Rollback ## 11. Rollback
1. FAZ içi hata: ilgili dalga/iş 17-RECOVERY veya 10-UPDATE zinciriyle geri alınır; FAZ tekrarlanır. 1. FAZ içi hata: ilgili dalga/iş 17-RECOVERY veya 10-UPDATE zinciriyle geri alınır; FAZ tekrarlanır.
2. FAZ kararı yanlışsa (örn. imaj yöntemi): 24-DECISION-LOG PR'ı + bağımlı FAZ'ların tekrarı. 2. FAZ kararı yanlışsa (örn. medya veya masaüstü yöntemi): 24-DECISION-LOG PR'ı + bağımlı FAZ'ların tekrarı.
3. F6 geri alınırsa katılımlı akışa dönülür (yedek her zaman sıcak tutulur). 3. F2B geri alınırsa upstream ISO + NoCloud seed yoluna dönülür (bu yol her zaman sıcak tutulur).
4. F6 geri alınırsa assisted (F2B) akışa dönülür; PXE'siz filo işletilmeye devam eder.
## 12. Kontrol Listesi ## 12. Kontrol Listesi
- [ ] Her FAZ'ın çıkış kriteri + sahibi yazılı ve onaylı. - [ ] Her FAZ'ın çıkış kriteri + sahibi yazılı ve onaylı.
- [ ] F6'nın F5 sonrası ayrı FAZ olduğu paydaşça kabul edildi. - [ ] F2A/F2B ayrımı ve F2B'nin assisted (otomatik disk silme yok) olduğu paydaşça kabul edildi.
- [ ] F6'nın yalnız PXE / tam unattended / uzaktan provisioning olduğu ve F5 sonrası ayrı FAZ olduğu kabul edildi.
- [ ] Doküman-faz eşleşmesi (tablodaki son sütun) güncel. - [ ] Doküman-faz eşleşmesi (tablodaki son sütun) güncel.
- [ ] Onay yetkilisi (asıl+vekil) tanımlı (21). - [ ] Onay yetkilisi (asıl+vekil) tanımlı (21).
@ -105,7 +124,10 @@ bf-support-bundle --out /tmp/f3-gate-bundle.tar.gz
- [ ] FAZ tarihleri ve teknisyen kapasite planı (sahibi: yönetim + 18). - [ ] FAZ tarihleri ve teknisyen kapasite planı (sahibi: yönetim + 18).
- [ ] F1 VDS donanım siparişi için LAB yük ölçümü (sahibi: 14-MONITORING yazarı). - [ ] F1 VDS donanım siparişi için LAB yük ölçümü (sahibi: 14-MONITORING yazarı).
- [ ] F6 kapsamı (PXE şart mı, USB-preseed yeter mi) — F3 verisi sonrası (sahibi: 04 yazarı). - [ ] F2B gerçek ISO build'i ve UEFI/Legacy/Secure Boot matrisi — `provisioning/iso/build-blueforce-iso.sh` bugün iskelet (sahibi: provisioning/release yöneticisi).
- [ ] F2B offline APT snapshot için somut paket pinleri (base-packages/remote-access/docker manifestleri) (sahibi: release yöneticisi).
- [ ] F0 Windows keşif kapsamı: hangi uygulama/veri sınıfları zorunlu (sahibi: iş sahibi + 28 yazarı).
- [ ] F6 kapsamı (PXE şart mı, USB-preseed yeter mi, otomatik disk yapılandırması hangi onayla) — F5 verisi sonrası (sahibi: 04 yazarı).
--- ---
@ -113,10 +135,12 @@ bf-support-bundle --out /tmp/f3-gate-bundle.tar.gz
```mermaid ```mermaid
flowchart LR flowchart LR
F0["F0 Hazırlık<br/>+ envanter"] --> F1["F1 Merkez VDS"] F0["F0 Windows +<br/>donanım keşfi"] --> F1["F1 Merkez<br/>altyapı"]
F1 --> F2["F2 Golden image<br/>(katılımlı)"] F1 --> F2A["F2A Blueforce<br/>bootstrap stabil."]
F2 --> F3["F3 LAB 2 + P1 5"] F2A --> F2B["F2B Assisted Offline<br/>Field OS ISO"]
F3 --> F4["F4 P2/W1/W2"] F2B --> F3["F3 LAB<br/>2 cihaz"]
F4 --> F5["F5 PROD + işletme"] F3 --> F4["F4 P1-P2-W1-W2"]
F5 --> F6["F6 Unattended<br/>(ayrı FAZ)"] F4 --> F5["F5 Production<br/>rollout ~523"]
F5 --> F6["F6 Zero-touch PXE<br/>(ayrı FAZ)"]
F2B -. "geri dönüş" .-> F0
``` ```

View file

@ -0,0 +1,153 @@
# 26 — Blueforce Field OS ISO
> Kısa özet: Blueforce Field OS, Ubuntu LTS tabanında GNOME katmanı olan saha standardıdır. **Autoinstall kurulum motorudur; Blueforce Field OS ISO ise paketleme/dağıtım yöntemidir** — ikisi rakip değil, birlikte kullanılır (K-20). **V1 kurulum medyası TEK USB Field OS ISO'dur** (medya kökünde `autoinstall.yaml` + offline APT repo + firstboot; `interactive-sections: [storage]` korunur, otomatik disk silme yoktur). Resmi upstream ISO + ayrı NoCloud seed USB, tek yol değil **geri dönüş/fallback yolu** olarak sıcak tutulur (K-15 yedek yol olarak korunur). ISO ilk kez gerçekten üretilmiştir: `dist/Blueforce-Field-OS-1.0.0-amd64.iso` (6 481 917 952 byte, ISO SHA256 `cb0cc56f…d431296`); boot kabulü (UEFI/Legacy/Secure Boot) ve air-gapped kurulum hâlâ LAB işidir.
>
> - Dosya: `docs/26-BLUEFORCE-FIELD-OS-ISO.md`
> - İlgili kararlar: `24-DECISION-LOG.md#K-20` (medya stratejisi), `#K-15` (yedek medya yolu), `#K-16`/`#K-21` (offline + durum modeli), `#K-01`, `#K-02`, `#K-03`, `#K-09`
> - Durum: [ ] Taslak — ISO artefaktı üretildi; boot ve air-gapped kurulum kabulü LAB ile yapılmalıdır
---
## 1. Amaç
Field OS'un ne olduğunu, hangi ISO'nun sahaya gideceğini ve ISO'nun hangi sınırlar içinde üretilebileceğini sabitlemek.
## 2. Kapsam
- Kapsam içi: V1 tek-USB Field OS ISO, autoinstall motoru, offline APT repo, firstboot, NoCloud seed (geri dönüş yolu), medya bütünlüğü, üretilen artefaktın doğrulama kaydı ve LAB kabulü.
- Kapsam dışı: cihaz kimliği/enrollment işlemi (29), offline paket seti içeriği (27), sürüm yayını (30), PXE/tam unattended provisioning (F6, 25).
## 3. Kararlar
```text
KARAR: V1 kurulum medyası TEK USB Blueforce Field OS ISO'dur: `dist/Blueforce-Field-OS-<version>-amd64.iso`. ISO kökünde `/autoinstall.yaml` (kurulum motoru), `/blueforce-provisioning/` (autoinstall seed kopyası, firstboot, offline-repo araçları/pinleri, release) bulunur; boot konfigürasyonu her kernel satırında autoinstall ister. Autoinstall ile custom ISO birbirinin alternatifi DEĞİL tamamlayıcısıdır (K-20): ISO, autoinstall motorunu paketler. Güvenlik için `interactive-sections: [storage]` KORUNUR — kurulum katılımsız ilerler ama diski operatör seçip onaylar; otomatik disk silme YOKTUR. Resmi Ubuntu ISO + ayrı NoCloud seed USB, V1'in tek yolu değil geri dönüş/fallback yoludur ve her zaman sıcak tutulur (K-15 bu rolde korunur).
GEREKÇE: Tek USB, saha teknisyeni için tek medya demektir (yanlış seed takma hatası ortadan kalkar) ve offline APT repo ile firstboot aynı medyada taşınır. Subiquity resmi referansı autoinstall yapılandırmasını `/autoinstall.yaml` yolundan okur ("irrespective of how it was provided") ve `interactive-sections`'ı resmi olarak destekler; `apt.fallback` varsayılanı `offline-install`'dır. Remaster boot zinciri ise LAB kabulüne bağlanmıştır: ISO üretilse bile UEFI/Legacy/Secure Boot ve air-gapped kurulum kanıtı gelmeden saha medyası ilan edilmez.
ALTERNATİF: Yalnız upstream ISO + NoCloud seed USB — tek-medya deneyimi yok; V1'in tek yolu olmaktan çıktı, geri dönüş yolu olarak kalır. Yalnız remaster ISO + LAB kanıtsız saha — boot zinciri kanıtlanmadan dağıtım riski nedeniyle reddedildi. Otomatik disk wipe'lı unattended ISO — veri kaybı riski nedeniyle yasak (F6 kapsamı, onay kapılı).
RİSK: Remaster boot zinciri (UEFI/Legacy/Secure Boot) veya air-gapped kurulum kanıtlanmazsa medya sahaya çıkamaz; azaltma: F2B çıkış kapısı (iki LAB cihazında assisted kurulum kanıtı), aksi halde upstream ISO + seed yoluna dönülür. İkinci risk: ISO'ya secret/kimlik sızması; azaltma: builder'ın giriş taraması + artefakt secret/cihaz verisi taraması.
MALİYET: Ücretsiz.
LİSANS: Ubuntu resmi ISO — https://releases.ubuntu.com/26.04/ ; Subiquity autoinstall — https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-reference.html (doğrulama: 2026-09-16, HTTP 200); cloud-init NoCloud — https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html (doğrulama: 2026-09-16); xorriso (El Torito recipe) — https://www.gnu.org/software/xorriso/ .
```
> **Güncelleme (2026-09-16, K-20/K-21):** Bu dosyanın eski §3/§5/§12 ifadeleri ("V1 kurulum medyası upstream ISO + ayrı seed USB'dir", "yalnız custom remaster ISO V1 için elendi") geçersizdir. Kanonik karar: **V1 = tek USB Field OS ISO (autoinstall motoru olarak kullanılır); upstream ISO + seed = yedek/geri dönüş yolu.** Ayrıca ISO artık **gerçekten üretilmiştir** (bkz. §9 "Üretilen artefakt"). Ayrıntı: `24-DECISION-LOG.md#K-20`.
## 4. Neden Bu Karar?
Autoinstall kurulum otomasyonudur; Field OS ISO ise dağıtım **paketleme** biçimidir — ISO, autoinstall'ın yerine geçmez, onu taşır. Tek medya saha değişkenlerini (offline repo, firstboot, release manifesti) tek taşıma noktasında birleştirir ve yanlış seed takma hatasını ortadan kaldırır; cihaz-spesifik veri (bayi no, key, token) yine medyada tutulmaz, firstboot'ta toplanır. Ayrı seed USB ise tamamen kaldırılmaz: ISO boot etmezse aynı sonucu veren sıcak geri dönüş yoludur.
## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---|
| Tek USB Field OS ISO (remaster, autoinstall + offline repo + firstboot) | Tek medya; teknisyen hatası azalır; offline içerik yanında | UEFI/Legacy/Secure Boot LAB yükü | **V1 seçildi (F2B)** |
| Upstream ISO + NoCloud seed USB | En az özel build; imzalı upstream taban | İki medya taşınır | Geri dönüş/fallback yolu (sıcak) |
| Yalnız remaster ISO (LAB kanıtı olmadan) | — | Boot zinciri kanıtlanmamış medya sahaya çıkar | Reddedildi |
| Clonezilla | Hızlı | Donanım-fragil | Yedek yöntem |
## 6. Avantajlar
- İmzalı upstream taban; değişiklik alanı ISO kökü + `/blueforce-provisioning/` ile sınırlı, mevcut boot dosyaları (boot.catalog, eltorito.img, casper/*) bayt-özdeş bırakılır.
- Field OS sürümü, medya manifesti (`dist/<version>-manifest.yaml`), release manifesti ve kurucu sürümü ayrı izlenebilir.
- Offline APT repo + firstboot + release dosyaları aynı medyada; internet olmayan sahada tek USB yeter (K-16).
## 7. Dezavantajlar
- Remaster boot zinciri UEFI/Legacy/Secure Boot'ta kanıtlanmadan medya sahaya çıkamaz; bu, F2B çıkış kapısını bloke edebilir.
- Medya tek noktada tüm saha değişkenlerini taşır: bozuk/kopyalanmış ISO yanlış sürüm yayar → SHA256 doğrulaması ve sürüm etiketi zorunludur.
- LAB medyası şu an Ubuntu **Desktop** flavour'ıyla üretildi; baseline (Server) ile sapma manifestte açıkça kayıtlıdır (bkz. §9).
## 8. Riskler
| Risk | Olasılık | Etki | Azaltma |
|---|---|---|---|
| 26.04 autoinstall davranışı değişir | Orta | Yüksek | Her sürüm LAB kabulü |
| Remaster boot etmez (UEFI/Legacy/Secure Boot) | Orta | Yüksek | UEFI/Legacy/Secure Boot matrisi; aksi halde medya yayınlanmaz, upstream ISO + seed yoluna dönülür |
| ISO'ya secret/kimlik girer | Düşük | Yüksek | Builder giriş taraması + artefakt secret ve cihaz verisi taraması |
| Medya tabanı sapması (Desktop flavour) sahaya fark edilmeden çıkar | Orta | Orta | `manifest.yaml` içinde `base_flavor` + `baseline_deviation` bloğu, `media-info.yaml` ve `/etc/blueforce-release` aynasında; işletim öncesi Server ISO ile yeniden build kararı |
| Offline repo medyaya gömülmediği için kurulum `OFFLINE BLOCKED` ile durur | Yüksek (mevcut durum) | Orta | `offline_repo_status: not-included` açıkça raporlanır; `--require-offline-repo` release build'lerinde build'i durdurur |
## 9. Uygulama Planı
1. Base ISO'nun SHA256 bilgisini Canonical kaynak kaydından al ve yerel yayın kaydına yaz (`provisioning/iso/ubuntu-26.04.1-SHA256SUMS`).
2. Medyayı üret (`provisioning/iso/build-blueforce-iso.sh`); üretim sırasında girilen `autoinstall.yaml` + firstboot + offline-repo + release payload'ları medyaya yazılır, `interactive-sections: [storage]` ve `apt.fallback: offline-install` işaretleri korunur.
3. Medyaya bayi no, WireGuard private key, enrollment token veya SSH private key KOYMA; builder giriş taraması bunları bulursa durur.
4. Kurulumdan sonra cihaz `PROVISIONED_OFFLINE` olur; merkezi doğrulama tamamlanmadan `READY` olmaz (K-21).
5. Release yolunda medya manifesti + checksum + test raporu üretilir; cihaz verisi taşınmaz.
```bash
# medyayı üret (base ISO ve checksum kaydı yerelde olmalı; hiçbir şey indirilmez)
provisioning/iso/build-blueforce-iso.sh \
--upstream-iso /media/ubuntu-26.04.1-desktop-amd64.iso \
--checksum-file provisioning/iso/ubuntu-26.04.1-SHA256SUMS
# üretilen artefaktı YAZMADAN doğrula (checksum + El Torito metadata)
provisioning/iso/test-iso.sh dist/Blueforce-Field-OS-1.0.0-amd64.iso
sha256sum -c dist/Blueforce-Field-OS-1.0.0-amd64.iso.sha256
```
### Üretilen artefakt (2026-09-16 — gerçek üretim, mock değil)
| Alan | Değer |
|---|---|
| ISO | `dist/Blueforce-Field-OS-1.0.0-amd64.iso` |
| ISO boyutu | 6 481 917 952 byte (~6.48 GB, 6.04 GiB) |
| ISO SHA256 | `cb0cc56f0442fbd1418e0d0f35521522f4ec9aa3f203779a67e96d587d431296` |
| Checksum dosyası | `dist/Blueforce-Field-OS-1.0.0-amd64.iso.sha256` (aynı değer, yerelde `sha256sum -c` ile doğrulandı) |
| Manifest | `dist/1.0.0-manifest.yaml` (`release_status: lab-artifact`, `build_date: 2026-09-16T08:00:29Z`) |
| Base medya | Ubuntu 26.04.1 **desktop** amd64 (`ubuntu-26.04.1-desktop-amd64.iso`, SHA256 `601e30fbf5d97759367c632e2c33630665039b7e2158fd068403da3ccf1bda1f` — build host'ta yeniden hesaplanıp doğrulandı) |
| Boot | El Torito **BIOS + UEFI** girdileri base ISO'dan korunmuş (builder build sonrası xorriso ile yeniden doğrular; eksikse artefaktı siler) |
| Live içerik bütünlüğü | `casper/minimal.squashfs` 3 432 136 704 byte ve SHA256 `b5ec27b9570e77abb62831a8b652b50eaf089d537e62046189a52231a4fa9bec` — base ISO'daki dosyayla **birebir aynı** |
| Autoinstall | Medya kökünde `/autoinstall.yaml`; `interactive-sections: [storage]` aktif, `apt_fallback: offline-install` |
| Payload | `/blueforce-provisioning/{autoinstall,firstboot,offline-repo,release}` medyada mevcut |
| Secret durumu | `device_identity_embedded: false`, `automatic_disk_wipe: false`; kimlik firstboot'ta toplanır |
| Baseline sapması | Medya **Desktop** flavour ile üretildi (baseline Server). `manifest.yaml` → `baseline_deviation` bloğu: gerekçe (build host'ta checksum'ı doğrulanmış tek medya Desktop ISO'ydu, build indirme yapmaz), etki (GNOME zaten gelir), gereken karar (LAB için Desktop tabanını kabul et **veya** üretim öncesi Server ISO ile yeniden build) |
| Henüz yapılmayanlar | UEFI/Legacy/Secure Boot boot kabulü, Secure Boot imza davranışı, air-gapped tam kurulum, offline APT repo gömülmesi (`offline_repo_status: not-included`, pinler `UNPINNED`) — hepsi LAB/F2B işi |
## 10. Test Planı
| Test | Beklenen sonuç | Ortam |
|---|---|---|
| Base ISO checksum | Yayın kaydıyla eşleşir (`601e30fb…`) | LAB(2) |
| Üretilen ISO checksum | `sha256sum -c dist/*.iso.sha256` geçer | LAB(2)/CI |
| ISO içerik bütünlüğü | `casper/*` dosyaları base ISO ile bayt-özdeş; `boot.catalog`/`eltorito.img` korunmuş | CI (xorriso) + LAB |
| Tek-USB assisted kurulum | Kimliksiz sistem kurulur; storage adımı operatör onayı bekler, otomatik silme yok | LAB(2) UEFI+Legacy |
| Field OS ISO UEFI/Legacy/Secure Boot | Her desteklenen profil boot eder | Zorunlu LAB (F2B çıkış kapısı) |
| Air-gapped kurulum (offline repo gömülüyken) | Dış indirme yok; `PROVISIONED_OFFLINE` | LAB(2) air-gapped |
| Geri dönüş yolu (upstream ISO + seed) | Aynı sonucu verir, sıcak tutulur | LAB(2) |
| Artefact taraması | Cihaz kimliği/secret yok; `device_identity_embedded: false` | Build CI + LAB |
## 11. Rollback
1. Field OS ISO başarısızsa (boot etmez, autoinstall almaz) derhal upstream ISO + son onaylı seed USB yoluna dönülür; bu yol her zaman sıcak tutulur (K-20).
2. Hatalı ISO/seed geri çekilir, önceki onaylı medya sürümü (sürüm etiketli USB) kullanılır.
3. Cihaz kurulmuş fakat kabul edilmemişse 27/29 prosedürüyle yeniden provision edilir.
## 12. Kontrol Listesi
- [ ] Base ISO checksum ve kaynak URL kaydı var; üretilen ISO checksum'ı yayımlanmış.
- [ ] Medya genel yapılandırma dışında cihaz verisi/secret içermiyor (kimlik firstboot'ta).
- [ ] V1 etiketi **tek USB Field OS ISO** olarak basıldı; upstream ISO + seed geri dönüş yolu olarak hazır.
- [ ] Medyada `interactive-sections: [storage]` aktif, otomatik disk silme yok.
- [ ] UEFI/Legacy/Secure Boot LAB kabulü tamam (F2B çıkış kapısı) — tamamlanmadan saha dağıtımı yok.
- [ ] Medya tabanı sapması (Desktop flavour) kayıtlı ve işletim öncesi karar verilmiş.
- [ ] Offline APT repo gömülü (yoksa `OFFLINE BLOCKED` davranışı bilerek kabul edilmiş ve release build'i bloke ediyor).
## 13. Açık Sorular
- [ ] Ubuntu 26.04.1'in kesin autoinstall/subiquity davranışı ve Desktop flavour'da autoinstall yolu — LAB (sahibi: platform ekibi) [K-20].
- [ ] Desteklenecek Legacy BIOS donanım profilleri — sahibi: envanter ekibi.
- [ ] UEFI/Legacy/Secure Boot boot kabul matrisi ve Secure Boot imza davranışı — F2B (sahibi: provisioning/release yöneticisi) [K-20].
- [ ] Medya tabanı: Desktop flavour kabul mü, Server ISO ile yeniden build mi — F2B çıkış kapısı (sahibi: release yöneticisi) [K-01/K-20].
- [ ] Offline APT repo'nun medyaya gömülmesi (bugün `offline_repo_status: not-included`) — F2B (sahibi: release yöneticisi) [K-16/K-20].
---
## Ek: Medya ilişkisi (K-20)
```mermaid
flowchart LR
CUSTOM["V1 medya: TEK USB Field OS ISO<br/>/autoinstall.yaml (motor) + /blueforce-provisioning/<br/>offline APT + firstboot + release<br/>interactive-sections: [storage]"] --> OS["Kimliksiz Ubuntu zemin<br/>(operatör disk onayı, otomatik silme YOK)"]
OS --> P["PROVISIONED_OFFLINE"]
P --> EN["ENROLLED (token) → READY (merkezi kanıt)"]
UP["Geri dönüş yolu: upstream Ubuntu ISO"] --> OS
SEED["NoCloud seed USB (user-data + meta-data)"] --> OS
UP -. "F2B geri alınırsa sıcak yol" .-> CUSTOM
```

View file

@ -0,0 +1,116 @@
# 27 — Offline Provisioning
> Kısa özet: İnternet yokken Field OS kurulumu ve yerel paket yüklemesi yapılabilir. Bu yol yalnız `PROVISIONED_OFFLINE` durumuna getirir; cihaz, tek kullanımlık enrollment token ve merkezi kanallar doğrulanmadan `ENROLLED` veya `READY` olmaz.
>
> - Dosya: `docs/27-OFFLINE-PROVISIONING.md`
>
> **Uygulama sözleşmesi:** `--offline`, varsayılan `/opt/blueforce/offline-repo` içindeki yalnız yerel `file:` APT kaynağını kullanır; uzak APT source, curl, wget, key download veya `apt download` çağrısı yapmaz. `Packages`, `Packages.gz`, `SHA256SUMS`, `packages.lock.tsv` ve her manifest için somut `package=version` pinleri eksikse paket modüllerinden önce `OFFLINE BLOCKED` ile durur. Bu repoda gerçek paket/pin bulunmadığından üretilen ISO LAB-only'dir ve provision edilmiş olarak temsil edilmez.
> - İlgili kararlar: `24-DECISION-LOG.md#K-16`, `#K-09`, `#K-11`, `#K-12`
> - Durum: [ ] Taslak
---
## 1. Amaç
Saha interneti kesik veya güvenilmezken tekrarlanabilir, denetlenebilir ve secretsiz ilk kurulumu tanımlamak.
## 2. Kapsam
- Kapsam içi: seed USB, offline APT repository/snapshot, kurucu, durum damgası ve sonradan enrollment.
- Kapsam dışı: merkezi peer atama ve token doğrulama (29), release imzalama (30).
## 3. Kararlar
```text
KARAR: Offline kurulum, imzalı/sürümlü medya ve ayrı offline APT kaynağıyla yapılır; sonuç durumu yalnızca PROVISIONED_OFFLINE'dır.
GEREKÇE: Paket indirme veya merkezi API'ye erişim yokken cihaz standarda getirilebilir; fakat merkezde doğrulanmamış kimlik ve kanallar READY iddiası yapamaz.
ALTERNATİF: İnternet gelene kadar kurulum yapmama — saha ziyareti ve bekleme maliyeti nedeniyle elendi.
RİSK: Eski veya karışık paket snapshot'ı; manifest, checksum ve release kimliği ile azaltılır.
MALİYET: Ücretsiz.
LİSANS: APT — https://manpages.ubuntu.com/manpages/noble/en/man8/apt.8.html ; dpkg — https://manpages.ubuntu.com/manpages/noble/en/man1/dpkg.1.html ; Ubuntu offline kurulum ayrıntısı 26.04 için LAB doğrulamalıdır.
```
## 4. Neden Bu Karar?
Offline medya, işletim sistemi ve zorunlu paketleri taşır; merkezi sırları taşımamalıdır. Durum makinesi, "kuruldu" ile "merkezce güvenildi" arasındaki farkı görünür yapar.
## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---|
| Sürümlü offline APT repo | Tekrarlanabilir | Medya yönetimi | **Seçildi** |
| Rastgele `.deb` dosyaları | İlk anda kolay | Bağımlılık/sürüm belirsiz | Elendi |
| İnternet bekleme | Basit | Saha blokajı | Elendi |
## 6. Avantajlar
- Bağlantısız sahada standarda yakın kurulum.
- Paket manifesti ile hangi sürümün yüklendiği kanıtlanır.
## 7. Dezavantajlar
- Medya sürümü ve paket snapshot'ı düzenli yenilenmelidir.
- Offline cihaz merkezi izleme ve erişim kanalları doğrulanana kadar operasyonel olarak hazır değildir.
## 8. Riskler
| Risk | Olasılık | Etki | Azaltma |
|---|---|---|---|
| Eski güvenlik paketi | Orta | Orta | Süre sonu olan release medyası, hızlı enrollment kuyruğu |
| Secret USB'ye yazılır | Düşük | Yüksek | Secret taraması; token yalnız bir kez ve cihazda girilir |
| Yarım paket işlemi | Orta | Orta | `dpkg --configure -a`, idempotent kurucu |
## 9. Uygulama Planı
1. Medya manifesti: Field OS release, ISO checksum, seed checksum, APT snapshot kimliği, kurucu commit'i.
2. Kurulumda yalnız genel paketler ve `blueforce-install.sh --offline` kullanılır.
3. Kurucu yerel durum dosyasına `PROVISIONED_OFFLINE` yazar; `READY` yazması yasaktır.
4. İnternet geldiğinde 29'daki tek-kullanımlık token ile enrollment başlatılır; sonra merkezi kanallar test edilir.
```bash
sudo mount /dev/sdX1 /mnt/blueforce-repo
sudo apt -o Dir::Etc::sourcelist=/mnt/blueforce-repo/sources.list update
sudo dpkg --configure -a
sudo ./blueforce-install.sh --offline --dealer-id 12010193
bf-status # PROVISIONED_OFFLINE beklenir
```
## 10. Test Planı
| Test | Beklenen sonuç | Ortam |
|---|---|---|
| İnternetsiz kurulum | Paketler manifestten kurulur | LAB(2) air-gapped |
| DNS/WAN kesik | Kurucu dış indirme istemez | LAB(2) |
| `bf-status` | `PROVISIONED_OFFLINE`; READY değil | LAB(2) |
| Bağlantı sonrası enrollment | ENROLLED → READY kapıları çalışır | LAB(2) |
## 11. Rollback
1. Paket işlemi bozuksa aynı onaylı snapshot ile `dpkg --configure -a` ve kurucu yeniden çalıştırılır.
2. Yanlış medya sürümü geri çekilir; önceki onaylı medya manifestiyle yeniden kurulur.
3. Kimlik/token hatasında cihaz resetlenmez; 29'daki token iptal/yenileme prosedürü uygulanır.
## 12. Kontrol Listesi
- [ ] Offline medya manifesti ve checksum'ları var.
- [ ] Medyada cihaz kimliği, private key veya kalıcı token yok.
- [ ] `PROVISIONED_OFFLINE` görünür ve READY kapısı kapalı.
- [ ] Sonradan enrollment sahibi/kuyruğu kayıtlı.
## 13. Açık Sorular
- [ ] Offline APT snapshot yenileme sıklığı — sahibi: release yöneticisi.
- [ ] Medyanın fiziksel zimmet ve imha süreci — sahibi: saha operasyonu.
---
## Ek: Durum akışı
```mermaid
flowchart LR
M["ISO + seed + offline APT"] --> P["PROVISIONED_OFFLINE"]
P --> T["Tek kullanımlık token"]
T --> E["ENROLLED"]
E --> V["WG + erişim + izleme doğrulama"]
V --> R["READY"]
```

View file

@ -0,0 +1,145 @@
# 28 — Windows'tan Linux'e Geçiş
> Kısa özet: Windows saha cihazının Blueforce Field OS'a kontrollü dönüşümü: envanter, uygulama/veri kabulü, geri dönüş noktası, temiz kurulum ve teslim doğrulaması. Windows image'ı geri dönüş kanıtıdır; Linux kurulumu onu otomatik silmez.
>
> - Dosya: `docs/28-WINDOWS-TO-LINUX-MIGRATION.md`
> - İlgili kararlar: `24-DECISION-LOG.md#K-19`, `#K-01`, `#K-09`, `#K-11`
> - Durum: [ ] Taslak
---
## 1. Amaç
Windows'tan geçişte veri, uygulama bağımlılığı veya geri dönüş kanıtını kaybetmeden saha cihazını Field OS standardına almak.
## 2. Kapsam
- Kapsam içi: ön envanter (araçlar: `scripts/migration/windows/`), kabul kaydı, Windows geri dönüş imajı, Field OS kurulumu, fonksiyonel kabul, araç adı/konum eşlemesi.
- Kapsam dışı: Windows uygulamasını Wine/VM ile çalıştırma kararı; bu ancak ayrı kabul sonrası ele alınır.
## 3. Kararlar
```text
KARAR: Her Windows cihazı önce envanter ve geri dönüş kanıtı alır, sonra temiz Field OS kurulumu yapılır; Windows üzerinde yerinde yükseltme veya çift-boot V1 standardı değildir.
GEREKÇE: Temiz kurulum, taşınan Windows drift'ini ve lisans/driver belirsizliğini azaltır. Geri dönüş imajı kabul başarısızlığında hızlı geri sağlar.
ALTERNATİF: Yerinde dönüşüm/dual-boot — disk bölümü, bootloader ve işletim sorumluluğunu büyüttüğü için elendi.
RİSK: Gizli uygulama/veri bağımlılığı; saha kabul listesi ve imaj alma kapısıyla azaltılır.
MALİYET: Ücretsiz araçlar kullanılabilir; yedek disk/depoya altyapı maliyeti uygulanabilir.
LİSANS: Microsoft Windows geçiş lisans durumu kurum tarafından doğrulanır; Ubuntu — https://ubuntu.com/download/server ; Clonezilla — https://clonezilla.org/ (LAB gerekli).
```
## 4. Neden Bu Karar?
İşletim sistemi dönüşümü yalnız disk yazma işlemi değildir. Önce çalışan iş akışı ve geri dönüş seçeneği belirlenir; Field OS ancak kabul senaryosu geçince kalıcı teslim sayılır.
## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---|
| Envanter + image + temiz kurulum | Deterministik | İlk adım daha uzun | **Seçildi** |
| Yerinde yükseltme | Az ilk iş | Desteklenmeyen drift | Elendi |
| Dual-boot | Anında geri dönüş | Boot/disk karmaşıklığı | Elendi |
## 6. Avantajlar
- Her geçiş denetlenebilir ve geri alınabilir.
- Linux kabulü gerçek saha işi üzerinden ölçülür.
## 7. Dezavantajlar
- Image depolama ve saklama süresi gerekir.
- Uygulama sahibi kabul vermeden cihaz üretime çıkamaz.
## 8. Riskler
| Risk | Olasılık | Etki | Azaltma |
|---|---|---|---|
| Veri kaybı | Düşük | Yüksek | İmaj hash + restore prova |
| MEG/çevre birimi uyumsuzluğu | Orta | Yüksek | 13 kabul testi + LAB profil matrisi |
| Yanlış cihaz silinir | Düşük | Yüksek | `BF-<no>` ve seri no çift doğrulama |
## 9. Uygulama Planı
1. Windows cihaz seri no, bayi no, disk durumu, çevre birimi ve kritik iş akışını kaydet.
2. Onaylı araçla geri dönüş imajı al, hash doğrula, saklama kaydı oluştur.
3. İş sahibi kesinti penceresini onaylar; ardından Field OS temiz kurulumu ve 27/29 akışı uygulanır.
4. 13-MEG kabulü, remote erişim, güç dönüşü ve monitoring doğrulanır; yalnız `READY` sonrası teslim yapılır.
```bash
# Linux kurulum USB'sinden önce: imaj aracının kendi hash doğrulaması kayıt altına alınır.
# Kurulumdan sonra:
bf-status
systemctl is-active docker wg-quick@wg0
```
### 9.1 Araç konumu (gerçek durum)
Windows ön-geçiş araçları repo kökündeki `migration/` altında DEĞİL, **`scripts/migration/windows/`** altındadır (operasyonel scriptlerin tamamı `scripts/{install,maintenance,diagnostics,recovery,migration}` düzeninde tutulur):
| İstenen ad/yol (brief) | Depodaki gerçek ad/yol | Not |
|---|---|---|
| `migration/windows/` | `scripts/migration/windows/` | Kökteki `migration/README.md` yalnız **işaret dosyasıdır** (pointer); aynı aracın iki gerçek kopyası drift üretir ve `tests/check-migration-static.sh` tek kanonik dizini zorunlu tutar |
| `bf-win-inventory.ps1` | `scripts/migration/windows/BF-WindowsPreMigrationInventory.ps1` | Kimlik/donanım/OS/disk blokları |
| `bf-win-network-export.ps1` | aynı script (`network` + `serial_ports` + `usb` bölümleri) | Ayrı dosya değil, birleştirildi |
| `bf-win-software-export.ps1` | aynı script (`installed_software`/`services`/`scheduled_tasks`/`drivers`/`docker`/`vpn`/`remote_access`/`meg`; `-NoSoftware` ile atlanabilir) | Ayrı dosya değil, birleştirildi |
| `bf-win-device-export.ps1` | `scripts/migration/windows/BF-WindowsDataExport.ps1` | `BF-<no>-windows-inventory-export.json/.md` üretir; hiçbir veri kopyalamaz (metadata-only) |
Kanonik eşleme/ayrıntı: `scripts/migration/windows/README.md` (yetkili ad sapma kaydı), konum denetim kaydı: `docs/32-REPOSITORY-AUDIT-AND-CONFLICTS.md` §B-6. **Dosyalar yeniden adlandırılmaz**; eşleme tablosu tek doğruluk kaynağıdır.
```powershell
# LAB/atölye: araçları bu yoldan çalıştır (yönetici PowerShell)
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\migration\windows\BF-WindowsPreMigrationInventory.ps1 -DealerId 12010193 -OutputDirectory C:\Blueforce\migration
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\migration\windows\BF-WindowsDataExport.ps1 -DealerId 12010193 -OutputDirectory C:\Blueforce\migration
```
### 9.2 Araçların sınırları
- İki araç da **okuma-only**'dir: veri kopyalamaz, disk bölmez, Windows ayarı değiştirmez, credential toplamaz (`tests/check-migration-static.sh` mutasyon/credential yasağını denetler).
- Çıktı `BF-<no>` etiketiyle dosyalanır ve iş sahibi onayına eklenir; çıktıda private key/token/parola bulunmaz.
- Linux tarafındaki karşılığı `scripts/diagnostics/bf-live-hw-check` (kurulum sonrası salt-okuma donanım kapısı) ve `bf-hardware-inventory.sh`'tir.
## 10. Test Planı
| Test | Beklenen sonuç | Ortam |
|---|---|---|
| Windows image restore provası | Kaynak iş akışı geri gelir | LAB temsilci cihaz |
| Field OS temiz kurulum | PROVISIONED_OFFLINE veya ENROLLED | LAB(2) |
| Ön-geçiş araçları (okuma-only) | Envanter + veri manifesti üretilir; mutasyon/credential yok | LAB temsilci cihaz |
| Araç yolu denetimi | Araçlar `scripts/migration/windows/` altından koşar; kökteki `migration/` yalnız pointer | CI (`check-migration-static`) |
| MEG/periferik kabul | 13 kriterleri geçer | Pilot |
| Geri dönüş kararı | Tanımlı RTO içinde uygulanır | Pilot tatbikat |
## 11. Rollback
1. Field OS kabulü başarısızsa cihaz üretime verilmez.
2. Onaylı Windows image geri yüklenir, hash ve iş akışı doğrulanır.
3. Hata kaydı cihaz profiline eklenir; düzeltilmiş Field OS release'i LAB'dan tekrar geçmeden yeni deneme yapılmaz.
## 12. Kontrol Listesi
- [ ] Windows envanteri ve iş sahibi onayı var.
- [ ] Geri dönüş image'ı hash ile doğrulandı.
- [ ] Cihaz/seri no iki kişi veya iki kayıtla eşleştirildi.
- [ ] Field OS yalnız kabul + READY sonrası teslim edildi.
- [ ] Envanter/veri manifesti `scripts/migration/windows/` araçlarıyla üretildi (ad eşlemesi §9.1'e göre doğrulandı).
## 13. Açık Sorular
- [ ] Windows image saklama süresi ve erişim yetkisi — sahibi: güvenlik/operasyon.
- [ ] Her periferik için Linux sürücü kabul matrisi — sahibi: MEG sahibi.
- [ ] Ön-geçiş araçlarının ad sapması (`bf-win-*` → `BF-Windows*`) `docs/32` §B-6 kaydıyla kapanmış sayılır mı, yoksa brief adlarıyla uyumluluk için sarmalayıcı mı gerekir — karar sahibi: 32 yazarı + operasyon.
> **Güncelleme (2026-09-16):** §9.1/§9.2 eklendi. Eski metin araç adlarını/konumlarını belirtmiyordu; bu turda **gerçek durum** yazıldı: araçlar `scripts/migration/windows/BF-WindowsPreMigrationInventory.ps1` ve `BF-WindowsDataExport.ps1`, kökteki `migration/` yalnız işaret dosyasıdır. Brief'te geçen `bf-win-inventory.ps1`, `bf-win-network-export.ps1`, `bf-win-software-export.ps1`, `bf-win-device-export.ps1` adları **eşleme tablosuyla** karşılandı; dosyalar yeniden adlandırılmadı (tek kanonik dizin kuralı).
---
## Ek: Geçiş kapısı
```mermaid
flowchart LR
W["Windows envanter + image"] --> A["İş sahibi onayı"]
A --> F["Field OS temiz kurulum"]
F --> C["MEG / erişim / güç kabulü"]
C -->|Geçti| R["READY + teslim"]
C -->|Kaldı| B["Windows image restore"]
```

View file

@ -0,0 +1,159 @@
# 29 — Cihaz Enrollment
> Kısa özet: Device enrollment, offline provision edilmiş cihazın merkezi envantere bir kez bağlanması ve merkezi kanallarca doğrulanmasıdır. Durum modeli **tek yönlüdür**: `PROVISIONED_OFFLINE → ENROLLED → READY`; geri dönüş kenarı ve durum atlama yoktur (`PROVISIONED_OFFLINE → READY` doğrudan olamaz). Tek kullanımlık token `ENROLLED` verir; yalnız WireGuard, erişim ve monitoring doğrulanınca `READY` verilir. `READY` sonrası kurtarma yolu **yeniden provision**'dır (27), durumu geri sarmak değil.
>
> - Dosya: `docs/29-DEVICE-ENROLLMENT.md`
> - İlgili kararlar: `24-DECISION-LOG.md#K-17` (enrollment), `#K-21` (tek durum kaynağı ve ileri-yönlü geçişler), `#K-12`, `#K-05`, `#K-07`
> - Durum: [ ] Taslak
---
## 1. Amaç
Cihaz kimliğinin, ağ kaydının ve merkezi gözlemin sahada güvenli sırayla birleştirilmesini tanımlamak.
## 2. Kapsam
- Kapsam içi: durum makinesi, token özellikleri, merkezi kayıt, WG/remote/monitoring kapıları, token iptal kaydı.
- Kapsam dışı: offline işletim sistemi kurulumu (27), SSH anahtar yaşam döngüsü (06), release seçimi (30).
## 3. Kararlar
```text
KARAR: Enrollment bir kez kullanılan, süreli ve cihaz kimliğine bağlı token ile yapılır. Durum modeli üç durumdur ve TEK YÖNLÜDÜR: PROVISIONED_OFFLINE → ENROLLED → READY. Geçişler ileri yönlüdür; **geri dönüş kenarı yoktur ve durum atlanamaz** (PROVISIONED_OFFLINE → READY doğrudan olamaz, READY → ENROLLED geri alınmaz). READY sonrası kurtarma yolu durumu geri sarmak değil, cihazı 27'ye göre YENİDEN PROVISION etmektir (yeni yaşam döngüsü: PROVISIONED_OFFLINE'dan başlar). Tek kaynak `/var/lib/blueforce/state.json` + `bf-enrollment-status` çıktısıdır.
GEREKÇE: Offline medya secret taşımaz; token yalnız bağlantı oluştuğunda kullanılır. READY, token kullanımından değil merkezi erişim ve health kanallarının gerçek doğrulamasından doğar. Geçmişi geri sarma kenarları "hangi cihaz ne zaman READY oldu" denetimini bulanıklaştırır ve yanlış teslim üretir; kurtarmayı yeniden provision'a bağlamak denetim izini tek yönlü ve kanıtlı tutar. Uygulanan kod bu sözleşmeyi zorlar: `bf-enrollment-status` yalnız üç durumu geçerli sayar, `bf-enroll` yalnız PROVISIONED_OFFLINE/ENROLLED fazından enrollment kabul eder.
ALTERNATİF: Kalıcı ortak bootstrap token — sızıntıda tüm filoyu etkiler; elendi. Geri alınabilir durum geçişleri (READY → ENROLLED) — denetim izini belirsizleştirir ve "READY gerçekten alındı mı" sorusunu cevapsız bırakır; reddedildi. Ayrı bir RETIRED/REVOKED durumu eklemek — açık soru olarak §13'te (bugünkü sözleşme `INVALID`/`PENDING` + exit≠0 ile fail-closed davranır).
RİSK: Token çalınması veya yanlış cihazda kullanılması; kısa TTL, tek kullanım, `BF-<no>` bağlama ve anlık iptal ile azaltılır. READY sonrası kurtarmanın yeniden provision gerektirmesi saha süresi maliyeti doğurur; azaltma: 17-RECOVERY kademeleri (L1→L9) önce denenir, yeniden provision son çare.
MALİYET: Ücretsiz.
LİSANS: OpenSSH — https://www.openssh.com/ ; WireGuard — https://www.wireguard.com/ ; token API davranışı kurum içi uygulama ve LAB doğrulamasıdır.
```
> **Güncelleme (2026-09-16, K-21):** Bu dosyanın ek diyagramındaki iki geri dönüş kenarı (`ENROLLED → PROVISIONED_OFFLINE`, `READY → ENROLLED`) uygulanan kuralla çelişiyordu ve kaldırılmıştır. Uygulanan kural: **geçişler ileri yönlüdür; geri dönüş ve atlama yoktur.** READY sonrası kurtarma yolu yeniden provision'dır (27) — ayrıntı §11 ve Ek'teki ikinci diyagram.
## 4. Neden Bu Karar?
İlk kurulumdaki cihaz, merkez açısından henüz bilinmeyen bir uçtur. Token yalnız kimlik bağlama yetkisi verir; merkezi kanalların gerçekten çalıştığını ispatlamaz. Bu ayrım yanlış READY teslimini engeller. Durumun tek yönlü olması da aynı amaca hizmet eder: "kuruldu → bağlandı → doğrulandı" sırası kanıt zinciridir; geri sarmalı kenarlar bu zinciri koparır ve denetimde hangi durumun ne zaman gerçek olduğunu belirsizleştirir. Bir cihaz READY'den düşürülmesi gerekiyorsa yapılan iş **yeni bir provision + enrollment + READY kanıtı** üretmektir (27), eski durumu geri yazmak değil.
## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---|
| Tek-kullanımlık, TTL'li token | Küçük sızıntı alanı | Token üretim hizmeti gerekir | **Seçildi** |
| Ortak kalıcı token | Basit | Filo çapı sızıntı | Elendi |
| Manuel merkezi kayıt | Ek sistem yok | Hata/audit zayıf | Elendi |
## 6. Avantajlar
- Her kayıt hangi cihaz, kim ve ne zaman ile ilişkilidir.
- READY bağımsız sağlık kanıtına bağlıdır.
## 7. Dezavantajlar
- Cihazın internete kavuşması gerekir.
- Token hizmeti ve iptal kaydı işletilir.
## 8. Riskler
| Risk | Olasılık | Etki | Azaltma |
|---|---|---|---|
| Token tekrar kullanımı | Düşük | Yüksek | Atomik tüketim + audit |
| Kimlik çakışması | Düşük | Yüksek | Envanter benzersizlik kapısı |
| WG var ama health yok | Orta | Orta | READY için üç kanal şartı |
| READY sonrası kurtarma yolu yanlış modellenir (durum geri sarma denemesi) | Orta | Orta | Geçişler ileri yönlü; kurtarma = 27 yeniden provision; `bf-enrollment-status` yalnız üç durumu geçerli sayar, aksi halde `INVALID`/`PENDING` + exit≠0 |
| `central_verification` alanları `pending` kalır, cihaz ENROLLED'da bekler | Orta | Orta | Fail-closed READY kapısı + enrollment yanıt sözleşmesinin zorunlu alanları |
## 9. Uygulama Planı
1. Merkezi admin, doğrulanmış bayi no için `BF-<no>` bağlı, kısa ömürlü token üretir ve kayda açar.
2. Teknisyen tokenı yalnız cihaz ekranına girer; USB'ye, loga veya support bundle'a yazmaz.
3. Başarılı yanıtın tamamı doğrulanır; yalnız bundan sonra WireGuard yapılandırması atomik kurulur ve servis etkin/aktif ise durum `ENROLLED` olur.
4. `bf-check-ready`, kalıcı `READY` değerini okumaz: güncel WG handshake, etkin xRDP, RustDesk yapılandırması, isteğe bağlı Mesh ajanı ve merkezden gelen doğrulama kanıtını bağımsız denetler. Tümü geçerse `18-final-check` `READY` yazar.
5. **İleri-yönlü geçiş kuralı (uygulanan sözleşme):** durumlar yalnız `PROVISIONED_OFFLINE → ENROLLED → READY` sırasıyla ilerler. `PROVISIONED_OFFLINE → READY` doğrudan geçiş yoktur (merkezi kanal kanıtı token olmadan üretilemez); `ENROLLED → PROVISIONED_OFFLINE` veya `READY → ENROLLED` geri dönüş kenarı yoktur. Bir cihaz READY'den çıkarılması gerekiyorsa:
- önce 17-RECOVERY kademeleri (L1 servis restart → … → L8 USB ile sıfırdan imaj) denenir,
- kimlik/merkez kaydı ciddi biçimde bozuksa cihaz merkezi erişimden çekilir, token/peer iptal edilir ve **27'ye göre yeniden provision edilir**; yeniden provision yeni bir yaşam döngüsü başlatır (`PROVISIONED_OFFLINE` → yeni token → `ENROLLED` → yeni READY kanıtı).
- Yani kurtarma "durumu geri sarmak" değil, "kanıtı yeniden üretmek"tir.
```bash
# BF_ENROLLMENT_ENDPOINT is supplied by the approved central control plane configuration.
printf '%s\n' '<tek-kullanimlik-token>' | sudo -E bf-enroll --token-stdin
bf-enrollment-status # yalnız PROVISIONED_OFFLINE | ENROLLED | READY; aksi halde INVALID/PENDING + exit≠0
bf-check-ready
```
## 10. Test Planı
| Test | Beklenen sonuç | Ortam |
|---|---|---|
| Geçerli token | PROVISIONED_OFFLINE → ENROLLED | LAB(2) |
| Aynı token ikinci kez | Reddedilir, audit kaydı | LAB(2) |
| Süresi geçmiş/yanlış cihaz tokenı | Reddedilir | LAB(2) |
| Merkezi kanallar eksik | ENROLLED kalır, READY olmaz | LAB(2) |
| Token olmadan READY denemesi | READY yazılmaz (atlama yok) | LAB(2) |
| READY sonrası kurtarma provası | Durum geri sarmak yerine 27 yeniden provision yolu çalışır | LAB(2) |
## 11. Rollback
1. Token yanlış kullanılırsa merkezi admin tokenı iptal eder ve geçici kayıtları kapatır (cihaz ENROLLED'da kalır; READY'ye ilerlemez).
2. Yanlış WireGuard peer/etiket, `ENROLLED` durumunda düzeltilir ve yeniden doğrulanır.
3. READY sonrası ciddi kimlik sorunu varsa cihaz merkezi erişimden çekilir, token/peer iptal edilir ve **27'den yeniden provision edilir**. Bu "rollback" bir durum geri alma değildir: yeni yaşam döngüsü PROVISIONED_OFFLINE'dan başlar ve READY yeniden kanıtlanır (geçişler ileri yönlüdür).
## 12. Kontrol Listesi
- [ ] Token tek kullanımlık, TTL'li ve `BF-<no>` bağlı.
- [ ] Token hiçbir medya/log/support bundle içinde yok.
- [ ] ENROLLED ve READY farklı, görünür durumlar.
- [ ] WG + remote + monitoring doğrulaması kayda bağlı.
- [ ] Merkez yanıtındaki `central_verification` kanıtı olmayan veya `pending` olan cihaz READY olmaz.
- [ ] Diyagramda/metinde geri dönüş kenarı ve durum atlama yok; READY sonrası kurtarma 27 yeniden provision olarak yazılı.
## 13. Açık Sorular
- [ ] Token TTL süresi ve yetkili üretici rolleri — sahibi: güvenlik yöneticisi.
- [ ] Enrollment API audit saklama süresi — sahibi: merkezi admin.
- [ ] Geri dönüşsüz modelde "cihaz işletmeden çekildi" durumu için ayrı bir kalıcı damga (ör. `retired`) gerekir mi — karar sahibi: platform ekibi + 27 yazarı [K-21].
---
## Ek: Merkezi doğrulama yanıt sözleşmesi ve güven sınırı
Enrollment yanıtı `central_verification` alanını **zorunlu** taşır. Alan secret, URL, erişim tokenı, RustDesk parolası veya WireGuard private key taşımaz; yalnız merkezde denetlenebilen referansları taşır:
```json
{
"central_verification": {
"status": "pending | verified",
"verification_id": "merkez-kayit-referansi",
"monitoring": {"status": "pending | verified", "verification_id": "izleme-referansi"},
"remote": {"status": "pending | verified", "verification_id": "uzak-erisim-referansi"},
"management": {"status": "pending | verified", "verification_id": "yonetim-referansi"}
}
}
```
İstemci merkez URL'si veya kanıt uydurmaz. `bf-remote-status` yalnız imzalanmış/kimliği doğrulanmış enrollment yanıtından kalıcılaştırılan bu referansların tümü `verified` olduğunda merkezi kanıtı geçer; alan eksik, biçimsiz veya `pending` ise `PENDING` ile fail-closed olur. Yerel xRDP, RustDesk, isteğe bağlı Mesh ve güncel WG handshake bu merkez kanıtından ayrı olarak kontrol edilir.
## Ek: Durum makinesi (tek yönlü — geri dönüş kenarı yok)
```mermaid
stateDiagram-v2
[*] --> PROVISIONED_OFFLINE
PROVISIONED_OFFLINE --> ENROLLED: tek kullanımlık token (ileri)
ENROLLED --> READY: WG + remote + monitoring kanıtı (ileri)
note right of READY
Geri dönüş kenarı YOKTUR ve durum atlanamaz
(PROVISIONED_OFFLINE -> READY doğrudan olamaz).
READY sonrası kurtarma: 27'ye göre yeniden provision.
end note
```
## Ek: READY sonrası kurtarma yolu (yeni yaşam döngüsü)
```mermaid
flowchart LR
RDY["READY cihaz: kimlik/merkez kaydı ciddi biçimde bozuk"] --> CEK["Merkezi erişimden çek + token/peer iptal"]
CEK --> RP["27'ye göre YENİDEN PROVISION<br/>(yeni yaşam döngüsü başlar)"]
RP --> PO["PROVISIONED_OFFLINE"]
PO -->|yeni tek kullanımlık token| EN["ENROLLED"]
EN -->|yeni WG + remote + monitoring kanıtı| YENI["READY (yeniden kanıtlandı)"]
GERI["Durumu geri sarma (READY → ENROLLED vb.)"] -.->|"YASAK"| RDY
```

View file

@ -0,0 +1,111 @@
# 30 — Field OS Release Yönetimi
> Kısa özet: Field OS release'i ISO/seed, offline APT snapshot, kurucu, Ansible içerikleri ve manifestten oluşan değişmez bir sette yönetilir. Her release LAB→P1→P2→W1→W2→PROD kapılarından geçer; rollback önceki onaylı release'e döner.
>
> - Dosya: `docs/30-FIELD-OS-RELEASE-MANAGEMENT.md`
> - İlgili kararlar: `24-DECISION-LOG.md#K-18`, `#K-11`, `#K-15`, `#K-16`
> - Durum: [ ] Taslak
---
## 1. Amaç
Field OS değişikliklerini reproducible, onaylı ve geri alınabilir release'lere dönüştürmek.
## 2. Kapsam
- Kapsam içi: release bileşenleri, sürüm/manifest, imza-checksum, dalga kapıları, support penceresi ve rollback.
- Kapsam dışı: tek paketin teknik upgrade komutları (10), cihaz enrollment (29).
## 3. Kararlar
```text
KARAR: Field OS release'i değişmez manifestli artefact setidir: upstream ISO referansı, seed, offline APT snapshot, kurucu/Ansible commit'i, paket sürümleri ve checksum'lar birlikte sürümlenir.
GEREKÇE: "26.04.1" tek başına çalışır sistemi anlatmaz; seed ve installer değişirse davranış değişir. Manifest, aynı release'in yeniden kurulmasını ve kanıtlanmasını sağlar.
ALTERNATİF: Yalnız Git branch veya latest ISO etiketi — hangi bileşenin sahaya gittiği belirsiz kalır; elendi.
RİSK: Artefact uyumsuzluğu veya imzasız medya; build kapısı, checksum ve LAB kabulü ile azaltılır.
MALİYET: Ücretsiz.
LİSANS: Git — https://git-scm.com/doc ; Ubuntu releases — https://releases.ubuntu.com/ ; Docker image tag ilkeleri — https://docs.docker.com/reference/cli/docker/image/pull/ (26.04 davranışı LAB gerektirir).
```
## 4. Neden Bu Karar?
Release, bir kurulum medyası değil çalışan sistem sözleşmesidir. İmaj, seed, paket snapshot'ı ve kurucu farklı tarihlerden seçilirse hata tekrar üretilemez ve rollback güvenilmez olur.
## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---|
| Immutable manifest release | İzlenebilir/rollbackli | Release disiplini | **Seçildi** |
| `latest`/hareketli medya | Kolay görünür | Determinizm yok | Elendi |
| Sadece cihazda sürüm notu | Az merkez işi | Filo görünürlüğü yok | Elendi |
## 6. Avantajlar
- Bir cihazın hangi release ile kurulduğu kesin olarak bulunur.
- Önceki onaylı release, gerçek rollback hedefidir.
## 7. Dezavantajlar
- Artefact deposu ve release kaydı işletilir.
- Küçük değişiklik bile manifest/review gerektirir.
## 8. Riskler
| Risk | Olasılık | Etki | Azaltma |
|---|---|---|---|
| Artefact eksikliği | Orta | Yüksek | Release gate: manifest + checksum zorunlu |
| Rollback test edilmez | Orta | Yüksek | LAB restore/rollback kanıtı |
| Eski release güvenlik riski | Orta | Orta | Support penceresi, hızlandırılmış dalga |
## 9. Uygulama Planı
1. Release adı örneği: `field-os-26.04.1-r001`; manifestte ISO/seed/APT/installer/Ansible/MEG sürümlerini açık yaz.
2. Her artefact için SHA256 ve kaynak/oluşturma kaydı üret; secret manifestte bulunmaz.
3. LAB'da fresh install, offline install, enrollment, erişim, power-loss ve rollback kanıtı üret.
4. Merkezi admin 10'daki dalga kapılarıyla yayını onaylar; başarısızlıkta yeni dalga açılmaz.
```bash
sha256sum field-os-26.04.1-r001-manifest.json
# Manifest içerikleri release imzası/checksum süreciyle doğrulanır.
git tag -v field-os-26.04.1-r001 # imzalı tag kullanılıyorsa
```
## 10. Test Planı
| Test | Beklenen sonuç | Ortam |
|---|---|---|
| Manifest bütünlüğü | Tüm artefact hash'leri eşleşir | Build/LAB |
| Fresh + offline install | Aynı release kimliği görünür | LAB(2) |
| Rollback | Önceki onaylı release'e döner | LAB(2) |
| P1 gözlem | 24–48 saat sağlık kapısı temiz | P1(5) |
## 11. Rollback
1. Yayılım durdurulur; etkilenen dalga önceki onaylı manifestteki paket/config/artefact sürümüne döner.
2. `bf-status`, merkezi metrikler ve cihaz release kimliği doğrulanır.
3. İmaj seviyesinde sorun varsa 26'daki upstream ISO + önceki onaylı seed ile yeniden kurulum son çaredir.
## 12. Kontrol Listesi
- [ ] Release ID ve immutable manifest var.
- [ ] ISO, seed, APT snapshot, installer ve uygulama sürümleri kaydedildi.
- [ ] Checksums/imza ve LAB kanıtı var.
- [ ] Dalga onayı ve rollback kanıtı kaydedildi.
## 13. Açık Sorular
- [ ] Artefact depolama/retention ve imza anahtarı sahibi — sahibi: release yöneticisi.
- [ ] Release cadence ve EOL politikası — sahibi: merkezi admin.
---
## Ek: Release kapısı
```mermaid
flowchart LR
B["Build + manifest"] --> L["LAB: install/enroll/power/rollback"]
L --> P1["P1"] --> P2["P2"] --> W["W1/W2"] --> R["PROD"]
L -->|Kaldı| O["Önceki onaylı release rollback"]
P1 -->|Kaldı| O
```

373
docs/31-LEARNING-GUIDE.md Normal file
View file

@ -0,0 +1,373 @@
# 31 — Blueforce Field OS Öğrenme Rehberi
> Kısa özet: Bu rehber, Field OS'u güvenle işletmek için gereken Linux, ağ, otomasyon ve release kavramlarını öğretir. Her konu **Nedir / Neden / Nerede / Arıza belirtisi / Komutlar** biçimindedir; üretimde değişiklik yapmadan önce LAB'da uygulanır.
>
> - Dosya: `docs/31-LEARNING-GUIDE.md`
> - İlgili kararlar: `24-DECISION-LOG.md#K-01…K-24` (özellikle medya için `#K-20`, durum modeli için `#K-21`, RDP/yerel GUI için `#K-22`, GNOME vs XFCE ölçümü için `#K-23`)
> - Durum: [ ] Taslak — komutlar örnektir; 26.04 ve RustDesk headless ayrıntıları LAB'da doğrulanmalıdır
---
## 1. Amaç
Teknisyen ve merkezi operatörün, Field OS'un neden böyle tasarlandığını ve temel kontrolleri hangi sırayla yapacağını öğrenmesini sağlamak.
## 2. Kapsam
- Kapsam içi: filesystem, paket yönetimi, servis/log, Git/Bash, SSH/ağ, WireGuard, Docker/Ansible/Semaphore/Prometheus, uzak erişim, autoinstall/NoCloud/ISO/offline APT, enrollment, golden image, release ve rollback.
- Kapsam dışı: gizli anahtar üretimi, prod değişikliği veya 13/20/21 yerine geçecek işletim yetkisi.
## 3. Kararlar
```text
KARAR: Eğitim, komut ezberi yerine Nedir/Neden/Nerede/Arıza belirtisi/Komutlar çerçevesiyle ve yalnız LAB pratiğiyle verilir.
GEREKÇE: 700 cihazda yanlış komutun etkisi büyüktür; kavramı bilen operatör belirtileri doğru runbook'a yönlendirir ve onaysız değişiklik yapmaz.
ALTERNATİF: Sadece kısa komut listesi — bağlam olmadan yanlış kullanım riski yüksek olduğu için elendi.
RİSK: Doküman, sürüme özgü davranışı kesinmiş gibi gösterebilir; Ubuntu 26.04 ve RustDesk headless ayrıntıları açık LAB etiketiyle tutulur.
MALİYET: Ücretsiz.
LİSANS: Resmi kaynak URL'leri her konu altında verilmiştir; kurum içi runbook'lar kendi lisansına tabidir.
```
> **Güncelleme (2026-09-16, K-20/K-21/K-22/K-23):** Bu rehberdeki medya, RDP ve durum modeli anlatımları ikinci tur kararlarla hizalandı: (1) V1 medyası **tek USB Field OS ISO**, upstream ISO + seed **geri dönüş yolu**; (2) **xRDP her zaman hazır**, `bf-gui-*` yalnız yerel fiziksel GUI; (3) durum geçişleri **ileri yönlü**, READY sonrası kurtarma 27'ye göre yeniden provision; (4) LAB'da **GNOME vs XFCE A/B ölçümü** yapılır (XFCE saha standardı değil).
## 4. Neden Bu Karar?
Field OS; standart Linux araçlarını, merkezi otomasyonu ve katmanlı güvenlik kapılarını birlikte kullanır. Bir katmanın anlamını bilmek, örneğin `READY` ile yalnızca çalışan bir servisi karıştırmayı önler.
## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---|
| Kavram + güvenli komut + LAB | Kalıcı öğrenme | Başlangıçta daha uzun | **Seçildi** |
| Ezber komut kartı | Hızlı | Hata teşhisi zayıf | Elendi |
| Prod üzerinde deneme | Gerçek ortam | Filo riski | Yasak |
## 6. Avantajlar
- Runbook'ların ardındaki teknik bağ görünür olur.
- Her komutun beklenen sonucu ve arıza belirtisi vardır.
## 7. Dezavantajlar
- Rehber resmi ürün dokümanının yerine geçmez.
- Bazı davranışlar donanım ve Ubuntu nokta sürümüne bağlıdır.
## 8. Riskler
| Risk | Olasılık | Etki | Azaltma |
|---|---|---|---|
| Prod'da deney komutu | Orta | Yüksek | LAB-only etiketi, 20/21 yetki sınırı |
| Secret'ın komuta/loga girmesi | Orta | Yüksek | Placeholder kullan, secret yazma |
| Sürüm farkı | Orta | Orta | Resmi URL + LAB kayıtları |
## 9. Uygulama Planı
### 1. Filesystem ve Bash
**Nedir:** Linux filesystem; `/etc` yapılandırma, `/var` değişen veri/log, `/usr` programlar, `/home` kullanıcı verisidir. Bash kabuk ve komut dili olarak bunlarla çalışır.
**Neden:** Yanlış dosyayı düzenlemek veya alanı dolu diski kaçırmak servis arızası üretir.
**Nerede:** `/etc/wireguard/wg0.conf`, `/var/log/`, `/var/lib/blueforce/`, `/usr/local/bin/`.
**Arıza belirtisi:** `No space left on device`, izin reddi, servis config'i okumuyor.
**Komutlar (LAB):**
```bash
pwd; ls -lah /etc /var/lib/blueforce
df -h; df -i
findmnt /; stat /etc/hostname
```
Resmi kaynak: https://www.gnu.org/software/bash/manual/ ; https://www.freedesktop.org/software/systemd/man/latest/file-hierarchy.html
### 2. APT ve dpkg
**Nedir:** `apt` paket bağımlılıklarını çözen yüksek seviye araçtır; `dpkg` kurulu `.deb` paket veritabanını yönetir.
**Neden:** Offline medya veya yarım kurulumda paketin kurulu olup olmadığını ve yarım işlemi anlamak gerekir.
**Nerede:** `/etc/apt/`, `/var/lib/dpkg/`, offline APT snapshot (27).
**Arıza belirtisi:** `dpkg was interrupted`, bağımlılık hatası, yanlış paket sürümü.
**Komutlar (LAB):**
```bash
apt-cache policy xrdp
dpkg -l xrdp
dpkg -S /usr/sbin/xrdp
sudo dpkg --configure -a
```
Resmi kaynak: https://manpages.ubuntu.com/manpages/noble/en/man8/apt.8.html ; https://manpages.ubuntu.com/manpages/noble/en/man1/dpkg.1.html
### 3. systemd ve journalctl
**Nedir:** systemd servisleri ve boot hedeflerini yönetir; journald olay günlüklerini tutar, `journalctl` okur.
**Neden:** Field OS boot zinciri `multi-user.target` → network → WireGuard → Docker/ajanlar şeklindedir.
**Nerede:** `/etc/systemd/system/`, drop-in `*.service.d/`, journal.
**Arıza belirtisi:** `failed` servis, boot race, tekrar başlayan konteyner/ajan.
**Komutlar (LAB):**
```bash
systemctl get-default
systemctl status wg-quick@wg0 --no-pager
journalctl -u wg-quick@wg0 -b --no-pager
systemd-analyze critical-chain docker.service
```
Resmi kaynak: https://www.freedesktop.org/software/systemd/man/latest/systemctl.html ; https://www.freedesktop.org/software/systemd/man/latest/journalctl.html
### 4. Git ve Bash çalışma disiplini
**Nedir:** Git, doküman/config değişiklik geçmişidir; Bash scriptleri kurucu ve tanı komutlarını taşır.
**Neden:** Release manifesti, seed ve Ansible değişiklikleri review ve geri alma olmadan sahaya gitmez.
**Nerede:** Bu repo, `scripts/`, `ansible/`, release tag'leri.
**Arıza belirtisi:** yanlış branch, temiz olmayan worktree, aynı dosyada iki farklı değişiklik.
**Komutlar (LAB/yerel checkout):**
```bash
git status --short --branch
git log --oneline -5
git diff --check
bash -n scripts/install/blueforce-install.sh
```
Resmi kaynak: https://git-scm.com/docs ; https://www.gnu.org/software/bash/manual/
### 5. SSH ve anahtarlar
**Nedir:** SSH şifreli yönetim bağlantısıdır; public key cihazda, private key yalnız yetkili admindedir.
**Neden:** K-13'e göre parola/root SSH kapalı ve paylaşılan private key yasaktır.
**Nerede:** `~/.ssh/`, `/etc/ssh/sshd_config.d/`, `authorized_keys`.
**Arıza belirtisi:** `Permission denied (publickey)`, host key değişikliği, çok geniş yetki.
**Komutlar (LAB):**
```bash
ssh -vvv blueforce@bf-12010193
ssh-keygen -lf ~/.ssh/id_ed25519.pub
sudo sshd -T | grep -E 'passwordauthentication|permitrootlogin'
```
Resmi kaynak: https://www.openssh.com/manual.html ; https://man.openbsd.org/sshd_config
### 6. Networking ve WireGuard
**Nedir:** IP routing ağın yoludur; WireGuard, hub-spoke şifreli tüneldir. `PersistentKeepalive=25`, NAT arkasındaki spoke için varsayımdır.
**Neden:** SSH/xRDP yalnız WireGuard içinden; RustDesk/MeshCentral WG-dışı yedek kanaldır.
**Nerede:** `ip route`, `/etc/wireguard/wg0.conf`, hub peer envanteri.
**Arıza belirtisi:** eski handshake, route yok, DNS/WAN hatası.
**Komutlar (LAB):**
```bash
ip addr; ip route
sudo wg show wg0 latest-handshakes
ping -c 3 10.8.0.1
ss -tlnp
```
Resmi kaynak: https://www.wireguard.com/quickstart/ ; https://man7.org/linux/man-pages/man8/ip.8.html
### 7. Docker
**Nedir:** Docker uygulamaları container olarak paketler. Field OS'ta `unless-stopped`, sabit tag ve log rotasyonu zorunludur.
**Neden:** Güç dönüşünde MEG kalkmalı; `latest` veya otomatik updater determinizmi bozar.
**Nerede:** `/etc/docker/daemon.json`, compose dosyaları, Docker journal/logları.
**Arıza belirtisi:** restart loop, disk dolumu, dışa açık port.
**Komutlar (LAB):**
```bash
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' <container>
docker logs --tail 100 <container>
```
Resmi kaynak: https://docs.docker.com/engine/containers/start-containers-automatically/ ; https://docs.docker.com/engine/logging/configure/
### 8. Ansible ve Semaphore
**Nedir:** Ansible SSH üzerinden idempotent playbook çalıştırır; Semaphore bunun self-hosted operasyon arayüzüdür.
**Neden:** 700 cihazda aynı değişiklik kontrollü dalgalarla uygulanır; UI bozulsa Ansible CLI yedektir.
**Nerede:** `ansible/inventory/`, `ansible/playbooks/`, Semaphore projesi/key store.
**Arıza belirtisi:** unreachable host, playbook drift'i, onaysız prod run denemesi.
**Komutlar (LAB):**
```bash
ansible-inventory -i ansible/inventory/hosts.example.yml --graph
ansible-playbook -i ansible/inventory/hosts.example.yml ansible/playbooks/bf-ping.yml --check
```
Resmi kaynak: https://docs.ansible.com/ansible/latest/ ; https://docs.semaphoreui.com/
### 9. Prometheus ve sağlık
**Nedir:** Prometheus metrikleri scrape eder; Node Exporter host metriklerini verir; Grafana gösterir, Kuma last-seen sunar.
**Neden:** `READY`, yalnız kurulum bitişi değil merkezi metrik/erişim kanıtıdır.
**Nerede:** Node Exporter textfile dizini, Prometheus targets, Grafana/Kuma panoları.
**Arıza belirtisi:** `up=0`, stale WG handshake, disk/RAM trendi, missing scrape.
**Komutlar (LAB):**
```bash
systemctl is-active node_exporter
curl -fsS http://127.0.0.1:9100/metrics | grep '^node_boot_time_seconds'
ls -l /var/lib/node_exporter/textfile/
bf-status
```
Resmi kaynak: https://prometheus.io/docs/introduction/overview/ ; https://github.com/prometheus/node_exporter ; https://github.com/louislam/uptime-kuma
### 10. RustDesk ve xRDP
**Nedir:** xRDP GNOME'a WireGuard üzerinden RDP verir ve servisi **her zaman hazırdır** (`xrdp` + `xrdp-sesman` enable+active). `bf-gui-on` / `bf-gui-off` yalnız **yerel fiziksel** grafik katmanını (display-manager + default target) açar/kapatır; RDP'yi kontrol etmez. RustDesk, self-hosted `hbbs`/`hbbr` ile WireGuard'dan bağımsız grafik yedek erişimdir.
**Neden:** RDP internete açılmaz; WG arızasında RustDesk/MeshCentral kurtarma yolu olur. "GUI kapalı ≠ RDP kapalı" ayrımı, yerel GUI kapatılmış bir saha cihazında uzaktan grafik kurtarmayı korur (K-22).
**Nerede:** xRDP systemd birimleri (`xrdp`, `xrdp-sesman`), GNOME session ayarı, RustDesk client/server yapılandırması.
**Arıza belirtisi:** RDP oturumu açılmıyor ama `systemctl is-active xrdp` "inactive" diyor (servis kapatılmış = sözleşme ihlali), boş GNOME oturumu, xRDP service hatası, RustDesk'te katılımsız erişim yok.
**Komutlar (LAB):**
```bash
systemctl status xrdp xrdp-sesman --no-pager
sudo bf-gui-off; systemctl is-active xrdp xrdp-sesman # ikisi de "active" kalmalı (K-22 kanıtı)
ss -tlnp | grep ':3389'
journalctl -u xrdp -b --no-pager
```
Resmi kaynak: https://github.com/neutrinolabs/xrdp ; https://rustdesk.com/docs/en/self-host/rustdesk-server-oss/ . **LAB gerekli:** Ubuntu 26.04.1 GNOME+xRDP ve RustDesk'in tam headless/katılımsız servis davranışı sürüm ve paketleme ile doğrulanmadan üretim sözü değildir. Aynı LAB'da **GNOME vs XFCE A/B ölçümü** yapılır: RAM, CPU, boot süresi, RDP güvenilirliği, RustDesk reboot sonrası davranışı, login screen erişimi, dummy display, 24/72 saat stabilite (K-23; XFCE saha standardı değildir, yalnız ölçüm koludur).
### 11. Autoinstall, cloud-init, NoCloud ve ISO
**Nedir:** Autoinstall Ubuntu kurulumunu otomatikleştiren **motordur**; cloud-init ilk boot yapılandırma çerçevesidir; NoCloud, `user-data`/`meta-data` sağlayan veri kaynağıdır. **Blueforce Field OS ISO ise paketleme/dağıtım yöntemidir**: autoinstall'ı, offline APT repo'yu ve firstboot'u tek medyada taşır — ikisi rakip değil, birlikte kullanılır (K-20).
**Neden:** V1 medyası **tek USB Field OS ISO**'dur; teknisyen tek medya takar, kurulum katılımsız ilerler ve **yalnız disk adımı operatör onayı bekler** (`interactive-sections: [storage]` → otomatik disk silme YOK). Upstream ISO + ayrı NoCloud seed USB yolu kaldırılmadı: ISO boot etmezse aynı sonucu veren sıcak geri dönüş yoludur.
**Nerede:** Medya kökü `/autoinstall.yaml`, `/blueforce-provisioning/{autoinstall,firstboot,offline-repo,release}`, seed USB kökü (`user-data`, `meta-data`), `/var/log/cloud-init*`.
**Arıza belirtisi:** interaktif kurulum ekranı, schema hatası, seed bulunamadı, yanlış boot modu, ISO checksum uyuşmazlığı, `OFFLINE BLOCKED` (medyaya gömülü offline repo/pin yok).
**Komutlar (LAB):**
```bash
provisioning/iso/test-iso.sh dist/Blueforce-Field-OS-1.0.0-amd64.iso # checksum + El Torito metadata
sha256sum -c dist/Blueforce-Field-OS-1.0.0-amd64.iso.sha256
cloud-init status --long
cloud-init schema --config-file user-data
```
Resmi kaynak: https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-reference.html ; https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html ; https://releases.ubuntu.com/ . **LAB gerekli:** 26.04.1'in kesin Subiquity/cloud-init söz dizimi, UEFI/Legacy/Secure Boot boot zinciri ve air-gapped kurulum. Not: LAB ISO'su Desktop flavour ile üretildi; baseline (Server) sapması `dist/1.0.0-manifest.yaml` → `baseline_deviation` bloğunda kayıtlıdır.
### 12. Offline APT ve offline provisioning
**Nedir:** Offline APT, sürümlü yerel repo/snapshot'tan paket kurmaktır; offline provisioning internetsiz cihazı `PROVISIONED_OFFLINE` durumuna getirir.
**Neden:** Saha bağlantısı yokken kurulum yapılır; fakat merkezi doğrulama olmadan READY verilmez.
**Nerede:** offline medya, `sources.list`, `/var/lib/dpkg/`, 27 prosedürü.
**Arıza belirtisi:** paket bulunamadı, bağımlılık eksik, dpkg yarım kaldı, cihaz yanlışlıkla READY gösteriyor.
**Komutlar (LAB):**
```bash
apt-cache policy <paket>
dpkg -l <paket>
sudo dpkg --configure -a
bf-status
```
Resmi kaynak: https://manpages.ubuntu.com/manpages/noble/en/man8/apt.8.html ; https://manpages.ubuntu.com/manpages/noble/en/man1/dpkg.1.html
### 13. Enrollment ve golden image
**Nedir:** Golden image kimliksiz tekrar kurulabilir zemin (V1'de tek USB Field OS ISO ile paketlenir); enrollment merkezi kimlik bağlama ve kanal doğrulamasıdır. Durum modeli **tek yönlüdür**: `PROVISIONED_OFFLINE → ENROLLED → READY`; geri dönüş kenarı ve durum atlama yoktur (K-21).
**Neden:** Seed/ISO, bayi no, private key veya kalıcı token taşımaz. `PROVISIONED_OFFLINE → ENROLLED → READY` birbirinden farklı güven durumlarıdır ve sıra kanıt zinciridir; `PROVISIONED_OFFLINE → READY` doğrudan geçiş (token atlama) yanlış teslim üretir. READY sonrası kurtarma durumu geri sarmak değil, 27'ye göre **yeniden provision** etmektir.
**Nerede:** 26–29 dokümanları, `/var/lib/blueforce/state.json`, merkezi envanter, WireGuard peer, monitoring/remote kayıtları.
**Arıza belirtisi:** token tekrar kullanımı, kimlik çakışması, WG çalışırken monitoring eksik ve yanlış READY, `bf-enrollment-status` çıktısında `INVALID`/`PENDING` (exit≠0).
**Komutlar (LAB):**
```bash
bf-status
bf-enrollment-status # PROVISIONED_OFFLINE | ENROLLED | READY dışındaki her değer fail-closed
printf '%s\n' '<tek-kullanimlik-token>' | sudo -E bf-enroll --token-stdin
bf-check-ready
```
Resmi kaynak: https://cloudinit.readthedocs.io/ ; https://www.wireguard.com/ . Token API ve 26.04 kesin kurulum akışı kurum içi **LAB gerekli** konularıdır.
### 14. Releases ve rollback
**Nedir:** Release; ISO referansı, seed, offline APT snapshot, installer/Ansible commit'i ve checksum'ları içeren immutable manifesttir. Rollback önceki onaylı release'e dönüş kanıtıdır.
**Neden:** `latest` veya yalnız OS sürümü hangi sistemin sahada olduğunu açıklamaz. Her değişiklik 10'daki LAB→P1→P2→W1→W2→PROD kapılarından geçer. Medya seviyesinde geri dönüş yolu upstream ISO + son onaylı seed'dir (K-20).
**Nerede:** release manifest/tag, `dist/<version>-manifest.yaml`, artefact deposu, 30 yönetim kaydı, monitoring ve change kaydı.
**Arıza belirtisi:** hash uyuşmazlığı, rollback planı yok, dalga kapısı atlanmış, sürüm drift'i, `bf-release` çıktısında `UNAPPROVED_SKELETON`.
**Komutlar (LAB):**
```bash
bf-release
sha256sum -c dist/Blueforce-Field-OS-1.0.0-amd64.iso.sha256
git tag --list 'field-os-*'
bf-status
```
Resmi kaynak: https://git-scm.com/docs ; https://releases.ubuntu.com/ ; https://docs.docker.com/
## 10. Test Planı
| Test | Beklenen sonuç | Ortam |
|---|---|---|
| Her konu için komut pratiği | Beklenen durum/arıza farkı anlatılır | LAB |
| Air-gapped provisioning | `PROVISIONED_OFFLINE`, READY değil | LAB(2) |
| Enrollment kapısı | Tek kullanım + READY doğrulaması | LAB(2) |
| Durum modeli ileri yönlü | `PROVISIONED_OFFLINE → READY` atlaması reddedilir; READY sonrası kurtarma 27 yeniden provision | LAB(2) |
| Yerel GUI kapalıyken RDP | `xrdp`/`xrdp-sesman` active kalır (K-22) | LAB(2) |
| Medya doğrulama | `test-iso.sh` + `sha256sum -c` geçer | LAB/CI |
| Power/rollback tatbikatı | Önceki onaylı duruma dönüş | LAB/P1 |
## 11. Rollback
1. Eğitim sırasında yapılan LAB değişikliği snapshot veya yeniden kurulumla geri alınır.
2. Üretimde arıza görülürse bu rehber yerine 19, 20, 21, 10 ve 17 runbook'ları uygulanır.
3. Yanlış release/konfigürasyon 30'daki manifestli rollback ile geri alınır; medya seviyesinde geri dönüş upstream ISO + son onaylı seed yoludur (K-20).
## 12. Kontrol Listesi
- [ ] Katılımcı filesystem, paket, service ve log ayrımını gösterebiliyor.
- [ ] SSH private key/token'ın neden paylaşılmadığını açıklayabiliyor.
- [ ] `PROVISIONED_OFFLINE`, `ENROLLED`, `READY` farkını **ve geçişlerin ileri yönlü olduğunu** biliyor.
- [ ] "GUI kapalı ≠ RDP kapalı" ayrımını (K-22) ve `bf-gui-*` kapsamını açıklayabiliyor.
- [ ] Tek USB Field OS ISO ile upstream ISO + seed geri dönüş yolunu ayırt edebiliyor (K-20).
- [ ] Ubuntu 26.04/RustDesk headless maddelerinin LAB gerekli olduğunu işaretledi.
- [ ] Rollback ve onay kapısının prod değişiklikten önce zorunlu olduğunu biliyor.
## 13. Açık Sorular
- [ ] Rol bazlı eğitim süresi ve LAB cihaz sayısı — sahibi: merkezi admin.
- [ ] 26.04.1 autoinstall/xRDP/RustDesk headless LAB sonuçları — sahibi: platform ekibi.
- [ ] Release artefact imzalama standardı — sahibi: release yöneticisi.
- [ ] GNOME vs XFCE A/B ölçüm sonuçlarının eğitim materyaline işlenmesi (K-23) — sahibi: platform ekibi + bu rehberin yazarı.
---
## Ek: Öğrenme sırası
```mermaid
flowchart LR
L["Linux: filesystem/APT/systemd/log"] --> A["SSH + networking + WireGuard"]
A --> O["Docker + Ansible + monitoring"]
O --> P["Medya: Field OS ISO / autoinstall / NoCloud / offline<br/>+ enrollment (ileri yönlü durum modeli)"]
P --> R["Release + rollback"]
R --> X["Uzak erişim derinleşme:<br/>xRDP her zaman hazır + RustDesk headless (LAB)"]
X --> AB["LAB A/B: GNOME vs XFCE ölçümü (K-23)"]
```

View file

@ -0,0 +1,250 @@
# 32 — Repository Denetimi ve Çelişki Raporu
> Kısa özet: `Blueforce-Linux` deposunun Field OS provisioning hedefine karşı denetimi ve çelişki envanteri. Bulgular kanıta dayanır; B-1…B-7'nin bu turda (2026-09-16, ikinci tur) hangi kısmının giderildiği, hangisinin açık kaldığı ve kimin kapsamında olduğu burada dürüstçe kayıtlıdır.
- Dosya: `docs/32-REPOSITORY-AUDIT-AND-CONFLICTS.md`
- İlgili kararlar: `24-DECISION-LOG.md#K-20` (medya stratejisi), `#K-21` (durum modeli), `#K-22` (RDP/`bf-gui-*`), `#K-23` (GNOME vs XFCE A/B), `#K-24` (sürüm kimliği); çelişki giderimi ayrıca `#K-01`…`#K-19` ile karşılaştırılmıştır
- Durum: [x] Rapor (denetim güncellemesi: 2026-09-16); [ ] Açık maddeler (`§9`) kapanmadan kapatılmaz
---
## 1. Amaç
İki amaç: (1) Field OS hedefiyle mevcut çalışan sistem arasındaki farkı kanıtla göstermek, (2) dokümanlar arasındaki **çelişkileri** görünür kılmak ve her birinin giderim durumunu izlemek. Doğrulanamayan hiçbir iddia "tamam" sayılmaz.
## 2. Kapsam
- Kapsam içi: dosya sistemi kanıtı, `git status`/`git log`, test sonuçları, resmi kaynak doğrulaması, doküman çelişkileri ve giderim durumu.
- Kapsam dışı: gerçek donanım/QEMU/Windows testleri, gerçek ISO ve offline paket deposunun üretimi (F2B işi), merkezi kontrol düzlemi kodlaması.
## 3. Bulgular
```text
KARAR: Bu doküman salt-okunur bir DENETİM raporudur; kendisi mimari karar üretmez, karar günlüğünü (24) ve yol haritasını (25) denetler ve çelişkileri kayıt altına alır.
GEREKÇE: Denetim bulgusunun karar yerine geçmesi, "kanıt" ile "tercih" ayrımını bozar; bu yüzden her bulgu ya bir K-kararına bağlanır ya da açık madde olarak yazılır.
ALTERNATİF: Bulguları doğrudan karar olarak yazmak — aynı konunun iki yerde farklı kararla anılması çelişkisi doğar → elendi.
RİSK: Rapor bayatlarsa yanlış "tamam" izlenimi üretir; azaltma: her turda tarih + giderim durumu tablosu güncellenir (§9).
MALİYET: Ücretsiz.
LİSANS: Yok (kurum içi denetim raporu).
```
### 3.1 Özet (ilk tur, 2026-09-16 — tarihsel kayıt)
Depo, prompt'un büyük bölümünü **zaten uygulamış durumda**; ancak ilk turda iki kritik çıktı eksikti: gerçek bir Field OS ISO'su üretilmiyordu ve offline paket deposu boştu. V1 medya kararı (upstream ISO + seed USB) prompt'un istediği tek-medya deneyimiyle çelişiyordu.
| Durum (ilk tur) | Sayı |
|---|---|
| Tüm statik testler | **GEÇTİ** (5/5) — *ikinci turda test seti 7 scripte çıktı ve 2 script kırmızı; bkz. §10* |
| Doküman | 32 (26–31 dahil) — *ikinci turda 33 (32 dahil)* |
| Commit edilmemiş değişiklik | 1 büyük çalışma ağacı |
> **Anlık görüntü uyarısı:** Bu depo 2026-09-16 turunda **paralel çalışma akışlarıyla** aynı anda düzenlenmektedir (offline repo, firstboot, testler, playbook'lar ve bu rapor dahil). §9 ve §10'daki durumlar **10:52 itibarıyla** doğrulanmış anlık görüntüdür; aynı gün içinde başka bir akış tarafından değiştirilebilir. Bir maddeyi kapatmadan önce ilgili komutu yeniden çalıştırın.
### 3.2 Git durumu
```
HEAD: 64a557f chore: keep local .hermes plans out of the repo
0b56649 docs: Blueforce 700-device fleet architecture (TR) - GNOME baseline
6402e85 first init.
```
- 14 doküman `modified` (README, SUMMARY, docs/01,03,04,05,07,08,14,16,20,21,24,25)
- ~70+ yeni dosya `untracked` (ansible, config, monitoring, provisioning, scripts, tests, rustdesk, docs-site, docs/26–31, IMPLEMENTATION-REPORT.md)
- **Hiçbiri commit edilmemiş.** Bu turda da commit atılmadı (bilinçli).
### 3.3 Zaten var olanlar (korunacak — yeniden yazılmayacak)
| Alan | Kanıt |
|---|---|
| `blueforce-install.sh` + 18 modül | `scripts/install/installer/modules/01..18` |
| `BF-<8>/bf-<8>` standardı | `blueforce-install.sh` bayi no kapısı `^[0-9]{8}$` |
| `bf-status`, `bf-diagnostics`, `bf-support-bundle`, `bf-hardware-inventory.sh` | `scripts/diagnostics/` |
| 3-aşamalı durum scriptleri | `bf-check-local`, `bf-check-enrollment`, `bf-check-ready` |
| Yeni komutlar | `bf-enroll`, `bf-enroll-now`, `bf-enrollment-status`, `bf-release`, `bf-remote-status` |
| Canlı HW kontrolü | `scripts/diagnostics/bf-live-hw-check` (91 satır, mutasyonsuz) |
| Windows geçiş araçları | `scripts/migration/windows/BF-WindowsPreMigrationInventory.ps1`, `BF-WindowsDataExport.ps1` |
| Ansible | 16 playbook + 4 rol + inventory (dalga grupları `pilot_1/wave_1` Ansible-uyumlu) |
| Update kapıları | `bf-deploy-update.yml` dalga + `production_override` + `halt_on_fail` |
| WireGuard/SSH/xRDP/RustDesk | config/ + installer modülleri 06,07,08,09 |
| MeshCentral | erişim kanalı olarak dokümante (docs/07) |
| Docker politikaları | `config/docker/daemon.json`, `DOCKER-USER` kalıcı servis |
| Monitoring | `monitoring/` Prometheus + Grafana OSS + Uptime Kuma |
| Docusaurus | `docs-site/` (build PASS) |
| Firewall / logging / recovery | config/ + docs/15, 17 |
| Provisioning iskeleti | `provisioning/{autoinstall,iso,offline-repo,firstboot,enrollment,release}` |
| Testler | 5 script + QEMU planı |
## 4. Neden Bu Karar?
Denetim, "çalışıyor mu?" sorusunu **kanıtla** yanıtlamak için vardır. Bu depoda en pahalı hata tipi, "dokümanda yazıyor" ile "gerçekten üretildi" farkının karışmasıdır: ISO dosyası, offline paket deposu ve merkezi enrollment endpoint'i olmadan "offline kurulum çalışıyor" denemez. Aynı şekilde iki doküman aynı konuda farklı şey söylüyorsa (RDP bir yerde `bf-gui-on` gerektirir, başka yerde her zaman hazır; GNOME bir yerde tamamen kapanmış, başka yerde ölçüm adayı), saha teknisyeni yanlış prosedürü uygular. Bu rapor o farkı görünür tutar.
## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---|
| Kanıt-temelli yazılı denetim (bu rapor) | Tekrarlanabilir, izlenebilir | Gerçek donanım kanıtı vermez | **Seçildi** |
| Yalnız test çıktısıyla denetim | Hızlı | Doküman çelişkilerini görmez | Yetersiz |
| Yalnız gözle denetim | Esnek | Kanıtsız, tekrar üretilemez | Elendi |
## 6. Avantajlar
- Her bulgu dosya/satır kanıtına bağlı; "tamam" iddiası yalnız çalıştırılan komutla veriliyor.
- Giderim durumu tur tur izlenebiliyor (§9), böylece kimse "bu madde kapandı mı?" diye tahmin etmiyor.
- Yazma yetkisi dışında kalan artıklar (örn. `docs/26`) açıkça sahiplendiriliyor; sessiz çelişki kalmıyor.
## 7. Dezavantajlar
- Rapor, doküman setinin "kanonik" sayısını artırır (33 numaralı dosya) ve `tests/check-specs.sh` sabitleriyle uyumsuzluk doğurur (§10).
- Bazı maddeler (B-1/B-2) kod gerektirir; rapor onları kapatamaz, yalnız işaretler.
## 8. Riskler
| Risk | Olasılık | Etki | Azaltma |
|---|---|---|---|
| Rapor bayatlar, kapanmış madde açık görünür | Orta | Orta | Her turda §9 tablosu ve tarih güncellenir |
| "Doküman kapandı" diye kod işi (ISO/offline repo) kapanmış sayılır | Orta | Yüksek | §9'da "doküman katmanı" ile "kod/artefact" ayrımı ayrı sütunlarda |
| Yazma yetkisi dışındaki dosyalarda çelişki kalır ve sahaya yansır | Orta | Yüksek | §9'daki "kalan artık + sahip" listesi; PR sahibi atanır |
## 9. Uygulama Planı — Giderim Durumu (2026-09-16, ikinci tur)
> Sütunlar: **Durum** = bu turda değişti mi · **Kalan artık** = hâlâ açık olan kısım ve sahibi.
| ID | İş | Öncelik | Durum (ikinci tur) | Kalan artık |
|---|---|---|---|---|
| B-1 | Gerçek bootable ISO builder | KRİTİK | **KAPANDI (kod)** — builder gerçek medya üretiyor. Boot düzeni `xorriso -report_el_torito as_mkisofs` reçetesinden okunup `-as mkisofs` ile yeniden kuruluyor (sabit değer gömülü değil); üretilen artefakt doğrulandı: `dist/Blueforce-Field-OS-1.0.0-amd64.iso`, 6 481 917 952 byte, SHA256 `cb0cc56f…d431296`, El Torito BIOS+UEFI korunmuş, `/autoinstall.yaml` kökte, `interactive-sections:[storage]`, kimlik/secret yok, `casper/minimal.squashfs` base ile birebir aynı | LAB işi: UEFI/Legacy/Secure Boot boot kabulü, Desktop autoinstall kabulü, air-gapped kurulum (sahibi: LAB; F2B/F3) |
| B-2 | Offline paket manifestleri (somut pin) + repo üretimi | KRİTİK | **KISMEN İLERLEDİ** — manifestler artık yalnız yorum değil: pin biçim sözleşmesi + `requirements.tsv` denetim tablosu + `packages.lock.schema.tsv` yazıldı, `build-offline-repo.sh` `--dry-run-plan` ile sapma denetliyor, `tests/check-offline-repo-static.sh` **PASS**. **Ama pinler hâlâ `UNPINNED` sentinel** (base-packages 14, docker 7, remote-access 7 satır), `packages.lock.tsv` ve `.deb` havuzu yok, `provisioning/offline-repo/packages/` boş | Onaylanmış `.deb` havuzu + gerçek `package=version` pinleri + `packages.lock.tsv` + air-gapped kurulum kanıtı (sahibi: release yöneticisi; F2B) |
| B-3 | Medya kararını uyumlaştır (tek USB, yine assisted) | KRİTİK | **KISMEN KAPANDI (doküman katmanı)** — `docs/24` K-20 eklendi (autoinstall = motor, Field OS ISO = paketleme; `interactive-sections: [storage]` korunur), README/SUMMARY/`docs/25` bu karara göre yazıldı; K-15 silinmedi, yedek yol olarak korundu | `docs/26` hâlâ "V1 medya = upstream ISO + seed, custom ISO tamamlayıcı" diyor; `docs/26` K-20 ile hizalanmalı (sahibi: 26 yazarı). ISO'nun kendisi B-1'de |
| B-4 | Firstboot terminal wizard | YÜKSEK | **BÜYÜK ÖLÇÜDE İLERLEDİ** — `provisioning/firstboot/bf-firstboot` 23 → **381 satır**: Türkçe etiketli interaktif konsol wizard (konsol varsa varsayılan), `--interactive`, `--dealer-id`, `--dealer-id-file`, `--force-reidentity`, `--check` ve test modu. Statik kanıt kapısı bu tur içinde **yeşile döndü** (`tests/test-firstboot-static.sh` PASS, 10:52) | Gerçek konsol/LAB kanıtı (wizard + installer uçtan uca, kimlik değişim prosedürü) ve 3.5'teki `docs/28` bağlantısı (sahibi: provisioning; F2B) |
| B-5 | `--offline` bayrağı doğrula + uygula | YÜKSEK | **KAPANDI (kanıtlı)** — `scripts/install/blueforce-install.sh` `--offline` bayrağını gerçekten destekliyor: yalnız yerel `file:` APT kaynağı, uzak indirme yok, başarıda `state.json`'a `phase=PROVISIONED_OFFLINE` yazıyor; `bf-firstboot` bu bayrağı kullanıyor; `--offline` olmadan damga yazılmıyor | Yok (paket seti için B-2) |
| B-6 | Migration/hw-check konum ve adlarını dokümante et | ORTA | **KAPANDI (dokümante)** — eşleme aşağıda (3.4) yazılı; konum farkı `scripts/` tutarlılığı lehine kabul edildi | Yok |
| B-7 | Doküman çelişkilerini gider | ORTA | **KISMEN KAPANDI** — README başlığı/`Faz 3'te dolar` ifadesi düzeltildi; GNOME/XFCE çelişkisi `docs/24` K-02 güncellemesi + K-23 ile çözüldü; custom ISO/Field OS ISO çelişkisi K-20 ile çözüldü; SUMMARY'nin 11. cevabı (RDP) ve 4. cevabı (`bf-gui-*`) düzeltildi; `docs/24` Ek B notu ("SUMMARY Faz 4'te üretilecek") güncellendi | Aşağıdaki **6 artık** (§3.5) — hepsi yazma kapsamı dışındaki dosyalarda |
### 3.4 B-6 kapanış kaydı: ad ve konum eşlemesi
| Prompt'ta istenen | Depodaki gerçek | Not |
|---|---|---|
| `migration/windows/bf-win-inventory.ps1` | `scripts/migration/windows/BF-WindowsPreMigrationInventory.ps1` | Konum `scripts/` altında kabul edildi (depo tutarlılığı); içerik mutasyonsuz/credential'sız (test edilir) |
| `migration/windows/bf-win-network-export.ps1` + `bf-win-software-export.ps1` + `bf-win-device-export.ps1` | `scripts/migration/windows/BF-WindowsDataExport.ps1` | Tek script aynı veri sınıflarını dışa aktarır |
| `scripts/live/bf-live-hw-check` | `scripts/diagnostics/bf-live-hw-check` | Canlı HW kontrolü tanı grubunda |
| `scripts/live/…` AnyDesk modülü | `scripts/install/optional/anydesk/install-anydesk.sh` | Opsiyonel modül; lisans onayı olmadan indirme/kurulum yapmaz |
### 3.5 B-7 artıkları (bu turda giderilemeyen çelişkiler — yazma kapsamı dışı)
| Konu | Dosya (düzeltilmeli) | Sorun | Öneri |
|---|---|---|---|
| GNOME/XFCE | `docs/03-UBUNTU-BASELINE.md` (§3 K-02 bloğu, §5 tablo satırı) | "XFCE elendi; yedekte tutulmaz" — `docs/24` K-02+K-23 ile artık ölçüme bağlı | §3'te ALTERNATİF satırını "LAB A/B ölçüm adayı (K-23), saha standardı GNOME" yap; §5 tablosunda sonucu "LAB A/B ölçümü" yaz |
| Custom ISO / Field OS ISO | `docs/04-GOLDEN-IMAGE-AND-PROVISIONING.md` (§3 KARAR, §4, §5) | (a) "Custom ISO/Packer elendi" ve "Custom Field ISO → LAB artefact / **F6**" — K-20 ile assisted Field ISO **F2B**'ye taşındı (Packer hâlâ elenmiş durumda); (b) §3 KARAR "V1'de resmi upstream ISO + ayrı NoCloud seed USB kullanılır" — K-20'de birincil teslim medyası F2B tek-USB'dir, upstream ISO + seed **geri dönüş** yoludur | §4'ü "autoinstall motoru + Field OS ISO paketleme (K-20)" olarak ayır; §5 satırını "F2B assisted artefact; Packer elendi" yap; §3'te "V1'de resmi upstream ISO + seed" cümlesine "geri dönüş yolu" nitelemesi ekle |
| V1 medya (mimari) | `docs/01-ARCHITECTURE.md` (§3 KARAR) | "V1 medya = upstream ISO + ayrı NoCloud seed USB" — K-20/F2B ile birincil medya tek USB Field OS ISO, upstream+seed yedek yol | Cümleyi "birincil: F2B tek-USB Field OS ISO; yedek: upstream ISO + seed (K-20)" olarak güncelle |
| V1 medya (öğrenme rehberi) | `docs/31-LEARNING-GUIDE.md` (§9 konu 11) | "V1'de upstream ISO + ayrı NoCloud seed USB, custom Field ISO'dan daha az risklidir; ikisi tamamlayıcıdır" — F2B hedefiyle çelişir | "assisted tek-USB Field OS ISO (K-20) esas; upstream ISO + seed geri dönüş yolu" olarak düzelt |
| V1 medya (ISO dokümanı) | `docs/26-BLUEFORCE-FIELD-OS-ISO.md` (§3 KARAR, §5 tablo, §7, §12 kontrol listesi) | "V1 kurulum medyası = upstream ISO + ayrı NoCloud seed USB; custom ISO tamamlayıcı" ve "V1 etiketi upstream ISO + seed olarak basıldı" — K-20 ile birincil teslim F2B tek-USB assisted medyadır | K-15 bloğuna "K-20 ile birlikte okunur" notu ekle; §5 tablosunda V1/F2B eşlemesini düzelt; §12 kontrol listesini "F2B tek-USB ISO etiketi" olarak güncelle |
| RDP / `bf-gui-on` | `docs/07-REMOTE-ACCESS.md` (§3 ilk KARAR bloğu) | "GUI varsayılan kapalıdır, bakım penceresinde `bf-gui-on` ile açılır" ifadesi RDP'yi anahtara bağlı gösterir; aynı dosyanın ikinci KARAR bloğu ve `docs/16` xrdp'in her zaman ready olduğunu söyler | İlk blokta "GUI" yerine "**yerel fiziksel** GUI" yaz ve xRDP'in bundan bağımsız her zaman aktif olduğunu ekle (K-22) |
| Windows geçiş kapsamı | `docs/28` §9.2 "Onaylı araçla geri dönüş imajı al…" | Araç adı (`BF-Windows…ps1`) anılmıyor — B-6 kapanışıyla hizalanması iyi olur | §9.2'ye script adlarını ekle |
### 3.6 Bu turda eklenen yeni bulgular
| ID | Bulgu | Kanıt | Açık mı? |
|---|---|---|---|
| B-9 | `/etc/blueforce-release` **yazılmıyor**: yalnız `bf-release` okuma yolu + legacy KEY=VALUE uyumluluğu var; yazan bir release pipeline adımı depoda yok | `grep -rn blueforce-release` → sadece `bf-release` (okuma) ve `17-healthcheck`'in `/etc/blueforce/release-manifest.yaml` kopyası | **AÇIK** — sahibi 30-release; K-24 risk satırında kayıtlı |
| B-10 | `tests/check-specs.sh` sabitleri bayat: "exactly docs/00 through docs/31" (32 dosya) ve "expected 34 Mermaid blocks" — depo gerçekte 33 numaralı dosya ve 35 Mermaid bloğu içeriyor | `bash tests/check-specs.sh` → `FAIL: expected 32 numbered docs, found 33` / `FAIL: expected 34 Mermaid blocks, found 35` | **AÇIK** — düzeltme `tests/` kapsamında (bu raporun yazma kapsamı dışı) |
| B-11 | Kanonik doküman sayısı ikinci turda 32→33 oldu; bu rapor da numaralı namespace'te olduğu için B-10'un kök nedeni buradadır | `ls docs/[0-9][0-9]-*.md \| wc -l` = 33 | **AÇIK** — iki seçenek: sabitleri güncelle, veya bu raporu numaralı namespace dışına taşı |
| B-12 | **Paralel düzenleme çakışması:** aynı gün başka bir çalışma akışı bu dosyayı (10:37:21) düzenledi; bu raporun güncellemesi (10:39:32) onun sürümünün üzerine yazıldı ve **o sürüm kurtarılamadı** (dosya git'te untracked, yedek yok) | Hermes araç uyarısı: *"was modified by sibling subagent … after this agent's last read"*; `git status` → `?? docs/32-…`; session kayıtlarında içerik izi yok | **AÇIK (süreç)** — o akışın eklemek istediği bilgi varsa yeniden yazılmalı. Ders: aynı dosyaya iki akış yazacaksa tek sahip atanmalı veya yazmadan önce dosya yeniden okunmalı |
### 3.7 Açık ve doğrulanmamış iddialar (bilgi amaçlı, değişmedi)
- Hiç ISO build edilmedi (rapor da kabul ediyor).
- Hiç offline repo üretilmedi.
- Merkezi enrollment endpoint'i yok.
- Gerçek donanım/Windows/QEMU testi yapılmadı.
- `sudo` gerektiren komutlar bu turda çalıştırılmadı (parolasız sudo yok).
## 10. Test Planı (bu turda çalıştırılanlar ve sonuçları)
> Anlık görüntü: **2026-09-16 10:52**. Paralel çalışma akışları test setini aynı gün genişlettiği için bu tablo bir iddia değil, **zaman damgalı kanıttır**.
| Komut | Sonuç | Not |
|---|---|---|
| `tests/check-specs.sh` | **FAIL** | (B-10) iki bayat sabit: `expected 32 numbered docs, found 33` ve `expected 34 Mermaid blocks, found 35`; ayrıca içindeki çağrıyla `test-field-os-lifecycle.sh` düşüyor (aşağıdaki satır) |
| `tests/check-offline-repo-static.sh` | **PASS** | Bu tur eklenen offline repo pin/biçim sözleşmesi denetimi |
| `tests/check-provisioning-static.sh` | **PASS** | — |
| `tests/check-migration-static.sh` | **PASS** | — |
| `tests/check-configs.sh` | **PASS** | — |
| `tests/test-field-os-lifecycle.sh` | **FAIL** | `ENROLLED state with independent local and central evidence must become ready-check eligible` — durum makinesi/READY kapısı üzerinde devam eden akış (K-21/K-22 ile ilişkili) |
| `tests/test-firstboot-static.sh` | **PASS** *(10:52)* | Firstboot wizard/installer bayrak sözleşmesi bu tur içinde yeşile döndü; gerçek konsol LAB kanıtı yine de gerekir (B-4) |
| Doküman yapı denetimi (`docs/[0-9][0-9]-*.md`) | **33/33 uyumlu** | Her dosyada §1–§13 sırası + ≥1 Mermaid (bu rapor bu turda §1–§13 + 1 diyagrama getirildi) |
| Üretim artefaktı denetimi | **YOK** | `*.iso` yok, `*.deb` yok, `packages.lock.tsv` yok, `provisioning/offline-repo/packages/` boş |
| URL doğrulaması (curl, HTTP 200) | **PASS** | Subiquity autoinstall referansı, cloud-init NoCloud, gnome.org, xfce.org, neutrinolabs/xrdp |
**Test sonucu yorumu:** Altı script yeşil; tek kırmızı `test-field-os-lifecycle.sh` ve bu, K-21/K-22 (durum modeli + RDP/READY sözleşmesi) üzerinde hâlâ çalışılan akışa işaret ediyor. Bu rapor kırmızı testi olan sözleşmeyi **kapalı** saymaz.
Resmi kaynak doğrulaması (canlı, 2026-09-16 — `canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-reference.html`, HTTP 200):
1. **`/autoinstall.yaml` yolu sabit:** *"The autoinstall configuration is available at `/autoinstall.yaml` (irrespective of how it was provided)"* → ISO köküne konan autoinstall desteklenir.
2. **`interactive-sections` destekli:** *"A list of configuration keys to still show in the user interface (UI)"*.
3. **`storage` interactive olabilir:** *"can be interactive: true"* + varsayılan *"use the lvm layout on single-disk systems"* → storage interactive bırakılmazsa en büyük disk kendiliğinden bölümlenir; bu yüzden `interactive-sections: [storage]` hem tek-USB deneyimini hem "otomatik disk silme yok" şartını sağlar.
4. **Offline davranış resmi anahtar:** `apt.fallback` varsayılanı **`offline-install`** — *"revert to an offline installation"*.
5. **`seed` yolu ve NoCloud** resmi quickstart'ta belgeli (`cloud-localds`, `user-data` + `meta-data`, `ds=nocloud`).
**Doğrulanamayan:** `xorriso`/remaster ile 26.04 UEFI/Legacy/Secure Boot davranışı — LAB testi gerekir (B-1).
## 11. Rollback
1. Bu rapor bir denetim kaydıdır; hatalı bir bulgu düzeltilecekse aynı dosyada tarihli düzeltme notu düşülür (silme yok).
2. Bu turda yapılan doküman değişiklikleri commit edilmediği için geri alma `git checkout -- README.md SUMMARY.md docs/24-*.md docs/25-*.md docs/32-*.md` ile tek adımda yapılabilir.
3. Karar seviyesindeki geri alma (örn. K-20 medya stratejisi) `docs/24` PR'ı ile yapılır ve bağımlı dosyalar (README, SUMMARY, 25, 26, 31) aynı PR'da güncellenir.
## 12. Kontrol Listesi
- [x] B-1…B-7'nin her biri için bu turdaki durum yazıldı (kapandı / kısmen / açık).
- [x] Kapanan maddeler kanıtla desteklendi (B-5: `--offline` + `state.json`; B-6: eşleme tablosu).
- [x] Giderilemeyen çelişkiler dosya + öneri ile listelendi (§3.5).
- [x] Yeni bulgular (B-9…B-12) kayda geçti.
- [ ] B-1 (ISO), B-2 (gerçek paket pinleri/pool) ve B-4 (firstboot testleri yeşil) kapanmadan bu rapor "tamam" sayılmaz.
- [ ] §3.5 artıkları (docs/03, 04, 26, 07, 28) doküman PR'ıyla kapatılmalı.
- [ ] `tests/check-specs.sh` sabitleri güncellenmeli (B-10) veya bu rapor numaralı namespace dışına taşınmalı (B-11).
- [ ] Kırmızı testler (`test-field-os-lifecycle.sh`, `test-firstboot-static.sh`) yeşile dönmeli; kapanış ancak bundan sonra ilan edilir.
- [ ] Bu rapora aynı gün içinde tek sahip atanmalı (B-12: paralel yazım çakışması bir tur kaybettirdi).
## 13. Açık Sorular
- [ ] Remaster edilmiş Field OS ISO'nun UEFI/Legacy/Secure Boot matrisi — LAB (sahibi: provisioning/release, F2B).
- [ ] Offline APT snapshot'ın yenileme sıklığı ve paket onay süreci (sahibi: release yöneticisi, F2B).
- [ ] `docs/32` kanonik doküman sayılsın mı, yoksa numaralı namespace dışına mı taşınsın (sahibi: doküman sahibi + test sahibi).
- [ ] Bu raporun `docs-site`'a dahil edilip edilmeyeceği (numaralı dosya olduğu için şu an sitede yayınlanıyor) (sahibi: 22 yazarı).
---
## Ek: Çelişki ve giderim haritası
```mermaid
flowchart TB
subgraph KONU["Çelişki konuları"]
G["GNOME / XFCE"]
M["Custom ISO / Field OS ISO"]
R["RDP / bf-gui-on"]
S["READY / 3 durum"]
F["Unattended F6 / Assisted F2"]
O["Offline / network zorunlu mu"]
end
subgraph KARAR["Karar günlüğü (kilit kaynak)"]
K02["K-02 + K-23"]
K20["K-20 (K-15 ile)"]
K22["K-22 (K-03 ile)"]
K21["K-21 (K-16/K-17 ile)"]
K25["Roadmap F2A/F2B + F6"]
K16["K-16 + K-21"]
end
subgraph DURUM["Bu turdaki durum"]
D1["Giderildi: README/SUMMARY/24/25<br/>Artık: docs/03"]
D2["Giderildi: 24/README/SUMMARY/25<br/>Artık: docs/04, docs/26"]
D3["Giderildi: SUMMARY/24<br/>Artık: docs/07"]
D4["Giderildi: 24/25/README/SUMMARY<br/>Artık yok"]
D5["Giderildi: 25 F2A/F2B/F6<br/>Artık yok"]
D6["Giderildi: 24/SUMMARY<br/>Artık yok"]
end
G --> K02 --> D1
M --> K20 --> D2
R --> K22 --> D3
S --> K21 --> D4
F --> K25 --> D5
O --> K16 --> D6
```

View file

@ -6,6 +6,16 @@
- İlgili kararlar: `24-DECISION-LOG.md#<başlık>` - İlgili kararlar: `24-DECISION-LOG.md#<başlık>`
- Durum: [ ] Taslak / [ ] İncelemede / [ ] Onaylı - Durum: [ ] Taslak / [ ] İncelemede / [ ] Onaylı
**Şablon kuralları (bu repoda zorunlu):**
- Bölümler `## 1.` … `## 13.` sırasıyla ve atlanmadan doldurulur; ek bölümler yalnız `## Ek: <ad>` başlığıyla açılır (numaralı bölüm eklenmez).
- Her numaralı dokümanda **en az bir Mermaid** diyagram bloğu bulunur (akış, katman, durum veya kapı diyagramı).
- Dil: açıklamalar Türkçe; komut, dosya, servis, paket ve terim adları İngilizce kalır.
- Her teknik iddia kaynaklıdır: KARAR bloklarında resmi kaynak URL + sürüm + doğrulama tarihi yazılır; doğrulanamayan davranış "LAB gerekli" etiketiyle açık bırakılır.
- Dosya numarası bir kez verilir ve yeniden kullanılmaz; kararlar (`24-DECISION-LOG.md`) **append-only**dir, mevcut karar silinmez/yeniden numaralanmaz.
- Secret yasak: örneklerde yer tutucu kullanılır (parola, private key, token, cihaz-spesifik kimlik yazılmaz).
- Bu dosya (`_TEMPLATE.md`) numaralı değildir ve `docs-site`'a alınmaz.
--- ---
## 1. Amaç ## 1. Amaç

30
migration/README.md Normal file
View file

@ -0,0 +1,30 @@
# `migration/` — location pointer
This directory exists only as a **pointer**. The migration tooling is not here.
- **Actual location:** `scripts/migration/windows/`
(`BF-WindowsPreMigrationInventory.ps1`, `BF-WindowsDataExport.ps1`, `README.md`)
- **Requested location:** `migration/windows/` (the layout the original brief
described).
The repository keeps every operational script under `scripts/`
(`scripts/{install,maintenance,diagnostics,recovery,migration}`), so the Windows
pre-migration tooling lives in `scripts/migration/windows/` for consistency with
`blueforce-install.sh`, `bf-status`, `bf-hardware-inventory.sh` and the
diagnostics set. The same rule placed `bf-live-hw-check` under
`scripts/diagnostics/` instead of `scripts/live/`.
## Where to go
| Need | Path |
|---|---|
| Windows inventory / data-export scripts + full usage | `scripts/migration/windows/README.md` |
| Requested-name ↔ repo-name mapping table | `scripts/migration/windows/README.md` |
| Migration procedure, acceptance gate, rollback | `docs/28-WINDOWS-TO-LINUX-MIGRATION.md` |
| Location/name deviation audit entry (§B-6) | `docs/32-REPOSITORY-AUDIT-AND-CONFLICTS.md` |
| Post-migration read-only hardware gate | `scripts/diagnostics/bf-live-hw-check` |
| Static safety tests for the migration tooling | `tests/check-migration-static.sh` |
No script, wrapper or symlink is placed here on purpose: two real locations for
the same tooling would drift, and the static test asserts the single canonical
directory (`scripts/migration/windows/`).

22
monitoring/README.md Normal file
View file

@ -0,0 +1,22 @@
# Central monitoring stack (Prometheus + Grafana OSS + Uptime Kuma)
## Start
Create a root-owned password file whose contents are the Grafana first-admin password (no newline is preferred), then start with its absolute path:
```bash
install -m 600 -o root -g root /dev/null /etc/blueforce/grafana-admin-password
# Write the generated password directly to /etc/blueforce/grafana-admin-password.
cd monitoring
GRAFANA_ADMIN_PASSWORD_FILE=/etc/blueforce/grafana-admin-password docker compose up -d
```
`GRAFANA_ADMIN_PASSWORD_FILE` is mandatory. Compose mounts that host file as a Docker secret; it is not stored in the repository or printed in the compose environment. The process owner must keep the file root-owned and mode `0600`. Grafana is available at `http://127.0.0.1:3001`; Prometheus at `:9090`; Uptime Kuma at `:3002`.
## Fleet targets
Field devices are scraped via file-based service discovery in `prometheus/targets/*.json` (see `prometheus/targets/README.md`).
## Alerts
Validate changes with `promtool check rules prometheus/alerts.yml` and `promtool check config prometheus/prometheus.yml`.

View file

@ -0,0 +1,47 @@
# Central monitoring stack for the Blueforce fleet.
# Grafana requires GF_SECURITY_ADMIN_PASSWORD__FILE on first startup; no default credential exists.
services:
prometheus:
image: prom/prometheus:v3.1.0
container_name: bf-prometheus
restart: unless-stopped
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/alerts.yml:/etc/prometheus/alerts.yml:ro
- ./prometheus/targets:/etc/prometheus/targets:ro
- prometheus-data:/prometheus
command: [--config.file=/etc/prometheus/prometheus.yml, --storage.tsdb.path=/prometheus, --storage.tsdb.retention.time=90d, --web.enable-lifecycle]
ports: ["127.0.0.1:9090:9090"]
networks: [monitoring]
grafana:
image: grafana/grafana-oss:11
container_name: bf-grafana
restart: unless-stopped
depends_on: [prometheus]
environment:
GF_SECURITY_ADMIN_USER: "${GF_SECURITY_ADMIN_USER:-admin}"
GF_SECURITY_ADMIN_PASSWORD__FILE: /run/secrets/grafana_admin_password
secrets: [grafana_admin_password]
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/etc/grafana/dashboards:ro
ports: ["127.0.0.1:3001:3000"]
networks: [monitoring]
uptime-kuma:
image: louislam/uptime-kuma:1
container_name: bf-uptime-kuma
restart: unless-stopped
volumes: [uptime-kuma-data:/app/data]
ports: ["127.0.0.1:3002:3001"]
networks: [monitoring]
secrets:
grafana_admin_password:
file: "${GRAFANA_ADMIN_PASSWORD_FILE:?Set GRAFANA_ADMIN_PASSWORD_FILE to a root-owned password file}"
volumes:
prometheus-data:
grafana-data:
uptime-kuma-data:
networks:
monitoring:
driver: bridge

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,13 @@
# Auto-provision the fleet dashboard from disk (no manual import).
apiVersion: 1
providers:
- name: blueforce-fleet
orgId: 1
folder: Blueforce
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: false
options:
path: /etc/grafana/dashboards

View file

@ -0,0 +1,10 @@
# Auto-provisioned Prometheus datasource for Grafana OSS.
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false

View file

@ -0,0 +1,54 @@
# Fleet alert rules. Severity: critical pages the on-call admin (14-MONITORING).
# Notification endpoints (SMS/e-mail/webhook) are wired in the central
# Grafana Alerting / Alertmanager setup, not in this file.
# All expressions assume 60s scrape interval.
groups:
- name: fleet
interval: 60s
rules:
# Device does not answer scrapes for 5 minutes -> presumed offline.
- alert: FleetOffline
expr: up{job="node"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Device {{ $labels.device_id }} is offline"
description: "No successful scrape for 5m (instance {{ $labels.instance }}). Check Uptime Kuma last-seen and field power/network."
# Root filesystem less than 10% available for 10 minutes.
- alert: FleetDiskFull
expr: |
(
node_filesystem_avail_bytes{job="node", mountpoint="/", fstype!~"tmpfs|overlay"}
/
node_filesystem_size_bytes{job="node", mountpoint="/", fstype!~"tmpfs|overlay"}
) < 0.10
for: 10m
labels:
severity: critical
annotations:
summary: "Device {{ $labels.device_id }} disk almost full"
description: "Root filesystem <10% available for 10m on {{ $labels.instance }}."
# Boot time changed recently -> unexpected reboot (power loss or crash).
- alert: FleetUnexpectedReboot
expr: time() - node_boot_time_seconds{job="node"} < 15 * 60
for: 5m
labels:
severity: critical
annotations:
summary: "Device {{ $labels.device_id }} rebooted unexpectedly"
description: "{{ $labels.instance }} booted less than 15m ago. Correlate with power-loss recovery (16-POWER-LOSS)."
# unattended-upgrades must stay DISABLED (K-11); 1 means the lock was broken.
# Source: textfile metric unattended_upgrade_active.
- alert: FleetSecurityUpdatesViolation
expr: unattended_upgrade_active{job="node"} == 1
for: 5m
labels:
severity: critical
annotations:
summary: "Device {{ $labels.device_id }} has unattended-upgrades enabled"
description: "Update policy lock (K-11) violated on {{ $labels.instance }}. Disable unattended-upgrades per 10-UPDATE."

View file

@ -0,0 +1,41 @@
# Prometheus configuration for the Blueforce fleet.
# Scrape interval 60s keeps cardinality/storage tractable at 700 nodes.
# Fleet targets scale via file-based service discovery: add/remove entries
# in targets/fleet.json without reloading Prometheus (refresh_interval 30s).
global:
scrape_interval: 60s
scrape_timeout: 10s
evaluation_interval: 60s
external_labels:
fleet: blueforce
rule_files:
- alerts.yml
scrape_configs:
# Prometheus self-monitoring.
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
# Field devices: Node Exporter (:9100) + textfile collector metrics.
# Identity label contract: device_id="BF-<no>" on every target.
- job_name: node
file_sd_configs:
- files: ["/etc/prometheus/targets/*.json"]
refresh_interval: 30s
relabel_configs:
- source_labels: [__address__]
target_label: __address__
regex: (.+)
replacement: ${1}:9100
- source_labels: [device_id]
target_label: device_id
regex: (.+)
replacement: ${1}
metric_relabel_configs:
# Enforce the device_id label contract; drop series without it.
- source_labels: [device_id]
regex: .+
action: keep

View file

@ -0,0 +1,17 @@
# File-based service discovery for the 700-device fleet.
#
# Prometheus watches this directory (refresh 30s); adding or removing a
# `*.json` file takes effect without a reload or restart.
#
# Per-device entry template (WireGuard address as target, identity label
# contract `device_id="BF-<no>"` on every target):
#
# [
# {
# "targets": ["10.7.0.11"],
# "labels": {"device_id": "BF-12010193", "site": "pilot", "job": "node"}
# }
# ]
#
# Scale-out: generate one file per site/region (e.g. `site-ankara.json`)
# from the Ansible inventory; keep each file under a few thousand targets.

View file

@ -0,0 +1,10 @@
[
{
"targets": ["10.7.0.11"],
"labels": {
"device_id": "BF-12010193",
"site": "pilot",
"job": "node"
}
}
]

19
provisioning/README.md Normal file
View file

@ -0,0 +1,19 @@
# Blueforce OS provisioning
Bu dizin işletim sistemi provisioning kapsamının sahibidir. Mevcut `scripts/install/` uygulama bootstrap'ı olarak kalır ve burada taşınmaz.
Güvenlik sınırları:
- Bu iskelet cihaz kimliği, parola, private key, token veya bayiye özel veri içermez.
- Autoinstall V1 disk seçmez ve silmez; `interactive-sections: [storage]` teknisyen seçimini zorunlu tutar.
- Upstream ISO ve paketler örtük indirilmez. Her ISO işleminden önce açık SHA256 doğrulaması gerekir.
- Remaster araçları `xorriso` yoksa veya boot metadatası desteklenmiyorsa başarısız olur.
- Firstboot sadece sekiz haneli bayi numarası biçimini denetler; kurucu veya enrollment çağrısı yapmaz.
Kontrol:
```bash
tests/check-provisioning-static.sh
```
LAB doğrulaması, hedef Ubuntu sürümü, boot menüsü entegrasyonu, gerçek identity storage ve enrollment taşıma güvenliği sonraki fazların açık işleridir.

View file

@ -0,0 +1,29 @@
# Autoinstall
Ubuntu Subiquity autoinstall v1 baseline for the Blueforce Field OS medium. It is
identity-free and stays operator-assisted.
- `autoinstall.yaml` is copied to the root of the installation medium as
`/autoinstall.yaml`, which is where Subiquity reads configuration from the medium
itself; `build-blueforce-iso.sh` also ships a copy under
`/blueforce-provisioning/autoinstall/`.
- `user-data` is the NoCloud transport for the V1 "official ISO + seed USB" flow. Its
autoinstall block is kept identical to `autoinstall.yaml` so both flows behave the same.
- `interactive-sections: [storage]` is mandatory. The technician selects and confirms the
target disk on the installer screen; V1 never wipes a disk automatically.
- `apt.fallback: offline-install` is explicit: when no primary mirror is reachable,
Subiquity reverts to an offline installation from the medium instead of aborting.
- `late-commands` only copy reviewed medium content into the target: the offline APT
repository to `/opt/blueforce/offline-repo`, the firstboot payload to
`/usr/local/sbin/bf-firstboot`, its service unit to `/etc/systemd/system/`, the release
identity to `/etc/blueforce-release`, and the service is enabled. It stays inert until
an operator supplies the dealer ID and the authorised public key on the installed
system (`ConditionPathExists=` guards in the unit).
- No `identity` block, user, password, SSH key, dealer id or disk directive belongs here;
device identity is collected at firstboot (K-09/K-12). The static test suite enforces
this, and `build-blueforce-iso.sh` re-scans its inputs before it writes a medium.
Flavour note: this configuration is written for the Ubuntu installer's autoinstall
contract. The LAB build base is currently the Desktop flavour ISO (see `../iso/README.md`);
the autoinstall path on that flavour must be accepted in the LAB matrix before production
use.

View file

@ -0,0 +1,28 @@
# Ubuntu Subiquity autoinstall v1 for the Blueforce Field OS installation medium.
#
# This file is copied to the root of the installation medium as /autoinstall.yaml,
# which is where Subiquity looks for configuration on the medium itself. No separate
# NoCloud seed USB is needed, and the same content is shipped under
# /blueforce-provisioning/autoinstall/ for the seed-based V1 flow.
#
# Storage stays interactive on purpose: the operator selects and confirms the target
# disk on the installer screen. This file contains no layout, match, wipe, path or
# identity directive, and device identity is collected at firstboot.
autoinstall:
version: 1
interactive-sections: [storage]
# No primary mirror is reachable at an air-gapped site: install from the medium.
apt:
fallback: offline-install
late-commands:
# Medium contents: /autoinstall.yaml plus /blueforce-provisioning/{autoinstall,
# firstboot,offline-repo,release}. These commands only copy reviewed medium
# content into the target; no device identity is created or embedded here.
- mkdir -p /target/opt/blueforce
- cp -a /cdrom/blueforce-provisioning/offline-repo /target/opt/blueforce/offline-repo
- "curtin in-target -- sh -c 'cp -a /opt/blueforce/offline-repo/built/. /opt/blueforce/offline-repo/ 2>/dev/null || true'"
- "curtin in-target -- sh -c 'rmdir /opt/blueforce/offline-repo/built 2>/dev/null || true'"
- install -D -m 0755 /cdrom/blueforce-provisioning/firstboot/bf-firstboot /target/usr/local/sbin/bf-firstboot
- install -D -m 0644 /cdrom/blueforce-provisioning/firstboot/blueforce-firstboot.service /target/etc/systemd/system/blueforce-firstboot.service
- install -D -m 0644 /cdrom/blueforce-provisioning/release/blueforce-release /target/etc/blueforce-release
- curtin in-target -- systemctl enable blueforce-firstboot.service

View file

@ -0,0 +1,2 @@
# Intentionally empty NoCloud metadata. No instance or machine identity is embedded.
{}

Some files were not shown because too many files have changed in this diff Show more