# Windows-порт ArgusNOC probe — для розробників

Спільний код з Linux: `probe/{main,analyzer,queue,api,config,enroll}.py`, `agentd/main.py`. Платформа обирається в `probe/plat.py`
(`sys.platform`): виконавці `runners/win_ping.py` + `runners/win_mtr.py`, `win/sysinfo_win.py`, `win/firewall.py`, `win/evtlog.py`.

## Файли

| файл | що |
|---|---|
| `win/svcbase.py` | `ArgusServiceBase(win32serviceutil.ServiceFramework)`: хостинг під `python.exe <script>` без pythonservice.exe; STOP/SHUTDOWN/PRESHUTDOWN → `stop_event`; ненульовий код → `SERVICE_STOPPED` з `ERROR_SERVICE_SPECIFIC_ERROR` (тригер SCM recovery); краш → Event Log 102 |
| `win/service_probe.py` | служба **ArgusProbe** «ArgusNOC Probe»: `wait_for_network` (default route, до 120 с) → `Probe()`; команда `restart` від центру (exit_code 1) перезапускає пробу всередині процесу |
| `win/service_agentd.py` | служба **ArgusAgentd** «ArgusNOC Agent»: `agentd.main.run(stop_event)` |
| `win/svcctl.py` | SCM через pywin32: `status/start_type/start/stop/restart` (використовує watchdog agentd) |
| `probe/runners/win_ping.py` | ICMP-движок: один raw-сокет на сімейство (`bind` на ANY обов'язковий), приймання в потоці, RTT з `perf_counter`, демультиплексація по (ident, seq) для Echo Reply і Time Exceeded/Unreachable; бурсти по 10 пакетів між `sleep` (таймер Windows ≈15 мс) ≈500 pps. API як у `ping.py`: `ping_batches()`, `ping()`, `close()` |
| `probe/runners/win_mtr.py` | traceroute на тому ж сокеті: TTL 1..30 сходинками по 8, далі до цілі; `count` пакетів на хоп; вихід як `mtr --json`; ASN/route-логіка реекспортується з `runners/mtr.py` |
| `win/firewall.py` | `netsh advfirewall`, лише правила `ArgusNOC:*`; кеш `data\firewall_applied.json`; CLI `python -m win.firewall init|apply '<json>'|remove|show` |
| `win/sysinfo_win.py` | `public_ips`, `has_global_ipv6`, `wait_for_network`, `os_name` (реєстр), `uptime/mem/cpu_load` (ctypes), `health`, `diag` (одна PowerShell-команда → JSON: interfaces/routes/dns/firewall/services/w32tm/defender) |
| `win/evtlog.py` | Event Log, джерело `ArgusNOC` (реєструє інсталятор) |
| `agentd/win_update.py` | скачати реліз (`?os=windows`, sha256 + ed25519) → `data\releases\` → відв'язаний `powershell -File win\update.ps1 -Zip … -FromAgent` |
| `probe/enroll.py` | самореєстрація (`--center --org --name --json`, `--unenroll`) — той самий модуль викликатиме і Linux `install.sh` |
| `win/common.ps1` | спільні функції PS: служби, ACL, robocopy-копіювання, `Install-CodeTree`, `Set-ArgusServices`, `Wait-ProbeRegistered` |
| `win/install.ps1` | bootstrap (`irm … | iex`): скачує zip → dot-source `win\common.ps1` з релізу → enroll → фаєрвол → служби |
| `win/update.ps1` | заміна коду з `.prev`-відкатом; `data\update.lock` на час роботи (watchdog і updater agentd не втручаються) |
| `win/uninstall.ps1` | служби, правила, каталоги; `-KeepData`, `-Notify` |
| `win/build_release.py` / `.ps1` | збірка `argus-probe_v<ver>_windows.zip` |
| `win/installer.nsi` + `win/build_installer.py` / `.ps1` | `argus-probe-setup_v<ver>.exe` (NSIS 3, Unicode, українська): сторінка параметрів, `/S` з ключами, запис у «Програми та компоненти», `uninstall.exe`, ярлик і автозапуск трею; збирається і на Linux (`apt install nsis`) |
| `win/preinstall.ps1` / `postinstall.ps1` | викликає setup.exe: зупинити стару версію (служби, трей) / `Complete-ArgusInstall` (enroll, фаєрвол, служби) → `install_result.txt` для фінальної сторінки; код 1 → setup.exe відкочує інсталяцію |
| `win/tray.py` | трей-іконка (pywin32 `Shell_NotifyIcon`, без сторонніх пакетів): стан із SCM + `public\status.json`, старт/стоп через `pythonw -m win.svcctl` з UAC, оновлення через `update.ps1 -Version` з UAC; побачивши `public\update.lock` — виходить і повертається через `cmd.exe`, коли лок зникне (інакше тримала б `python\`); один екземпляр (mutex; `--debug` його обходить і пише в консоль), лог `%LOCALAPPDATA%\ArgusNOC\tray.log`. Win11 ховає нові іконки в overflow (^) — `IsPromoted` намагається винести на панель |
| `win/tray_start.ps1` | запуск трею без підвищення (explorer.exe + ярлик), перевірка процесу, хвіст `tray.log` при невдачі — викликає setup.exe і кнопка фінальної сторінки |
| `win/tray_window.py` | вікно керування (tkinter, окремий процес): стан, старт/стоп/перезапуск/оновлення (UAC), перегляд probe.log/agentd.log/служби/Event Log, копіювання діагностики; відкривається подвійним кліком на іконці |
| `win/make_icons.py` | генерує `argus*.ico` (Pillow, лише при зміні дизайну) |

## Шляхи

`probe/config.py`: `BASE` = каталог коду (`Program Files\ArgusNOC`, замінюється при оновленні), `ROOT` = дані (`ProgramData\ArgusNOC`).
`ROOT\public\` (ACL: Users R) — `status.json` (пише проба кожен heartbeat: версія, ім'я/id, стан на центрі, heartbeat_ok, checks, nat, has_ipv6,
desired_version), `update.lock` (копія лока для трею), `last_update.json`. Решта ROOT — лише SYSTEM/Administrators.
`config.yaml` — у BASE, `secrets.env` — у ROOT. `ARGUS_ROOT=<dir>` перекриває ROOT (для тестів). На Linux ROOT = BASE (нічого не змінилось).

## Збірка релізу і setup.exe

```powershell
.\win\build_release.ps1 [-EmbedVersion 3.12.10] [-EmbedZip <python-3.12.x-embed-amd64.zip>] [-Out dist]   # zip для автооновлення
.\win\build_installer.ps1 [-Zip dist\argus-probe_v0.2.16_windows.zip]                                       # setup.exe (потрібен NSIS)
# або на Linux: python3 win/build_release.py && python3 win/build_installer.py --zip dist/argus-probe_v0.2.16_windows.zip
```

**Підпис (SmartScreen).** Непідписаний setup.exe дає «ОС Windows захистила цей ПК». `build_installer.ps1 -Sign '<команда>'`
передає команду в NSIS (`!uninstfinalize` + `!finalize`): підписуються і вбудований `uninstall.exe`, і сам `setup.exe`.
Приклад з OV-сертифікатом на USB-токені (signtool з Windows SDK): `signtool sign /fd SHA256 /tr http://timestamp.digicert.com
/td SHA256 /n "Назва організації"`; з Azure Artifact Signing — `signtool sign /fd SHA256 /tr … /td SHA256 /dlib
Azure.CodeSigning.Dlib.dll /dmdf metadata.json`. Timestamp обов'язковий (підпис живе після закінчення сертифіката).
Підпис не прибирає попередження одразу — SmartScreen накопичує репутацію видавця; головне — підписувати всі релізи одним сертифікатом.

setup.exe = той самий zip + NSIS-обгортка: копіює дерево у Program Files\ArgusNOC, викликає `win\postinstall.ps1`, пише
`HKLM\...\Uninstall\ArgusNOC` (DisplayName/Version/Icon/UninstallString/EstimatedSize), ярлик «ArgusNOC Tray» і `HKLM\...\Run`.
Оновлення поверх: `preinstall.ps1` зупиняє служби і трей, файли перезаписуються, токен і config.yaml зберігаються. Автооновлення
з центру і далі йде через zip + `update.ps1` — setup.exe потрібен лише для першої інсталяції (і як варіант ручного оновлення).

Рантайм — embeddable CPython x64 з python.org (типово) або `--standalone auto` (python-build-standalone від astral-sh з GitHub,
для CI без доступу до python.org; з нього прибираються test/tkinter/idle/pip). Обидва дають однаковий `python\` для проби —
відрізняється лише `STAMP`, тож перше автооновлення з одного рантайму на інший просто замінить `python\`.
Скрипт переписує `python312._pth` (`..` → каталог з probe/agentd/win; `Lib\site-packages`, `win32`,
`win32\lib`, `pythonwin`; `import site` вимкнено), ставить залежності `pip install --target … --platform win_amd64 --python-version 3.12
--only-binary=:all:` (крос-збірка з Linux працює), кладе `pywin32_system32\*.dll` поруч із `python.exe`, пише `python\STAMP`
(відбиток дерева — `update.ps1` замінює `python\` лише коли він змінився, і тільки тоді зупиняє ArgusAgentd), пакує zip з коренем
`argus-probe/` і друкує sha256. Публікація: `argusctl release publish --os windows dist\argus-probe_v0.2.16_windows.zip`.

## Тестування без інсталятора

```powershell
# від адміністратора, з каталогу релізу (розпакованого zip) або репозиторію з системним Python + pywin32:
$env:ARGUS_ROOT = 'C:\tmp\argus'                       # дані окремо
python -m probe.enroll --center https://api.argusnoc.net --json
python win\service_probe.py debug                       # проба у консолі (Ctrl+C = stop)
python win\service_agentd.py debug
python -m probe.main --diag                             # diag у stdout
python -m win.firewall show
Get-EventLog -LogName Application -Source ArgusNOC -Newest 20
```

`python win\service_probe.py install|remove|start|stop` — керування службою через pywin32 (інсталятор робить те саме через `New-Service`/`sc.exe`).

## Оновлення (§8) — послідовність

1. Проба отримує `desired_version` → пише `data\update_request.json`; agentd (`updater`, кожні 20 с) → `win_update.do_update`:
   скачати, перевірити sha256/підпис, `data\releases\argus-probe_v<ver>.zip`, запустити `update.ps1 -Zip … -FromAgent` як
   відв'язаний процес (DETACHED_PROCESS) і повернутись.
2. `update.ps1`: `update.lock` → розпакувати в `.new\` → перевірити дерево → стоп ArgusProbe (+ ArgusAgentd, якщо `STAMP` python\
   змінився) → бекап `data\` → `data_backup_<stamp>` (3 останні) → поточне у `.prev\` → копія нового → старт → свіжий
   `data\heartbeat` за 30 с → `data\update_result.json`, Event Log 300 → перезапуск ArgusAgentd на новий код.
   Інакше — відкат з `.prev\`, `data\update_failed.json {ts,error}` (потрапляє в наступний `diag`), Event Log 301.
   `finally` завжди знімає lock і піднімає служби.

## Що не протестовано на Windows (немає стенду)

Служби (SCM, PRESHUTDOWN, recovery), `netsh`, raw-сокети IPv6, PowerShell-скрипти (парсер PS7 без помилок, логіка — вичитка),
embeddable-запуск pywin32, трей (win32gui — лише статичний аналіз), сам setup.exe (зібраний makensis на Linux — не запускався).
Перевірено на Linux: ICMP-движок v4 (loopback, 300 адрес × 3 за 1.9 с), traceroute, повний цикл проби проти мок-центру
(enroll → pending → online → метрики → `restart`/`diag` → 401×3 → зупинка ≤1 с), крос-збірка релізу.
