Files
AWatch-rus/docs/runbook.md
T

724 lines
28 KiB
Markdown
Executable File
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.
# Runbook
## Быстрый health-check
### Proxmox web gateway
Если на host `<GATEWAY_HOST>` развёрнут `nginx` gateway, базовые проверки такие:
```sh
systemctl status nginx --no-pager
nginx -t
curl -I -sS -H 'Host: <PUBLIC_GATEWAY_FQDN>' http://127.0.0.1/
curl -k -fsS -H 'Host: <PUBLIC_GATEWAY_FQDN>' https://127.0.0.1/healthz
curl -k -I -sS -H 'Host: <PUBLIC_GATEWAY_FQDN>' https://127.0.0.1/ | head
curl -k -u "$(awk -F= '/^user=/{u=$2}/^password=/{p=$2}END{print u\":\"p}' <GATEWAY_CREDENTIALS_FILE>)" \
-H 'Host: <PUBLIC_GATEWAY_FQDN>' -fsS https://127.0.0.1/ | grep -F '<PUBLIC_GATEWAY_FQDN>'
```
Playbook для повторного rollout:
```sh
ANSIBLE_HOST_KEY_CHECKING=False \
ansible-playbook -i ansible/inventory.ini ansible/deploy_proxmox_web_gateway.yml
```
### Public gateway через pfSense
Публичная схема:
```text
Internet -> <PUBLIC_GATEWAY_FQDN> -> pfSense WAN <WAN_IP> -> NAT 80/443 -> nginx <GATEWAY_HOST>
```
Нормальное состояние:
- `https://<PUBLIC_GATEWAY_FQDN>/healthz` -> `200 ok` без auth;
- `https://<PUBLIC_GATEWAY_FQDN>/` без auth -> `401`;
- `http://<PUBLIC_GATEWAY_FQDN>/healthz` -> `301` на HTTPS;
- после Basic Auth:
- `/` -> gateway index;
- `/r/file1c/brief` -> 1C brief;
- `/r/grafana/api/health` -> Grafana health;
- `/r/aw/api/0/info` -> AW server info.
Gateway credential хранится только на `<GATEWAY_HOST>`:
```sh
sudo cat <GATEWAY_CREDENTIALS_FILE>
```
pfSense NAT backup перед автоматической правкой:
```sh
ls -1t /opt/infra-admin/backups/pfsense-gateway-nat-*.json | head
```
Проверка pfSense REST/API и pending firewall changes:
```sh
set -a
. <OPERATOR_HOME>/.config/tsj-bot/pfsense.env.readonly
set +a
curl -ksS -H "X-API-Key: $PFSENSE_API_KEY" "$PFSENSE_URL/api/v2/firewall/apply"
```
NAT должен содержать `WAN tcp 443 -> <GATEWAY_HOST>:443` и `WAN tcp 80 -> <GATEWAY_HOST>:80`.
WAN rules должны содержать pass на `<GATEWAY_HOST>:80` и `<GATEWAY_HOST>:443`.
### На Proxmox
```sh
pct status <CT_ID>
pct config <CT_ID>
pct exec <CT_ID> -- systemctl is-active activitywatch-server.service
pct exec <CT_ID> -- curl -fsS http://127.0.0.1:5600/api/0/info
```
### На host-based инсталляции
Для подтвержденного размещения на `<GATEWAY_HOST>`:
```sh
ps -ef | grep -E 'aw-server-rust|pfsense-aw-poller' | grep -v grep
curl -fsS http://127.0.0.1:5600/api/0/info
ss -ltnp | grep 5600
```
Ожидаемо должны быть видны:
- `aw-server-rust` с `--webpath /opt/aw-webui-ru`;
- `pfsense-aw-poller.py --config /etc/aw-pfsense/poller.json`.
### Внутри CT
```sh
systemctl status activitywatch-server.service --no-pager
journalctl -u activitywatch-server.service -n 100 --no-pager
curl -fsS http://127.0.0.1:5600/api/0/info
ss -ltnp | grep 5600
```
### Расширенный DLP transport health-check
На AW-server:
```sh
/usr/local/bin/aw-health-check
/usr/local/bin/dlp-health-check --json
```
Что проверяет дополнительно:
- свежесть DLP bucket-ов (`aw-dlp-endpoint-signals_*`, `aw-file-operations_*`);
- наличие transport/self-test telemetry (`queueDepth`, `eventsEnqueued`, `eventsFlushed`, `sendFailures`) в endpoint self-test;
- последние значения transport-счетчиков по каждому `aw-dlp-endpoint-signals_*` bucket;
- runtime-срез `aw-file-operations_*`: последние `collector_health`, счетчики очереди/отправки, последние операции без полного пути;
- runtime-срез `aw-dlp-incidents_*`: по умолчанию только metadata/age без тяжёлого чтения events; при ручном `--incident-sample-limit > 0` — количество реальных incident events в sample, self-test count, severity/action/rule rollup и до 5 последних incidents с коротким `message_excerpt`;
- API-доступность базовых сервисов.
Интерпретация:
- `FAIL` — есть критичная проблема (service/API/stale transport);
- `WARN` — сигнал для оператора (например, bucket еще не активирован на хосте, `queueDepth > 100`, новый рост `sendFailuresDelta >= 1`, нет `collector_health` в sample), но без hard-fail.
- `sendFailures` — накопительный счётчик collector-а; основной health-check предупреждает только по delta относительно сохранённого baseline в `AW_DLP_HEALTH_STATE_DIR`, чтобы старые восстановленные ошибки не висели вечным warning.
Пороги можно переопределить без изменения collector-ов:
```sh
/usr/local/bin/dlp-health-check --json \
--endpoint-queue-warn-depth 100 \
--endpoint-send-failure-warn-count 1 \
--incident-sample-limit 0 \
--fileops-sample-limit 20 \
--fileops-queue-warn-depth 100 \
--fileops-send-failure-warn-count 1
```
Для разового разбора последних DLP incidents можно включить sampling вручную:
```sh
/usr/local/bin/dlp-health-check --json --incident-sample-limit 20
```
Если AW-server медленно отдаёт большой `aw-dlp-incidents_*` bucket, такой запуск может вернуть `incident-runtime: WARN` по timeout. Это не должно использоваться как hard-fail основного мониторинга.
## Проверка RU patch
```sh
grep -n 'aw-ru-patch\|aw-sw-cleanup' /opt/activitywatch/webui-ru/index.html
ls -l /opt/activitywatch/webui-ru/js/
```
## Telegram bot: DLP режим и форензика
Для оператора `DetMirAuto` каноническое поведение такое:
- Кнопка DLP должна явно показывать текущий режим и целевое действие:
- `DLP сейчас: наблюдение | включить блокировку`
- `DLP сейчас: блокировка | включить наблюдение`
- `DLP сейчас: смешанный | выровнять в блокировку`
- Slash-путь для проверки без переключения: `/dlp_mode`
- Slash-путь для переключения: `/dlp_mode_toggle`
Интерпретация:
- `наблюдение` = endpoint/email правила работают в monitor-поведении (`alert/log`), без жёсткого `block`;
- `блокировка` = endpoint/email правила переведены в `block`;
- `смешанный` = часть endpoint/email правил уже `block`, часть ещё monitor-like; нормальный следующий ход — выровнять policy.
Форензика:
- Кнопка в меню называется `Форензика Windows логов`;
- Это человеко-понятный операторский вход в bounded Hayabusa path;
- Slash-команда остаётся прежней: `/aw_dfir /path/to/package.zip HOST [CASE_ID] [MODE]`
Если Telegram показывает старую клавиатуру:
- Нажать `Статус` или `/start`;
- Бот обязан прислать свежую custom-keyboard с актуальным DLP label;
- Старая кнопка в чате может ещё приходить как текст, но бот должен её принять и после ответа вернуть свежую клавиатуру.
Проверить:
- есть `aw-ru-patch.js`;
- есть `aw-sw-cleanup.js`;
- `index.html` содержит оба include;
- `service-worker.js` заменён cleanup-версией.
## Рабочее время (worktime)
Если в деплое включен `aw_apply_worktime_settings: true`, playbook применяет базовую категоризацию (classes) и view `worktime`.
Контрольные AQL-шаблоны для расчета рабочего времени: `docs/worktime_aql_detmir.md`.
Период рабочего времени задаётся переменными:
- `aw_worktime_from` (например `00:00`)
- `aw_worktime_to` (например `17:00`)
Playbook вычисляет `durationDefault` автоматически (включая смены через полночь) и выставляет:
`/api/0/settings/startOfDay` и `/api/0/settings/durationDefault`.
### Management report API
На `:5610` сейчас есть два server-side отчёта:
- классический `RDP worktime`:
- `/reports/worktime/today`
- форматы: `json`, `csv`, `html`
- управленческий `management`:
- `/reports/worktime/management`
- форматы: `json`, `csv`, `html`
Управленческий отчёт дополнительно показывает:
- рабочее окно отдельно от календарной активности;
- очередь действий руководителя;
- тренд за несколько дней;
- свежесть источников данных;
- executive summary `Что делать сегодня`.
Практические проверки:
```sh
curl -fsS 'http://127.0.0.1:5610/reports/worktime/today?day=today' | jq '.[:5]'
curl -fsS 'http://127.0.0.1:5610/reports/worktime/management?day=today' | jq '.summary,.executive'
curl -fsS 'http://127.0.0.1:5610/reports/worktime/management?format=html&day=today' | head
curl -fsS 'http://127.0.0.1:5610/reports/worktime/management?day=today&owner=Сменный%20руководитель' | jq '.filters,.summary,.owner_rollups'
curl -fsS 'http://127.0.0.1:5610/reports/worktime/management?day=today&department=Операторы%201С' | jq '.filters,.summary,.department_rollups'
```
Важно:
- первый `management`-запрос после очистки cache может быть тяжёлым, потому что
API пересчитывает trend и source freshness;
- повторные запросы должны быть быстрыми за счёт cache в
`/var/lib/activitywatch/worktime-cache`;
- по умолчанию cache живёт `300` секунд и прогревается локально через
`aw-worktime-autoheal.service`, чтобы manager-страницы не висели на cold-start;
- default warm-path должен ходить в `format=json`, потому что этого достаточно
для наполнения cache и это заметно легче, чем cold HTML render;
- warm timeout по умолчанию `70` секунд и задаётся через
`AW_WORKTIME_MANAGEMENT_WARM_TIMEOUT_SECONDS`;
- management report поддерживает scope-фильтры `owner` и `department`; они
пересчитывают `summary`, `rows`, `actions` и rollups под выбранный срез, а не
просто прячут строки в HTML;
- `trend` в filtered-view сейчас сознательно отключён: это удерживает latency в
рабочем диапазоне и не заставляет сервер заново пересчитывать исторический
ряд на каждый менеджерский клик;
- alias-файл теперь может содержать не только `users`, но и `owners`;
блок `owners` нужен для manager-facing каталога ответственных:
`display_name`, `title`, `department`, `contact`, `escalation_to`, `notes`;
- alias-файл для нормализации сотрудников по умолчанию:
`/etc/activitywatch/worktime-manager-aliases.json`.
## Типовые инциденты
### Hayabusa: операторский сценарий по умолчанию
Текущий production-сценарий уже не требует ручного `accept/process-inbox`.
Нормальный путь для оператора:
1. На Windows-хосте запустить:
```powershell
powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-upload-hayabusa-to-aw-server.ps1 -HoursBack 6 -CaseId 30
```
2. Сервер `<AW_SERVER_HOST>` сам:
- примет `zip` и `.meta.json` в `/opt/activitywatch/aw-rus-ops/drop`;
- запустит `aw-hayabusa`;
- посчитает severity/score;
- создаст case при уровне от `medium`;
- отправит Telegram alert при уровне от `high`.
3. Проверить результат:
```bash
systemctl is-active aw-hayabusa-drop.path
systemctl is-failed aw-hayabusa-drop.service || true
aw-hayabusa doctor
aw-hayabusa inventory
cat /opt/hayabusa/state/latest-intake.json
journalctl -u aw-hayabusa-drop.service -n 80 --no-pager
curl -fsS http://127.0.0.1:5602/api/0/dlp/cases/30
```
Ожидаемо:
- `latest-intake.json` имеет `status=ok`;
- `drop` после обработки пустой;
- в case есть `forensics.hayabusa`;
- Telegram alert уже уходит в операторский чат.
Production scheduled task на `SHARKON2025`:
- `ActivityWatch Hayabusa Upload`
- principal `Администратор`, `LogonType=Interactive`, `RunLevel=Highest`
- период `6` часов, lookback `6` часов
- нормальный `LastTaskResult=0`
Если `LastTaskResult=3221225794` (`0xC0000142`) и в `C:\ProgramData\AWatch-rus\logs\hayabusa-upload.log` нет новой строки, скрипт не стартовал. На текущем RDP-хосте это воспроизводится даже минимальной SYSTEM-задачей с `powershell.exe`; пересоздать Hayabusa task как interactive/highest от `Администратор`, не от `SYSTEM`.
Если upload прошёл, но сервер не обработал пакет:
```bash
sudo systemctl reset-failed aw-hayabusa-drop.path aw-hayabusa-drop.service
sudo systemctl start aw-hayabusa-drop.path
sudo systemctl start aw-hayabusa-drop.service
find /opt/activitywatch/aw-rus-ops/drop -maxdepth 1 -type f -ls
find /opt/hayabusa/inbox/incoming -maxdepth 1 -type f -ls
```
`drop` и `incoming` после успешной обработки должны быть пустыми; latest intake должен указывать на последний пакет `SHARKON2025`.
### Hayabusa: manual fallback / production validation end-to-end
Цель: подтвердить один реальный путь
- Windows EVTX export
- перенос пакета на `<AW_SERVER_HOST>`
- intake через `aw-hayabusa`
- генерация отчёта
- привязка bounded metadata к операторскому follow-up
Минимальный production-proven сценарий:
Этот путь нужен только если:
- надо руками прогнать старый пакет;
- надо повторно разобрать archived zip;
- надо отладить сам `aw-hayabusa` без drop-автоматики.
1. На Windows-хосте сделать экспорт:
```powershell
powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-evtx-for-hayabusa.ps1 -DaysBack 1
```
2. Проверить, что пакет реально появился:
```powershell
Get-ChildItem 'C:\ProgramData\AWatch-rus\forensics\evtx-exports' |
Sort-Object LastWriteTime -Descending |
Select-Object -First 5 Name,Length,LastWriteTime
```
Ожидаемо должен появиться zip вида:
- `HOST-YYYYMMDD-HHMMSS.zip`
3. Перенести zip на `<AW_SERVER_HOST>` в операторскую рабочую зону.
4. На `AW-server` проверить раннер:
```bash
aw-hayabusa doctor
aw-hayabusa inventory
aw-hayabusa profiles
```
5. Принять пакет:
```bash
aw-hayabusa accept --package /path/to/HOST-YYYYMMDD-HHMMSS.zip --host HOST
```
6. Обработать inbox:
```bash
aw-hayabusa process-inbox --mode incident
```
7. Проверить результат:
```bash
aw-hayabusa inventory
readlink -f /opt/hayabusa/state/latest-run
readlink -f /opt/hayabusa/state/latest-HOST
find /opt/hayabusa/reports/HOST -maxdepth 2 -type f | sort
```
Ожидаемо должны быть:
- `summary.html`
- `manifest.json`
- `run.log`
- `timeline.jsonl` или `timeline.csv`
- `logon-summary-*.csv`
8. Проверить traceability:
```bash
cat /opt/hayabusa/state/latest-intake.json
find /opt/hayabusa/archive/packages/HOST -maxdepth 1 -type f | sort
find /opt/hayabusa/archive/extracted/HOST -maxdepth 2 -type f | sort
```
Нужно зафиксировать:
- `host`
- `intake_id`
- `sha256`
- `package_path`
- `report_dir`
- `status`
9. Для AW-rus/operator follow-up заносить только bounded metadata:
- `tool=hayabusa`
- `host`
- `mode=incident`
- `status`
- `intake_id`
- `package_path`
- `sha256`
- `report_dir`
- `summary_html`
- `timeline_path`
- `manifest_path`
Не заносить в case / comments:
- сырые EVTX
- полный Sigma output
- полный timeline body
10. Если прогона не получилось, записать tuning backlog:
- пустой/битый zip
- нет EVTX в payload
- слабый audit scope на Windows
- шумный или слишком тяжёлый результат
- неочевидная трассировка от пакета к отчёту
Acceptance для этого сценария:
- есть хотя бы один реальный zip-пакет;
- `aw-hayabusa` провёл intake и analysis без ручной импровизации;
- артефакты трассируются от `HOST` до `report_dir`;
- follow-up не тащит сырые forensic данные в обычные AW buckets.
Example regression proof template:
- `host=HOST-EXAMPLE`
- `case_id=CASE_ID_EXAMPLE`
- `intake_id=INTAKE_ID_EXAMPLE`
- `sha256=SHA256_EXAMPLE`
- `report_dir=/opt/hayabusa/reports/HOST-EXAMPLE/REPORT_RUN_ID_EXAMPLE`
- `latest-intake.json` status: `ok`
- AW-rus case linkage stored under `forensics.hayabusa`
Типовые проблемы, найденные при validation:
- Windows zip с backslash path separators давал `unzip` warning rc=1; wrapper не должен валить intake на таком предупреждении.
- timeline режимы должны использовать `rules/config`, а не корень rules directory.
После live proof держать как regression checks:
```bash
aw-hayabusa doctor
aw-hayabusa inventory
cat /opt/hayabusa/state/latest-intake.json
readlink -f /opt/hayabusa/state/latest-run
```
### DLP не виден в вебе
Быстрый чек сервера:
```sh
curl -fsS http://127.0.0.1:5600/api/0/buckets | jq -r 'keys[] | select(test("^aw-dlp-"))'
curl -fsS http://127.0.0.1:5600/api/0/buckets/aw-dlp-incidents_HOST-EXAMPLE | jq '{end:.metadata.end}'
```
Контролируемый тест ingest:
```sh
TS=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
PAYLOAD=$(jq -nc --arg ts "$TS" '{timestamp:$ts,duration:0,data:{ruleId:"selftest-dlp-incident",action:"alert",severity:"low",message:"Self-test DLP incident from runbook",signalType:"self_test",username:"AUTOTEST",sessionId:0,hostname:"HOST-EXAMPLE",source:"self-test"}}')
curl -fsS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-dlp-incidents_HOST-EXAMPLE/heartbeat?pulsetime=60' -H 'Content-Type: application/json' --data "$PAYLOAD"
curl -fsS 'http://127.0.0.1:5600/api/0/buckets/aw-dlp-incidents_HOST-EXAMPLE/events?limit=5' | jq '.[0].data'
```
Если API видит событие, а bucket-страница в UI показывает старые `First/last event`, нажать `Обновить` на странице bucket и раскрыть `Events`.
### Проверка WAL failover (Windows collectors)
Цель: подтвердить, что при недоступности AW API события не теряются, а буферизуются в локальной очереди и автоматически отправляются после восстановления связи.
На RDP-хосте (`<WINDOWS_HOST>`) в PowerShell под администратором:
1) Проверить/обнулить очереди:
```powershell
$q1 = 'C:\ProgramData\AWatch-rus\file-operations-queue*.jsonl'
$q2 = 'C:\ProgramData\AWatch-rus\dlp-endpoint-signals-queue*.jsonl'
Get-Item $q1,$q2 | Select Name,Length,LastWriteTime
```
2) Временно заблокировать исходящий доступ на AW API (`:5600`):
```powershell
New-NetFirewallRule -DisplayName 'AWatch WAL Test Block 5600' -Direction Outbound -Action Block -Protocol TCP -RemotePort 5600 -ErrorAction SilentlyContinue
Enable-NetFirewallRule -DisplayName 'AWatch WAL Test Block 5600'
```
3) Сгенерировать тест-событие `file-operations`:
```powershell
$p = Join-Path $env:USERPROFILE 'Desktop\aw_wal_test.txt'
Set-Content -LiteralPath $p -Value ('wal-test ' + (Get-Date -Format o))
Start-Sleep -Seconds 5
```
4) Убедиться, что очередь выросла:
```powershell
Get-Item $q1,$q2 | Select Name,Length,LastWriteTime
```
5) Снять блокировку и дождаться flush:
```powershell
Disable-NetFirewallRule -DisplayName 'AWatch WAL Test Block 5600'
Start-Sleep -Seconds 20
Get-Item $q1,$q2 | Select Name,Length,LastWriteTime
```
Ожидаемо:
- на шаге 4 длина как минимум одного queue-файла увеличивается;
- на шаге 5 очередь уменьшается (в идеале до `0` или близко к фоновому уровню).
6) Проверка на AW server:
```sh
curl -fsS 'http://<AW_SERVER_HOST>:5600/api/0/buckets/aw-file-operations_<AW_SERVER_HOST>/events?limit=10' | jq '.[0].data'
```
После теста удалить правило:
```powershell
Remove-NetFirewallRule -DisplayName 'AWatch WAL Test Block 5600' -ErrorAction SilentlyContinue
```
### PowerShell MCP на AWatch-rus Windows host
Для `AWatch-rus` Windows host `<WINDOWS_HOST>` интерактивный путь из Linux/Codex закреплён через `SSH`, а не через `WSMan`.
Быстрый вход:
```bash
cd <PROJECT_ROOT>
bash scripts/install_detmir_powershell_mcp.sh
```
После переоткрытия `pwsh` или новой Codex-session:
```powershell
detmir-win-test
detmir-win-ps 'hostname; Get-Date -Format o'
detmir-win-shell
```
Канонический документ:
- `docs/POWERSHELL_MCP_REMOTE_RU.md`
Правило:
- `WinRM` использовать для `ansible aw_windows` и playbook-ов;
- `SSH` использовать для `powershell-windows` MCP, operator PowerShell и ad-hoc remote execution.
### У пользователей всплывает окно PowerShell
Ожидаемое поведение collector-ов: запуск hidden (`-WindowStyle Hidden`).
Проверка на Windows хосте:
```powershell
Get-CimInstance Win32_Process |
Where-Object { $_.CommandLine -and ($_.CommandLine -match 'browser-domains-native-collector.ps1' -or $_.CommandLine -match 'dlp-endpoint-signals-collector.ps1') } |
Select-Object SessionId, ProcessId, CommandLine
```
Если нужно экстренно убрать снимки инцидентов:
1. Поставить `incidentCapture.screenshotEnabled = false` в `deployment-config.json` (для каждого StateRoot).
2. Запустить `Start-ScheduledTask -TaskName 'ActivityWatch Recovery'`.
### Windows host: `Активное время = 0s`, хотя `window`-события есть
Симптом:
- в Activity view за день видно `Worktime = 0s`;
- `Top Window Titles / Top Categories / Category Tree` пустые;
- при этом bucket `aw-watcher-window_HOST-EXAMPLE` содержит свежие события.
Подтвержденная причина:
- watcher `afk` "залип" в `status=afk` без `not-afk`;
- из-за этого дневная сводка не считает подтвержденную активность.
Быстрый recovery (с Linux admin host):
1. Проверить учетку входа. Рабочую учетную запись задавать как параметр экземпляра: `HOST-EXAMPLE\WINDOWS_USER_EXAMPLE`.
2. Поднять remote execution через `wmiexec.py` с auth-file:
```sh
cat > /tmp/detmir-windows.auth << 'EOF'
username = WINDOWS_USER_EXAMPLE
password = <PASSWORD>
domain = HOST-EXAMPLE
EOF
chmod 600 /tmp/detmir-windows.auth
```
3. Запустить recovery task:
```sh
wmiexec.py -nooutput -A /tmp/detmir-windows.auth <WINDOWS_HOST> \
"powershell -NoProfile -Command \"Start-ScheduledTask -TaskName 'ActivityWatch Recovery'\""
```
4. Запустить все launch tasks:
```sh
wmiexec.py -nooutput -A /tmp/detmir-windows.auth <WINDOWS_HOST> \
"powershell -NoProfile -Command \"Get-ScheduledTask | Where-Object TaskName -like 'ActivityWatch Launch *' | ForEach-Object { Start-ScheduledTask -TaskName \$_.TaskName }\""
```
5. Подождать 10-20 секунд и проверить API на AW server (`<AW_SERVER_HOST>:5600`):
```sh
curl -fsS 'http://<AW_SERVER_HOST>:5600/api/0/buckets/aw-watcher-afk_HOST-EXAMPLE/events?limit=30' \
| jq '{latest:.[0].timestamp, statuses:(group_by(.data.status)|map({status:.[0].data.status,count:length}))}'
```
Ожидаемо после фикса:
- в свежих AFK-событиях появляется `status=not-afk`;
- `aw-watcher-window_HOST-EXAMPLE` продолжает обновляться;
- после обновления страницы UI дневная сводка перестает быть `0s`.
### Сервис не стартует
```sh
systemctl cat activitywatch-server.service
cat /etc/activitywatch/aw-server.env
journalctl -xeu activitywatch-server.service --no-pager
```
Частые причины:
- битый URL релиза;
- неполная распаковка архива;
- занят порт;
- не созданы каталоги или пользователь;
- ошибка в env-файле.
### API отвечает, но UI без русификации
Проверить:
- патч реально вставлен в `index.html`;
- browser cache/service worker очищен;
- reverse proxy не отдаёт старую статику;
- сервис был перезапущен после правок.
Повторное применение:
```sh
bash <CT_BOOTSTRAP_DIR>/apply_webui_ru_patch.sh
systemctl restart activitywatch-server.service
```
### После обновления UI сломался патч
- сравнить `index.html` с backup;
- заново применить patch script;
- проверить словарь в `aw-ru-patch.js`;
- при необходимости откатить только Web UI override.
## Перед любыми изменениями
1. Сделать snapshot или `vzdump`.
2. Сохранить текущий `/etc/activitywatch/aw-server.env`.
3. Сохранить текущий `index.html`.
4. Зафиксировать текущую версию `aw-server-rust`.
## Критерии готовности
- systemd unit стартует без ручного вмешательства;
- API `/api/0/info` отвечает локально;
- UI открывается;
- русификация присутствует;
- rollback-путь понятен оператору.
## Аудит CryptoPro и готовности подписантов
Для повторяемой проверки сертификатов подписантов и встроенных лицензий CryptoPro:
```bash
cd <PROJECT_ROOT>/ansible
ansible-playbook -i inventory.ini audit_cryptopro_windows.yml
```
Итоговый JSON-отчёт сохраняется локально в:
```text
/tmp/aw-rus-cryptopro-audit-<user>/rdp-prod-cryptopro-audit.json
```
Отчёт содержит матрицу по профилям:
- `requestedUser`
- `profileUser`
- `thumbprint`
- `subject`
- `hasPrivateKey`
- `embeddedLicenseOk`
- `embeddedLicenseStatus`
- `container` если виден
- `actionNeeded`