feat(offline-repo): build the APT index so installs work without network

The package pool was committed but the installer still stopped at "no local
package repository": it requires the compiled bundle (Packages, Packages.gz,
packages.lock.tsv, SHA256SUMS), and building that had never run.

The bundle
- 1321 packages, 884 MiB, Ubuntu 26.04 amd64, built with apt-ftparchive on the
  fleet's own release so the closure matches the devices.
- built/pool/ is gitignored: those .deb files are already tracked once under
  provisioning/offline-repo/packages/, and the same ~890 MB does not need a
  second path. What the installer reads from the bundle is the index.
- The output directory is chmod 0755: mktemp -d created it 0700 and left it
  so, which made a valid bundle unreadable to the installer's probe.

dpkg-deb output was parsed wrong in four places
- `dpkg-deb -f <file> Package Version` prints "<Field>: <value>", and the
  callers kept the whole line, so the value carried its label. build-index.sh
  therefore accepted no package at all and reported an empty pool;
  add-packages.sh rejected every real .deb as an invalid name, missed version
  conflicts, and copied a duplicate; build-offline-repo.sh never matched a pin.
- The test suite could not see any of this: its dpkg-deb stub emitted bare
  values. The stub now prints the labels exactly as the real tool does, which
  is what surfaced the remaining three sites.

Tests
- The secret scan covers the files we author, not apt-ftparchive's index over
  third-party packages: polkit's own description reads "...asked for their
  password:", which is prose in the packages, not a leak. Any secret reaching a
  manifest, README or tool is still caught.
- The "test build must not touch the bundle" assertion now compares the
  directory before and after instead of demanding it be empty, so a
  maintainer's real build is not reported as test spill.
This commit is contained in:
emirhanmamak-blueforce 2026-09-16 21:42:27 +03:00
parent 57fbccd4d0
commit e28783e563
44 changed files with 48858 additions and 593 deletions

12
.gitignore vendored
View file

