Blueforce-Linux/docs/05-ONE-CLICK-INSTALLER.md
emirhanmamak-blueforce e28783e563 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.
2026-09-16 21:42:27 +03:00

244 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 05 — Tek-Tık Kurucu (One-Click Installer)
> 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`
- İ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
---
## 1. Amaç
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
- 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/installer/modules/`), imaj üretim yöntemi (04), filo-side yapılandırma (09), arayüzün tüm ekranları (33).
## 3. Kararlar
```text
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 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: 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ı. 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.
LİSANS: Yok (şirket içi script + standart kütüphane ile yazılmış Python arayüz).
```
```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. 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). 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. `--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.
MALİYET: Ücretsiz.
LİSANS: Yok.
```
## 4. Neden Bu Karar?
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
| 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 |
| Non-idempotent tek script | Daha basit kod | Kesintide temiz-kurulum zorunluluğu | Elendi |
| İnteraktif sihirbaz (çok sorulu) | Esnek | Teknisyen hatası, otomasyona kapalı | Elendi |
## 6. Avantajlar
- Tek giriş noktası (`sudo bf`) = eğitim maliyeti minimum, runbook tek sayfa.
- Idempotency = kesinti korkusu yok, "bir daha koş" her sorunun ilk cevabı.
- 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
- 18 modülün her biri için idempotency testi yazma maliyeti.
- 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
| Risk | Olasılık | Etki | Azaltma |
|---|---|---|---|
| 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ı (`^[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 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ı
### 1. Giriş noktası — grafik operatör uygulaması (ana yol)
```bash
sudo bf # grafik arayüz (bfos paketi, bfos/ui_gui.py, Tkinter)
```
Akış:
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` | Root, Ubuntu sürümü, RAM/disk ön kontrolü; ağ yalnız bilgi (kapı değil) |
| 02 | `02-system` | Temel APT kaynakları, `unattended-upgrades` kapatma, saat dilimi (K-11) |
| 03 | `03-user` | `blueforce` bakım kullanıcısı, kısıtlı sudoers, zorunlu yönetici anahtarı (K-13) |
| 04 | `04-hostname` | Doğrulanmış bayi no'dan hostname `bf-<no>` (K-12) |
| 05 | `05-network` | Ağ raporu: yerel bağlantı, rota/DNS (DNS zorunlu tutulmaz — offline kurulum) |
| 06 | `06-wireguard` | Cihaz anahtar çifti; `wg0.conf` enrollment'ın işidir (K-05/K-17) |
| 07 | `07-ssh` | Yönetici anahtarı doğrulandıktan **sonra** sshd sertleştirme |
| 08 | `08-rdp` | xRDP + GNOME oturumu, terminale boot varsayılanı, multi-user (K-03/K-22) |
| 09 | `09-rustdesk` | Yalnız pinli/sağlanan paketten RustDesk istemci (K-04) |
| 10 | `10-docker` | Docker CE + rotasyonlu `daemon.json` + enable (K-10) |
| 11 | `11-meg` | MEG yer tutucusu: vendor `.deb` varsa kurar, yoksa SKIP + kabul listesi |
| 12 | `12-monitoring` | Node Exporter + textfile collector dizini (K-07) |
| 13 | `13-firewall` | UFW deny-in tabanı + Docker yayın-portu politikasının kalıcılığı (K-05/K-10) |
| 14 | `14-update-policy` | APT hold'ları + otomatik update zamanlayıcılarını kapatma (K-11) |
| 15 | `15-logrotate` | journald tavanları + uygulama logrotate politikası (K-14) |
| 16 | `16-gui` | GNOME minimal + bakım araçları; terminale boot varsayılanı (K-03) |
| 17 | `17-healthcheck` | Teşhis ve enrollment komutlarını kurar; enrollment'ı çalıştırmaz |
| 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)
- 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ı).
- 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.
### 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ı
| Test | Beklenen sonuç | Ortam |
|---|---|---|
| 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) |
| 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, başarı damgası yok | LAB(2) |
| `--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
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, hostname modülü üzerine yazar).
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
- [ ] 18 modül dosyası `scripts/install/installer/modules/` altında numaralı ve çalışır durumda.
- [ ] 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}$`).
- [ ] `18-final-check` K-10/K-11 kapılarını ve tkinter denetimini içeriyor.
- [ ] 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
- [ ] 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ı).
- [ ] Arayüzün grafik ekran gerektirmeyen cihazlarda varsayılan modu (`--tui` mi, grafik mi) — saha ölçümü ile karar (sahibi: 33 + saha operasyonu).
---
## Ek: Kurulum Akışı (grafik arayüz)
```mermaid
flowchart TB
CMD["sudo bf<br/>(grafik operatör uygulaması)"] --> EKRAN{"Grafik ekran +<br/>python3-tk var mı?"}
EKRAN -->|Hayır| TUI["Türkçe yönlendirme + exit 3<br/>sudo bf --tui"]
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"]
M18 -->|Hepsi OK| OFFLINE["PROVISIONED_OFFLINE"]
M18 -->|FAIL| LOG["modül nosu + log yolu<br/>--from / --resume ile devam"]
OFFLINE --> ENR["Token → ENROLLED"]
ENR --> READY["Merkez kanalları doğrulandı → READY"]
```