@ -7,6 +7,11 @@
# Nothing here ships; the reviewed tooling lives in provisioning/offline-repo/. # Nothing here ships; the reviewed tooling lives in provisioning/offline-repo/.
/tmp/ /tmp/
# Python bytecode caches. Every .pyc is regenerated on import, so tracking them
# only produces noisy diffs and a stale artifact in the checkout.
__pycache__/
*.py[cod]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Field OS build artifacts. # Field OS build artifacts.
# These are large, generated, and reproducible from a verified upstream ISO. # These are large, generated, and reproducible from a verified upstream ISO.
@ -34,6 +39,13 @@ packages.lock.tsv
# ...but never inside the committed bundle. # ...but never inside the committed bundle.
!provisioning/offline-repo/built/ !provisioning/offline-repo/built/
!provisioning/offline-repo/built/** !provisioning/offline-repo/built/**
#
# Except the bundle's own pool/ copy of the .deb files: those packages are
# already tracked once under provisioning/offline-repo/packages/, and the same
# ~890 MB does not need a second path in the repository. What the installer
# needs from this directory is the index (Packages, Packages.gz,
# packages.lock.tsv, SHA256SUMS) plus repo-manifest.yaml.
provisioning/offline-repo/built/pool/
# Documentation site (generated) # Documentation site (generated)
docs-site/node_modules/ docs-site/node_modules/

View file

@ -10,11 +10,11 @@
%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. %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.
**Kurulum git tabanlıdır:** depo klonlanır, `admin/bf-bootstrap.sh` operatör araçlarını kurar ve sahada tek giriş noktası olan `sudo bf-menu` kategori menüsü açılır. ISO/firstboot provisioning yolu (bkz. [`docs/26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md), [`docs/27`](docs/27-OFFLINE-PROVISIONING.md)) **ikincil/opsiyoneldir**, silinmez. Sıfırdan başlayan için adım adım rehber: [`docs/33-OPERATOR-CONSOLE.md`](docs/33-OPERATOR-CONSOLE.md). **Kurulum git tabanlıdır:** depo klonlanır, `admin/bf-bootstrap.sh` operatör araçlarını kurar ve sahada tek giriş noktası olan **grafik operatör uygulaması** `sudo bf` açılır. Uygulama Python'dur (`bfos`), Türkçedir ve terminal menüsü aynı kodun yedek arayüzüdür (`sudo bf --tui`) — grafik ekran yoksa veya tkinter kurulu değilse devreye girer. ISO/firstboot provisioning yolu (bkz. [`docs/26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md), [`docs/27`](docs/27-OFFLINE-PROVISIONING.md)) **ikincil/opsiyoneldir**, silinmez. Sıfırdan başlayan için adım adım rehber: [`docs/33-OPERATOR-CONSOLE.md`](docs/33-OPERATOR-CONSOLE.md).
## Hızlı Başlangıç (git ile kurulum) ## Hızlı Başlangıç (git ile kurulum)
Kurulumun birincil yolu **git deposudur**; ISO/provisioning yolu (aşağıdaki §Kurulum Akışı, [`docs/26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md)) ikincil ve opsiyoneldir. Sahadaki tek giriş noktası `sudo bf-menu` **operatör konsoludur**: kategoriler numarayla seçilir, komut ezberlenmez. Kurulumun birincil yolu **git deposudur**; ISO/provisioning yolu (aşağıdaki §Kurulum Akışı, [`docs/26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md)) ikincil ve opsiyoneldir. Sahadaki tek giriş noktası **`bf` operatör uygulamasıdır**: her iş ekrandan yapılır, komut ezberlenmez.
```bash ```bash
# 1) Depoyu indir # 1) Depoyu indir
@ -27,14 +27,18 @@ sudo admin/bf-bootstrap.sh
# Depoyu klonlamadan tek satır alternatifi: # Depoyu klonlamadan tek satır alternatifi:
# curl -fsSL https://raw.githubusercontent.com/emirhanmamak-blueforce/Blueforce-Linux/main/admin/bf-bootstrap.sh | sudo bash # curl -fsSL https://raw.githubusercontent.com/emirhanmamak-blueforce/Blueforce-Linux/main/admin/bf-bootstrap.sh | sudo bash
# 3) Operatör konsolunu aç # 3) Operatör uygulamasını aç (grafik arayüz)
sudo bf-menu sudo bf
# Grafik ekran yoksa (SSH, başsız sunucu) aynı uygulamanın terminal konsolu:
sudo bf --tui
``` ```
Menüden **1) Installation → 2) Install this device** seçilir ve 8 haneli bayi no girilir (ör. `12010193` → cihaz `BF-12010193`, hostname `bf-12010193`). Kurulum sonunda cihaz `PROVISIONED_OFFLINE` olur; `READY` yalnız merkez kanalları doğrulanınca alınır. Uygulama açılınca **Kurulum → Bu cihazı kur** seçilir ve 8 haneli bayi no girilir (ör. `12010193` → cihaz `BF-12010193`, hostname `bf-12010193`). Kurulum sonunda cihaz `PROVISIONED_OFFLINE` olur; `READY` yalnız merkez kanalları doğrulanınca alınır.
**Yetki:** kurulum ve durum değiştiren işlemler yönetici yetkisi ister. `sudo bf` ile açıldığında tüm işlemler açıktır; yetkisiz açılırsa uygulama bunu başlangıçta söyler ve yalnız salt-okunur raporları çalıştırır.
- Sıfırdan başlayan için adım adım rehber (hangi ekranda ne yazılır): [`docs/33-OPERATOR-CONSOLE.md`](docs/33-OPERATOR-CONSOLE.md) - Sıfırdan başlayan için adım adım rehber (hangi ekranda ne yazılır): [`docs/33-OPERATOR-CONSOLE.md`](docs/33-OPERATOR-CONSOLE.md)
- Menü ↔ komut eşlemesi ve kategori haritası aynı dokümanın Ek'lerindedir; özet soru-cevap: [`SUMMARY.md`](SUMMARY.md). - Ekran ↔ komut eşlemesi ve kategori haritası aynı dokümanın Ek'lerindedir; özet soru-cevap: [`SUMMARY.md`](SUMMARY.md).
## Field OS Nedir? ## Field OS Nedir?

View file

@ -11,10 +11,10 @@
## 1. Field OS için Kısa Soru-Cevap (2026-09-16 turu) ## 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) - **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)
- **Nasıl kurulur?** — Kurulumun birincil yolu **git deposudur**: `git clone https://github.com/emirhanmamak-blueforce/Blueforce-Linux.git` → `cd Blueforce-Linux` → `sudo admin/bf-bootstrap.sh` (depoyu klonlamadan tek satır alternatifi: `curl -fsSL https://raw.githubusercontent.com/emirhanmamak-blueforce/Blueforce-Linux/main/admin/bf-bootstrap.sh | sudo bash`) → `sudo bf-menu`. Bootstrap yalnız operatör araçlarını ve `bf-*` komutlarını kurar; **cihaz kimliği (bayi no) İSTEMEZ**, disk bölmez, veri silmez, onaysız güncelleme yapmaz (idempotent). Kimlik ilk `bf-menu` seansında girilir. ISO/firstboot yolu ([`26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md)/[`27`](docs/27-OFFLINE-PROVISIONING.md)) **ikincil/opsiyoneldir** ve silinmez. Ayrıntılı adım adım rehber: [`33`](docs/33-OPERATOR-CONSOLE.md). → [`05`](docs/05-ONE-CLICK-INSTALLER.md), [`33`](docs/33-OPERATOR-CONSOLE.md) (K-09, K-12, K-20) - **Nasıl kurulur?** — Kurulumun birincil yolu **git deposudur**: `git clone https://github.com/emirhanmamak-blueforce/Blueforce-Linux.git` → `cd Blueforce-Linux` → `sudo admin/bf-bootstrap.sh` (depoyu klonlamadan tek satır alternatifi: `curl -fsSL https://raw.githubusercontent.com/emirhanmamak-blueforce/Blueforce-Linux/main/admin/bf-bootstrap.sh | sudo bash`) → **`sudo bf`** (grafik operatör uygulaması) → `Kurulum` sekmesi → **"Bu cihazı kur"** → 8 haneli bayi no → onay. Bootstrap yalnız operatör araçlarını ve `bf-*` komutlarını kurar; **cihaz kimliği (bayi no) İSTEMEZ**, disk bölmez, veri silmez, onaysız güncelleme yapmaz (idempotent). **Kurulum internetsizdir:** paketler depoyla gelir (`provisioning/offline-repo/packages/`, 1321 `.deb`, ~888 MB, Git LFS) ve kuran kişi hiçbir şey indirmez; kurucu yerel paket deposunu kendi bulur (`--offline` gerekmez). Grafik ekran veya `python3-tk` yoksa uygulama Türkçe yönlendirme yapıp **`sudo bf --tui`** terminal konsolunu önerir (çıkış kodu 3). ISO/firstboot yolu ([`26`](docs/26-BLUEFORCE-FIELD-OS-ISO.md)/[`27`](docs/27-OFFLINE-PROVISIONING.md)) **ikincil/opsiyoneldir** ve silinmez. Ayrıntılı adım adım rehber: [`33`](docs/33-OPERATOR-CONSOLE.md). → [`05`](docs/05-ONE-CLICK-INSTALLER.md), [`33`](docs/33-OPERATOR-CONSOLE.md) (K-09, K-12, K-20)
- **Menü nasıl kullanılır?** — Sahadaki tek giriş noktası `sudo bf-menu` **operatör konsoludur**: 5 kategori numarayla seçilir, komut ezberlenmez (**1** Installation, **2** Device operations, **3** Central (preparation), **4** Tools, **5** Device information; ana ekranda `q` = çıkış, alt menülerde `0` = geri). Her komut çalıştırılmadan önce ekranda gösterilir ve onay istenir; yıkıcı işlemler iki kez sorar. Kurulumda "**Dealer number (8 digits)**" sorulur (ör. `12010193` → `BF-12010193` / `bf-12010193`); geçersiz format kurulumu başlatmaz (K-12). Filo işlemleri (kategori 2) hedefi **tek cihaz (bayi no) / grup-dalga (lab, pilot_1, pilot_2, wave_1, wave_2, production) / tüm filo** olarak seçtirir; wave-scoped playbook'ta "tüm filo" dalga dalga ilerler ve her dalga ayrı onaylanır, production için `production` yazmak zorunludur (K-11). Onaysız güncelleme seçeneği **yoktur**; `bf-gui-*` yalnız **yerel fiziksel** ekranı açar/kapatır, RDP (xrdp/xrdp-sesman) her zaman hazırdır (K-22). Şifre üretimi opsiyoneldir ve **varsayılan kapalıdır** (`bf-creds --generate`; parola ekrana/loga gitmez). Denetim log'u `/var/log/blueforce-console.log` (mod `600`). → [`33`](docs/33-OPERATOR-CONSOLE.md), [`09`](docs/09-FLEET-MANAGEMENT.md), [`10`](docs/10-UPDATE-AND-ROLLBACK-POLICY.md) (K-11, K-13, K-22) - **Arayüz nasıl kullanılır?** — Sahadaki tek giriş noktası **grafik operatör uygulamasıdır**: `sudo bf` (Python paketi `bfos`, Tkinter arayüzü `bfos/ui_gui.py`); pencere başlığı `Blueforce Field OS — Yönetim`. Basit modda üç düğme vardır (`Kur`, `Durum`, `Çıkış`); **`Detaylı mod`** ile 5 sekme açılır: `Kurulum`, `Cihaz işlemleri`, `Merkez hazırlığı`, `Araçlar`, `Cihaz bilgisi`. Hedef üst çubuktan seçilir: **Bayi no (8 hane)** (`^[0-9]{8}$` doğrulamalı) veya dalga grubu (lab, pilot_1, pilot_2, wave_1, wave_2, production); geçersiz format komut üretmez (K-12). Her komut çalıştırılmadan önce ekranda gösterilir ve onay istenir; yıkıcı işlemler iki kez sorar; production kapsamı için ek onay (ve `production` yazma) gerekir (K-11). Grafik ekran yoksa (SSH/başsız oturum) veya `python3-tk` kurulu değilse uygulama Türkçe hata verip **`sudo bf --tui`** terminal konsolunu önerir (çıkış kodu 3); eski bash menü `sudo admin/bf-menu` yalnız **yedek**tir, ana yol değildir. `sudo bf` = tam yetki; root değilse uygulama başlangıçta uyarır ve yalnız salt-okunur raporları çalıştırır (yazma işlemleri çalıştırma katmanında da reddedilir). Onaysız güncelleme seçeneği **yoktur**; `bf-gui-*` yalnız **yerel fiziksel** ekranı açar/kapatır, RDP (xrdp/xrdp-sesman) her zaman hazırdır (K-22). Şifre üretimi opsiyoneldir ve **varsayılan kapalıdır** (`bf-creds --generate`; parola ekrana/loga gitmez). Denetim log'u `/var/log/blueforce-console.log` (mod `600`). → [`33`](docs/33-OPERATOR-CONSOLE.md), [`05`](docs/05-ONE-CLICK-INSTALLER.md), [`09`](docs/09-FLEET-MANAGEMENT.md), [`10`](docs/10-UPDATE-AND-ROLLBACK-POLICY.md) (K-11, K-13, K-22)
- **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) - **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) - **Offline kurulum nasıl çalışır?** — Paketler **git deposunda taşınır** (`provisioning/offline-repo/packages/`, 1321 `.deb`, ~888 MB, Git LFS ile takip edilir) ve kuran kişi **hiçbir şey indirmez**: `sudo bf` → Kurulum sekmesi → "Bu cihazı kur" akışı internetsiz çalışır. Kurucu yerel paket deposunu **kendi bulur** (sıra: `BF_OFFLINE_REPO` → `<repo>/provisioning/offline-repo/built` → `/opt/blueforce/offline-repo`); `--offline` bayrağı **zorunlu değildir**, yalnız yerel depoyu zorlar. İki hata durumu vardır ve ikisinde de paket adımlarına geçilmez, sistemde hiçbir değişiklik yapılmaz: depo hiç yoksa aranan yollar + `ÇIKIŞ NEDENİ: yerel paket deposu bulunamadı.` (exit 1); depo bozuksa `OFFLINE BLOCKED: <sebep>` (exit 1). Grafik arayüzün gerektirdiği `python3-tk` paketi de offline depoda pinlidir. Sonuç `PROVISIONED_OFFLINE`'dır. → [`27`](docs/27-OFFLINE-PROVISIONING.md), [`05`](docs/05-ONE-CLICK-INSTALLER.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) - **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 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) - **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)
@ -24,7 +24,7 @@
- **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) - **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. - **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 12 soru + öğrenme rehberi sorusu, karar günlüğünde S-28…S-37 olarak izlenir ([Ek B-2](docs/24-DECISION-LOG.md)); bu tura `docs/33` ile **"Nasıl kurulur?"** ve **"Menü nasıl kullanılır?"** soruları eklendi (yukarıdaki ilk bloğun devamı). Bu 12 soru + öğrenme rehberi sorusu, karar günlüğünde S-28…S-37 olarak izlenir ([Ek B-2](docs/24-DECISION-LOG.md)); bu tura `docs/33` ile **"Nasıl kurulur?"** ve **"Arayüz nasıl kullanılır?"** soruları eklendi (yukarıdaki ilk bloğun devamı); kurulum ve arayüz anlatımı grafik operatör uygulaması (`sudo bf`) üzerinedir, terminal yolu yedektir.
--- ---
@ -47,13 +47,16 @@ Bu 12 soru + öğrenme rehberi sorusu, karar günlüğünde S-28…S-37 olarak i
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) 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) 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 akış: USB medyasıyla Ubuntu kur → 7. **Yeni PC kurulumu kaç adım?** — Teknisyen için akış: USB medyasıyla Ubuntu kur → depo kopyasını getir
`blueforce-install.sh --offline` + 8 haneli bayi no → `bf-status` ile `PROVISIONED_OFFLINE` gör → → `sudo bf` → Hedef: bayi no → Kurulum → "Bu cihazı kur" (8 haneli bayi no) → durumda `PROVISIONED_OFFLINE` gör →
internet geldiğinde enrollment ve merkezi kontrolleri tamamla; yalnız sonra `READY` teslim edilir. → internet geldiğinde enrollment ve merkezi kontrolleri tamamla; yalnız sonra `READY` teslim edilir.
[`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) Paketler depoyla geldiği için kurulumda **indirme yapılmaz**. →
8. **Installer nasıl çalışacak?** — Tek giriş `blueforce-install.sh --dealer-id <8hane>`; çevrimdışı yol [`05`](docs/05-ONE-CLICK-INSTALLER.md), [`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)
`--offline` ile yerel medya/snapshot kullanır ve yalnız `PROVISIONED_OFFLINE` yazar. `READY`, installer 8. **Installer nasıl çalışacak?** — Tek giriş grafik operatör uygulamasıdır (`sudo bf` → `Kurulum` sekmesi →
çıkış kodu değil enrollment sonrası merkezi WireGuard, erişim ve monitoring doğrulamasının sonucudur. → "Bu cihazı kur"); bu düğme `blueforce-install.sh --dealer-id <8hane>` motorunu çağırır ve paket deposunu
otomatik bulur (`--offline` zorunlu değil). Yedek yol `sudo bf --tui`, acil yedek bash menü `sudo admin/bf-menu`.
Kurucu 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) [`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
@ -171,7 +174,7 @@ kaynak URL'ler [`24 Ek A`](docs/24-DECISION-LOG.md).
## Ek: QA Doğrulama (2026-09-16 güncellemesi) ## Ek: QA Doğrulama (2026-09-16 güncellemesi)
- **33 numaralı doküman mevcut:** `docs/00` … `docs/32` (+ `docs/_TEMPLATE.md`); kanonik set `00`–`31`, `32` denetim/çelişki raporudur. - **33 numaralı doküman mevcut:** `docs/00` … `docs/32` (+ `docs/_TEMPLATE.md`); kanonik set `00`–`31`, `32` denetim/çelişki raporudur.
- **Ek (operatör konsolu):** `docs/33-OPERATOR-CONSOLE.md` eklendi (§1–§13 + Mermaid; git kurulumu + `bf-menu` menü rehberi). Numaralı set artık `docs/00` … `docs/33` (34 dosya). - **Ek (operatör konsolu):** `docs/33-OPERATOR-CONSOLE.md` eklendi (§1–§13 + Mermaid; git kurulumu + grafik operatör arayüzü (`sudo bf`) ve `sudo bf --tui` yedek konsolu rehberi). Numaralı set artık `docs/00` … `docs/33` (34 dosya).
- **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). - **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:** 33/33 dosyada ≥1 diyagram; toplam 35 blok (30 dosyada 1, birkaçında 2+). - **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 (kalan `BF-xxxx`/`BF-no` ifadeleri şablon yer tutucudur). - **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).
@ -185,5 +188,7 @@ kaynak URL'ler [`24 Ek A`](docs/24-DECISION-LOG.md).
`check-provisioning-static.sh`, `test-firstboot-static.sh` **GEÇTİ**; `check-specs.sh` iki **bayat sabit** nedeniyle düşer `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). (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). 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, - **Üretim artefaktı:** `*.deb` havuzu artık repoda: `provisioning/offline-repo/packages/` altında 1321 paket (~888 MB, Git LFS),
merkezi enrollment endpoint'i yok. Yani "offline kurulum çalışıyor" bugün **kanıtlanmış değildir**; F2A/F2B kapıları açıktır. pin manifestolarında `UNPINNED` sentinel kalmadı ve `python3-tk` (grafik arayüz) dahil pinlendi. Ancak kurucunun aradığı `built/` bundle dizini
(Packages, Packages.gz, packages.lock.tsv, SHA256SUMS, pool/) bu checkout'ta **yok**; bu haliyle kurulum "yerel paket deposu yok" diyerek fail-closed durur,
repo sorumlusunun `build-index.sh` koşup `built/` dizinini commit'lemesi gerekir. Merkezi enrollment endpoint'i yok; air-gapped kurulum ve ISO boot kabulü LAB işidir.

Binary file not shown.

Binary file not shown.

View file

@ -62,6 +62,10 @@ def build_parser() -> argparse.ArgumentParser:
) )
parser.add_argument("-h", "--help", action="help", help="bu yardımı göster ve çık") parser.add_argument("-h", "--help", action="help", help="bu yardımı göster ve çık")
parser.add_argument("-V", "--version", action="store_true", help="sürümü göster ve çık") parser.add_argument("-V", "--version", action="store_true", help="sürümü göster ve çık")
parser.add_argument("--gui", action="store_true",
help="grafik arayüzü aç (varsayılan davranış)")
parser.add_argument("--tui", action="store_true",
help="terminal konsolunu aç (grafik arayüz açılamıyorsa yedek)")
parser.add_argument("--full", action="store_true", parser.add_argument("--full", action="store_true",
help="detaylı mod: tüm kategoriler ve 21 playbook görünür") help="detaylı mod: tüm kategoriler ve 21 playbook görünür")
parser.add_argument("--dry-run", action="store_true", parser.add_argument("--dry-run", action="store_true",
@ -1010,10 +1014,33 @@ def catalogue_lines() -> List[str]:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Entry point # Entry point
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def _wants_tui(args: argparse.Namespace) -> bool:
"""Whether this invocation belongs to the terminal console.
The GUI is the default. A few flags exist only in the terminal console --
they print text and exit, or tune terminal rendering -- so an invocation
carrying one of them must stay there instead of being swallowed by the
router. ``--tui`` forces it explicitly.
"""
if args.tui:
return True
if args.gui:
return False
# --list prints a text catalogue (the TUI's report format) and exits; it is
# a report, not an interactive surface, so it stays in the terminal console.
return bool(args.list or args.check or args.list_targets or args.no_color
or args.log_file != config.DEFAULT_LOG_FILE
or args.lang != config.DEFAULT_LANG)
def main(argv: Optional[Sequence[str]] = None) -> int: def main(argv: Optional[Sequence[str]] = None) -> int:
parser = build_parser() parser = build_parser()
args = parser.parse_args(argv) args = parser.parse_args(argv)
if args.tui and args.gui:
sys.stderr.write("HATA: --tui ve --gui birlikte kullanılamaz.\n")
return config.EXIT_USAGE
if args.version: if args.version:
sys.stdout.write("{0} {1}\n".format(config.PROGRAM_NAME, config.VERSION)) sys.stdout.write("{0} {1}\n".format(config.PROGRAM_NAME, config.VERSION))
return config.EXIT_OK return config.EXIT_OK
@ -1023,6 +1050,23 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
value=args.lang, supported=", ".join(config.SUPPORTED_LANGS)) + "\n") value=args.lang, supported=", ".join(config.SUPPORTED_LANGS)) + "\n")
return config.EXIT_USAGE return config.EXIT_USAGE
if not _wants_tui(args):
# Imported lazily: the GUI pulls in tkinter, and the terminal console
# must keep working on a device where that import is unavailable.
from . import ui_gui
gui_argv: List[str] = []
if args.full:
gui_argv.append("--full")
if args.dry_run:
gui_argv.append("--dry-run")
if args.repo:
gui_argv.extend(["--repo", args.repo])
if args.inventory:
gui_argv.extend(["--inventory", args.inventory])
if args.list:
gui_argv.append("--list")
return ui_gui.main(gui_argv)
try: try:
repo = resolve_repo(args.repo) repo = resolve_repo(args.repo)
except Refused as exc: except Refused as exc:

View file

@ -325,6 +325,10 @@ MSG_COMMAND_TITLE = "Çalıştırılacak komut"
MSG_RUN_ASK = "Bu komut çalıştırılsın mı? [y=çalıştır / c=--check ile çalıştır / N=iptal]: " MSG_RUN_ASK = "Bu komut çalıştırılsın mı? [y=çalıştır / c=--check ile çalıştır / N=iptal]: "
MSG_DESTRUCTIVE_WARN = "DURUM DEĞİŞTİREN İŞLEM: {label}" MSG_DESTRUCTIVE_WARN = "DURUM DEĞİŞTİREN İŞLEM: {label}"
MSG_DESTRUCTIVE_SUB = "Onaylamadan önce yukarıdaki komutu okuyun; seçilen cihazları değiştirir." MSG_DESTRUCTIVE_SUB = "Onaylamadan önce yukarıdaki komutu okuyun; seçilen cihazları değiştirir."
MSG_ROOT_REQUIRED = (
"Bu işlem BU cihazda root yetkisi gerektirir.\n"
"Konsolu tekrar sudo ile başlatın: sudo bf"
)
MSG_DESTRUCTIVE_GATE = ( MSG_DESTRUCTIVE_GATE = (
"Yıkıcı işlem kapısı: {label} seçilen cihazları değiştirir. " "Yıkıcı işlem kapısı: {label} seçilen cihazları değiştirir. "
"Bu onaydan sonra komut üretilir ve çalıştırmadan önce ayrıca sorulur." "Bu onaydan sonra komut üretilir ve çalıştırmadan önce ayrıca sorulur."

View file

@ -276,6 +276,11 @@ class Runner:
self.audit.write(config.AUDIT_CANCEL, "operatör iptal etti :: " + command_string(argv)) self.audit.write(config.AUDIT_CANCEL, "operatör iptal etti :: " + command_string(argv))
return RunResult(argv=list(argv), cancelled=True) return RunResult(argv=list(argv), cancelled=True)
# NOTE: this method does not enforce root. The terminal console gates a
# destructive operation earlier (App.require_root, config.MSG_ROOT_REQUIRED)
# and the GUI gates it at its execution choke point (ui_gui.run_plan).
# Enforcing here as well double-guarded the same rule and fired before
# the console's own message, so the operator saw the wrong explanation.
self.audit.write(config.AUDIT_RUN, command_string(final)) self.audit.write(config.AUDIT_RUN, command_string(final))
result = self._spawn_exit_code(final) result = self._spawn_exit_code(final)
result.check = "--check" in final result.check = "--check" in final

View file

@ -194,7 +194,10 @@ PLAYBOOK_DIR_REL = "ansible/playbooks"
MENU_REL = "admin/bf-menu" MENU_REL = "admin/bf-menu"
TK_PACKAGE_HINT = "sudo apt install -y python3-tk" TK_PACKAGE_HINT = "sudo apt install -y python3-tk"
TERMINAL_MENU_HINT = "sudo bf-menu" # Grafik arayüz açılamadığında kullanılacak yol: aynı bfos uygulamasının
# terminal konsolu. Bash menü (bf-menu) hâlâ yedek olarak durur ama Python
# konsolu ana yoldur, o yüzden önce o önerilir.
TERMINAL_MENU_HINT = "sudo bf --tui"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# tkinter -- opsiyonel ice aktarma # tkinter -- opsiyonel ice aktarma
@ -257,9 +260,19 @@ class Operation:
description: str = "" description: str = ""
playbook: str = "" playbook: str = ""
options: tuple[OptionSpec, ...] = () options: tuple[OptionSpec, ...] = ()
requires_root: bool = False # None means "not stated": the value is then derived from `destructive` by
# the property below, so a write operation cannot be marked read-only by
# omission. An operation that genuinely differs states the value.
requires_root: bool | None = None
needs_dealer: bool = False needs_dealer: bool = False
@property
def needs_root(self) -> bool:
"""Whether this operation must run as root to be allowed."""
if self.requires_root is None:
return bool(self.destructive)
return bool(self.requires_root)
@property @property
def is_fleet(self) -> bool: def is_fleet(self) -> bool:
return self.scope in ("simple", "wave", "release") return self.scope in ("simple", "wave", "release")
@ -318,7 +331,7 @@ class CommandPlan:
commands: tuple[tuple[str, ...], ...] = () commands: tuple[tuple[str, ...], ...] = ()
destructive: bool = False destructive: bool = False
requires_root: bool = False requires_root: bool | None = None
operation_id: str = "" operation_id: str = ""
target_label: str = "" target_label: str = ""
notes: tuple[str, ...] = () notes: tuple[str, ...] = ()
@ -326,6 +339,13 @@ class CommandPlan:
dry_run: bool = False dry_run: bool = False
check_mode: bool = False check_mode: bool = False
@property
def needs_root(self) -> bool:
"""Whether running this plan requires root (derived when not stated)."""
if self.requires_root is None:
return bool(self.destructive)
return bool(self.requires_root)
@property @property
def lines(self) -> tuple[str, ...]: def lines(self) -> tuple[str, ...]:
"""Kopyala/yapistir edilebilir komut satirlari (bf-menu %q ile ayni fikir).""" """Kopyala/yapistir edilebilir komut satirlari (bf-menu %q ile ayni fikir)."""
@ -580,7 +600,7 @@ def preview_text(
parts.append(f"Hedef : {target.label}") parts.append(f"Hedef : {target.label}")
elif plan.target_label: elif plan.target_label:
parts.append(f"Hedef : {plan.target_label}") parts.append(f"Hedef : {plan.target_label}")
if plan.requires_root: if plan.needs_root:
parts.append("Yetki : root gerekir — konsolu sudo ile açın.") parts.append("Yetki : root gerekir — konsolu sudo ile açın.")
if plan.check_mode: if plan.check_mode:
parts.append("Kip : --check (deneme) — hiçbir değişiklik uygulanmaz.") parts.append("Kip : --check (deneme) — hiçbir değişiklik uygulanmaz.")
@ -1073,6 +1093,12 @@ def run_plan(
for line in plan.lines: for line in plan.lines:
emit(f"[deneme] {line}") emit(f"[deneme] {line}")
return RunResult(0, True, "[deneme] komut çalıştırılmadı.\n") return RunResult(0, True, "[deneme] komut çalıştırılmadı.\n")
if plan.needs_root and not write_operations_enabled():
# Last line of defence: every execution path funnels through this
# function, so the root rule holds even if a future caller builds the
# plan itself instead of going through the GUI's run button.
emit("[ENGELLENDİ] " + NON_ROOT_DISABLED_HINT)
return RunResult(2, False, NON_ROOT_DISABLED_HINT + "\n")
chunks: list[str] = [] chunks: list[str] = []
for index, argv in enumerate(plan.commands, start=1): for index, argv in enumerate(plan.commands, start=1):
@ -1411,7 +1437,7 @@ class CatalogueBackend:
return CommandPlan( return CommandPlan(
commands=(tuple(argv),), commands=(tuple(argv),),
destructive=operation.destructive, destructive=operation.destructive,
requires_root=operation.requires_root, requires_root=operation.needs_root,
operation_id=operation.id, operation_id=operation.id,
target_label=target.label + (f" (dalga {wave})" if wave else ""), target_label=target.label + (f" (dalga {wave})" if wave else ""),
notes=tuple(notes), notes=tuple(notes),
@ -1654,7 +1680,7 @@ class CatalogueBackend:
return CommandPlan( return CommandPlan(
commands=tuple(commands), commands=tuple(commands),
destructive=operation.destructive, destructive=operation.destructive,
requires_root=operation.requires_root, requires_root=operation.needs_root,
operation_id=operation.id, operation_id=operation.id,
target_label=label, target_label=label,
notes=tuple(notes), notes=tuple(notes),
@ -1801,18 +1827,30 @@ class ModuleBackend:
category = get("category") or "Cihaz işlemleri" category = get("category") or "Cihaz işlemleri"
if category not in CATEGORIES: if category not in CATEGORIES:
category = "Cihaz işlemleri" category = "Cihaz işlemleri"
scope = get("scope") or get("declared_scope") or "simple" scope = str(get("scope") or get("declared_scope") or "simple")
prompt = get("prompt") or "none" prompt = get("prompt") or "none"
destructive = bool(get("destructive") or get("is_destructive") or False)
# requires_root is left None unless the source actually states it, so the
# Operation property derives it from `destructive`. Reading the field
# with a False default marked every real operation read-only and the
# root gate never fired; the catalogue's `destructive` flag is the
# honest signal. An operation that states the value keeps that answer.
stated_root = None
if isinstance(item, dict):
if "requires_root" in item:
stated_root = bool(item["requires_root"])
elif getattr(item, "requires_root", None) is not None:
stated_root = bool(item.requires_root)
return Operation( return Operation(
id=str(op_id), id=str(op_id),
label=str(label), label=str(label),
category=str(category), category=str(category),
destructive=bool(get("destructive") or get("is_destructive") or False), destructive=destructive,
scope=str(scope), scope=scope,
description=str(get("description") or ""), description=str(get("description") or ""),
playbook=str(get("playbook") or ""), playbook=str(get("playbook") or ""),
options=_prompt_options(str(prompt)), options=_prompt_options(str(prompt)),
requires_root=bool(get("requires_root") or False), requires_root=stated_root,
needs_dealer=bool(get("needs_dealer") or False), needs_dealer=bool(get("needs_dealer") or False),
) )
@ -2069,7 +2107,7 @@ class ModuleBackend:
return CommandPlan( return CommandPlan(
commands=(tuple(argv),), commands=(tuple(argv),),
destructive=operation.destructive, destructive=operation.destructive,
requires_root=operation.requires_root, requires_root=operation.needs_root,
operation_id=operation.id, operation_id=operation.id,
target_label=f"cihaz {dealer_to_device_id(dealer)} ({dealer_to_host(dealer)})", target_label=f"cihaz {dealer_to_device_id(dealer)} ({dealer_to_host(dealer)})",
notes=tuple(notes), notes=tuple(notes),
@ -2100,7 +2138,7 @@ class ModuleBackend:
return CommandPlan( return CommandPlan(
commands=(tuple(argv),), commands=(tuple(argv),),
destructive=operation.destructive, destructive=operation.destructive,
requires_root=operation.requires_root, requires_root=operation.needs_root,
operation_id=operation.id, operation_id=operation.id,
target_label="bu cihaz (yerel)", target_label="bu cihaz (yerel)",
notes=tuple(notes), notes=tuple(notes),
@ -2466,6 +2504,14 @@ class BfGui: # pragma: no cover - pencere gerektirir (statik testler dokunmaz)
text="DENEME KİPİ (--dry-run): komutlar gösterilir, çalıştırılmaz.", text="DENEME KİPİ (--dry-run): komutlar gösterilir, çalıştırılmaz.",
foreground="#a05000", foreground="#a05000",
).pack(anchor="w") ).pack(anchor="w")
if not is_root():
# Söylenmezse operatör kurulumu başlatır ve yarı yolda yetki
# hatasıyla karşılaşır; kapı bu yüzden başlangıçta görünür.
ttk.Label(
header,
text=ROOT_REQUIRED_NOTICE,
foreground="#a05000",
).pack(anchor="w")
def _build_mode_bar(self) -> None: def _build_mode_bar(self) -> None:
bar = ttk.Frame(self.root) bar = ttk.Frame(self.root)
@ -2866,6 +2912,14 @@ class BfGui: # pragma: no cover - pencere gerektirir (statik testler dokunmaz)
self.output.write_now("=" * 72) self.output.write_now("=" * 72)
def _start_run(self, plan: CommandPlan) -> None: def _start_run(self, plan: CommandPlan) -> None:
if plan.needs_root and not write_operations_enabled():
# The button is disabled for this case, so reaching here means the
# gate was bypassed (stale widget state, a future caller). Refusing
# in the one place every run goes through keeps the rule true for
# the whole surface instead of one control.
self.output.write_now("[ENGELLENDİ] " + NON_ROOT_DISABLED_HINT)
self.status.configure(text="Yönetici yetkisi gerekli.")
return
self.running = True self.running = True
self.cancel_event.clear() self.cancel_event.clear()
self.run_button.configure(state="disabled") self.run_button.configure(state="disabled")
@ -2978,6 +3032,34 @@ def print_catalogue(backend, stream=None) -> None:
) )
# ---------------------------------------------------------------------------
# Yetki kapisi
# ---------------------------------------------------------------------------
def is_root() -> bool:
"""True when the process can install, write state and drive Ansible.
The installer writes to the filesystem, manages services and applies the
firewall baseline, so the GUI is expected to run under sudo. Reports read
the repository and the inventory and work without it, which is why the
console still starts as a normal user and explains the difference instead
of refusing to launch.
"""
geteuid = getattr(os, "geteuid", None)
return True if geteuid is None else geteuid() == 0
ROOT_REQUIRED_NOTICE = (
"Yönetici yetkisi yok: salt-okunur raporlar çalışır, kurulum ve değişiklik "
"gerektiren işlemler kapalıdır. Tam yetki için: sudo bf"
)
NON_ROOT_DISABLED_HINT = "Bu işlem yönetici yetkisi gerektirir (sudo bf)."
def write_operations_enabled() -> bool:
"""Whether operations that change the device may be offered."""
return is_root()
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Baslatma # Baslatma
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View file

@ -1,37 +1,40 @@
# 05 — Tek-Tık Kurucu (One-Click Installer) # 05 — Tek-Tık Kurucu (One-Click Installer)
> Kısa özet: Sahada tek komutla cihazı READY yapan `blueforce-install.sh`: bayi no doğrulaması, 18 modül, idempotent yeniden-koşu ve final-check kapısı. Teknisyenin çalıştırdığı tek şeydir. > Kısa özet: Sahadaki tek giriş noktası **grafik operatör uygulamasıdır**: `sudo bf` → Kurulum sekmesi → "Bu cihazı kur". Bayi no doğrulaması, 18 modül, idempotent yeniden-koşu ve final-check kapısı `blueforce-install.sh`'ın işidir. Paketler git deposunda taşınır; **kurulum internetsizdir**. Terminal yedeği: `sudo bf --tui`.
- Dosya: `docs/05-ONE-CLICK-INSTALLER.md` - Dosya: `docs/05-ONE-CLICK-INSTALLER.md`
- İlgili kararlar: `24-DECISION-LOG.md#K-09` (tek kurucu), `K-11` (update kapalı), `K-12` (kimlik formatı), `K-03` (bf-gui), `K-10` (Docker disiplini) - İlgili kararlar: `24-DECISION-LOG.md#K-09` (tek kurucu), `K-11` (update kapalı), `K-12` (kimlik formatı), `K-03` (bf-gui), `K-10` (Docker disiplini), `K-16` (offline)
- İlgili dokümanlar: `33-OPERATOR-CONSOLE.md` (arayüz rehberi), `27-OFFLINE-PROVISIONING.md` (paket deposu sözleşmesi)
- Durum: [ ] Taslak - Durum: [ ] Taslak
--- ---
## 1. Amaç ## 1. Amaç
Saha teknisyeninin uzmanlık gerektirmeden cihazı standarda getirmesi: tek komut, tek girdi (bayi no), net sonuç (READY / hata + log yolu). Yarım kalan veya hatalı kurulumun güvenli tekrarı. Saha teknisyeninin uzmanlık gerektirmeden cihazı standarda getirmesi: tek giriş noktası (`sudo bf`), tek girdi (8 haneli bayi no), net sonuç (`PROVISIONED_OFFLINE` / hata + neden). Yarım kalan veya hatalı kurulumun güvenli tekrarı.
Teknisyen terminal komutu, bayrak veya dosya yolu ezberlemez: kurulum grafik arayüzdeki düğmeyle başlatılır, komut arayüzde gösterilir ve onay alınmadan çalıştırılmaz.
## 2. Kapsam ## 2. Kapsam
- Kapsam içi: komut arayüzü, bayi-no doğrulama, 18 modül listesi ve sırası, idempotency kuralları, log/bundle çıktısı, çıkış kodları. - Kapsam içi: grafik uygulama üzerinden kurulum akışı, kurucu betiğin komut yüzeyi, bayi-no doğrulama, 18 modül listesi ve sırası, paket deposu keşfi ve hata durumları, idempotency kuralları, log/çıkış kodları.
- Kapsam dışı: modül iç kodları (repo `scripts/install/`), imaj üretim yöntemi (04), filo-side yapılandırma (09). - Kapsam dışı: modül iç kodları (repo `scripts/install/installer/modules/`), imaj üretim yöntemi (04), filo-side yapılandırma (09), arayüzün tüm ekranları (33).
## 3. Kararlar ## 3. Kararlar
```text ```text
KARAR: Saha kurulumu tek giriş noktasıdır: `sudo ./blueforce-install.sh --dealer-id <8hane>`; bayi no `^[0-9]{8}$` ile doğrulanır, uymayan girdi kurucuyu başlatmaz. KARAR: Saha kurulumunun tek giriş noktası grafik operatör uygulamasıdır: `sudo bf` → Kurulum sekmesi → "Bu cihazı kur". Kurucu betik (`scripts/install/blueforce-install.sh`) bu düğmenin çağırdığı motordur ve aynı akış terminalden `sudo bf --tui` ile de yürütülebilir. Eski bash menü (`admin/bf-menu`) yedek yoldur, ana yol değildir.
GEREKÇE: Tek komut teknisyen hatasını en aza indirir; format kapısı yanlış kimliğin 9 sisteme yayılmasını baştan engeller (K-12). GEREKÇE: Tek giriş noktası ve tek düğme teknisyen hatasını en aza indirir; grafik arayüz komutu çalıştırmadan önce gösterir ve onay ister, böylece görünmez komut kalmaz. Bayi no `^[0-9]{8}$` ile doğrulanır; format kapısı yanlış kimliğin 9 sisteme yayılmasını baştan engeller (K-12).
ALTERNATİF: Çok scriptli kurulum (ağ.sh, gui.sh, …) — sıra hatası ve atlanan adım riski nedeniyle elendi. ALTERNATİF: Yalnız bash menülü kurulum (`admin/bf-menu`) — grafik arayüz yoksa çalışır ama sahadaki ana yolu oluşturmaz; yedek olarak korundu. Çok scriptli kurulum (ağ.sh, gui.sh, …) — sıra hatası ve atlanan adım riski nedeniyle elendi.
RİSK: Tek dosya büyüdükçe bakımı zorlaşır; azaltma: 18 bağımsız modül + ana orkestratör ayrımı. RİSK: Tek dosya büyüdükçe bakımı zorlaşır; azaltma: 18 bağımsız modül + ana orkestratör ayrımı. Grafik arayüz `python3-tk` gerektirir; azaltma: paket offline depoda pinlidir ve ekransız/paketsiz ortamda uygulama Türkçe yönlendirme yapıp `--tui` önerir.
MALİYET: Ücretsiz. MALİYET: Ücretsiz.
LİSANS: Yok (şirket içi script). LİSANS: Yok (şirket içi script + standart kütüphane ile yazılmış Python arayüz).
``` ```
```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. Paketler yerel paket deposundan gelir ve depo otomatik bulunur; `--offline` bayrağı zorunlu değildir.
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). 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). Depoyu kuran kişinin hatırlaması gereken bayrak sayısı sıfırdır.
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. `--offline` bayrağını zorunlu tutmak — operatör bayrağı unuttuğunda kurulum durur; bayrak geriye uyumluluk için korundu.
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.
LİSANS: Yok. LİSANS: Yok.
@ -39,115 +42,203 @@ LİSANS: Yok.
## 4. Neden Bu Karar? ## 4. Neden Bu Karar?
700 cihaz × teknisyen başına değişen beceri = kurulumun "tek komut + tek soru" olması şarttır. 18 modül, her kararın (update-kapalı, Docker-disiplini, GUI-anahtarı, kimlik, servisler) ayrı denetlenebilir adımdır; modül numarası log satırında görünür, arıza hangi adımda kaldıysa oradan devam edilir. 700 cihaz × teknisyen başına değişen beceri = kurulumun "tek düğme + tek soru" olması şarttır. Grafik arayüz bu düğmeyi verir; 18 modül ise her kararın (update-kapalı, Docker-disiplini, GUI-anahtarı, kimlik, servisler) ayrı denetlenebilir adımıdır. Modül numarası log satırında görünür, arıza hangi adımda kaldıysa oradan devam edilir; arayüz aynı durumu "Kurulum modül durumu" işlemiyle salt-okunur gösterir.
## 5. Alternatifler ## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç | | Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---| |---|---|---|---|
| Grafik arayüz + tek kurucu betik | Tek giriş, komut ezberi yok, onay görünür | Grafik katman gerekir (`python3-tk`, ekran) | **Seçildi** |
| Yalnız bash menülü kurulum (`admin/bf-menu`) | Grafik katman gerekmez | Ana yol olarak terminal bilgisi ister; ekran yok | Yedek yol |
| Çok scriptli kurulum | Küçük dosyalar | Sıra/atlama hatası | Elendi | | Çok scriptli kurulum | Küçük dosyalar | Sıra/atlama hatası | Elendi |
| Non-idempotent tek script | Daha basit kod | Kesintide temiz-kurulum zorunluluğu | Elendi | | Non-idempotent tek script | Daha basit kod | Kesintide temiz-kurulum zorunluluğu | Elendi |
| İnteraktif sihirbaz (çok sorulu) | Esnek | Teknisyen hatası, otomasyona kapalı | Elendi | | İnteraktif sihirbaz (çok sorulu) | Esnek | Teknisyen hatası, otomasyona kapalı | Elendi |
## 6. Avantajlar ## 6. Avantajlar
- Tek komut = eğitim maliyeti minimum, runbook tek sayfa. - Tek giriş noktası (`sudo bf`) = eğitim maliyeti minimum, runbook tek sayfa.
- Idempotency = kesinti korkusu yok, "bir daha koş" her sorunun ilk cevabı. - Idempotency = kesinti korkusu yok, "bir daha koş" her sorunun ilk cevabı.
- Final-check = READY iddiası ölçülebilir, dalga kapılarına bağlanır. - Final-check = kurulum sonucu ölçülebilir, dalga kapılarına bağlanır.
- İnternetsiz çalışır: paketler repoda gelir, kuran kişi hiçbir şey indirmez.
## 7. Dezavantajlar ## 7. Dezavantajlar
- 18 modülün her biri için idempotency testi yazma maliyeti. - 18 modülün her biri için idempotency testi yazma maliyeti.
- Tek giriş noktası, modül-atlama (debug) ihtiyacında `--from/--only` bayraklarını zorunlu kılar. - Tek giriş noktası, modül-atlama (debug) ihtiyacında `--from/--only/--resume` bayraklarını zorunlu kılar.
- Grafik arayüz `python3-tk` paketine ve bir grafik oturuma bağlıdır; bu ikisi yoksa operatör terminal konsoluna (`sudo bf --tui`) düşer.
## 8. Riskler ## 8. Riskler
| Risk | Olasılık | Etki | Azaltma | | Risk | Olasılık | Etki | Azaltma |
|---|---|---|---| |---|---|---|---|
| Modül idempotent değil (çift kayıt) | Orta | Orta | Çift-koşu testi (§10) + code review kuralı | | Modül idempotent değil (çift kayıt) | Orta | Orta | Çift-koşu testi (§10) + code review kuralı |
| Teknisyen yanlış no girer | Orta | Orta | Format kapısı + onay ekranı (`BF-xxxx, onaylıyor musunuz?`) | | Teknisyen yanlış no girer | Orta | Orta | Format kapısı (`^[0-9]{8}$`) + onay ekranı (`BF-12010193, onaylıyor musunuz?`) |
| Kurucu yarıda kesilir, cihaz ara durumda sahaya alınır | Düşük | Yüksek | Final-check exit 0 olmadan READY yok; `bf-status` bunu gösterir | | Kurucu yarıda kesilir, cihaz ara durumda sahaya alınır | Düşük | Yüksek | Final-check exit 0 olmadan kurulum tamamlanmaz; arayüzdeki "Kurulum modül durumu" ve `bf-status` bunu gösterir |
| Grafik arayüz açılmaz (başsız oturum) | Orta | Düşük | Uygulama Türkçe hata + `sudo bf --tui` önerir (19 §T-10) |
| `python3-tk` eksik kalır | Düşük | Orta | Paket offline depoda pinli; `18-final-check` tkinter'ı denetler ve eksikse WARN yazar |
## 9. Uygulama Planı ## 9. Uygulama Planı
1. Komut arayüzü: ### 1. Giriş noktası — grafik operatör uygulaması (ana yol)
```bash ```bash
sudo ./blueforce-install.sh --dealer-id 12010193 [--from 07] [--only 18-final-check] [--check] sudo bf # grafik arayüz (bfos paketi, bfos/ui_gui.py, Tkinter)
# --check: değişiklik yapmadan denetim (final-check + modül durumları)
# --from: belirtilen modülden devam et | --only: tek modül koş
``` ```
2. Bayi-no doğrulama: `^[0-9]{8}$`; uymazsa exit 2 + kullanım mesajı. Geçerse onay ekranı (`BF-12010193` + hostname `bf-12010193` gösterilir). Akış:
3. 18 modül (sıra sözleşmedir, yeri `scripts/install/`):
| # | Modül | İşlev (dayandığı karar) | 1. Pencere açılır: başlık **Blueforce Field OS — Yönetim**.
2. Basit modda üç düğme vardır: `Kur`, `Durum`, `Çıkış`. Üstteki **`Detaylı mod`** ile 5 sekme açılır: `Kurulum`, `Cihaz işlemleri`, `Merkez hazırlığı`, `Araçlar`, `Cihaz bilgisi`.
3. Üst çubuktaki **Hedef > Bayi no (8 hane)** alanına numara yazılır (örn. `12010193`); format geçersizse arayüz uyarır ve komut üretilmez.
4. `Kurulum` sekmesinde **`Bu cihazı kur`** (veya hiçbir şey yazmayan ön kontrol **`Kurulum ön kontrolü (--check)`**) seçilir.
5. Arayüz komutu **önce ekranda gösterir**, sonra onay ister; onaylanmadan çalıştırılmaz. Yıkıcı işlemler ikinci kez sorar; production kapsamı ayrı onay ister.
6. Kurulum çıktısı ekranda akar; bitince sonuç ve çıkış kodu görünür.
`Bu cihazı kur` işleminin iki opsiyonel seçeneği vardır: `Yerel çevrimdışı depodan kur (--offline)` ve `Zaten OK olan modülleri atla (--resume)`. **`--offline` işaretlenmese de varsayılan kaynak yerel depodur**; bayrak yalnız niyeti sabitler.
### 2. Yedek yol — terminal konsolu
```bash
sudo bf --tui # aynı uygulamanın terminal arayüzü (grafik ekran yoksa / tkinter yoksa)
```
- `DISPLAY` ve `WAYLAND_DISPLAY` tanımlı değilse (SSH, başsız kurulum) uygulama Türkçe açıklama yazar ve **çıkış kodu 3** ile `sudo bf --tui` önerir.
- `python3-tk` kurulu değilse (`tkinter` import edilemiyor) aynı şekilde Türkçe hata + `python3-tk` kurulum ipucu verir ve **çıkış kodu 3** ile terminal konsolunu önerir.
- Eski bash menü `sudo admin/bf-menu` **yalnız yedek yoldur**; dokümanlarda ve iş akışında ana yol olarak kullanılmaz.
### 3. Yetki
- `sudo bf` = tam yetki (kurulum, servis, firewall, Ansible).
- Root değilse uygulama başlangıçta uyarır ve yalnız **salt-okunur raporları** çalıştırır: "Yönetici yetkisi yok: salt-okunur raporlar çalışır, kurulum ve değişiklik gerektiren işlemler kapalıdır. Tam yetki için: sudo bf". Yazma işlemleri hem arayüzde kapalıdır hem de çalıştırma katmanında reddedilir (onaylansa bile komut çalışmaz).
### 4. Bayi-no doğrulama
`^[0-9]{8}$`; uymazsa arayüz geçersiz numarayı reddeder ve kurucu komut üretilmez (doğrudan CLI kullanımında exit 2 + kullanım mesajı). Geçerliyse onay ekranında `BF-12010193` + hostname `bf-12010193` gösterilir.
### 5. 18 modül (sıra sözleşmedir, yeri `scripts/install/installer/modules/`)
| # | Modül | İşlev |
|---|---|---| |---|---|---|
| 01 | `01-precheck` | OS sürümü (26.04.1+), disk/RAM minimumu, root yetkisi, internet kontrolü | | 01 | `01-precheck` | Root, Ubuntu sürümü, RAM/disk ön kontrolü; ağ yalnız bilgi (kapı değil) |
| 02 | `02-dealer-id` | No doğrulama + hostname `bf-<no>` atama (K-12) | | 02 | `02-system` | Temel APT kaynakları, `unattended-upgrades` kapatma, saat dilimi (K-11) |
| 03 | `03-base-packages` | Temel paketler + `unattended-upgrades` kapatma (K-11) | | 03 | `03-user` | `blueforce` bakım kullanıcısı, kısıtlı sudoers, zorunlu yönetici anahtarı (K-13) |
| 04 | `04-gnome` | GNOME kurulumu (K-02) | | 04 | `04-hostname` | Doğrulanmış bayi no'dan hostname `bf-<no>` (K-12) |
| 05 | `05-xrdp` | xRDP + sesman + `bf-gui-on/off` yazımı (K-03) | | 05 | `05-network` | Ağ raporu: yerel bağlantı, rota/DNS (DNS zorunlu tutulmaz — offline kurulum) |
| 06 | `06-ssh-hardening` | `blueforce` kullanıcısı, key-auth, root/password kapalı (K-13) | | 06 | `06-wireguard` | Cihaz anahtar çifti; `wg0.conf` enrollment'ın işidir (K-05/K-17) |
| 07 | `07-wireguard` | Spoke config, peer `bf-<no>`, keepalive 25, `wg-quick@wg0` enable (K-05) | | 07 | `07-ssh` | Yönetici anahtarı doğrulandıktan **sonra** sshd sertleştirme |
| 08 | `08-docker` | Docker CE + `unless-stopped` + log rotasyonu + localhost-bind (K-10) | | 08 | `08-rdp` | xRDP + GNOME oturumu, terminale boot varsayılanı, multi-user (K-03/K-22) |
| 09 | `09-meg` | MEG konteyneri (sabit tag, `latest` yok) (K-10) | | 09 | `09-rustdesk` | Yalnız pinli/sağlanan paketten RustDesk istemci (K-04) |
| 10 | `10-rustdesk` | RustDesk istemci + VDS adresi + `BF-<no>` etiketi (K-04) | | 10 | `10-docker` | Docker CE + rotasyonlu `daemon.json` + enable (K-10) |
| 11 | `11-meshcentral` | MeshCentral agent + `BF-<no>` adı (K-04) | | 11 | `11-meg` | MEG yer tutucusu: vendor `.deb` varsa kurar, yoksa SKIP + kabul listesi |
| 12 | `12-monitoring` | Node Exporter + textfile + Uptime Push (K-07) | | 12 | `12-monitoring` | Node Exporter + textfile collector dizini (K-07) |
| 13 | `13-firewall` | UFW profili (SSH/RDP yalnızca WG) (K-05) | | 13 | `13-firewall` | UFW deny-in tabanı + Docker yayın-portu politikasının kalıcılığı (K-05/K-10) |
| 14 | `14-inventory` | `bf-hardware-inventory.sh` koşusu, JSON+MD üretimi (02) | | 14 | `14-update-policy` | APT hold'ları + otomatik update zamanlayıcılarını kapatma (K-11) |
| 15 | `15-logging` | journald + Docker rotasyon + `bf-status/bf-diagnostics` (K-14) | | 15 | `15-logrotate` | journald tavanları + uygulama logrotate politikası (K-14) |
| 16 | `16-boot` | multi-user default + BIOS notu + servis enable denetimi (K-03) | | 16 | `16-gui` | GNOME minimal + bakım araçları; terminale boot varsayılanı (K-03) |
| 17 | `17-ansible-bootstrap` | Bootstrap SSH key + ilk pull/Ansible kaydı (K-06/K-13) | | 17 | `17-healthcheck` | Teşhis ve enrollment komutlarını kurar; enrollment'ı çalıştırmaz |
| 18 | `18-final-check` | Tüm kapılar: kimlik, servisler, update-kapalı, Docker politikası, READY damgası | | 18 | `18-final-check` | Son yaşam döngüsü kapısı: offline yerelde tamamlanır, `READY` merkez kanıtı ister; tkinter denetimi (yoksa WARN) |
Modül çıkış kodları: `0` OK, `3` SKIP (opsiyonel adım, yapılacak bir şey yok), diğer ≠ 0 FAIL.
### 6. Paket deposu — otomatik keşif, iki hata durumu, sıfır indirme
Installer paket deposunu **kendi bulur**; `--offline` bayrağı **gerekmez** (geriye uyumluluk için kabul edilir, yalnız yerel depoyu zorlar). Arama sırası (ilk eksiksiz aday kazanır):
1. `BF_OFFLINE_REPO` ortam değişkeni (operatör/repo sorumlusu override'ı),
2. `<repo>/provisioning/offline-repo/built` (git deposuyla gelen paket seti),
3. `/opt/blueforce/offline-repo` (cihaza kurulmuş paket deposu).
Depo bulunduğunda ekrana Türkçe duyuru yazılır: `Yerel paket deposu bulundu: <N> paket, <boyut>` + `Depo`, `Kaynak`, `Kip` satırları. İki farklı başarısızlık vardır ve **ikisinde de paket modüllerinden önce durulur, sistemde hiçbir değişiklik yapılmaz**:
| Durum | Ekran | Sonuç |
|---|---|---|
| Depo hiçbir yerde yok | `HATA: Yerel paket deposu yok. Repo sorumlusuna başvurun: provisioning/offline-repo/README.md` + aranan yollar + `ÇIKIŞ NEDENİ: yerel paket deposu bulunamadı.` | exit 1 |
| Depo bulundu ama bozuk/eksik | `OFFLINE BLOCKED: <sebep>` + Türkçe çıkış nedeni (ör. checksum uyuşmazlığı, eksik `Packages`/`packages.lock.tsv`, eksik pin) | exit 1 |
Uzak APT kaynağı, `curl`, `wget`, anahtar indirme veya `apt download` çağrılmaz. Yalnız geliştirme/test için `--online` vardır ve üretim cihazlarında kullanılmaz (DİKKAT uyarısı yazar).
### 7. Kurucu betiğin komut yüzeyi (yedek/otomasyon)
```bash
sudo ./scripts/install/blueforce-install.sh --dealer-id 12010193 [--from 07] [--only 18-final-check] [--resume] [--check] [--yes]
# --check : değişiklik yapmadan denetim (final-check + modül durumları)
# --from : belirtilen modülden devam et | --only: tek modül koş | --resume: OK modülleri atla
# --yes : onay sormadan başla (otomasyon; GUI bu bayrağı kullanmaz)
# --offline: yerel depoyu zorla (varsayılan zaten yerel) | --online: yalnız geliştirme/test
```
### 8. Idempotency kuralları (her modül için zorunlu)
4. Idempotency kuralları (her modül için zorunlu):
- Varlık kontrolü önce (`id blueforce`, `dpkg -l`, `systemctl is-enabled`): varsa atla, yoksa kur. - Varlık kontrolü önce (`id blueforce`, `dpkg -l`, `systemctl is-enabled`): varsa atla, yoksa kur.
- Dosya yazımları atomik (tmp + rename) ve tekrarlanabilir (aynı girdi → aynı çıktı). - Dosya yazımları atomik (tmp + rename) ve tekrarlanabilir (aynı girdi → aynı çıktı).
- Anahtar/secret üretimi bir kez (`/var/lib/blueforce/.modül-done` damgası); `--force` olmadan yeniden üretilmez. - Anahtar/secret üretimi bir kez; `--force` olmadan yeniden üretilmez.
- Her modül `/var/log/blueforce-install.log` dosyasına `MODÜL-NO + OK/SKIP/FAIL` satırı yazar. - Her modül `/var/log/blueforce-install.log` dosyasına modül no + OK/SKIP/FAIL satırı yazar.
5. Çıkış kodları: `0` READY, `1` modül hatası (no logda), `2` argüman/format hatası.
### 9. Çıkış kodları ve sonuç durumu
Kurucu betik: `0` başarı (offline akışta `PROVISIONED_OFFLINE`), `1` engellendi veya modül hatası (no logda), `2` argüman/format hatası. Grafik/terminal arayüz: `3` grafik katman yok (ekran veya `python3-tk`), `2` kullanım hatası (`--tui` + `--gui` birlikte verilemez), `0` normal kapanış.
`READY` bir kurucu çıkış kodu değildir: offline akış `PROVISIONED_OFFLINE` yazar, `READY` yalnız merkezi kanallar doğrulandığında (29/30) yazılır.
## 10. Test Planı ## 10. Test Planı
| Test | Beklenen sonuç | Ortam | | Test | Beklenen sonuç | Ortam |
|---|---|---| |---|---|---|
| Geçersiz no (7/9 hane, harf, boş) | exit 2, kurulum başlamaz | LAB(2) | | Geçersiz no (7/9 hane, harf, boş) | Arayüz reddeder / CLI exit 2, kurulum başlamaz | LAB(2) |
| `sudo bf` ile kurulum (grafik) | Komut önce gösterilir, onay ister, kurulum `PROVISIONED_OFFLINE` ile biter | LAB(2) masaüstü oturumu |
| `DISPLAY` yokken `sudo bf` | Türkçe yönlendirme + `sudo bf --tui` önerisi, exit 3, sistem değişmez | LAB(2) SSH |
| `python3-tk` yokken `sudo bf` | Türkçe "tkinter bulunamadı" + paket ipucu, exit 3 | LAB(2) minimal imaj |
| Her modülün çift-koşusu | İkinci koşu exit 0, durum değişmez (diff boş) | LAB(2) | | Her modülün çift-koşusu | İkinci koşu exit 0, durum değişmez (diff boş) | LAB(2) |
| Elektrik-kesintisi simülasyonu (modül 09 ortasında kill) | `--from 09` ile tamamlanır, final exit 0 | LAB(2) | | Elektrik-kesintisi simülasyonu (modül 09 ortasında kill) | `--from 09` veya arayüzdeki `--resume` ile tamamlanır, final exit 0 | LAB(2) |
| `18-final-check` tüm kapıları | Eksik adımda FAIL + modül nosu, READY yok | LAB(2) | | `18-final-check` tüm kapıları | Eksik adımda FAIL + modül nosu, başarı damgası yok | LAB(2) |
| `--check` kuru koşusu | Değişiklik yapmaz, rapor üretir | P1(5) | | `--check` kuru koşusu (GUI ön kontrol / CLI) | Değişiklik yapmaz, rapor üretir | P1(5) |
| Depo yokken kurulum denemesi | Türkçe hata + aranan yollar + çıkış nedeni, exit 1, sistem değişmez | LAB(2) |
| Depo bozukken kurulum denemesi | `OFFLINE BLOCKED: <sebep>` + Türkçe çıkış nedeni, exit 1 | LAB(2) |
## 11. Rollback ## 11. Rollback
1. Modül hatası: logdaki modül nosundan `--from <no>` ile devam edilir. 1. Modül hatası: arayüzdeki "Kurulum modül durumu"/log ile modül nosu bulunur; `--from <no>` veya arayüzdeki `Başarısız kuruluma devam et (--resume)` ile sürdürülür.
2. Yanlış no: doğru no ile tam yeniden koşu (idempotent, kimlik modülü üzerine yazar). 2. Yanlış no: doğru no ile tam yeniden koşu (idempotent, hostname modülü üzerine yazar).
3. Düzelmeyen cihaz: 17-RECOVERY LEVEL 9 (temiz imaj + kurucu). 3. Düzelmeyen cihaz: 17-RECOVERY LEVEL 9 (temiz imaj + kurucu).
4. Paket deposu bozuksa sahada onarılmaz: repo sorumlusu depoyu yeniden üretip commit eder, güncel git tarafı çekilir ve komut tekrarlanır (27 §11).
## 12. Kontrol Listesi ## 12. Kontrol Listesi
- [ ] 18 modül dosyası `scripts/install/` altında numaralı ve çalışır durumda. - [ ] 18 modül dosyası `scripts/install/installer/modules/` altında numaralı ve çalışır durumda.
- [ ] Her modülde varlık-kontrolü + damga + log satırı var. - [ ] Grafik arayüzde `Kurulum` sekmesi ve `Bu cihazı kur` işlemi bayi-no kapısına bağlı.
- [ ] Uygulama ekransız ortamda ve tkinter yokken Türkçe yönlendirme + `--tui` önerisi verip exit 3 ile çıkıyor.
- [ ] Her modülde varlık-kontrolü + log satırı var.
- [ ] Format regex'i 02 ile aynı (`^[0-9]{8}$`). - [ ] Format regex'i 02 ile aynı (`^[0-9]{8}$`).
- [ ] `18-final-check` K-10/K-11 kapılarını denetliyor (unless-stopped, latest yok, update kapalı). - [ ] `18-final-check` K-10/K-11 kapılarını ve tkinter denetimini içeriyor.
- [ ] `--check/--from/--only` bayrakları çalışıyor. - [ ] Depo keşif sırası ve iki hata durumu (yok / bozuk) ekranda Türkçe ve fail-closed.
- [ ] `--check/--from/--only/--resume` bayrakları çalışıyor; `--offline` zorunlu değil.
## 13. Açık Sorular ## 13. Açık Sorular
- [ ] Kurucu USB'de mi, merkezden `curl` ile mi alınacak (saha interneti varsayımı) (sahibi: Faz 3, 04 yazarı ile ortak). - [ ] Paket setinin sahaya taşınması: git clone (Git LFS) mı, repo sorumlusunun hazırladığı `/opt/blueforce/offline-repo` kopyası mı birincil? (sahibi: Faz 3, 04/27 yazarları ile ortak)
- [ ] MEG sabit tag'inin kim tarafından, hangi onay ile güncelleneceği (sahibi: 10-UPDATE / 12-DOCKER yazarları). - [ ] MEG sabit tag'inin kim tarafından, hangi onay ile güncelleneceği (sahibi: 10-UPDATE / 12-DOCKER yazarları).
- [ ] Arayüzün grafik ekran gerektirmeyen cihazlarda varsayılan modu (`--tui` mi, grafik mi) — saha ölçümü ile karar (sahibi: 33 + saha operasyonu).
--- ---
## Ek: Kurucu Akış ## Ek: Kurulum Akışı (grafik arayüz)
```mermaid ```mermaid
flowchart TB flowchart TB
CMD["sudo ./blueforce-install.sh<br/>--dealer-id <8hane>"] --> FMT{"^[0-9]8$ ?"} CMD["sudo bf<br/>(grafik operatör uygulaması)"] --> EKRAN{"Grafik ekran +<br/>python3-tk var mı?"}
FMT -->|Hayır| EXIT2["exit 2<br/>kullanım mesajı"] EKRAN -->|Hayır| TUI["Türkçe yönlendirme + exit 3<br/>sudo bf --tui"]
FMT -->|Evet| M01["01-precheck … 17-bootstrap<br/>(idempotent modüller)"] EKRAN -->|Evet| HEDEF["Hedef: Bayi no (8 hane)"]
HEDEF --> FMT{"^[0-9]{8}$ ?"}
FMT -->|Hayır| UYARI["Arayüz uyarır<br/>komut üretilmez"]
FMT -->|Evet| KUR["Kurulum sekmesi<br/>'Bu cihazı kur'"]
KUR --> ONAY["Komut gösterilir + onay<br/>(yıkıcı: ikinci onay)"]
ONAY --> DEPO{"Yerel paket deposu aranır<br/>BF_OFFLINE_REPO → built → /opt/blueforce/offline-repo"}
DEPO -->|"hiçbiri yok"| YOK["Türkçe hata + aranan yollar<br/>ÇIKIŞ NEDENİ: bulunamadı · exit 1"]
DEPO -->|"bozuk/eksik"| BOZ["OFFLINE BLOCKED: sebep<br/>exit 1 · sistem değişmez"]
DEPO -->|"eksiksiz"| M01["01-precheck … 17-healthcheck<br/>(idempotent modüller)"]
M01 --> M18["18-final-check"] M01 --> M18["18-final-check"]
M18 -->|Tümü OK| READY["READY damgası<br/>bf-status yeşil"] M18 -->|Hepsi OK| OFFLINE["PROVISIONED_OFFLINE"]
M18 -->|FAIL| LOG["modül nosu + log yolu<br/>--from ile devam"] M18 -->|FAIL| LOG["modül nosu + log yolu<br/>--from / --resume ile devam"]
OFFLINE --> ENR["Token → ENROLLED"]
ENR --> READY["Merkez kanalları doğrulandı → READY"]
``` ```

View file

@ -14,8 +14,8 @@ Tekrar eden arızalarda teşhisi hızlandırmak: belirtiyi bulan teknisyen/admin
## 2. Kapsam ## 2. Kapsam
- Kapsam içi: boot, ağ/VPN, SSH, RDP/GUI, RustDesk, MeshCentral, Docker/MEG, disk, monitoring belirti tabloları. - Kapsam içi: boot, ağ/VPN, SSH, RDP/GUI, RustDesk, MeshCentral, Docker/MEG, disk, monitoring ve operatör arayüzü (grafik/terminal konsol, paket deposu keşfi) belirti tabloları.
- Kapsam dışı: seviye kararları (17), adım-adım kurulum (20), merkez onay akışı (21). - Kapsam dışı: seviye kararları (17), adım-adım kurulum (20), merkez onay akışı (21), arayüzün tüm ekranları (33).
## 3. Kararlar ## 3. Kararlar
@ -69,6 +69,14 @@ docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.RestartPolicy}}'
df -h / /var/lib/docker | cat df -h / /var/lib/docker | cat
``` ```
Aynı raporlar operatör arayüzünden de okunur (komut ezberi gerekmez):
```text
Arayüzden: sudo bf → `Durum` düğmesi (Durum özeti) ya da Detaylı mod → Cihaz bilgisi sekmesi
(kimlik, ağ/WireGuard, kaynaklar, servisler, son journal hataları — hepsi salt-okunur)
Komutla (yedek): sudo bf --tui → Cihaz bilgisi menüsü
```
### T-1 — Cihaz açılmıyor / ekranda hiçbir şey yok ### T-1 — Cihaz açılmıyor / ekranda hiçbir şey yok
| # | Neden (olasılık sırası) | Tanı | Çözüm | | # | Neden (olasılık sırası) | Tanı | Çözüm |
@ -147,6 +155,29 @@ Yükseltme: WG kalkmıyorsa RustDesk/MeshCentral ile devam (17-L4); onlar de yok
| 2 | Uptime Push gelmiyor | Kuma "last seen" | Cihaz interneti + push job'ı (T-3'e dön) | | 2 | Uptime Push gelmiyor | Kuma "last seen" | Cihaz interneti + push job'ı (T-3'e dön) |
| 3 | Etiket yanlış (`BF-<no>`) | Prometheus target etiketleri | Envanterle eşitle (02) | | 3 | Etiket yanlış (`BF-<no>`) | Prometheus target etiketleri | Envanterle eşitle (02) |
### T-10 — Grafik operatör arayüzü açılmıyor (`sudo bf` pencere açmıyor / hemen kapanıyor)
| # | Neden (olasılık sırası) | Tanı | Çözüm |
|---|---|---|---|
| 1 | Ekran yok (SSH/başsız oturum) | Ekranda "Grafik ekran bulunamadı (DISPLAY ve WAYLAND_DISPLAY tanımlı değil)" + yönlendirme; çıkış kodu `3` | `sudo bf --tui` ile aynı işlemleri terminal konsolunda yürüt; yerel ekranda pencereli kullanmak için oturum açık bir makinede `sudo bf` |
| 2 | `python3-tk` kurulu değil (minimal imaj) | "tkinter bulunamadı; masaüstü arayüzü başlatılamıyor" + paket ipucu; çıkış kodu `3` | Çevrimdışı sahada paket kurulmaz: paket offline depoda pinlidir (`manifests/base-packages.txt`) ve normal kurulumla gelir. Eksikse `sudo bf --tui` ile devam et ve merkeze bildir; kurulum sonu kapısı (`18-final-check`) bu durumda **WARN** yazar ve kurulumu düşürmez |
| 3 | `DISPLAY` var ama X/Wayland erişilemiyor (ssh -X yok, oturum kilitli) | "Pencere açılamadı; grafik oturumuna erişilemiyor" + teknik ayrıntı; çıkış kodu `3` | `sudo bf --tui`; yerel oturumda çalıştırıyorsan kilitli ekranı aç veya `ssh -X` yerine yerel konsolu kullan |
| 4 | Uygulama açıldı ama yazma işlemleri kapalı | Arayüzde "Yönetici yetkisi yok: salt-okunur raporlar çalışır, kurulum ve değişiklik gerektiren işlemler kapalıdır. Tam yetki için: sudo bf" | Komutu `sudo` ile çalıştır; aynı işlem root olmadan çalıştırma katmanında da reddedilir |
Yükseltme: pencere hiç açılmıyorsa ve `sudo bf --tui` de çalışmıyorsa (Python 3.8+ yok veya `bfos` paketi bulunamıyor) merkeze telefon aç; yedek bash menü `sudo admin/bf-menu` ana yol değildir, yalnız acil yedektir.
### T-11 — "Yerel paket deposu bulunamadı" (kurulum paket adımlarına geçmiyor)
| # | Neden (olasılık sırası) | Tanı | Çözüm |
|---|---|---|---|
| 1 | Depo cihazda hiç yok | Ekranda `HATA: Yerel paket deposu yok. Repo sorumlusuna başvurun: provisioning/offline-repo/README.md` + **aranan yollar** + `ÇIKIŞ NEDENİ: yerel paket deposu bulunamadı.`; çıkış kodu `1`, sistem değişmez | Paket setini git ile tam olarak getir (paketler Git LFS ile gelir) ya da repo sorumlusunun bıraktığı kopyayı `/opt/blueforce/offline-repo` altına koy; kurulumu tekrar çalıştır |
| 2 | `packages/` dolu ama indeks (`built/`) yok | Aynı "depo yok" hatası: aday eksiksiz sayılmaz | Kurucu `Packages`, `Packages.gz`, `packages.lock.tsv`, `SHA256SUMS` dosyalarının **hepsini** arar; yalnız `.deb` havuzu yetmez. Repo sorumlusu `build-index.sh` ile `built/` dizinini üretip commit eder |
| 3 | Depo bulundu ama bozuk/eksik | `OFFLINE BLOCKED: <sebep>` + Türkçe çıkış nedeni (ör. checksum doğrulaması başarısız, kilit biçimi bozuk, pinlenen paket depoda yok); çıkış kodu `1` | Sahada onarılmaz: repo sorumlusu seti `add-packages.sh` + `build-index.sh` ile yeniden üretir, güncel git tarafı çekilir ve komut tekrarlanır. Cihazda hiçbir değişiklik yapılmadı |
| 4 | `BF_OFFLINE_REPO` yanlış | `OFFLINE BLOCKED: BF_OFFLINE_REPO mutlak bir yerel dizin olmalıdır.` | Değişkeni kaldır (varsayılan arama sırası kullanılır) veya geçerli bir mutlak dizin ver |
| 5 | "--offline bayrağını vermedim" kuşkusu | — | Bayrak **zorunlu değil**: kurucu depoyu her durumda otomatik arar (sıra: `BF_OFFLINE_REPO` → `<repo>/provisioning/offline-repo/built` → `/opt/blueforce/offline-repo`) |
Yükseltme: paket seti sahada üretilmez/onarılmaz. İki denemede de aynı hata varsa cihazda hata ayıklamayı bırak, repo sorumlusuna başvur; kurulum paket adımlarına geçmediği için cihaz yarım durumda **kalmaz** (fail-closed).
## 10. Test Planı ## 10. Test Planı
| Test | Beklenen sonuç | Ortam | | Test | Beklenen sonuç | Ortam |
@ -154,6 +185,9 @@ Yükseltme: WG kalkmıyorsa RustDesk/MeshCentral ile devam (17-L4); onlar de yok
| Her T girdisindeki ilk tanı komutu LAB'da çalışır | Komut + örnek çıktı bu dosyada | LAB(2) | | Her T girdisindeki ilk tanı komutu LAB'da çalışır | Komut + örnek çıktı bu dosyada | LAB(2) |
| WG-kill → T-3 → L4 onarım tatbikatı | < hedef sürede tünel döner | P1(5) | | WG-kill → T-3 → L4 onarım tatbikatı | < hedef sürede tünel döner | P1(5) |
| Disk-doldur simülasyonu → T-8 | Alarm + temizlik çalışır | LAB(2) | | Disk-doldur simülasyonu → T-8 | Alarm + temizlik çalışır | LAB(2) |
| `DISPLAY` yok + `python3-tk` yok senaryoları → T-10 | Her iki durumda Türkçe yönlendirme, çıkış kodu 3, `--tui` önerisi | LAB(2) |
| Depo dizini kaldırılmış cihazda kurulum → T-11 | Türkçe hata + aranan yollar + çıkış nedeni, exit 1, sistem değişmez | LAB(2) |
| Yalnız `packages/`, indeks yok → T-11/2 | Kurulum "depo yok" diyerek durur (fail-closed) | LAB(2) |
## 11. Rollback ## 11. Rollback
@ -165,12 +199,15 @@ Yanlış çözüm uygulandıysa 17-L3 (son değişikliği geri alma) işletilir;
- [ ] Komutların tamamı LAB 26.04.1 imajında denendi. - [ ] Komutların tamamı LAB 26.04.1 imajında denendi.
- [ ] Hiçbir girdide secret/şifre yazılmıyor. - [ ] Hiçbir girdide secret/şifre yazılmıyor.
- [ ] MEG'e özgü satırlar 13 ile çapraz linkli. - [ ] MEG'e özgü satırlar 13 ile çapraz linkli.
- [ ] T-10/T-11 çıkış kodları (3 / 1) ve hata metinleri gerçek çıktıyla doğrulandı.
- [ ] Arayüz yolu belirtilen her girdide "Arayüzden" + "Komutla (yedek)" satırı var.
## 13. Açık Sorular ## 13. Açık Sorular
- [ ] Kesin `bf-gui-*` birim adları + GNOME oturum satırı — LAB çıktısı (sahibi: Faz 3). - [ ] Kesin `bf-gui-*` birim adları + GNOME oturum satırı — LAB çıktısı (sahibi: Faz 3).
- [ ] MEG log hacmi ölçümü → T-8 eşikleri (sahibi: 12-DOCKER yazarı). - [ ] MEG log hacmi ölçümü → T-8 eşikleri (sahibi: 12-DOCKER yazarı).
- [ ] Turkcell CGNAT tanı notları — P1 verisi (sahibi: 08-WIREGUARD yazarı). - [ ] Turkcell CGNAT tanı notları — P1 verisi (sahibi: 08-WIREGUARD yazarı).
- [ ] `python3-tk` dışındaki grafik bağımlılıklarının da offline pinde gerekli olup olmadığı — LAB doğrulaması (sahibi: 27 + 33 yazarları).
--- ---
@ -178,7 +215,10 @@ Yanlış çözüm uygulandıysa 17-L3 (son değişikliği geri alma) işletilir;
```mermaid ```mermaid
flowchart TB flowchart TB
SORUN["Arıza bildirimi<br/>bf-status + systemctl --failed"] --> ACILIR{"Cihaz açılıyor mu?"} SORUN["Arıza bildirimi<br/>bf-status + systemctl --failed"] --> ARAC{"Sorun cihaz mı,<br/>operatör arayüzü mü?"}
ARAC -->|"Arayüz açılmıyor"| T10["T-10: DISPLAY / tkinter → sudo bf --tui"]
ARAC -->|"Kurulum paket adımına geçmiyor"| T11["T-11: yerel paket deposu"]
ARAC -->|Cihaz| ACILIR{"Cihaz açılıyor mu?"}
ACILIR -->|Hayır| T1["T-1: güç / BIOS / disk"] ACILIR -->|Hayır| T1["T-1: güç / BIOS / disk"]
ACILIR -->|Evet| BOOTOK{"Servisler kalkıyor mu?"} ACILIR -->|Evet| BOOTOK{"Servisler kalkıyor mu?"}
BOOTOK -->|Hayır| T2["T-2: failed servis / disk dolu"] BOOTOK -->|Hayır| T2["T-2: failed servis / disk dolu"]

View file

@ -1,41 +1,43 @@
# 20 — Saha Teknisyeni Runbook # 20 — Saha Teknisyeni Runbook
> Kısa özet: Sahada cihaz başına giden teknisyenin el kitabı. Tek akış: USB'yi tak → kur → bayi numarasını gir → READY'yi gör → çık. Komut ezberi istenmez; her adımda "ne göreceksin, ne yazacaksın" vardır. > Kısa özet: Sahada cihaz başına giden teknisyenin el kitabı. Tek akış: USB'yi tak → Ubuntu'yu kur → `sudo bf` → bayi numarasını gir → sonucu gör. Komut ezberi istenmez: kurulum, durum ve destek paketi grafik arayüzdeki düğmelerle yapılır; her adımda "ne göreceksin, ne yazacaksın" vardır.
- Dosya: `docs/20-FIELD-TECHNICIAN-RUNBOOK.md` - Dosya: `docs/20-FIELD-TECHNICIAN-RUNBOOK.md`
- İlgili kararlar: `24-DECISION-LOG.md#K-09` (USB → script → bayi no → READY), `#K-12` (bayi no → `BF-<no>`), `#K-14` (`bf-status`, support-bundle) - İlgili kararlar: `24-DECISION-LOG.md#K-09` (USB → kurucu → bayi no), `#K-12` (bayi no → `BF-<no>`), `#K-14` (`bf-status`, support-bundle), `#K-16` (offline kurulum)
- İlgili dokümanlar: `33-OPERATOR-CONSOLE.md` (arayüz rehberi), `27-OFFLINE-PROVISIONING.md` (paket deposu)
- Durum: [ ] Taslak - Durum: [ ] Taslak
--- ---
## 1. Amaç ## 1. Amaç
Teknisyenin sahada tek başına, telefonsuz bile cihazı READY hale getirmesi. Bu dosya "neden" anlatmaz, "ne yap" söyler. Neden merak eden 01'e bakar. Teknisyenin sahada tek başına, telefonsuz bile cihazı teslim edilebilir hale getirmesi. Bu dosya "neden" anlatmaz, "ne yap" söyler. Neden merak eden 01'e bakar.
## 2. Kapsam ## 2. Kapsam
- Kapsam içi: USB ile sıfır kurulum, bayi no girişi, READY kontrolü, arızalı cihazda ilk 3 hareket, merkeze ne zaman telefon açılacağı. - Kapsam içi: USB ile sıfır kurulum, grafik arayüzü açma, bayi no girişi, kurulum sonucunu doğrulama, arızalı cihazda ilk 3 hareket, merkeze ne zaman telefon açılacağı.
- Kapsam dışı: merkez onayları (21), arıza teşhis tablosu detayı (19 — sadece yönlendirme), update dalgaları (10). - Kapsam dışı: merkez onayları (21), arıza teşhis tablosu detayı (19 — sadece yönlendirme), update dalgaları (10).
## 3. Kararlar ## 3. Kararlar
```text ```text
KARAR: Teknisyen akışı 4 adımdır: USB → kur → bayi no → READY. Teknisyen hostname, IP, anahtar, config dosyası ile uğraşmaz; hepsini `blueforce-install.sh` yapar. KARAR: Teknisyen akışı 4 adımdır: USB → Ubuntu kur → `sudo bf` ile kurulum (bayi no) → sonucu gör. Grafik arayüz tek giriş noktasıdır; terminal konsolu (`sudo bf --tui`) yalnız grafik ekran veya tkinter yoksa kullanılan yedektir. Teknisyen hostname, IP, anahtar, paket yolu veya config dosyası ile uğraşmaz; paketler depoyla gelir ve kurulumda internet gerekmez.
GEREKÇE: K-09'un hedef akışı; insan hatası en çok elle config'de olur. Teknisyenin tek manuel girdisi 8 haneli bayi numarasıdır (K-12). GEREKÇE: K-09/K-16'nın hedef akışı; insan hatası en çok elle config ve ezberlenen komutta olur. Teknisyenin tek manuel girdisi 8 haneli bayi numarasıdır (K-12).
ALTERNATİF: Teknisyene tam Linux eğitimi + manuel kurulum — 700 cihazda standardizasyon bozulur; elendi. ALTERNATİF: Teknisyene tam Linux eğitimi + manuel kurulum — 700 cihazda standardizasyon bozulur; elendi. Yalnız bash menülü kurulum — grafik arayüz varken gereksiz terminal bilgisi ister; yedek yol olarak korundu.
RİSK: Script USB'si eski sürüm kalır; azaltma: USB etiketinde sürüm + merkezden "şu sürümden eski USB'yi kullanma" duyurusu. RİSK: Depo kopyası eski sürüm kalır; azaltma: repo sürüm etiketi + merkezden "şu sürümden eski kopyayı kullanma" duyurusu.
MALİYET: Ücretsiz. MALİYET: Ücretsiz.
LİSANS: Yok. LİSANS: Yok.
``` ```
## 4. Neden Bu Karar? ## 4. Neden Bu Karar?
Teknisyenin işi "bilgisayarcılık" değil "bayi cihazını çalışır teslim etmek"tir. Karar, teknisyenin dikkatini tek kritik noktaya toplar: bayi numarasını doğru girmek. Geri kalan her şey script + merkez otomasyonudur. Bu yüzden bu dosya kısa cümlelerle, emir kipiyle yazılır. Teknisyenin işi "bilgisayarcılık" değil "bayi cihazını çalışır teslim etmek"tir. Karar, teknisyenin dikkatini tek kritik noktaya toplar: bayi numarasını doğru girmek. Geri kalan her şey uygulama + merkez otomasyonudur. Bu yüzden bu dosya kısa cümlelerle, emir kipiyle yazılır.
## 5. Alternatifler ## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç | | Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---| |---|---|---|---|
| Grafik arayüz (`sudo bf`) | Tek düğme, komut ezberi yok, onay görünür | Grafik katman gerekir | **Seçildi** |
| Detaylı Linux kılavuzu | Bilgili teknisyen | Uzun, hata açık, herkes okumaz | Elendi | | Detaylı Linux kılavuzu | Bilgili teknisyen | Uzun, hata açık, herkes okumaz | Elendi |
| Merkezden uzaktan kurulum (teknisyensiz) | Saha maliyeti yok | İlk kurulumda cihazda internet/VPN yok | Elendi (ilk kurulum sahada) | | Merkezden uzaktan kurulum (teknisyensiz) | Saha maliyeti yok | İlk kurulumda cihazda internet/VPN yok | Elendi (ilk kurulum sahada) |
@ -43,19 +45,21 @@ Teknisyenin işi "bilgisayarcılık" değil "bayi cihazını çalışır teslim
- Yeni teknisyen 1 saatlik eğitimle sahaya çıkar. - Yeni teknisyen 1 saatlik eğitimle sahaya çıkar.
- Her cihaz aynı adımla kurulur; "ustalık farkı" kapanır. - Her cihaz aynı adımla kurulur; "ustalık farkı" kapanır.
- Kurulum internetsiz çalışır: paket seti depodan gelir, hiçbir şey indirilmez.
## 7. Dezavantajlar ## 7. Dezavantajlar
- Script'in yapmadığını teknisyen de yapamaz; olağandışı durumda merkeze bağımlıdır (bilerek böyle). - Uygulamanın yapmadığını teknisyen de yapamaz; olağandışı durumda merkeze bağımlıdır (bilerek böyle).
- USB medyası fiziksel lojistik ister (sürüm takibi). - Depo kopyası (paket setiyle birlikte ~888 MB) fiziksel/lojistik takip ister.
## 8. Riskler ## 8. Riskler
| Risk | Olasılık | Etki | Azaltma | | Risk | Olasılık | Etki | Azaltma |
|---|---|---|---| |---|---|---|---|
| Bayi no yanlış girilir | Orta | Yüksek | Script ekranda `BF-<no>`'yu gösterir, teknisyen bayiye onaylatır | | Bayi no yanlış girilir | Orta | Yüksek | Arayüz `BF-<no>`'yu gösterir, teknisyen bayiye onaylatır |
| Eski USB ile kurulum | Orta | Orta | USB sürüm etiketi + kurucu açılışta sürümü söyler | | Eski depo kopyası ile kurulum | Orta | Orta | Repo sürüm etiketi + kurucu başlangıçta depoyu ve paket sayısını söyler |
| READY görülmeden sahadan çıkılır | Düşük | Yüksek | Kontrol listesi imzalanmadan iş kapanmaz | | Grafik arayüz açılmaz (ekran/tkinter) | Orta | Orta | `sudo bf --tui` yedeği; sorun merkeze bildirilir |
| Sonuç görülmeden sahadan çıkılır | Düşük | Yüksek | Kontrol listesi imzalanmadan iş kapanmaz |
## 9. Uygulama Planı ## 9. Uygulama Planı
@ -69,82 +73,109 @@ Teknisyenin işi "bilgisayarcılık" değil "bayi cihazını çalışır teslim
> Göreceğin: Ubuntu kurulum ekranı. Bunu görmüyorsan: USB tam takılı mı, BIOS boot sırası USB mi? Olmazsa merkeze telefon aç. > Göreceğin: Ubuntu kurulum ekranı. Bunu görmüyorsan: USB tam takılı mı, BIOS boot sırası USB mi? Olmazsa merkeze telefon aç.
**ADIM 2 — Ubuntu'yu kur.** **ADIM 2 — Ubuntu'yu kur ve depo kopyasını getir.**
1. Ekrandaki kurulumu varsayılanlarla tamamla (dil: Türkçe, disk: tamamını kullan). 1. Ekrandaki kurulumu varsayılanlarla tamamla (dil: Türkçe, disk: tamamını kullan).
2. Kurulum bitince USB'yi çıkarma, bilgisayarı yeniden başlat. 2. Kurulum bitince USB'yi çıkarma, bilgisayarı yeniden başlat.
3. Repo kopyasını cihaza getir (merkezin söylediği yolla: `git clone` — paketler Git LFS ile birlikte gelir — veya hazır kopya `/opt/blueforce/offline-repo` altına konur).
**ADIM 3 — Kurucu scripti çalıştır, bayi numarasını gir.** > Paketler repoyla birlikte gelir; **internetten hiçbir şey indirilmez**.
**ADIM 3 — Grafik arayüzü aç, bayi numarasını gir, kurulumu başlat.**
1. Terminali aç, şunu yaz: 1. Terminali aç, şunu yaz:
```bash ```bash
sudo ./blueforce-install.sh sudo bf
``` ```
2. Script senden **8 haneli bayi numarasını** ister. Faturadaki/sistemdeki numarayı yaz, Enter'a bas. Örnek: `12010193`. 2. Pencere açılır: başlık **Blueforce Field OS — Yönetim**. (Ekran yoksa veya tkinter yoksa uygulama Türkçe uyarı verir; o zaman `sudo bf --tui` yaz — §D.)
3. Script ekranda şunu gösterecek: 3. Üst çubuktaki **Hedef > Bayi no (8 hane)** alanına **8 haneli bayi numarasını** yaz. Faturadaki/sistemdeki numarayı kullan. Örnek: `12010193`.
4. **Kurulum** sekmesi → **`Bu cihazı kur`**.
5. Arayüz komutu ekranda gösterir ve onay ister. `BF-12010193` görünüyor mu? Doğruysa onayla; yanlışsa iptal et ve numarayı düzelt.
```text ```text
Device ID: BF-12010193 Arayüzden: sudo bf → Hedef: Bayi no (8 hane) → Kurulum → "Bu cihazı kur"
Hostname: bf-12010193 Komutla (yedek):
sudo bf --tui # terminal konsolu; Kurulum menüsünden aynı akış
sudo ./scripts/install/blueforce-install.sh --dealer-id 12010193
``` ```
4. Numarayı bayiye sesli onaylat: "Cihaz numaranız BF-12010193, doğru mu?" Yanlışsa scriptten çık, tekrar çalıştır. 6. Numarayı bayiye sesli onaylat: "Cihaz numaranız BF-12010193, doğru mu?"
**ADIM 4 — READY'yi gör, çık.** **ADIM 4 — Sonucu gör, çık.**
1. Script bitene kadar bekle (kapatma, fişi çekme). 1. Kurulum bitene kadar bekle (kapatma, fişi çekme). Cihazın açık kalması şart.
2. Sonunda şunu çalıştır: 2. Sonucu gör:
```bash ```text
bf-status Arayüzden: `Durum` düğmesi (basit mod) → Durum özeti ya da Kurulum sekmesi → "Kurulum modül durumu"
Komutla (yedek): bf-status
``` ```
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. 3. **`PROVISIONED_OFFLINE`** görmen normaldir: kurulum yerelde tamamlandı, cihaz internetsiz kuruldu. Bu durumda cihaz `READY` değildir.
4. Token sonrası **ENROLLED** görünür; WireGuard, remote ve monitoring merkezi doğrulaması bitince **READY** görmelisin. 4. İnternet geldiğinde merkezden tek-kullanımlık enrollment token iste ve 29'daki adımı uygula.
5. READY sonrası kontrol listesini doldur (§12), sahadan çık. 5. Token sonrası **ENROLLED** görünür; WireGuard, remote ve monitoring merkezi doğrulaması bitince **READY** görürsün.
6. READY sonrası kontrol listesini doldur (§12), sahadan çık.
### B. Arızalı cihaza gittin — ilk 3 hareket ### B. Arızalı cihaza gittin — ilk 3 hareket
1. `bf-status` çalıştır. Kırmızı satır hangisi? Not al. 1. Durumu oku:
2. Bilgisayarı bir kez yeniden başlat. Düzelirse READY'yi gör, çık.
3. Düzelmezse support paketini al, merkeze gönder:
```bash ```text
bf-support-bundle --out /tmp/bf-<bayi-no>-bundle.tar.gz Arayüzden: sudo bf → `Durum` düğmesi (Durum özeti)
Komutla (yedek): bf-status
```
Kırmızı satır hangisi? Not al.
2. Bilgisayarı bir kez yeniden başlat. Düzelirse READY'yi gör, çık.
3. Düzelmezse destek paketini al, merkeze gönder:
```text
Arayüzden: sudo bf → Detaylı mod → Araçlar sekmesi → "Destek paketi oluştur"
Komutla (yedek): bf-support-bundle --out /tmp/bf-<bayi-no>-bundle.tar.gz
``` ```
Paketi merkeze ilet (telefonla nasıl göndereceğin söylenir), merkez sana söylemeden başka komut çalıştırma. Paketi merkeze ilet (telefonla nasıl göndereceğin söylenir), merkez sana söylemeden başka komut çalıştırma.
### C. Merkeze telefon açma kuralları ### C. Merkeze telefon açma kuralları
Hemen ara: READY alamıyorsan, aynı arıza 2 cihazda üst üste çıktıysa, donanım kırık/yanık kokusu varsa, bayi numarasından emin değilsen. Hemen ara: sonucu alamıyorsan, aynı arıza 2 cihazda üst üste çıktıysa, donanım kırık/yanık kokusu varsa, bayi numarasından emin değilsen.
Arama: cihaz başında ol, `BF-<no>`'yu ve `bf-status` çıktısını hazır tut. Arama: cihaz başında ol, `BF-<no>`'yu ve durum özetini/`bf-status` çıktısını hazır tut.
### D. Grafik arayüz açılmıyor — yedek yol
| Belirti | Ne yap |
|---|---|
| "Grafik ekran bulunamadı" / SSH oturumu | `sudo bf --tui` yaz; aynı işlemler terminal menüsünde numaralarla yürür |
| "tkinter bulunamadı" | `sudo bf --tui` ile devam et ve merkeze bildir; `python3-tk` paketi kurulum depounda pinlidir (19 §T-10) |
| Pencere açıldı ama dondu | Pencereyi kapat, `sudo bf --tui` ile devam et, merkeze bildir |
## 10. Test Planı ## 10. Test Planı
| Test | Beklenen sonuç | Ortam | | Test | Beklenen sonuç | Ortam |
|---|---|---| |---|---|---|
| Sıfır teknisyen 4 adımı desteksiz yapar | READY < hedef süre | LAB(2) | | Sıfır teknisyen 4 adımı desteksiz yapar | Hedef teslim durumu < hedef süre | LAB(2) |
| Yanlış bayi no girilir | Script uyarır, kuruluma devam etmez | LAB(2) | | Yanlış bayi no girilir | Uygulama uyarır, komut üretilmez/çalışmaz | LAB(2) |
| Elektrik kesintisi simülasyonu sonrası `bf-status` | READY döner | LAB(2) | | Ekransız oturumda `sudo bf` | Türkçe yönlendirme + `sudo bf --tui` önerisi, exit 3 | LAB(2) |
| Depo kopyası olmayan cihazda kurulum | Türkçe hata + aranan yollar + çıkış nedeni, sistem değişmez | LAB(2) |
| Elektrik kesintisi simülasyonu sonrası durum | Kurulum kaldığı yerden tamamlanır; durum doğru raporlanır | LAB(2) |
## 11. Rollback ## 11. Rollback
Teknisyen rollback yapmaz. Kurulum yarıda kesildiyse: baştan başla (ADIM 1). İkinci denemede de olmazsa merkeze telefon aç — merkez 17-RECOVERY'den seviyeyi seçer. Teknisyen rollback yapmaz. Kurulum yarıda kesildiyse: `sudo bf` → Kurulum sekmesi → **`Başarısız kuruluma devam et (--resume)`** (komutla yedek: aynı kurucu `--resume` bayrağıyla). İkinci denemede de olmazsa merkeze telefon aç — merkez 17-RECOVERY'den seviyeyi seçer.
## 12. Kontrol Listesi ## 12. Kontrol Listesi
- [ ] USB sürümü güncel (etiket + merkez duyurusu eşleşiyor). - [ ] Depo kopyası/checkout sürümü güncel (etiket + merkez duyurusu eşleşiyor).
- [ ] `BF-<no>` bayiye onaylatıldı. - [ ] `BF-<no>` bayiye onaylatıldı.
- [ ] `bf-status` → READY görüldü. - [ ] Kurulum sonucu görüldü (`PROVISIONED_OFFLINE` normaldir; READY için §A/ADIM 4-5).
- [ ] Cihaz fişe + internete bağlı bırakıldı. - [ ] Cihaz fişe + internete bağlı bırakıldı.
- [ ] İş formu dolduruldu (bayi no, tarih, teknisyen adı). - [ ] İş formu dolduruldu (bayi no, tarih, teknisyen adı).
## 13. Açık Sorular ## 13. Açık Sorular
- [ ] Kurulum USB'sinin dağıtım/sürüm takip yöntemi (sahibi: merkezi admin, 21). - [ ] Depo kopyasının sahaya taşınma/sürüm takip yöntemi (git clone + LFS mı, hazır kopya mı) (sahibi: merkezi admin, 21).
- [ ] İş formu kağıt mı mobil mi (sahibi: 25-ROADMAP). - [ ] İş formu kağıt mı mobil mi (sahibi: 25-ROADMAP).
- [ ] Hedef kurulum süresi (öneri: P2'de ölçülecek). - [ ] Hedef kurulum süresi (öneri: P2'de ölçülecek).
@ -155,11 +186,15 @@ Teknisyen rollback yapmaz. Kurulum yarıda kesildiyse: baştan başla (ADIM 1).
```mermaid ```mermaid
flowchart TB flowchart TB
USB["USB tak → aç"] --> KUR["Ubuntu kur"] USB["USB tak → aç"] --> KUR["Ubuntu kur"]
KUR --> SCR["sudo ./blueforce-install.sh"] KUR --> DEPO["Depo kopyasını getir<br/>(paketler LFS ile gelir, indirme yok)"]
SCR --> NO["Bayi no gir<br/>(8 hane)"] DEPO --> BF["sudo bf"]
NO --> ONAY["BF-no'yu bayiye onaylat"] BF --> EKRAN{"Grafik ekran +<br/>python3-tk var mı?"}
ONAY --> BEKLE["Script bitsin"] EKRAN -->|Hayır| TUI["sudo bf --tui<br/>(yedek terminal konsolu)"]
BEKLE --> READY["bf-status → READY?"] EKRAN -->|Evet| NO["Hedef: Bayi no (8 hane)"]
READY -->|evet| CIK["Formu doldur → çık"] TUI --> NO
READY -->|hayır| TEL["Merkeze telefon aç"] NO --> ONAY["Arayüz komutu gösterir + onay<br/>BF-no'yu bayiye onaylat"]
ONAY --> BEKLE["Kurulum bitsin<br/>(kapatma / fişi çekme)"]
BEKLE --> DURUM["Durum: PROVISIONED_OFFLINE / ENROLLED / READY"]
DURUM -->|"READY"| CIK["Formu doldur → çık"]
DURUM -->|"sorun var"| TEL["Destek paketi + merkeze telefon aç"]
``` ```

View file

@ -1,27 +1,28 @@
# 21 — Merkezi Yönetici Runbook # 21 — Merkezi Yönetici Runbook
> Kısa özet: Merkezdeki yöneticinin el kitabı: hangi işlemi kim onaylar, update'i kim başlatır, dalga kapıları nasıl açılır, teknisyen ne zaman sahaya sürülür. Yetki ve sıra bu dosyadadır. > Kısa özet: Merkezdeki yöneticinin el kitabı: hangi işlemi kim onaylar, update'i kim başlatır, dalga kapıları nasıl açılır, teknisyen ne zaman sahaya sürülür. Yetki ve sıra bu dosyadadır. Filo işlemleri grafik arayüzden (`sudo bf` → `Cihaz işlemleri`) yürütülür; komut satırı karşılıkları yedek/otomasyon yoludur.
- Dosya: `docs/21-CENTRAL-ADMIN-RUNBOOK.md` - Dosya: `docs/21-CENTRAL-ADMIN-RUNBOOK.md`
- İlgili kararlar: `24-DECISION-LOG.md#K-06` (Ansible/Semaphore), `#K-11` (onay zinciri + dalgalar), `#K-13` (anahtar dağıtımı) - İlgili kararlar: `24-DECISION-LOG.md#K-06` (Ansible/Semaphore), `#K-11` (onay zinciri + dalgalar), `#K-13` (anahtar dağıtımı)
- İlgili dokümanlar: `33-OPERATOR-CONSOLE.md` (arayüz rehberi), `10-UPDATE-AND-ROLLBACK-POLICY.md` (dalga politikası), `20-FIELD-TECHNICIAN-RUNBOOK.md` (saha)
- Durum: [ ] Taslak - Durum: [ ] Taslak
--- ---
## 1. Amaç ## 1. Amaç
Merkezde "kim, neyi, hangi sırayla yapar" sorusunu kapatmak. Update'i kim başlatır, dalgayı kim onaylar, acil durumda kim karar verir — hepsi burada yazar, kriz anında aranmaz. Merkezde "kim, neyi, hangi sırayla yapar" sorusunu kapatmak. Update'i kim başlatır, dalgayı kim onaylar, acil durumda kim karar verir — hepsi burada yazar, kriz anında aranmaz. İşlemler tek arayüzde toplanır: `sudo bf` (grafik) veya `sudo bf --tui` (yedek terminal konsolu).
## 2. Kapsam ## 2. Kapsam
- Kapsam içi: rol ve onay yetkileri, update başlatma akışı, dalga kapı prosedürü, anahtar/erişim yönetimi, acil durum yetkisi, günlük/haftalık rutinler. - Kapsam içi: rol ve onay yetkileri, update başlatma akışı, dalga kapı prosedürü, arayüz üzerinden filo işlemi yürütme, anahtar/erişim yönetimi, acil durum yetkisi, günlük/haftalık rutinler.
- Kapsam dışı: tek cihaz kurulum adımları (20), komut-seviyesi teşhis (19), kurtarma seviye tablosu (17 — buradan çağrılır). - Kapsam dışı: tek cihaz kurulum adımları (20), komut-seviyesi teşhis (19), kurtarma seviye tablosu (17 — buradan çağrılır), merkez sunucusunun kurulumu (arayüzde yalnız "Merkez hazırlığı" denetimleri vardır).
## 3. Kararlar ## 3. Kararlar
```text ```text
KARAR: Update'i yalnızca merkezi yönetici başlatır; teknisyen, bayi veya otomatik zamanlayıcı update tetikleyemez. Her dalga geçişi açık onay (Semaphore kapı adımı + kayıt) gerektirir. KARAR: Update'i yalnızca merkezi yönetici başlatır; teknisyen, bayi veya otomatik zamanlayıcı update tetikleyemez. Her dalga geçişi açık onay gerektirir: arayüzden çalıştırıldığında "production" yazma adımı + production ek onayı, gündelik/sürüm kapsamlı işlemlerde kapı sözcüğü; Semaphore iş akışı kullanılıyorsa kapı adımı Semaphore'dadır.
GEREKÇE: K-11 "onaysız update yasak" ilkesinin operasyonel karşılığıdır; tek başlatma noktası = denetlenebilirlik. Otomatik tetikleyici olmaması, zamanlanmış sürprizleri engeller. GEREKÇE: K-11 "onaysız update yasak" ilkesinin operasyonel karşılığıdır; tek başlatma noktası = denetlenebilirlik. Otomatik tetikleyici olmaması, zamanlanmış sürprizleri engeller. Onay olmadan komut üretilmez: arayüz komutu önce gösterir, kapı adımını geçmeyen çağrıda bayrağı kendiliğinden eklemez.
ALTERNATİF: Takvimli otomatik dalga başlatma — onay kaydı zayıflar, arızada "kim başlattı" belirsizleşir; elendi. ALTERNATİF: Takvimli otomatik dalga başlatma — onay kaydı zayıflar, arızada "kim başlattı" belirsizleşir; elendi.
RİSK: Yönetici izindeyken update kilitlenir; azaltma: en az 2 onay yetkilisi tanımlanır (asıl + vekil). RİSK: Yönetici izindeyken update kilitlenir; azaltma: en az 2 onay yetkilisi tanımlanır (asıl + vekil).
MALİYET: Ücretsiz. MALİYET: Ücretsiz.
@ -39,7 +40,7 @@ LİSANS: Yok.
## 4. Neden Bu Karar? ## 4. Neden Bu Karar?
700 cihazda en tehlikeli cümle "ben başlattım sandım"dır. Başlatma yetkisini tek role toplamak, hem hatayı hem suçu önler: hata olursa kayıttan bulunur, suçlama olursa kayıt konuşur. Vekil kuralı, tek kişiye bağımlılığı kaldırır. 700 cihazda en tehlikeli cümle "ben başlattım sandım"dır. Başlatma yetkisini tek role toplamak, hem hatayı hem suçu önler: hata olursa kayıttan bulunur, suçlama olursa kayıt konuşur. Vekil kuralı, tek kişiye bağımlılığı kaldırır. Arayüz bu disiplini görünür kılar: dalga kapsamlı her işlemde komut, bayraklarıyla birlikte ekranda gösterilir, production dalgası için `production` yazılmadan komut çalışmaz; her karar `/var/log/blueforce-console.log` (mod `600`) dosyasına düşer.
## 5. Alternatifler ## 5. Alternatifler
@ -48,24 +49,28 @@ LİSANS: Yok.
| Herkes update başlatabilir | Hızlı | Onaysız yayılım, denetimsizlik | Elendi | | Herkes update başlatabilir | Hızlı | Onaysız yayılım, denetimsizlik | Elendi |
| Tam otomatik pipeline | Eforsuz | Politika ihlali (K-11) | Elendi | | Tam otomatik pipeline | Eforsuz | Politika ihlali (K-11) | Elendi |
| Tek onay yetkilisi | Net | İzin/hastalıkta kilit | Elendi (asıl+vekil) | | Tek onay yetkilisi | Net | İzin/hastalıkta kilit | Elendi (asıl+vekil) |
| Yalnız serbest `ansible-playbook` komutu | Esnek | Kapı bayrakları elle yazılır, atlanabilir | Elendi (arayüz kapıyı bayrakla bağlar) |
## 6. Avantajlar ## 6. Avantajlar
- Her update'in "kim başlattı, kim onayladı, ne zaman" kaydı vardır. - Her update'in "kim başlattı, kim onayladı, ne zaman" kaydı vardır (konsol denetim günlüğü).
- Acil durumda bile sıra korunur; hız, sıranın içinde ayarlanır. - Acil durumda bile sıra korunur; hız, sıranın içinde ayarlanır.
- Hedef seçimi tek yerden yapılır: tek cihaz (bayi no), dalga grubu (lab, pilot_1, pilot_2, wave_1, wave_2, production) veya tüm filo.
## 7. Dezavantajlar ## 7. Dezavantajlar
- Onay adımı update'i yavaşlatır (bilerek; hız 25-ROADMAP'ta acil yoluyla dengelenir). - Onay adımı update'i yavaşlatır (bilerek; hız 25-ROADMAP'ta acil yoluyla dengelenir).
- Semaphore kapı adımı atlanırsa politika kağıtta kalır — teknik zorunluluk şarttır. - Kapı adımı atlanırsa (serbest CLI ile run) politika kağıtta kalır — teknik zorunluluk şarttır.
- Arayüz grafik katman gerektirir; merkez sunucusunda yoksa `sudo bf --tui` kullanılır.
## 8. Riskler ## 8. Riskler
| Risk | Olasılık | Etki | Azaltma | | Risk | Olasılık | Etki | Azaltma |
|---|---|---|---| |---|---|---|---|
| Kapı adımı bypass edilir (CLI'dan direkt run) | Orta | Yüksek | Semaphore dışı prod run'ı yasaktır; denetim logu izlenir | | Kapı adımı bypass edilir (arayüz dışı CLI run) | Orta | Yüksek | Semaphore/arayüz dışı prod run yasaktır; denetim günlüğü izlenir |
| İki yetkili de ulaşılamaz | Düşük | Orta | Acil iletişim zinciri + önceden tanımlı duraklatma (devam etme, durdur) | | İki yetkili de ulaşılamaz | Düşük | Orta | Acil iletişim zinciri + önceden tanımlı duraklatma (devam etme, durdur) |
| Anahtar sızıntısı | Düşük | Yüksek | Per-admin anahtar iptali + rotasyon (K-13) | | Anahtar sızıntısı | Düşük | Yüksek | Per-admin anahtar iptali + rotasyon (K-13) |
| Yanlış hedefe işlem (grup/tüm filo) | Orta | Yüksek | Hedef arayüzde tek tek seçilir; `--limit` komutta görünür, onay istenir |
## 9. Uygulama Planı ## 9. Uygulama Planı
@ -81,16 +86,34 @@ LİSANS: Yok.
### Standart update akışı (yönetici gözüyle) ### Standart update akışı (yönetici gözüyle)
1. Update talebi gelir (güvenlik bülteni / MEG sürümü / OS yaması) → etki + geri alma planı raporlanır. 1. Update talebi gelir (güvenlik bülteni / MEG sürümü / OS yaması) → etki + geri alma planı raporlanır.
2. Onay yetkilisi LAB'ı başlatır: Semaphore'da job + kayıt. 2. Onay yetkilisi LAB'ı başlatır: Semaphore'da job + kayıt (veya arayüzden dalga işlemi).
3. LAB çıkış kriteri tuttuysa P1 → P2 → W1 → W2 → PROD, her kapıda açık onay. 3. LAB çıkış kriteri tuttuysa P1 → P2 → W1 → W2 → PROD, her kapıda açık onay.
4. Herhangi bir dalgada halt kuralı (18) tetiklenirse: yayılım DURUR, 17-L3 işletilir, kök neden bulunmadan kapı açılmaz. 4. Herhangi bir dalgada halt kuralı (18) tetiklenirse: yayılım DURUR, 17-L3 işletilir, kök neden bulunmadan kapı açılmaz.
5. PROD bitiminde kapanış kaydı (başarı/arıza sayısı, süre). 5. PROD bitiminde kapanış kaydı (başarı/arıza sayısı, süre).
```bash ```text
# örnek: onaylı dalga run (operatör, onay sonrası) Arayüzden (merkez operatörü):
ansible-playbook playbooks/update.yml --limit wave_p1 sudo bf → Detaylı mod → Cihaz işlemleri sekmesi
# örnek: kapı kontrolü — onaysız PROD engeli (ilke; teknik karşılığı Semaphore'da) → işlemi seç (örn. "Kontrollü yeniden başlatma" / "Onaylı paket güncellemesi")
# PROD limiti yalnızca "prod-onay" etiketli job ile koşar → Hedef: dalga grubu (lab, pilot_1, pilot_2, wave_1, wave_2, production)
→ kapı adımı: production için 'production' yazmak ZORUNLU
→ komut ekranda gösterilir → onay → çalışır
Komutla (yedek / otomasyon):
ansible-playbook -i ansible/inventory/... ansible/playbooks/bf-reboot.yml \
--limit wave_p1 -e operation_wave=wave_1 -e operation_confirmed=true
# production_override / allow_production_reboot yalnız production ek onayıyla eklenir.
# Kapı kuralı: önceki dalga sağlık kapısını geçmediyse çalıştırılmaz (10).
```
Planlama/denetim komutlarının arayüz karşılıkları:
```text
Arayüzden: sudo bf → Merkez hazırlığı sekmesi
(envanter hedefleri ve dalga/cihaz sayıları, PLACEHOLDER denetimi,
merkez uç noktaları, hazırlık kontrol listesi — hepsi salt-okunur)
Komutla (yedek): sudo bf --tui → Merkez hazırlığı menüsü
admin/bf-menu --list-targets # yedek bash menüsü
``` ```
### Anahtar ve erişim yönetimi ### Anahtar ve erişim yönetimi
@ -101,34 +124,39 @@ 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. Cihaz durumu için arayüzden `Durum` düğmesi ya da `Cihaz işlemleri > Kurulum fazı raporu`.
- 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). - 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ı
| Test | Beklenen sonuç | Ortam | | Test | Beklenen sonuç | Ortam |
|---|---|---| |---|---|---|
| Onaysız PROD run denemesi | Engellenir + log düşer | Merkez test | | Onaysız PROD run denemesi | Engellenir (`production` yazılmadan komut çalışmaz) + denetim günlüğüne yazılır | Merkez test |
| Semaphore kapalıyken Ansible CLI run | Onaylı iş CLI'dan koşar (K-06) | LAB(2) | | Semaphore kapalıyken Ansible CLI run | Onaylı iş CLI'dan koşar (K-06) | LAB(2) |
| Vekil ile dalga onayı | Kayıtta vekil adı görünür | Merkez test | | Vekil ile dalga onayı | Kayıtta vekil adı görünür | Merkez test |
| Arayüzden dalga hedefi seçimi | Komutta doğru `--limit` görünür, yanlış hedefte komut üretilmez | Merkez test |
| Grafik arayüz yokken yönetim işlemleri | `sudo bf --tui` ile aynı işlemler yürütülür | LAB(2) SSH |
## 11. Rollback ## 11. Rollback
1. Hatalı dalga: 17-L3 + 10-UPDATE rollback zinciri; kapı bir sonraki dalgaya kapatılır. 1. Hatalı dalga: 17-L3 + 10-UPDATE rollback zinciri; kapı bir sonraki dalgaya kapatılır.
2. Hatalı onay: aynı yetkili (veya vekil) durdurma kararı verir; "devam" kararı verilemez, yalnızca "dur + geri al". 2. Hatalı onay: aynı yetkili (veya vekil) durdurma kararı verir; "devam" kararı verilemez, yalnızca "dur + geri al".
3. Arayüzden başlatılan yanlış kapsamlı işlem: komut ekranda ve denetim günlüğünde hedefiyle kayıtlıdır; kapsam bu kayıttan doğrulanıp geri alma adımı planlanır.
## 12. Kontrol Listesi ## 12. Kontrol Listesi
- [ ] Asıl + vekil isimle tanımlı ve iletişim bilgileri güncel. - [ ] Asıl + vekil isimle tanımlı ve iletişim bilgileri güncel.
- [ ] Semaphore'da PROD kapı adımı teknik olarak zorunlu. - [ ] Semaphore'da PROD kapı adımı teknik olarak zorunlu; arayüzde production için 'production' yazma adımı çalışıyor.
- [ ] Her dalga onayı kayıtlı (kim, ne zaman, hangi sürüm). - [ ] Her dalga onayı kayıtlı (kim, ne zaman, hangi sürüm) — `/var/log/blueforce-console.log` mod `600`.
- [ ] Ayrılan personelin anahtarı kaldırıldı. - [ ] Ayrılan personelin anahtarı kaldırıldı.
- [ ] Merkez operatörleri grafik arayüzü (`sudo bf`) ve yedek terminal konsolunu (`sudo bf --tui`) biliyor.
## 13. Açık Sorular ## 13. Açık Sorular
- [ ] Acil tanımı eşikleri (CVSS/aktif sömürü) — 10-UPDATE ile ortak onay (sahibi: yönetici). - [ ] Acil tanımı eşikleri (CVSS/aktif sömürü) — 10-UPDATE ile ortak onay (sahibi: yönetici).
- [ ] Kapanış raporu formatı ve saklama süresi (sahibi: 25-ROADMAP). - [ ] Kapanış raporu formatı ve saklama süresi (sahibi: 25-ROADMAP).
- [ ] Read-only yönetim panosu kapsamı (sahibi: 14-MONITORING yazarı). - [ ] Read-only yönetim panosu kapsamı (sahibi: 14-MONITORING yazarı).
- [ ] Arayüzün merkez sunucusunda (`sudo bf --tui` mi, grafik mi) varsayılan kullanımı (sahibi: 33 + merkez ekibi).
--- ---
@ -139,7 +167,10 @@ flowchart TB
TALEP["Update talebi<br/>(güvenlik / MEG / OS)"] --> KAYIT["Değişiklik kaydı<br/>kapsam + geri alma planı"] TALEP["Update talebi<br/>(güvenlik / MEG / OS)"] --> KAYIT["Değişiklik kaydı<br/>kapsam + geri alma planı"]
KAYIT --> ONAY{"Onay yetkilisi<br/>(asıl veya vekil)"} KAYIT --> ONAY{"Onay yetkilisi<br/>(asıl veya vekil)"}
ONAY -->|Ret| KAPANIR["Kapanış kaydı (ret)"] ONAY -->|Ret| KAPANIR["Kapanış kaydı (ret)"]
ONAY -->|Onay| LAB["LAB(2) → P1(5) → P2(20)"] ONAY -->|Onay| UI["sudo bf → Cihaz işlemleri<br/>hedef: dalga grubu"]
UI --> KAPI0{"Kapı adımı<br/>production ise 'production' yaz"}
KAPI0 -->|Geçmedi| DUR0["Komut çalışmaz<br/>(denetim günlüğü: CANCEL)"]
KAPI0 -->|Geçti| LAB["LAB(2) → P1(5) → P2(20)"]
LAB --> KAPI{"Sağlık kapısı temiz mi?"} LAB --> KAPI{"Sağlık kapısı temiz mi?"}
KAPI -->|Halt| DUR["Yayılım DURUR → 17-L3 rollback"] KAPI -->|Halt| DUR["Yayılım DURUR → 17-L3 rollback"]
KAPI -->|Temiz| WAVE["W1(50) → W2(100) → PROD(~523)"] KAPI -->|Temiz| WAVE["W1(50) → W2(100) → PROD(~523)"]

View file

@ -1,83 +1,102 @@
# 27 — Offline Provisioning # 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. > Kısa özet: Paketler **git deposunun içindedir** (`provisioning/offline-repo/packages/`, 1321 `.deb`, ~888 MB, Git LFS ile takip edilir). Saha kurulumu internet **gerektirmez** ve kuran kişi **hiçbir şey indirmez**: `sudo bf` → Kurulum sekmesi → "Bu cihazı kur" → 8 haneli bayi no → onay. 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` > - Dosya: `docs/27-OFFLINE-PROVISIONING.md`
> >
> **Uygulama sözleşmesi:** Kurucu, paket setini **kendi kendine bulur** ve varsayılan olarak yerel depodan kurar; `--offline` bayrağı artık **gerekmez** (geriye uyumluluk için kabul edilir, yalnız yerel depoyu zorlar). Arama sırası: (1) `BF_OFFLINE_REPO` ortam değişkeni, (2) `<depo>/provisioning/offline-repo/built`, (3) `/opt/blueforce/offline-repo`; ilk eksiksiz aday kazanır. Yerel `file:` APT kaynağı dışında hiçbir kaynak kullanılmaz: uzak APT source, curl, wget, anahtar indirme veya `apt download` çağrılmaz. **İnternet kurulum için gerekmez.** İki farklı başarısızlık vardır: depo hiç yoksa kurulum Türkçe bir hata ve `ÇIKIŞ NEDENİ: yerel paket deposu bulunamadı.` ile durur; depo bulunup da doğrulanamazsa (eksik `Packages`, `Packages.gz`, `packages.lock.tsv`, `SHA256SUMS`; checksum uyuşmazlığı; boş/bozuk kilit; eksik pin) `OFFLINE BLOCKED: <sebep>` ile durur. Her iki durumda da paket modüllerinden önce durulur, kurulum başlamaz ve sistemde hiçbir değişiklik yapılmaz. Paketler bu git deposunda taşınır ve **repo sorumlusu** tarafından `add-packages.sh` + `build-index.sh` ile bu depoya konur; kuran kişi hiçbir şey indirmez. Yalnız geliştirme/test için `--online` vardır ve **üretim cihazlarında kullanılmaz**. > **Uygulama sözleşmesi:** Kurucu, paket setini **kendi kendine bulur** ve varsayılan olarak yerel depodan kurar; `--offline` bayrağı artık **gerekmez** (geriye uyumluluk için kabul edilir, yalnız yerel depoyu zorlar). Arama sırası: (1) `BF_OFFLINE_REPO` ortam değişkeni, (2) `<depo>/provisioning/offline-repo/built`, (3) `/opt/blueforce/offline-repo`; ilk eksiksiz aday kazanır. Yerel `file:` APT kaynağı dışında hiçbir kaynak kullanılmaz: uzak APT source, curl, wget, anahtar indirme veya `apt download` çağrılmaz. **İnternet kurulum için gerekmez.** İki farklı başarısızlık vardır: depo hiç yoksa kurulum Türkçe bir hata, aranan yollar ve `ÇIKIŞ NEDENİ: yerel paket deposu bulunamadı.` ile durur; depo bulunup da doğrulanamazsa (eksik `Packages`, `Packages.gz`, `packages.lock.tsv`, `SHA256SUMS`; checksum uyuşmazlığı; boş/bozuk kilit; eksik pin) `OFFLINE BLOCKED: <sebep>` ile durur. Her iki durumda da paket modüllerinden önce durulur, kurulum başlamaz ve sistemde hiçbir değişiklik yapılmaz. Paketler bu git deposunda taşınır ve **repo sorumlusu** tarafından `add-packages.sh` + `build-index.sh` ile bu depoya konur; kuran kişi hiçbir şey indirmez. Yalnız geliştirme/test için `--online` vardır ve **üretim cihazlarında kullanılmaz**. Grafik arayüz `python3-tk` paketine bağlıdır; bu paket offline depoda bilinçli olarak pinlidir (`manifests/base-packages.txt`).
> - İlgili kararlar: `24-DECISION-LOG.md#K-16`, `#K-09`, `#K-11`, `#K-12` > - İlgili kararlar: `24-DECISION-LOG.md#K-16`, `#K-09`, `#K-11`, `#K-12`
> - İlgili dokümanlar: `05-ONE-CLICK-INSTALLER.md` (kurucu), `33-OPERATOR-CONSOLE.md` (arayüz), `provisioning/offline-repo/README.md` (paket hazırlama)
> - Durum: [ ] Taslak > - Durum: [ ] Taslak
--- ---
## 1. Amaç ## 1. Amaç
Saha interneti kesik veya güvenilmezken tekrarlanabilir, denetlenebilir ve secretsiz ilk kurulumu tanımlamak. Saha interneti kesik veya güvenilmezken tekrarlanabilir, denetlenebilir ve secretsiz ilk kurulumu tanımlamak. Kuran kişi paket aramaz, indirmez, bayrak ezberlemez: paket seti depoyla birlikte gelir.
## 2. Kapsam ## 2. Kapsam
- Kapsam içi: seed USB veya git checkout, offline APT repository/snapshot, kurucunun yerel depoyu otomatik bulması, durum damgası ve sonradan enrollment. - Kapsam içi: git checkout (paketler dahil) veya kurulmuş `/opt/blueforce/offline-repo`, offline APT bundle (`built/`), kurucunun yerel depoyu otomatik bulması, iki hata durumu, grafik arayüz üzerinden kurulum, `python3-tk` pini, durum damgası ve sonradan enrollment.
- Kapsam dışı: merkezi peer atama ve token doğrulama (29), release imzalama (30). - Kapsam dışı: merkezi peer atama ve token doğrulama (29), release imzalama (30), paket setinin nasıl üretildiği (repo sorumlusunun işi, `provisioning/offline-repo/README.md`).
## 3. Kararlar ## 3. Kararlar
```text ```text
KARAR: Offline kurulum, imzalı/sürümlü medya ya da git checkout ve ayrı offline APT kaynağıyla yapılır; paket seti kurucu tarafından otomatik bulunur ve sonuç durumu yalnızca PROVISIONED_OFFLINE'dır. KARAR: Paket seti git deposunda taşınır ve cihaz grafik arayüzden (`sudo bf` → Kurulum → "Bu cihazı kur") internetsiz kurulur; paket deposu kurucu tarafından otomatik bulunur ve 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. Operatörün hatırlaması gereken bayrak sayısı azaldıkça saha hatası azalır: yerel depo varsayılan kaynaktı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. Kuran kişinin indirme yapmaması ve bayrak hatırlamaması saha hatasını ortadan kaldırır: yerel depo varsayılan ve tek kaynaktır.
ALTERNATİF: İnternet gelene kadar kurulum yapmama — saha ziyareti ve bekleme maliyeti nedeniyle elendi. Kurulumu `--offline` bayrağına bağlamak — operatör bayrağı unuttuğunda kurulum durur; otomatik keşif seçildi, bayrak geriye uyumluluk için korundu. ALTERNATİF: İnternet gelene kadar kurulum yapmama — saha ziyareti ve bekleme maliyeti nedeniyle elendi. Kurulumu `--offline` bayrağına bağlamak — operatör bayrağı unuttuğunda kurulum durur; otomatik keşif seçildi, bayrak geriye uyumluluk için korundu.
RİSK: Eski veya karışık paket snapshot'ı; manifest, checksum ve release kimliği ile azaltılır. Yanlış/eski depo bulunması; arama sırası ve her adayda eksiksizlik denetimi ile azaltılır. RİSK: Eski veya karışık paket snapshot'ı; manifest, checksum ve release kimliği ile azaltılır. Yanlış/eski depo bulunması; arama sırası ve her adayda eksiksizlik denetimi ile azaltılır. Grafik arayüzün `python3-tk` paketine bağımlılığı; paketin offline depoda pinlenmesi ve eksikliğinde `sudo bf --tui` yedeği ile azaltılır.
MALİYET: Ücretsiz. MALİYET: Ücretsiz (depo boyutu git tarafında ~888 MB).
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. 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? ## 4. Neden Bu Karar?
Offline medya ve git deposu, 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. Paket setini **repo sorumlusu** hazırlayıp sürümlediği için sahada "hangi paket nereden geldi?" sorusu `packages.lock.tsv` ve `repo-manifest.yaml` ile yanıtlanır; kuran kişi paket aramaz ve indirmez. **Paketler artık ayrı bir medyada değil, bu git deposunda taşınır:** `provisioning/offline-repo/packages/` altında 1321 `.deb` (~888 MB) durur ve `.gitattributes` ile **Git LFS** üzerinden takip edilir (`*.deb filter=lfs ...`). Bu yüzden sahada "hangi paket nereden geldi?" sorusu ne internete ne de dış medyaya bağlıdır; aynı soru `packages.lock.tsv` ve `repo-manifest.yaml` ile yanıtlanır. Kuran kişi paket aramaz ve **indirmez**; klonladığı depo seti zaten taşır.
Durum makinesi, "kuruldu" ile "merkezce güvenildi" arasındaki farkı görünür yapar: offline kurulum yalnız `PROVISIONED_OFFLINE` üretir.
## 5. Alternatifler ## 5. Alternatifler
| Alternatif | Artı | Eksi | Sonuç | | Alternatif | Artı | Eksi | Sonuç |
|---|---|---|---| |---|---|---|---|
| Sürümlü offline APT repo | Tekrarlanabilir | Medya yönetimi | **Seçildi** | | Sürümlü offline APT bundle + repoda taşınan paketler | Tekrarlanabilir, indirme yok, medya lojistiği yok | Git deposu ~888 MB büyür (LFS) | **Seçildi** |
| Ayrı fiziksel seed USB medyası | Depo küçük kalır | Medya zimmeti/sürüm takibi, kaybolma riski | Yedek/geri dönüş yolu |
| Rastgele `.deb` dosyaları | İlk anda kolay | Bağımlılık/sürüm belirsiz | Elendi | | Rastgele `.deb` dosyaları | İlk anda kolay | Bağımlılık/sürüm belirsiz | Elendi |
| İnternet bekleme | Basit | Saha blokajı | Elendi | | İnternet bekleme | Basit | Saha blokajı | Elendi |
| Yerel depoyu operatörün `--offline` bayrağıyla göstermesi | Basit | Bayrak unutulunca kurulum durur, saha hatası üretir | Elendi (bayrak geriye uyumluluk için korundu) | | Yerel depoyu operatörün `--offline` bayrağıyla göstermesi | Basit | Bayrak unutulunca kurulum durur, saha hatası üretir | Elendi (bayrak geriye uyumluluk için korundu) |
## 6. Avantajlar ## 6. Avantajlar
- Bağlantısız sahada standarda yakın kurulum. - Bağlantısız sahada standarda yakın kurulum; **kuran kişi hiçbir indirme yapmaz** ve bayrak ezberlemez.
- Paket manifesti ile hangi sürümün yüklendiği kanıtlanır. - Paket manifesti ve kilit dosyası ile hangi sürümün yüklendiği kanıtlanır.
- Kurulum yapan kişi **hiçbir indirme yapmaz** ve bayrak ezberlemez: paketler zaten deponun içindedir, kurucu depoyu kendisi bulur. - Nesil farkı yoktur: repo sorumlusu paketleri bir kez commit eder, 700 cihaz aynı setten kurulur.
- Kurulum akışı grafik arayüzde tek düğmedir; terminal bilgisi gerekmez.
## 7. Dezavantajlar ## 7. Dezavantajlar
- Medya sürümü ve paket snapshot'ı düzenli yenilenmelidir. - Git deposu paket setiyle birlikte büyür (LFS clone/pull maliyeti); derin clone sahada zaman alabilir.
- Offline cihaz merkezi izleme ve erişim kanalları doğrulanana kadar operasyonel olarak hazır değildir. - Offline cihaz merkezi izleme ve erişim kanalları doğrulanana kadar operasyonel olarak hazır değildir.
- Paket snapshot'ı yenilenmediği sürece güvenlik güncellemeleri geride kalır (onaysız update zaten yasak, K-11).
## 8. Riskler ## 8. Riskler
| Risk | Olasılık | Etki | Azaltma | | Risk | Olasılık | Etki | Azaltma |
|---|---|---|---| |---|---|---|---|
| Eski güvenlik paketi | Orta | Orta | Süre sonu olan release medyası, hızlı enrollment kuyruğu | | Eski güvenlik paketi | Orta | Orta | Süre sonu olan release medyası/repo sürümü, 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 | | Secret repoda kalır | Düşük | Yüksek | Secret taraması (test), token yalnız bir kez ve cihazda girilir |
| Yarım paket işlemi | Orta | Orta | `dpkg --configure -a`, idempotent kurucu | | Yarım paket işlemi | Orta | Orta | `dpkg --configure -a`, idempotent kurucu |
| Kurucu yanlış/eski depoyu bulur | Düşük | Orta | Arama sırası sabittir; aday eksiksiz değilse kullanılmaz, en son `BF_OFFLINE_REPO` ile açıkça yönlendirilebilir | | Kurucu yanlış/eski depoyu bulur | Düşük | Orta | Arama sırası sabittir; aday eksiksiz değilse kullanılmaz, `BF_OFFLINE_REPO` ile açıkça yönlendirilebilir |
| Paketler var ama indeks (`built/`) yok | Orta | Yüksek | Kurucu fail-closed durur ("yerel paket deposu yok"); repo sorumlusu `build-index.sh` koşup `built/` dizinini commit eder |
| Grafik arayüz açılamıyor | Orta | Düşük | `python3-tk` offline depoda pinli; ekransız ortamda `sudo bf --tui` |
## 9. Uygulama Planı ## 9. Uygulama Planı
1. Medya manifesti: Field OS release, ISO checksum, seed checksum, offline APT snapshot kimliği, kurucu commit'i. 1. Medya/repo manifesti: Field OS release, ISO checksum, offline APT snapshot kimliği, kurucu commit'i.
2. Paket setini **repo sorumlusu** hazırlar: `.deb` dosyalarını `add-packages.sh` ile depoya koyar, `build-index.sh` ile `provisioning/offline-repo/built` indeksini üretir, `packages.lock.tsv` / `SHA256SUMS` / `repo-manifest.yaml` ile birlikte commit eder. Kuran kişi yalnız git tarafını çeker. 2. Paket setini **repo sorumlusu** hazırlar: `.deb` dosyalarını `add-packages.sh` ile `provisioning/offline-repo/packages/` altına koyar (Git LFS), pinleri `manifests/*.txt` içinde `package=version` olarak doldurur, `build-index.sh` ile `provisioning/offline-repo/built/` indeksini (`Packages`, `Packages.gz`, `packages.lock.tsv`, `SHA256SUMS`, `pool/`) üretir. Kuran kişi yalnız git tarafını çeker.
3. Kurulumda yalnız genel paketler kullanılır; kurucu yerel depoyu **otomatik bulur** ve `--offline` gerekmez. 3. Kurulumda yalnız genel paketler kullanılır; kurucu yerel depoyu **otomatik bulur** ve `--offline` gerekmez.
4. Kurucu yerel durum dosyasına `PROVISIONED_OFFLINE` yazar; `READY` yazması yasaktır. 4. Kurucu yerel durum dosyasına `PROVISIONED_OFFLINE` yazar; `READY` yazması yasaktır.
5. İnternet geldiğinde 29'daki tek-kullanımlık token ile enrollment başlatılır; sonra merkezi kanallar test edilir. 5. İnternet geldiğinde 29'daki tek-kullanımlık token ile enrollment başlatılır; sonra merkezi kanallar test edilir.
Kuran kişinin komutu (bayrak yok, tek zorunlu bilgi bayi numarası): ### Kuran kişinin akışı (grafik arayüz — ana yol)
1. Depoyu getir: `git clone <repo>` (paketler Git LFS ile gelir) — veya repo sorumlusunun bıraktığı kopyada `/opt/blueforce/offline-repo` hazırdır.
2. `sudo bf` — pencere açılır: **Blueforce Field OS — Yönetim**.
3. Üst çubuk **Hedef > Bayi no (8 hane)** alanına numarayı yaz (örn. `12010193`).
4. **Kurulum** sekmesi → **`Bu cihazı kur`** (hiçbir şey yazmayan ön kontrol için: `Kurulum ön kontrolü (--check)`).
5. Komut ekranda gösterilir, onay istenir; onaylandıktan sonra kurulum çalışır. `--offline` işaretlemek **zorunlu değildir** (varsayılan kaynak zaten yerel depodur).
6. Sonuç: `PROVISIONED_OFFLINE`. Arayüzdeki **Kurulum modül durumu** ve `bf-status` ile doğrulanır.
```bash ```bash
# Konsoldan (önerilen): Python operatör konsolu; bash yedek yolu sudo bf-menu # Ana yol (grafik arayüz)
sudo bf sudo bf
# → Hedef: Bayi no (8 hane) → Kurulum sekmesi → "Bu cihazı kur"
# Doğrudan kurucu ile # Yedek yol (grafik ekran yoksa veya python3-tk yoksa): aynı uygulamanın terminal arayüzü
sudo bf --tui
# Doğrudan kurucu ile (otomasyon / teşhis)
sudo ./scripts/install/blueforce-install.sh --dealer-id 12010193 sudo ./scripts/install/blueforce-install.sh --dealer-id 12010193
# Bilinçli olarak başka bir depo dizinini göstermek (nadir, repo sorumlusu isterse) # Bilinçli olarak başka bir depo dizinini göstermek (nadir, repo sorumlusu isterse)
@ -85,6 +104,10 @@ BF_OFFLINE_REPO=/mnt/blueforce-repo/built \
sudo ./scripts/install/blueforce-install.sh --dealer-id 12010193 sudo ./scripts/install/blueforce-install.sh --dealer-id 12010193
``` ```
### Depo keşfi ve iki hata durumu
Sıra (ilk eksiksiz aday kazanır): `BF_OFFLINE_REPO` → `<repo>/provisioning/offline-repo/built` → `/opt/blueforce/offline-repo`. Bir aday "eksiksiz" sayılmak için `Packages`, `Packages.gz`, `packages.lock.tsv` ve `SHA256SUMS` dosyalarının hepsini içermelidir; yalnız `packages/` (ham `.deb` havuzu) yeterli **değildir** — indeksin `build-index.sh` ile üretilmiş olması gerekir.
Depo bulunduğunda ekranda şu duyuru çıkar: Depo bulunduğunda ekranda şu duyuru çıkar:
```text ```text
@ -98,7 +121,16 @@ Depo hiçbir yerde yoksa kurulum başlamadan durur:
```text ```text
HATA: Yerel paket deposu yok. Repo sorumlusuna başvurun: provisioning/offline-repo/README.md HATA: Yerel paket deposu yok. Repo sorumlusuna başvurun: provisioning/offline-repo/README.md
...
Aranan yerler:
- depodaki hazır paket seti — yok: <repo>/provisioning/offline-repo/built
- cihazdaki kurulu paket deposu — yok: /opt/blueforce/offline-repo
Ne yapmalı:
1) Paket setini git ile tam olarak getirin ... ya da /opt/blueforce/offline-repo altına koyun.
2) Kurulumu yeniden çalıştırın.
Kuruluma hiç başlanmadı; sistemde hiçbir değişiklik yapılmadı.
ÇIKIŞ NEDENİ: yerel paket deposu bulunamadı. ÇIKIŞ NEDENİ: yerel paket deposu bulunamadı.
``` ```
@ -114,15 +146,22 @@ OFFLINE BLOCKED: <sebep>
`--online` yalnız geliştirme/test makinesi içindir; paketleri internetten çeker ve üretim cihazlarında kullanılmaz. `--online` yalnız geliştirme/test makinesi içindir; paketleri internetten çeker ve üretim cihazlarında kullanılmaz.
### `python3-tk` neden offline depoda pinli?
Grafik operatör uygulaması Tkinter ile yazılmıştır ve tkinter Ubuntu'da **ayrı bir pakettir** (`python3-tk`); minimal Ubuntu Server kurulumunda Python vardır ama tkinter yoktur. İnternetsiz sahada `apt install python3-tk` **çalışmaz**, bu yüzden paket ve bağımlılık kümesi (`python3-tk`, `python3.14-tk`, `libtk8.6`) `provisioning/offline-repo/manifests/base-packages.txt` içinde pinlidir ve depodan gelir. Kurulum sonu kapısı `scripts/install/installer/modules/18-final-check.sh` tkinter'ı denetler; eksikse **WARN** yazar ve kurulumu düşürmez (cihaz yine kurulur, operatör terminal konsoluna `sudo bf --tui` ile devam eder).
## 10. Test Planı ## 10. Test Planı
| Test | Beklenen sonuç | Ortam | | Test | Beklenen sonuç | Ortam |
|---|---|---| |---|---|---|
| İnternetsiz kurulum | Paketler manifestten kurulur, dış indirme yok | LAB(2) air-gapped | | İnternetsiz kurulum | Paketler manifestten kurulur, dış indirme yok | LAB(2) air-gapped |
| Depo yolu verilmeden kurulum | Kurucu depoyu otomatik bulur (`repo/built`), duyuru satırını yazar | LAB(2) | | Depo yolu verilmeden kurulum | Kurucu depoyu otomatik bulur (`repo/built`), duyuru satırını yazar | LAB(2) |
| Grafik arayüzden kurulum | `sudo bf` → Kurulum → "Bu cihazı kur" ile `PROVISIONED_OFFLINE` | LAB(2) masaüstü oturumu |
| `BF_OFFLINE_REPO` ile başka dizin | Verilen dizin kullanılır; geçersizse durur | LAB(2) | | `BF_OFFLINE_REPO` ile başka dizin | Verilen dizin kullanılır; geçersizse durur | LAB(2) |
| Depo hiç yok | Türkçe hata + `ÇIKIŞ NEDENİ: yerel paket deposu bulunamadı.`, exit 1, sistem değişmez | LAB(2) | | Depo hiç yok | Türkçe hata + aranan yollar + `ÇIKIŞ NEDENİ: yerel paket deposu bulunamadı.`, exit 1, sistem değişmez | LAB(2) |
| Depo var ama bozuk (checksum/pin) | `OFFLINE BLOCKED: <sebep>` + Türkçe çıkış nedeni, exit 1, sistem değişmez | LAB(2) | | Depo var ama bozuk (checksum/pin) | `OFFLINE BLOCKED: <sebep>` + Türkçe çıkış nedeni, exit 1, sistem değişmez | LAB(2) |
| Yalnız `packages/` var, `built/` yok | Depo "yok" sayılır, kurulum başlamaz (fail-closed) | LAB(2) |
| `python3-tk` eksik imaj | `sudo bf` Türkçe hata + ipucu, exit 3; `18-final-check` WARN yazar, kurulum düşmez | LAB(2) |
| DNS/WAN kesik | Kurucu dış indirme istemez; `01-precheck` ağı yalnız bilgi olarak yazar, `05-network` DNS zorunlu tutmaz | LAB(2) | | DNS/WAN kesik | Kurucu dış indirme istemez; `01-precheck` ağı yalnız bilgi olarak yazar, `05-network` DNS zorunlu tutmaz | LAB(2) |
| `--online` üretimde denenmez | Yalnız geliştirme/test; kurucu uyarı yazar | LAB(2) | | `--online` üretimde denenmez | Yalnız geliştirme/test; kurucu uyarı yazar | LAB(2) |
| `bf-status` | `PROVISIONED_OFFLINE`; READY değil | LAB(2) | | `bf-status` | `PROVISIONED_OFFLINE`; READY değil | LAB(2) |
@ -131,26 +170,29 @@ OFFLINE BLOCKED: <sebep>
## 11. Rollback ## 11. Rollback
1. Paket işlemi bozuksa aynı onaylı snapshot ile `dpkg --configure -a` ve kurucu yeniden çalıştırılır. 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. 2. Yanlış repo sürümü geri çekilir; önceki onaylı manifestle yeniden kurulur.
3. Yerel depo bozuksa sahada onarılmaz: repo sorumlusu depoyu yeniden üretip commit eder (`add-packages.sh` + `build-index.sh`), kurulum yapan kişi güncel git tarafını çekip komutu tekrarlar. 3. Yerel depo bozuksa sahada onarılmaz: repo sorumlusu depoyu yeniden üretip commit eder (`add-packages.sh` + `build-index.sh`), kurulum yapan kişi güncel git tarafını çekip komutu tekrarlar.
4. Kimlik/token hatasında cihaz resetlenmez; 29'daki token iptal/yenileme prosedürü uygulanır. 4. Kimlik/token hatasında cihaz resetlenmez; 29'daki token iptal/yenileme prosedürü uygulanır.
5. Arayüz sorunuysa (ekran/tkinter) rollback gerekmez: `sudo bf --tui` ile aynı akış yürütülür.
## 12. Kontrol Listesi ## 12. Kontrol Listesi
- [ ] Offline medya/git manifesti ve checksum'ları var. - [ ] Paket seti repoda: `provisioning/offline-repo/packages/` (1321 `.deb`, Git LFS ile takip ediliyor).
- [ ] Paket seti repoda: `provisioning/offline-repo/built` (`Packages`, `Packages.gz`, `packages.lock.tsv`, `SHA256SUMS`, `pool/`). - [ ] Bundle indeksi repoda: `provisioning/offline-repo/built/` (`Packages`, `Packages.gz`, `packages.lock.tsv`, `SHA256SUMS`, `pool/`).
- [ ] `repo-manifest.yaml` güncel; pin listesi ile paket havuzu uyuşuyor. - [ ] `repo-manifest.yaml` güncel; pin listesi ile paket havuzu uyuşuyor.
- [ ] Medyada/depoda cihaz kimliği, private key veya kalıcı token yok. - [ ] Depoda/repoda cihaz kimliği, private key veya kalıcı token yok.
- [ ] `python3-tk` ve bağımlılıkları `manifests/base-packages.txt` içinde pinli; `18-final-check` tkinter'ı denetliyor.
- [ ] Kuran kişinin komutu `--offline` bayrağı istemiyor; depo otomatik bulunuyor. - [ ] Kuran kişinin komutu `--offline` bayrağı istemiyor; depo otomatik bulunuyor.
- [ ] Grafik arayüz akışı (bayi no → Kurulum → "Bu cihazı kur") yazılı ve ekransız ortam için `sudo bf --tui` belgeli.
- [ ] İnternet kapalıyken kurulum tamamlanıyor; dış indirme çağrısı yok. - [ ] İnternet kapalıyken kurulum tamamlanıyor; dış indirme çağrısı yok.
- [ ] `PROVISIONED_OFFLINE` görünür ve READY kapısı kapalı. - [ ] `PROVISIONED_OFFLINE` görünür ve READY kapısı kapalı.
- [ ] Sonradan enrollment sahibi/kuyruğu kayıtlı. - [ ] Sonradan enrollment sahibi/kuyruğu kayıtlı.
## 13. Açık Sorular ## 13. Açık Sorular
- [ ] Offline APT snapshot yenileme sıklığı — sahibi: release yöneticisi. - [ ] Offline paket seti yenileme sıklığı ve Git LFS bant genişliği/kotaysa sahaya taşıma yöntemi — sahibi: release yöneticisi.
- [ ] Medyanın fiziksel zimmet ve imha süreci — sahibi: saha operasyonu. - [ ] Paketlerin sahaya taşınmasında git clone mu, hazır `/opt/blueforce/offline-repo` kopyası mı birincil — sahibi: saha operasyonu + 05 yazarı.
- [ ] Git checkout ile ISO/medya yolundan hangisinin birincil olduğu ve `--online` geliştirme yolunun hangi ortamlarda serbest olduğu — sahibi: yönetim + 26/33 yazarları. - [ ] `--online` geliştirme yolunun hangi ortamlarda serbest olduğu — sahibi: yönetim + 26/33 yazarları.
--- ---
@ -158,8 +200,9 @@ OFFLINE BLOCKED: <sebep>
```mermaid ```mermaid
flowchart LR flowchart LR
M["ISO/seed veya git checkout<br/>+ repodaki offline APT"] --> D{"Kurucu yerel depoyu arar<br/>BF_OFFLINE_REPO → repo/built → /opt/blueforce/offline-repo"} M["git checkout (paketler LFS ile)<br/>veya /opt/blueforce/offline-repo"] --> G["sudo bf → Kurulum → 'Bu cihazı kur'"]
D -->|"hiçbiri yok / eksik"| X["HATA: Yerel paket deposu yok<br/>ÇIKIŞ NEDENİ: bulunamadı · exit 1<br/>sistem değişmez"] G --> D{"Kurucu yerel depoyu arar<br/>BF_OFFLINE_REPO → repo/built → /opt/blueforce/offline-repo"}
D -->|"hiçbiri yok / eksik"| X["HATA: Yerel paket deposu yok<br/>aranan yollar + ÇIKIŞ NEDENİ · exit 1<br/>sistem değişmez"]
D -->|"eksiksiz aday bulundu"| V{"Doğrulama katmanı:<br/>dosya listesi, SHA256SUMS, kilit, pin ↔ pool"} D -->|"eksiksiz aday bulundu"| V{"Doğrulama katmanı:<br/>dosya listesi, SHA256SUMS, kilit, pin ↔ pool"}
V -->|"başarısız"| B["OFFLINE BLOCKED: sebep<br/>ÇIKIŞ NEDENİ: doğrulanamadı · exit 1"] V -->|"başarısız"| B["OFFLINE BLOCKED: sebep<br/>ÇIKIŞ NEDENİ: doğrulanamadı · exit 1"]
V -->|"geçti"| P["PROVISIONED_OFFLINE"] V -->|"geçti"| P["PROVISIONED_OFFLINE"]

File diff suppressed because it is too large Load diff

View file

@ -146,9 +146,13 @@ inspect_deb() {
printf ' dpkg-deb çıktısı: %s\n' "$raw" >&2 printf ' dpkg-deb çıktısı: %s\n' "$raw" >&2
return 1 return 1
fi fi
DEB_PKG="$(printf '%s\n' "$raw" | sed -n '1p')" # Only the value of each field belongs in these variables: dpkg-deb prints
DEB_VER="$(printf '%s\n' "$raw" | sed -n '2p')" # "<Field>: <value>", and keeping the label made every real package fail the
DEB_ARCH="$(printf '%s\n' "$raw" | sed -n '3p')" # name check ("Package: curl" is not a package name). The test suite stubs
# dpkg-deb, so this only shows up against real .deb files.
DEB_PKG="$(printf '%s\n' "$raw" | sed -n 's/^Package: //p')"
DEB_VER="$(printf '%s\n' "$raw" | sed -n 's/^Version: //p')"
DEB_ARCH="$(printf '%s\n' "$raw" | sed -n 's/^Architecture: //p')"
if [[ ! "$DEB_PKG" =~ $PACKAGE_NAME_RE ]]; then if [[ ! "$DEB_PKG" =~ $PACKAGE_NAME_RE ]]; then
printf 'HATA: paket adı geçersiz: "%s" (%s)\n' "$DEB_PKG" "$file" >&2 printf 'HATA: paket adı geçersiz: "%s" (%s)\n' "$DEB_PKG" "$file" >&2
@ -175,8 +179,12 @@ scan_existing() {
[[ -d "$PACKAGES_DIR" ]] || return 0 [[ -d "$PACKAGES_DIR" ]] || return 0
while IFS= read -r -d '' deb; do while IFS= read -r -d '' deb; do
sha="$(file_sha256 "$deb")" sha="$(file_sha256 "$deb")"
pkg="$(dpkg-deb -f "$deb" Package 2>/dev/null || true)" # Strip the "<Field>: " label dpkg-deb prints; the map keys are package and
ver="$(dpkg-deb -f "$deb" Version 2>/dev/null || true)" # version values, and a labelled key never matches the incoming candidate
# (so a version conflict went undetected and the same package was copied
# twice).
pkg="$(dpkg-deb -f "$deb" Package 2>/dev/null | sed -n 's/^Package: //p' || true)"
ver="$(dpkg-deb -f "$deb" Version 2>/dev/null | sed -n 's/^Version: //p' || true)"
if [[ -z "$pkg" || -z "$ver" ]]; then if [[ -z "$pkg" || -z "$ver" ]]; then
printf 'UYARI: packages/ içindeki okunamayan dosya yok sayıldı: %s\n' "${deb##*/}" >&2 printf 'UYARI: packages/ içindeki okunamayan dosya yok sayıldı: %s\n' "${deb##*/}" >&2
continue continue

View file

@ -284,10 +284,14 @@ fi
while IFS= read -r -d '' deb; do while IFS= read -r -d '' deb; do
base="$(basename "$deb")" base="$(basename "$deb")"
# dpkg-deb prints "<Field>: <value>"; only the value belongs in the pool key,
# so strip the label. Taking the whole line made every package fail the name
# check and the pool came out empty -- a stub dpkg-deb in the test suite hides
# this, so it only surfaces against real .deb files.
raw="$(dpkg-deb -f "$deb" Package Version Architecture 2>/dev/null || true)" raw="$(dpkg-deb -f "$deb" Package Version Architecture 2>/dev/null || true)"
package="$(printf '%s\n' "$raw" | sed -n '1p')" package="$(printf '%s\n' "$raw" | sed -n 's/^Package: //p')"
version="$(printf '%s\n' "$raw" | sed -n '2p')" version="$(printf '%s\n' "$raw" | sed -n 's/^Version: //p')"
arch="$(printf '%s\n' "$raw" | sed -n '3p')" arch="$(printf '%s\n' "$raw" | sed -n 's/^Architecture: //p')"
if [[ ! "$package" =~ $PACKAGE_NAME_RE || -z "$version" ]]; then if [[ ! "$package" =~ $PACKAGE_NAME_RE || -z "$version" ]]; then
POOL_ERRORS+=("$base: geçerli bir Debian paketi okunamadı; add-packages.sh ile ekleyin") POOL_ERRORS+=("$base: geçerli bir Debian paketi okunamadı; add-packages.sh ile ekleyin")
continue continue
@ -447,6 +451,11 @@ else
rm -rf "$OUTPUT" rm -rf "$OUTPUT"
mv -- "$STAGE" "$OUTPUT" mv -- "$STAGE" "$OUTPUT"
STAGE='' STAGE=''
# mktemp -d creates 0700, and this directory is delivered: the installer reads
# the bundle, an operator inspects it, and a build host may copy it as another
# user. A private mode makes a valid bundle look missing (the installer's
# readability probe sees an unreadable directory), so publish it explicitly.
chmod 0755 "$OUTPUT"
printf 'OK: depo yazıldı: %s\n' "$(rel_to_repo "$OUTPUT")" printf 'OK: depo yazıldı: %s\n' "$(rel_to_repo "$OUTPUT")"
fi fi
@ -540,8 +549,8 @@ printf ' git add -f %s\n' "$(rel_to_repo "$OUTPUT")"
printf ' git add %s\n' "$(rel_to_repo "$MANIFEST_OUT")" printf ' git add %s\n' "$(rel_to_repo "$MANIFEST_OUT")"
printf ' git commit -m "offline repo: %d paket, offline APT indeksi"\n' "$POOL_TOTAL" printf ' git commit -m "offline repo: %d paket, offline APT indeksi"\n' "$POOL_TOTAL"
if [[ ! -d "$SCRIPT_DIR/build" ]]; then if [[ ! -d "$SCRIPT_DIR/build" ]]; then
printf '\nNOT: provisioning/iso/build-blueforce-iso.sh offline depoyu "build" adıyla arıyor.\n' printf '\nNOT: provisioning/iso/build-blueforce-iso.sh varsayılan olarak bu dizini ("built/") arar.\n'
printf ' ISO ile aynı yolu kullanmak isterseniz: ./build-index.sh --output build\n' printf ' Eski "build/" adı yalnız uyarıyla kabul edilir; bu çıktı ISO ile uyumludur.\n'
fi fi
printf '\nDoğrulama (hedef cihazda /opt/blueforce/offline-repo):\n' printf '\nDoğrulama (hedef cihazda /opt/blueforce/offline-repo):\n'
printf ' cd /opt/blueforce/offline-repo && sha256sum -c SHA256SUMS\n' printf ' cd /opt/blueforce/offline-repo && sha256sum -c SHA256SUMS\n'

View file

@ -249,7 +249,10 @@ for list in "$SCRIPT_DIR/manifests/base-packages.txt" "$SCRIPT_DIR/manifests/rem
pins=$((pins + 1)) pins=$((pins + 1))
found=0 found=0
for deb in "${debs[@]}"; do for deb in "${debs[@]}"; do
[[ "$(dpkg-deb -f "$deb" Package)=$(dpkg-deb -f "$deb" Version)" == "$pin" ]] && found=1 && break # dpkg-deb prints "<Field>: <value>", so the label has to be stripped
# before the value can be compared with a "package=version" pin.
deb_pin="$(dpkg-deb -f "$deb" Package | sed -n 's/^Package: //p')=$(dpkg-deb -f "$deb" Version | sed -n 's/^Version: //p')"
[[ "$deb_pin" == "$pin" ]] && found=1 && break
done done
[[ "$found" -eq 1 ]] || { printf 'Pinned package not supplied locally: %s\n' "$pin" >&2; exit 1; } [[ "$found" -eq 1 ]] || { printf 'Pinned package not supplied locally: %s\n' "$pin" >&2; exit 1; }
done < "$list" done < "$list"

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -0,0 +1,3 @@
e608222b648a07f0223e0dde07b62f3e67e52620de5ee7ab786fc23093fe7cac Packages
9253dabe3c6e547176acebec84512024b392f55f5f556fb8b523f7553e8341c0 Packages.gz
d2846fefb7961ebed6a8156fa6a4c1abcc6b5d5aa4f9ba4afad01915b50a905d packages.lock.tsv

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -41,6 +41,23 @@ smartmontools=7.5-2
ufw=0.36.2-9build1 ufw=0.36.2-9build1
iptables=1.8.11-2ubuntu3 iptables=1.8.11-2ubuntu3
# --- operator GUI (bfos/ui_gui.py; module 18-final-check.sh) ---
# tkinter ships as its own package on Ubuntu, so a minimal image has Python but
# no GUI. Without it the operator application cannot open its window at all --
# and the fleet has no network to fetch it from.
python3-tk=3.14.3-0ubuntu2
# python3-tk's own dependency closure. Pinned explicitly because the installer
# resolves nothing from a remote source: whatever the pin does not name must
# already be on the image.
python3.14-tk=3.14.4-1ubuntu0.2
libtk8.6=8.6.17-1build1
libreadline8t64=8.3-4
readline-common=8.3-4
libffi8=3.5.2-4
libsqlite3-0=3.46.1-9ubuntu0.2
libbrotli1=1.2.0-3build1
openssl=3.5.5-1ubuntu3.5
# --- monitoring (module 12-monitoring.sh) --- # --- monitoring (module 12-monitoring.sh) ---
# The textfile collector ships inside this package; no extra dependency is needed. # The textfile collector ships inside this package; no extra dependency is needed.
prometheus-node-exporter=1.10.2-1 prometheus-node-exporter=1.10.2-1

View file

@ -22,6 +22,15 @@ base-packages.txt smartmontools scripts/diagnostics/bf-diagnostics SMART disk he
base-packages.txt ufw scripts/install/installer/modules/13-firewall.sh host firewall baseline base-packages.txt ufw scripts/install/installer/modules/13-firewall.sh host firewall baseline
base-packages.txt iptables scripts/install/installer/modules/13-firewall.sh rule backends used by the firewall module base-packages.txt iptables scripts/install/installer/modules/13-firewall.sh rule backends used by the firewall module
base-packages.txt prometheus-node-exporter scripts/install/installer/modules/12-monitoring.sh node exporter including the textfile collector base-packages.txt prometheus-node-exporter scripts/install/installer/modules/12-monitoring.sh node exporter including the textfile collector
base-packages.txt python3-tk scripts/install/installer/modules/18-final-check.sh tkinter is a separate package on Ubuntu; without it the operator GUI cannot start
base-packages.txt python3.14-tk scripts/install/installer/modules/18-final-check.sh python3-tk dependency closure; the installer resolves nothing from a remote source
base-packages.txt libtk8.6 scripts/install/installer/modules/18-final-check.sh python3-tk dependency closure; the installer resolves nothing from a remote source
base-packages.txt libreadline8t64 scripts/install/installer/modules/18-final-check.sh python3-tk dependency closure; the installer resolves nothing from a remote source
base-packages.txt readline-common scripts/install/installer/modules/18-final-check.sh python3-tk dependency closure; the installer resolves nothing from a remote source
base-packages.txt libffi8 scripts/install/installer/modules/18-final-check.sh python3-tk dependency closure; the installer resolves nothing from a remote source
base-packages.txt libsqlite3-0 scripts/install/installer/modules/18-final-check.sh python3-tk dependency closure; the installer resolves nothing from a remote source
base-packages.txt libbrotli1 scripts/install/installer/modules/18-final-check.sh python3-tk dependency closure; the installer resolves nothing from a remote source
base-packages.txt openssl scripts/install/installer/modules/18-final-check.sh python3-tk dependency closure; the installer resolves nothing from a remote source
remote-access.txt rustdesk scripts/install/installer/modules/09-rustdesk.sh pinned vendor .deb; not in the Ubuntu archive remote-access.txt rustdesk scripts/install/installer/modules/09-rustdesk.sh pinned vendor .deb; not in the Ubuntu archive
remote-access.txt libxdo3 scripts/install/installer/modules/09-rustdesk.sh rustdesk 1.4.9 Depends; pulled explicitly because the vendor .deb is installed with dpkg, not from an APT source remote-access.txt libxdo3 scripts/install/installer/modules/09-rustdesk.sh rustdesk 1.4.9 Depends; pulled explicitly because the vendor .deb is installed with dpkg, not from an APT source
remote-access.txt libva-x11-2 scripts/install/installer/modules/09-rustdesk.sh rustdesk 1.4.9 Depends; pulled explicitly because the vendor .deb is installed with dpkg, not from an APT source remote-access.txt libva-x11-2 scripts/install/installer/modules/09-rustdesk.sh rustdesk 1.4.9 Depends; pulled explicitly because the vendor .deb is installed with dpkg, not from an APT source

Can't render this file because it has a wrong number of fields in line 13.

Binary file not shown.

BIN
provisioning/offline-repo/packages/libffi8_3.5.2-4_amd64.deb (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -5,6 +5,23 @@ LOG_FILE="${LOG_FILE:-/var/log/blueforce-install.log}"; STATE_DIR="${STATE_DIR:-
[[ "${1:-}" == --check ]] && CHECK_MODE=1 [[ "${1:-}" == --check ]] && CHECK_MODE=1
OFFLINE="${BF_OFFLINE:-0}"; SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" OFFLINE="${BF_OFFLINE:-0}"; SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
log() { echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) [18-final-check] $1" | tee -a "$LOG_FILE"; } log() { echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) [18-final-check] $1" | tee -a "$LOG_FILE"; }
# Operator GUI runtime gate. The field application is the Python GUI (bfos), and
# tkinter is a separate package on Ubuntu: a minimal image has python3 but no
# window toolkit, so the operator would be left with no working entry point and
# no network to fix it. Checked here because every install path reaches this
# module, offline or not. When the GUI is unavailable the terminal console still
# works, so this reports the degradation instead of aborting the install.
if python3 -c 'import tkinter' >/dev/null 2>&1; then
log 'OK: operatör arayüzü (tkinter) hazır'
else
log 'WARN: tkinter yok; operatör arayüzü açılamaz. Terminal konsolu çalışır: sudo bf --tui'
log 'WARN: python3-tk paketi bu imajda eksik (offline depoda pinli: provisioning/offline-repo/manifests/base-packages.txt)'
fi
if [[ "$CHECK_MODE" == 1 ]]; then
command -v bf >/dev/null 2>&1 || log 'WARN(check): bf komutu PATH üzerinde yok; kurulum bf simgesini oluşturur'
fi
if [[ "$OFFLINE" == 1 ]]; then if [[ "$OFFLINE" == 1 ]]; then
# Only local gates are meaningful offline. No central peer, handshake, or READY claim. # Only local gates are meaningful offline. No central peer, handshake, or READY claim.
for g in 01-precheck 02-system 03-user 04-hostname 05-network 06-wireguard 07-ssh 08-rdp 10-docker 11-meg 12-monitoring 13-firewall 14-update-policy 15-logrotate 16-gui 17-healthcheck; do for g in 01-precheck 02-system 03-user 04-hostname 05-network 06-wireguard 07-ssh 08-rdp 10-docker 11-meg 12-monitoring 13-firewall 14-update-policy 15-logrotate 16-gui 17-healthcheck; do

View file

@ -146,7 +146,15 @@ for column in package version architecture sha256 source; do
done done
# 7. No secrets, keys, or device identities in the offline repository assets. # 7. No secrets, keys, or device identities in the offline repository assets.
if grep -RniE --exclude-dir=.git \ #
# The committed built/ index is excluded: it is byte-for-byte apt-ftparchive
# output over third-party packages, and upstream descriptions legitimately
# contain the trigger words as prose (polkit's description ends "...without
# being asked for their password:"), so scanning it reports the packages, not a
# leak. Nothing is hidden by this: any secret that reached a manifest or a
# README would still be caught below, and build-index.sh embeds no credentials
# of its own -- its records are package name/version/sha256/size/source.
if grep -RniE --exclude-dir=.git --exclude-dir=built \
'(BEGIN [A-Z ]*PRIVATE KEY|(password|passwd|token|api[_-]?key|secret)[[:space:]]*[:=]|AKIA[0-9A-Z]{16}|BF-[0-9]{8})' \ '(BEGIN [A-Z ]*PRIVATE KEY|(password|passwd|token|api[_-]?key|secret)[[:space:]]*[:=]|AKIA[0-9A-Z]{16}|BF-[0-9]{8})' \
"$OFFLINE_REPO"; then "$OFFLINE_REPO"; then
fail 'secret-like or device-specific value found in provisioning/offline-repo' fail 'secret-like or device-specific value found in provisioning/offline-repo'
@ -417,6 +425,11 @@ cat > "$STUB_BIN/dpkg-deb" <<'STUB'
#!/usr/bin/env bash #!/usr/bin/env bash
# Test stub: real dpkg-deb reads the control member; the identity is read from # Test stub: real dpkg-deb reads the control member; the identity is read from
# the <package>_<version>_<arch>.deb file name instead. # the <package>_<version>_<arch>.deb file name instead.
#
# The output carries the "<Field>: <value>" labels exactly as dpkg-deb prints
# them. Emitting bare values made every consumer look correct while the real
# tool was mis-parsed -- the pool came out empty against genuine .deb files
# because the callers kept the label and "Package: curl" is not a package name.
set -euo pipefail set -euo pipefail
[[ "${1:-}" == '-f' ]] || exit 1 [[ "${1:-}" == '-f' ]] || exit 1
file="${2:-}" file="${2:-}"
@ -431,9 +444,9 @@ version="${rest##*_}"; package="${rest%_*}"
[[ -n "$package" && -n "$version" && -n "$arch" ]] || exit 1 [[ -n "$package" && -n "$version" && -n "$arch" ]] || exit 1
for field in "$@"; do for field in "$@"; do
case "$field" in case "$field" in
Package) printf '%s\n' "$package" ;; Package) printf 'Package: %s\n' "$package" ;;
Version) printf '%s\n' "$version" ;; Version) printf 'Version: %s\n' "$version" ;;
Architecture) printf '%s\n' "$arch" ;; Architecture) printf 'Architecture: %s\n' "$arch" ;;
*) printf '\n' ;; *) printf '\n' ;;
esac esac
done done
@ -541,6 +554,11 @@ rc=0; out="$(bash "$BUILD_INDEX" --packages "$PKG_OUT" --output "$FOREIGN_OUT" -
# The real build. # The real build.
OUT="$TMP/owner-built" OUT="$TMP/owner-built"
# Snapshot the committed bundle directory before this build. The assertion below
# is about THIS build not writing there -- not about the directory being empty:
# a maintainer's machine may hold an untracked/committed real build, and an
# absolute "must be clean" check reported that legitimate bundle as test spill.
built_state_before="$(git -C "$REPO_ROOT" status --porcelain -- "provisioning/offline-repo/built" 2>/dev/null || true)"
rc=0; out="$(bash "$BUILD_INDEX" --packages "$PKG_OUT" --output "$OUT" --manifest-dir "$TMP/m" --manifest-out "$TMP/repo-manifest.yaml" 2>&1)" || rc=$? rc=0; out="$(bash "$BUILD_INDEX" --packages "$PKG_OUT" --output "$OUT" --manifest-dir "$TMP/m" --manifest-out "$TMP/repo-manifest.yaml" 2>&1)" || rc=$?
[[ "$rc" -eq 0 ]] && pass 'build-index.sh builds a bundle from the accepted pool' || { fail "build-index.sh build failed (exit $rc)"; printf '%s\n' "$out" >&2; } [[ "$rc" -eq 0 ]] && pass 'build-index.sh builds a bundle from the accepted pool' || { fail "build-index.sh build failed (exit $rc)"; printf '%s\n' "$out" >&2; }
for artifact in Packages Packages.gz packages.lock.tsv SHA256SUMS; do for artifact in Packages Packages.gz packages.lock.tsv SHA256SUMS; do
@ -609,7 +627,9 @@ record_after="$(sha256sum "$TMP/repo-manifest.yaml" | awk '{print $1}')"
grep -q 'DEĞİŞİKLİK YOK' <<<"$out" && pass 'build-index.sh reports the unchanged bundle instead of rewriting it' || fail 'build-index.sh must report an unchanged bundle' grep -q 'DEĞİŞİKLİK YOK' <<<"$out" && pass 'build-index.sh reports the unchanged bundle instead of rewriting it' || fail 'build-index.sh must report an unchanged bundle'
[[ "$record_before" == "$record_after" ]] && pass 'an unchanged rebuild leaves the repository record untouched' || fail 'an unchanged rebuild must not restamp the repository record' [[ "$record_before" == "$record_after" ]] && pass 'an unchanged rebuild leaves the repository record untouched' || fail 'an unchanged rebuild must not restamp the repository record'
git_status_note="$(git -C "$REPO_ROOT" status --porcelain -- "provisioning/offline-repo/built" 2>/dev/null || true)" git_status_note="$(git -C "$REPO_ROOT" status --porcelain -- "provisioning/offline-repo/built" 2>/dev/null || true)"
[[ -z "$git_status_note" ]] && pass 'the test build never touches the committed bundle directory' || fail "the test build wrote into provisioning/offline-repo/built: $git_status_note" [[ "$git_status_note" == "$built_state_before" ]] \
&& pass 'the test build never touches the committed bundle directory' \
|| fail "the test build changed provisioning/offline-repo/built: before=[$built_state_before] after=[$git_status_note]"
# A missing pin: report, then stop, without touching the previous bundle. # A missing pin: report, then stop, without touching the previous bundle.
rm -f "$PKG_OUT/rustdesk_9.9.9-lab-fixture_amd64.deb" rm -f "$PKG_OUT/rustdesk_9.9.9-lab-fixture_amd64.deb"

View file

@ -1011,12 +1011,41 @@ class TestCli(unittest.TestCase):
self.assertIn("yalnızca Türkçe", err.getvalue()) self.assertIn("yalnızca Türkçe", err.getvalue())
def test_invalid_repository_is_a_usage_error(self): def test_invalid_repository_is_a_usage_error(self):
# This asserts the terminal console's own repository resolution. The
# flag is explicit because --repo is valid for the GUI too: without
# --tui the router would hand the call to the window and the assertion
# would measure nothing (it also hangs a test runner with a display).
err = io.StringIO() err = io.StringIO()
with mock.patch("sys.stderr", err): with mock.patch("sys.stderr", err):
code = cli.main(["--repo", os.path.join(self.tmp.name, "not-a-repo")]) code = cli.main(["--tui", "--repo", os.path.join(self.tmp.name, "not-a-repo")])
self.assertEqual(config.EXIT_USAGE, code) self.assertEqual(config.EXIT_USAGE, code)
self.assertIn("Blueforce deposu", err.getvalue()) self.assertIn("Blueforce deposu", err.getvalue())
def test_gui_is_the_default_surface(self):
"""Bayraksız çağrı grafik arayüze gider (varsayılan davranış)."""
args = build_args([])
self.assertFalse(cli._wants_tui(args))
def test_tui_flag_forces_the_terminal_console(self):
self.assertTrue(cli._wants_tui(build_args(["--tui"])))
self.assertFalse(cli._wants_tui(build_args(["--gui"])))
def test_terminal_only_flags_stay_in_the_terminal(self):
"""Rapor ve terminal ayarı bayrakları GUI'ye kaptırılmaz."""
for argv in (["--list"], ["--list-targets"], ["--check"], ["--no-color"]):
self.assertTrue(cli._wants_tui(build_args(argv)), argv)
# Dil bayrağı yalnız varsayılandan FARKLI verildiğinde terminale çeker;
# --lang tr varsayılanın kendisidir ve GUI'yi engellemez.
self.assertTrue(cli._wants_tui(build_args(["--lang", "en"])))
self.assertFalse(cli._wants_tui(build_args(["--lang", "tr"])))
def test_gui_and_tui_together_are_refused(self):
err = io.StringIO()
with mock.patch("sys.stderr", err):
code = cli.main(["--gui", "--tui"])
self.assertEqual(config.EXIT_USAGE, code)
self.assertIn("birlikte kullanılamaz", err.getvalue())
def test_list_is_non_interactive_and_covers_21_operations(self): def test_list_is_non_interactive_and_covers_21_operations(self):
out = io.StringIO() out = io.StringIO()
with mock.patch("sys.stdout", out): with mock.patch("sys.stdout", out):

View file

@ -169,7 +169,9 @@ class TestHeadlessAndTkinter(StaticCase):
self.assertIsNotNone(message) self.assertIsNotNone(message)
self.assertIn("DISPLAY", message) self.assertIn("DISPLAY", message)
self.assertIn("SSH", message) self.assertIn("SSH", message)
self.assertIn("bf-menu", message) # Grafik arayüz açılamadığında önerilen yol aynı uygulamanın terminal
# konsoludur; bash menü artık yedek (bkz. TERMINAL_MENU_HINT).
self.assertIn("bf --tui", message)
self.assertIn("python3 -m bfos.ui_gui", message) self.assertIn("python3 -m bfos.ui_gui", message)
def test_display_and_wayland_are_accepted(self) -> None: def test_display_and_wayland_are_accepted(self) -> None:
@ -182,14 +184,14 @@ class TestHeadlessAndTkinter(StaticCase):
message = ui.tkinter_missing_message("No module named '_tkinter'") message = ui.tkinter_missing_message("No module named '_tkinter'")
self.assertIn("python3-tk", message) self.assertIn("python3-tk", message)
self.assertIn("apt", message) self.assertIn("apt", message)
self.assertIn("bf-menu", message) self.assertIn("bf --tui", message)
self.assertIn("_tkinter", message) self.assertIn("_tkinter", message)
def test_window_start_error_is_turkish(self) -> None: def test_window_start_error_is_turkish(self) -> None:
message = ui.window_start_error(RuntimeError("no display name and no $DISPLAY")) message = ui.window_start_error(RuntimeError("no display name and no $DISPLAY"))
self.assertIn("Pencere açılamadı", message) self.assertIn("Pencere açılamadı", message)
self.assertIn("ssh -X", message) self.assertIn("ssh -X", message)
self.assertIn("bf-menu", message) self.assertIn("bf --tui", message)
def test_import_does_not_create_a_window(self) -> None: def test_import_does_not_create_a_window(self) -> None:
# tkinter varsa bile modul import'u bir Tk koku olusturmamalidir: # tkinter varsa bile modul import'u bir Tk koku olusturmamalidir:
@ -222,7 +224,80 @@ class TestHeadlessAndTkinter(StaticCase):
contextlib.redirect_stderr(stderr): contextlib.redirect_stderr(stderr):
code = ui.main([]) code = ui.main([])
self.assertEqual(code, 3) self.assertEqual(code, 3)
self.assertIn("bf-menu", stderr.getvalue()) # Aynı uygulamanın terminal konsoluna yönlendirmeli (bf --tui).
self.assertIn("bf --tui", stderr.getvalue())
class TestRootGate(StaticCase):
"""Tam yetkili GUI: yazma işlemleri root ister, raporlar her zaman açık.
Katalogun kendi `destructive` işareti ölçüt alınır (bkz. ui_gui
_normalize_operation); bu yüzden testler katalogdan örnek seçer, sabit
isimlere bağlanmaz.
"""
def test_is_root_reflects_the_effective_uid(self) -> None:
original = os.geteuid
try:
os.geteuid = lambda: 0
self.assertTrue(ui.is_root())
self.assertTrue(ui.write_operations_enabled())
os.geteuid = lambda: 1000
self.assertFalse(ui.is_root())
self.assertFalse(ui.write_operations_enabled())
finally:
os.geteuid = original
def test_destructive_operations_require_root(self) -> None:
writes = [op for op in self.backend.list_operations() if op.destructive]
self.assertTrue(writes, "katalogda yazma işlemi yok; kapı test edilemez")
for op in writes:
self.assertTrue(op.needs_root,
f"{op.id} yıkıcı ama needs_root değil")
def test_read_only_operations_do_not_require_root(self) -> None:
reports = [op for op in self.backend.list_operations() if not op.destructive]
self.assertTrue(reports, "katalogda salt-okunur işlem yok")
frozen = [op.id for op in reports if op.needs_root]
self.assertEqual([], frozen,
"salt-okunur işlemler root istememeli: " + ", ".join(frozen))
def test_notice_names_the_command_to_get_privileges(self) -> None:
self.assertIn("sudo bf", ui.ROOT_REQUIRED_NOTICE)
self.assertIn("sudo bf", ui.NON_ROOT_DISABLED_HINT)
def test_run_plan_refuses_a_root_plan_without_root(self) -> None:
"""Son savunma hattı: çalıştırma tek noktadan geçer."""
plan = ui.CommandPlan(
commands=(("true",),),
destructive=True,
requires_root=True,
operation_id="install-local",
)
original = os.geteuid
try:
os.geteuid = lambda: 1000
result = ui.run_plan(plan)
finally:
os.geteuid = original
self.assertFalse(result.ok)
self.assertEqual(2, result.exit_code)
self.assertIn("yönetici yetkisi", result.output.lower())
def test_run_plan_allows_a_root_plan_when_root(self) -> None:
plan = ui.CommandPlan(
commands=(("true",),),
destructive=True,
requires_root=True,
operation_id="install-local",
)
original = os.geteuid
try:
os.geteuid = lambda: 0
result = ui.run_plan(plan)
finally:
os.geteuid = original
self.assertTrue(result.ok, result.output)
class TestSimpleModeLayout(StaticCase): class TestSimpleModeLayout(StaticCase):