docs(public): sanitize release materials

This commit is contained in:
igor04091968
2026-06-03 08:52:20 +03:00
parent 18653e1a2f
commit a1801fca5f
175 changed files with 1121 additions and 17893 deletions
+2
View File
@@ -39,3 +39,5 @@ data/
# Release assets kept outside git
/install-kit-awindows-*.zip
/install-kit-awindows-*.tar.gz
/install-kit-awindows-*/
/scripts/configure_pfsense_gateway_nat.py
@@ -1,51 +0,0 @@
# Phase 17 Summary: Production Validation
## Result
Phase 17 is closed.
## What was proven live
A real end-to-end forensic path was executed and closed on production infrastructure:
1. Windows EVTX package was exported on `SHARKON2025`.
2. The real zip package was transferred to `10.10.10.13`.
3. `aw-hayabusa accept` and `aw-hayabusa process-inbox --mode incident` were run through the standard wrapper.
4. Hayabusa generated a real report set with bounded traceability metadata.
5. The result was linked back into AW-rus case management as bounded `forensics.hayabusa` metadata.
## Live proof record
- host: `SHARKON2025`
- case id: `30`
- intake id: `20260521T125653Z_SHARKON2025-phase17-rerun3`
- package path: `/opt/hayabusa/archive/packages/SHARKON2025/20260521T125653Z_SHARKON2025-phase17-rerun3.zip`
- sha256: `e86b9abbfc1d706ac706c6c8a89509ab17023344c50880641e9175f73f1198d4`
- report dir: `/opt/hayabusa/reports/SHARKON2025/20260521T125654Z_incident_20260521T125653Z_SHARKON2025-phase17-rerun3`
- report artifacts:
- `summary.html`
- `manifest.json`
- `run.log`
- `timeline.jsonl`
- `logon-summary-successful.csv`
- `logon-summary-failed.csv`
## Production bugs found and fixed during validation
- `aw-hayabusa` treated `unzip` warning return code `1` as a hard failure for Windows-created zip archives that use backslashes as path separators.
- timeline modes were using the wrong Hayabusa config path; the wrapper must pass `rules/config`, not the rules root.
Both issues were fixed in `aw-server/hayabusa/aw-hayabusa.sh` and retested live against the same package.
## Why this closes the phase
- the path is no longer theoretical or docs-only; it was proven on a real Windows export package
- traceability from host to package to report directory is explicit
- AW-rus case linkage now stores bounded forensic metadata exactly as designed
- the remaining gaps are operational tuning items, not missing core implementation
## Tuning backlog after the live run
- keep at least one preserved sample EVTX zip for future regression reruns
- consider a self-check in `aw-hayabusa doctor` for `rules/config` completeness
- optionally persist a compact machine-readable proof manifest for future audits
+4 -49
View File
@@ -1,53 +1,8 @@
# Установка экземпляра для эксперта
Этот документ дает короткий воспроизводимый путь проверки экземпляра без
привязки к личному стенду разработчика.
Основной пошаговый документ находится здесь:
## 1. Подготовка
- [`docs/INSTALL_FOR_EXPERT_RU.md`](docs/INSTALL_FOR_EXPERT_RU.md)
1. Склонировать репозиторий.
2. Создать приватную конфигурацию:
```bash
cp private-config/deploy.env.example private-config/deploy.env
```
3. Создать локальный Ansible inventory на основе:
```bash
cp ansible/inventory.example.ini ansible/inventory.ini
```
4. Заполнить адреса, учетные данные и токены конкретного тестового стенда.
## 2. Сборка
```bash
cd adk-rust
cargo build --release --workspace
```
## 3. Проверки до установки
```bash
scripts/quality-gate.sh
ansible-playbook --syntax-check -i ansible/inventory.ini ansible/deploy_aw_server.yml
```
## 4. Установка
Базовый серверный путь описан в `docs/INSTALL_RU.md`. Конкретный playbook
выбирается по проверяемой схеме: серверный runtime, Windows collectors,
Grafana dashboards или портал оператора.
## 5. Smoke-проверка
После установки:
```bash
detmir-check
detmir-status
```
Ожидаемый результат: статус `OK`, отсутствуют критичные service failures и
stale/dead buckets для обязательных источников.
Файл в корне оставлен как совместимая ссылка для внешних проверок и старых
закладок.
+1 -1
View File
@@ -21,7 +21,7 @@
- `<AW_SERVER_HOST>` — основной `AW-rus` server, health, worktime/reporting, DLP server-side services, `Hayabusa` processing.
- `<GATEWAY_HOST>` — operator/gateway host, Telegram bot, web gateway, часть `1C` analytics runtime.
- `<WINDOWS_HOST>``SHARKON2025`, Windows/RDP host с collector toolkit.
- `<WINDOWS_HOST>``HOST-EXAMPLE`, Windows/RDP host с collector toolkit.
- `<GRAFANA_HOST>` — Grafana.
- `<FIREWALL_HOST>``pfSense`, сетевой perimeter и VPN.
+9 -9
View File
@@ -954,7 +954,7 @@ systemctl is-active tsj-guardian-bot tsj-guardian-watchdog gost-tg
- read-only SQLite audit показал, что `/var/lib/activitywatch/aw-server-rust/sqlite.db`
занимает около `6.8G`, `freelist_count=0`; VACUUM сам по себе не
освободит место, потому что размер занят live events;
- основной источник роста: `aw-session-events_SHARKON2025` - около
- основной источник роста: `aw-session-events_HOST-EXAMPLE` - около
`6.9M` строк и `~5GB` payload, с пиками `1.2M-2.4M` process-level
событий в сутки за 2026-05-29..2026-06-01;
- production live config на RDP был `pollSeconds=5` и
@@ -967,7 +967,7 @@ systemctl is-active tsj-guardian-bot tsj-guardian-watchdog gost-tg
`C:\ProgramData\AWatch-rus\switch-backups\deployment-config.before-disable-process-events-20260602T061836Z.json`;
- старый `worktime-session-collector.ps1` PID `8476` остановлен, collector
поднят заново штатными `ActivityWatch Launch [...]` tasks/guard;
- delta-gate: `metadata.end` bucket `aw-session-events_SHARKON2025`
- delta-gate: `metadata.end` bucket `aw-session-events_HOST-EXAMPLE`
остался `2026-06-02T06:27:11.197Z` через 75 секунд, постоянный поток
остановлен;
- базовый сбор не сломан: `detmir-check --json` OK,
@@ -982,10 +982,10 @@ systemctl is-active tsj-guardian-bot tsj-guardian-watchdog gost-tg
- на время операции остановлены AW-related timers/services и
`activitywatch-server.service`, чтобы не было writer'ов к SQLite;
- scoped delete удалил только события bucket
`aw-session-events_SHARKON2025` с `eventType=process_start` или
`aw-session-events_HOST-EXAMPLE` с `eventType=process_start` или
`eventType=process_stop`;
- удалено `6,906,190` шумных process-level событий;
- сохранены logon events: после trim в `aw-session-events_SHARKON2025`
- сохранены logon events: после trim в `aw-session-events_HOST-EXAMPLE`
осталось `174` события, recent samples имеют `eventType=logon`;
- `PRAGMA integrity_check` до и после `VACUUM`: `ok`;
- DB уменьшилась с `6.8G` до `350M`, rootfs AW server вернулся к
@@ -1108,10 +1108,10 @@ systemctl is-active tsj-guardian-bot tsj-guardian-watchdog gost-tg
- `hardening-recovery.ps1` выполнен по existing
`C:\ProgramData\AWatch-rus\deployment-config.json`; после recovery
config остался `processEventsEnabled=false`, `logonEnabled=true`,
`pollSeconds=5`, users включают `SHARKON2025\Администратор`;
- exact task check: `ActivityWatch Launch [SHARKON2025_Администратор]`
`pollSeconds=5`, users включают `HOST-EXAMPLE\Администратор`;
- exact task check: `ActivityWatch Launch [HOST-EXAMPLE_Администратор]`
существует, ошибочный
`ActivityWatch Launch [SHARKON2025_Administrator]` отсутствует,
`ActivityWatch Launch [HOST-EXAMPLE_Administrator]` отсутствует,
`ActivityWatch Recovery` существует;
- `AWatchRusCollectorGuard` running/automatic; Windows
`validate-deployment.ps1` вернул `overallOk=True`;
@@ -1308,7 +1308,7 @@ systemctl is-active tsj-guardian-bot tsj-guardian-watchdog gost-tg
`dlp_counts={ok:22,warn:0,fail:0}` and `ok_for_operator=true`.
46. `[done]` Перенести Proxmox DetMir contour smoke на Rust-first helper:
- добавлен crate `aw-contour-smoke`;
- `scripts/aw-contour-smoke-<GATEWAY_HOST>.sh` теперь Rust-first wrapper:
- `scripts/aw-contour-smoke-gateway.sh` теперь Rust-first wrapper:
ищет `AW_CONTOUR_SMOKE_RUST`,
`$CARGO_TARGET_DIR/release/aw-contour-smoke`,
`adk-rust/target/release/aw-contour-smoke`,
@@ -1575,7 +1575,7 @@ systemctl is-active tsj-guardian-bot tsj-guardian-watchdog gost-tg
`.playwright-cli/page-2026-06-02T17-34-04-725Z.png`.
- portal link repair after Grafana/gateway smoke: quick links now open in a
separate tab, `worktime_report` points to explicit HTML
`/reports/worktime/management?format=html&host=SHARKON2025`,
`/reports/worktime/management?format=html&host=HOST-EXAMPLE`,
`1С действия` is shown in the portal, and gateway `/r/aw-worktime` is
pinned to the same HTML report. Production smoke: AW UI, Worktime, 1C
brief, and 1C actions returned `200 text/html`; Grafana links correctly
+11 -11
View File
@@ -116,7 +116,7 @@ fn run_proxmox_remote() -> Result<i32> {
check_tcp(&mut counts, "nginx http", "127.0.0.1", 80);
check_tcp(&mut counts, "nginx https", "127.0.0.1", 443);
check_tcp(&mut counts, "proxmox web", "127.0.0.1", 8006);
check_tcp(&mut counts, "1C company API", "10.10.10.2", 8710);
check_tcp(&mut counts, "1C company API", "192.0.2.2", 8710);
check_tcp(&mut counts, "clickhouse native", "127.0.0.1", 9000);
check_tcp(&mut counts, "clickhouse http", "127.0.0.1", 8123);
if let Ok(out) = command_output("ss", &["-tulpn"]) {
@@ -141,7 +141,7 @@ fn run_proxmox_remote() -> Result<i32> {
"go proxmox gui",
"https://127.0.0.1/go/proxmox-gui",
&[301, 302, 307, 308],
Some("https://10.10.10.2:8006/"),
Some("https://192.0.2.2:8006/"),
);
check_http_redirect(
&mut counts,
@@ -149,7 +149,7 @@ fn run_proxmox_remote() -> Result<i32> {
"go file1c brief",
"https://127.0.0.1/go/file1c-brief",
&[301, 302, 307, 308],
Some("http://10.10.10.2:8710/manager/brief"),
Some("http://192.0.2.2:8710/manager/brief"),
);
check_http_redirect(
&mut counts,
@@ -157,7 +157,7 @@ fn run_proxmox_remote() -> Result<i32> {
"go file1c actions",
"https://127.0.0.1/go/file1c-actions",
&[301, 302, 307, 308],
Some("http://10.10.10.2:8710/manager/actions"),
Some("http://192.0.2.2:8710/manager/actions"),
);
section("1C Company API");
@@ -165,24 +165,24 @@ fn run_proxmox_remote() -> Result<i32> {
&mut counts,
&no_redirect_http,
"1C root redirect",
"http://10.10.10.2:8710/",
"http://192.0.2.2:8710/",
&[307],
);
for (name, url) in [
("1C /health", "http://10.10.10.2:8710/health"),
("1C /api/health", "http://10.10.10.2:8710/api/health"),
("1C manager brief", "http://10.10.10.2:8710/manager/brief"),
("1C /health", "http://192.0.2.2:8710/health"),
("1C /api/health", "http://192.0.2.2:8710/api/health"),
("1C manager brief", "http://192.0.2.2:8710/manager/brief"),
(
"1C manager actions",
"http://10.10.10.2:8710/manager/actions",
"http://192.0.2.2:8710/manager/actions",
),
(
"1C manager recovery",
"http://10.10.10.2:8710/manager/recovery",
"http://192.0.2.2:8710/manager/recovery",
),
(
"1C weekly digest",
"http://10.10.10.2:8710/manager/digest/weekly",
"http://192.0.2.2:8710/manager/digest/weekly",
),
] {
check_http_code(&mut counts, &http, name, url, &[200]);
+1 -1
View File
@@ -9,7 +9,7 @@ use serde::Serialize;
use serde_json::{Value, json};
const DEFAULT_DB_PATH: &str = "/var/lib/activitywatch/aw-server-rust/sqlite.db";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
#[derive(Debug, Parser)]
#[command(author, version, about = "Read-only ActivityWatch SQLite growth guard")]
@@ -11,7 +11,7 @@ use serde_json::Value;
const DEFAULT_DB_PATH: &str = "/var/lib/activitywatch/aw-server-rust/sqlite.db";
const DEFAULT_BACKUP_DIR: &str = "/var/lib/activitywatch/backups/db";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
const ALLOWED_EVENT_TYPES: &[&str] = &["process_start", "process_stop"];
#[derive(Debug, Parser)]
+2 -2
View File
@@ -472,14 +472,14 @@ mod tests {
r#"
# comment
AW_EXPECT_START_OF_DAY="00:00"
AW_EXPECT_LANDINGPAGE=/#/activity/SHARKON2025/view/
AW_EXPECT_LANDINGPAGE=/#/activity/HOST-EXAMPLE/view/
BAD KEY=value
"#,
);
assert_eq!(parsed.get("AW_EXPECT_START_OF_DAY").unwrap(), "00:00");
assert_eq!(
parsed.get("AW_EXPECT_LANDINGPAGE").unwrap(),
"/#/activity/SHARKON2025/view/"
"/#/activity/HOST-EXAMPLE/view/"
);
assert!(!parsed.contains_key("BAD KEY"));
}
+2 -2
View File
@@ -24,7 +24,7 @@ struct Cli {
#[arg(long)]
legacy_script: PathBuf,
#[arg(long, default_value = "10.10.10.13")]
#[arg(long, default_value = "192.0.2.13")]
server_host: String,
#[arg(long, default_value = "5600")]
@@ -249,7 +249,7 @@ mod tests {
let cli = Cli {
kind: InstallKind::Client,
legacy_script: script,
server_host: "10.10.10.13".to_string(),
server_host: "192.0.2.13".to_string(),
server_port: "5600".to_string(),
poll_interval: "5".to_string(),
version: "0.13.2".to_string(),
+2 -2
View File
@@ -25,10 +25,10 @@ struct Cli {
#[arg(long, default_value = "http://127.0.0.1:5610")]
worktime_api: String,
#[arg(long, default_value = "192.168.100.18")]
#[arg(long, default_value = "198.51.100.18")]
rdp_host: String,
#[arg(long, default_value = "SHARKON2025")]
#[arg(long, default_value = "HOST-EXAMPLE")]
rdp_hostname: String,
#[arg(long, default_value = "/var/lib/activitywatch/health")]
+1 -1
View File
@@ -53,7 +53,7 @@ struct Cli {
#[arg(long, default_value = "http://127.0.0.1:5610")]
worktime_base: String,
#[arg(long, default_value = "SHARKON2025")]
#[arg(long, default_value = "HOST-EXAMPLE")]
host: String,
#[arg(long, default_value_t = 99.97)]
+5 -5
View File
@@ -7,8 +7,8 @@ use clap::Parser;
use reqwest::blocking::Client;
use serde_json::Value;
const DEFAULT_SERVER: &str = "http://10.10.10.13:5600";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_SERVER: &str = "http://192.0.2.13:5600";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
const BUCKETS: &[&str] = &[
"aw-dlp-endpoint-signals",
"aw-dlp-incidents",
@@ -410,7 +410,7 @@ fn get_json(client: &Client, url: &str, _timeout_seconds: u64) -> Result<Value>
}
fn check_cors(_client: &Client, server: &str) -> u16 {
let origin = "http://10.10.10.13:5600";
let origin = "http://192.0.2.13:5600";
let first = curl_status(&format!("{server}/api/0/settings/"), origin);
if first == 200 {
return first;
@@ -532,7 +532,7 @@ mod tests {
#[test]
fn metadata_only_bucket_event_skips_missing_deep_event_read() {
let index = serde_json::json!({
"aw-watcher-window_SHARKON2025": {
"aw-watcher-window_HOST-EXAMPLE": {
"metadata": {
"end": "2026-06-02T00:00:00Z"
}
@@ -540,7 +540,7 @@ mod tests {
});
let event = bucket_event(
"http://127.0.0.1:1",
"aw-watcher-window_SHARKON2025",
"aw-watcher-window_HOST-EXAMPLE",
Some(&index),
false,
1,
+4 -4
View File
@@ -7,9 +7,9 @@ use clap::Parser;
use reqwest::blocking::Client;
use serde_json::Value;
const DEFAULT_SERVER: &str = "http://10.10.10.13:5600";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_RDP_HOST: &str = "192.168.100.18";
const DEFAULT_SERVER: &str = "http://192.0.2.13:5600";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
const DEFAULT_RDP_HOST: &str = "198.51.100.18";
const BUCKETS: &[(&str, &str)] = &[
("aw-watcher-afk", "AFK watcher"),
("aw-watcher-window", "Window watcher"),
@@ -423,7 +423,7 @@ fn check_cors(client: &Client, server: &str) -> u16 {
let url = format!("{server}/api/0/settings/");
client
.get(&url)
.header("Origin", "http://10.10.10.13:5600")
.header("Origin", "http://192.0.2.13:5600")
.send()
.map(|response| response.status().as_u16())
.unwrap_or(0)
@@ -270,9 +270,9 @@ mod tests {
let kit = root.join("install-kit-awindows-20260427-211240");
fs::create_dir_all(&kit).unwrap();
fs::write(kit.join("unexpected.txt"), "kit").unwrap();
fs::create_dir_all(kit.join("server-configs-192.168.100.18")).unwrap();
fs::create_dir_all(kit.join("server-configs-198.51.100.18")).unwrap();
fs::write(
kit.join("server-configs-192.168.100.18/config.deployment-config.json"),
kit.join("server-configs-198.51.100.18/config.deployment-config.json"),
"{}",
)
.unwrap();
+6 -6
View File
@@ -12,10 +12,10 @@ use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use serde::Serialize;
use serde_json::Value;
const DEFAULT_AW_API: &str = "http://10.10.10.13:5600/api/0";
const DEFAULT_WORKTIME_URL: &str = "http://10.10.10.13:5610";
const DEFAULT_ONE_C_URL: &str = "http://10.10.10.2:8710";
const DEFAULT_HOSTNAME: &str = "SHARKON2025";
const DEFAULT_AW_API: &str = "http://192.0.2.13:5600/api/0";
const DEFAULT_WORKTIME_URL: &str = "http://192.0.2.13:5610";
const DEFAULT_ONE_C_URL: &str = "http://192.0.2.2:8710";
const DEFAULT_HOSTNAME: &str = "HOST-EXAMPLE";
const DEFAULT_GATEWAY_HOST: &str = "detmir.example.local";
#[derive(Debug, Parser)]
@@ -298,13 +298,13 @@ fn service_checks(args: &Cli) -> Vec<ServiceCheck> {
}
checks.push(tcp_check(
"192.168.100.18",
"198.51.100.18",
5985,
args.tcp_timeout_seconds,
true,
));
checks.push(tcp_check(
"192.168.100.18",
"198.51.100.18",
22,
args.tcp_timeout_seconds,
true,
+1 -1
View File
@@ -4,7 +4,7 @@ use std::process::Command;
use anyhow::{Context, Result};
use clap::Parser;
const DEFAULT_SSH_TARGET: &str = "igor@10.10.10.13";
const DEFAULT_SSH_TARGET: &str = "igor@192.0.2.13";
const DEFAULT_REMOTE_COMMAND: &str = "sudo -n /usr/local/bin/dlp-health-check --json";
#[derive(Debug, Parser)]
@@ -12,7 +12,7 @@ use serde_json::{Value, json};
const DEFAULT_GRAFANA_URL: &str = "http://127.0.0.1:3000";
const DEFAULT_DASHBOARD_UID: &str = "detmir-aw-main";
const DEFAULT_DASHBOARD_FILE: &str = "/etc/grafana/provisioning/dashboards/aw/detmir-aw-main.json";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
const OLD_MEASUREMENTS: &[&str] = &["aw_window_event", "aw_afk_event"];
const REQUIRED_MEASUREMENTS: &[&str] = &[
"aw_rdp_worktime_hourly",
@@ -670,9 +670,9 @@ mod tests {
assert_eq!(
render_query_vars(
"r.host == \"${host}\" or r.host == \"$host\"",
"SHARKON2025"
"HOST-EXAMPLE"
),
"r.host == \"SHARKON2025\" or r.host == \"SHARKON2025\""
"r.host == \"HOST-EXAMPLE\" or r.host == \"HOST-EXAMPLE\""
);
}
+1 -1
View File
@@ -6,7 +6,7 @@ use clap::Parser;
use detmir_core::{exit_codes, now_utc_rfc3339};
use serde::Serialize;
const DEFAULT_SSH_TARGET: &str = "igor@10.10.10.13";
const DEFAULT_SSH_TARGET: &str = "igor@192.0.2.13";
const REQUIRED_UNITS: &[&str] = &[
"activitywatch-server.service",
"aw-worktime-api.service",
+4 -4
View File
@@ -52,14 +52,14 @@ struct Cli {
#[arg(
long,
default_value = "http://10.10.10.13:5610",
default_value = "http://192.0.2.13:5610",
env = "DETMIR_PORTAL_WORKTIME_URL"
)]
worktime_url: String,
#[arg(
long,
default_value = "http://10.10.10.2:8710",
default_value = "http://192.0.2.2:8710",
env = "DETMIR_PORTAL_ONE_C_URL"
)]
one_c_url: String,
@@ -1836,7 +1836,7 @@ fn links() -> PortalLinks {
portal: "/portal/".to_string(),
grafana_dashboards: "/dashboards".to_string(),
detmir_activitywatch:
"/d/detmir-aw-main/detmir-activitywatch?orgId=1&from=now-48h&to=now&timezone=browser&var-host=SHARKON2025&refresh=5m"
"/d/detmir-aw-main/detmir-activitywatch?orgId=1&from=now-48h&to=now&timezone=browser&var-host=HOST-EXAMPLE&refresh=5m"
.to_string(),
dlp_security_dashboard:
"/d/detmir-dlp-security?orgId=1&from=now-30d&to=now&timezone=browser".to_string(),
@@ -1845,7 +1845,7 @@ fn links() -> PortalLinks {
dlp_overview_dashboard:
"/d/awatch-dlp-overview?orgId=1&from=now-30d&to=now&timezone=browser".to_string(),
aw_ui: "/r/aw/".to_string(),
worktime_report: "/reports/worktime/management?format=html&host=SHARKON2025".to_string(),
worktime_report: "/reports/worktime/management?format=html&host=HOST-EXAMPLE".to_string(),
file1c_brief: "/r/file1c/brief".to_string(),
file1c_actions: "/r/file1c/actions".to_string(),
}
@@ -136,20 +136,20 @@ fn seed_server_dlp_events(inventory: &str) -> Result<()> {
let ts = Utc::now().format("%Y-%m-%dT%H:%M:%SZ");
let script = format!(
r#"cat >/tmp/aw-endpoint-seed.json <<'JSON'
{{"timestamp":"{ts}","duration":0.0,"data":{{"hostname":"SHARKON2025","signalType":"self_test","source":"diag_and_manual_restart","username":"system","queueDepth":0,"eventsEnqueued":0,"eventsFlushed":0,"sendFailures":0}}}}
{{"timestamp":"{ts}","duration":0.0,"data":{{"hostname":"HOST-EXAMPLE","signalType":"self_test","source":"diag_and_manual_restart","username":"system","queueDepth":0,"eventsEnqueued":0,"eventsFlushed":0,"sendFailures":0}}}}
JSON
cat >/tmp/aw-fileops-seed-host.json <<'JSON'
{{"timestamp":"{ts}","duration":0.0,"data":{{"hostname":"SHARKON2025","operation":"self_test","source":"diag_and_manual_restart"}}}}
{{"timestamp":"{ts}","duration":0.0,"data":{{"hostname":"HOST-EXAMPLE","operation":"self_test","source":"diag_and_manual_restart"}}}}
JSON
cat >/tmp/aw-fileops-seed-server.json <<'JSON'
{{"timestamp":"{ts}","duration":0.0,"data":{{"hostname":"10.10.10.13","operation":"self_test","source":"diag_and_manual_restart"}}}}
{{"timestamp":"{ts}","duration":0.0,"data":{{"hostname":"192.0.2.13","operation":"self_test","source":"diag_and_manual_restart"}}}}
JSON
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-dlp-endpoint-signals_SHARKON2025' -H 'Content-Type: application/json' -d '{{"client":"aw-dlp-endpoint-signals","type":"aw.dlp.endpoint.signal","hostname":"SHARKON2025"}}' >/dev/null 2>&1 || true
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-file-operations_SHARKON2025' -H 'Content-Type: application/json' -d '{{"client":"aw-file-operations","type":"aw.file.operation","hostname":"SHARKON2025"}}' >/dev/null 2>&1 || true
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-file-operations_10.10.10.13' -H 'Content-Type: application/json' -d '{{"client":"aw-file-operations","type":"aw.file.operation","hostname":"10.10.10.13"}}' >/dev/null 2>&1 || true
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-dlp-endpoint-signals_SHARKON2025/heartbeat?pulsetime=30' -H 'Content-Type: application/json' --data-binary @/tmp/aw-endpoint-seed.json >/dev/null
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-file-operations_SHARKON2025/heartbeat?pulsetime=30' -H 'Content-Type: application/json' --data-binary @/tmp/aw-fileops-seed-host.json >/dev/null
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-file-operations_10.10.10.13/heartbeat?pulsetime=30' -H 'Content-Type: application/json' --data-binary @/tmp/aw-fileops-seed-server.json >/dev/null
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-dlp-endpoint-signals_HOST-EXAMPLE' -H 'Content-Type: application/json' -d '{{"client":"aw-dlp-endpoint-signals","type":"aw.dlp.endpoint.signal","hostname":"HOST-EXAMPLE"}}' >/dev/null 2>&1 || true
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-file-operations_HOST-EXAMPLE' -H 'Content-Type: application/json' -d '{{"client":"aw-file-operations","type":"aw.file.operation","hostname":"HOST-EXAMPLE"}}' >/dev/null 2>&1 || true
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-file-operations_192.0.2.13' -H 'Content-Type: application/json' -d '{{"client":"aw-file-operations","type":"aw.file.operation","hostname":"192.0.2.13"}}' >/dev/null 2>&1 || true
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-dlp-endpoint-signals_HOST-EXAMPLE/heartbeat?pulsetime=30' -H 'Content-Type: application/json' --data-binary @/tmp/aw-endpoint-seed.json >/dev/null
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-file-operations_HOST-EXAMPLE/heartbeat?pulsetime=30' -H 'Content-Type: application/json' --data-binary @/tmp/aw-fileops-seed-host.json >/dev/null
curl -sS -X POST 'http://127.0.0.1:5600/api/0/buckets/aw-file-operations_192.0.2.13/heartbeat?pulsetime=30' -H 'Content-Type: application/json' --data-binary @/tmp/aw-fileops-seed-server.json >/dev/null
"#
);
let _ = ansible_command(
@@ -170,7 +170,7 @@ fn restart_windows_collectors(inventory: &str) {
fn seed_windows_dlp_events(inventory: &str) {
log("Seeding endpoint/file-ops events from aw_windows...");
let script = r#"powershell -NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference = 'Stop'; $ts = (Get-Date).ToUniversalTime().ToString('o'); $api='http://10.10.10.13:5600/api/0'; $endpoint=@{timestamp=$ts;duration=0.0;data=@{hostname='SHARKON2025';signalType='self_test';source='diag_and_manual_restart';username=$env:USERNAME;queueDepth=0;eventsEnqueued=0;eventsFlushed=0;sendFailures=0}} | ConvertTo-Json -Depth 8 -Compress; $fileops=@{timestamp=$ts;duration=0.0;data=@{hostname='SHARKON2025';operation='self_test';source='diag_and_manual_restart';username=$env:USERNAME}} | ConvertTo-Json -Depth 8 -Compress; Invoke-RestMethod -Method Post -Uri $api'/buckets/aw-dlp-endpoint-signals_SHARKON2025' -ContentType 'application/json' -Body '{\"client\":\"aw-dlp-endpoint-signals\",\"type\":\"aw.dlp.endpoint.signal\",\"hostname\":\"SHARKON2025\"}' -TimeoutSec 15 -DisableKeepAlive -ErrorAction SilentlyContinue | Out-Null; Invoke-RestMethod -Method Post -Uri $api'/buckets/aw-file-operations_SHARKON2025' -ContentType 'application/json' -Body '{\"client\":\"aw-file-operations\",\"type\":\"aw.file.operation\",\"hostname\":\"SHARKON2025\"}' -TimeoutSec 15 -DisableKeepAlive -ErrorAction SilentlyContinue | Out-Null; Invoke-RestMethod -Method Post -Uri $api'/buckets/aw-dlp-endpoint-signals_SHARKON2025/heartbeat?pulsetime=30' -ContentType 'application/json' -Body $endpoint -TimeoutSec 15 -DisableKeepAlive | Out-Null; Invoke-RestMethod -Method Post -Uri $api'/buckets/aw-file-operations_SHARKON2025/heartbeat?pulsetime=30' -ContentType 'application/json' -Body $fileops -TimeoutSec 15 -DisableKeepAlive | Out-Null; Write-Output 'windows-dlp-seeded'""#;
let script = r#"powershell -NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference = 'Stop'; $ts = (Get-Date).ToUniversalTime().ToString('o'); $api='http://192.0.2.13:5600/api/0'; $endpoint=@{timestamp=$ts;duration=0.0;data=@{hostname='HOST-EXAMPLE';signalType='self_test';source='diag_and_manual_restart';username=$env:USERNAME;queueDepth=0;eventsEnqueued=0;eventsFlushed=0;sendFailures=0}} | ConvertTo-Json -Depth 8 -Compress; $fileops=@{timestamp=$ts;duration=0.0;data=@{hostname='HOST-EXAMPLE';operation='self_test';source='diag_and_manual_restart';username=$env:USERNAME}} | ConvertTo-Json -Depth 8 -Compress; Invoke-RestMethod -Method Post -Uri $api'/buckets/aw-dlp-endpoint-signals_HOST-EXAMPLE' -ContentType 'application/json' -Body '{\"client\":\"aw-dlp-endpoint-signals\",\"type\":\"aw.dlp.endpoint.signal\",\"hostname\":\"HOST-EXAMPLE\"}' -TimeoutSec 15 -DisableKeepAlive -ErrorAction SilentlyContinue | Out-Null; Invoke-RestMethod -Method Post -Uri $api'/buckets/aw-file-operations_HOST-EXAMPLE' -ContentType 'application/json' -Body '{\"client\":\"aw-file-operations\",\"type\":\"aw.file.operation\",\"hostname\":\"HOST-EXAMPLE\"}' -TimeoutSec 15 -DisableKeepAlive -ErrorAction SilentlyContinue | Out-Null; Invoke-RestMethod -Method Post -Uri $api'/buckets/aw-dlp-endpoint-signals_HOST-EXAMPLE/heartbeat?pulsetime=30' -ContentType 'application/json' -Body $endpoint -TimeoutSec 15 -DisableKeepAlive | Out-Null; Invoke-RestMethod -Method Post -Uri $api'/buckets/aw-file-operations_HOST-EXAMPLE/heartbeat?pulsetime=30' -ContentType 'application/json' -Body $fileops -TimeoutSec 15 -DisableKeepAlive | Out-Null; Write-Output 'windows-dlp-seeded'""#;
let _ = ansible_windows_shell(inventory, script);
}
+3 -3
View File
@@ -1513,18 +1513,18 @@ mod tests {
#[test]
fn transport_counter_key_separates_sessions() {
let data = json!({
"hostname": "SHARKON2025",
"hostname": "HOST-EXAMPLE",
"sessionId": 4,
"username": "USER4"
});
assert_eq!(
transport_counter_key(
"file-operations",
"aw-file-operations_SHARKON2025",
"aw-file-operations_HOST-EXAMPLE",
&data,
"sendFailures"
),
"file-operations:aw-file-operations_SHARKON2025:SHARKON2025:4:USER4:sendFailures"
"file-operations:aw-file-operations_HOST-EXAMPLE:HOST-EXAMPLE:4:USER4:sendFailures"
);
}
+10 -10
View File
@@ -11,7 +11,7 @@ const DEFAULT_AW_API_BASE: &str = "http://127.0.0.1:5600/api/0";
const DEFAULT_CASE_API_BASE: &str = "http://127.0.0.1:5602/api/0/dlp/cases";
const DEFAULT_INFLUX_ORG: &str = "proxmox";
const DEFAULT_INFLUX_BUCKET: &str = "aw_metrics";
const DEFAULT_HOSTS: &str = "SHARKON2025";
const DEFAULT_HOSTS: &str = "HOST-EXAMPLE";
#[derive(Debug, Parser)]
#[command(about = "AW DLP InfluxDB exporter")]
@@ -1009,7 +1009,7 @@ mod tests {
"id": 10,
"timestamp": "2026-05-15T10:00:00Z",
"data": {
"hostname": "SHARKON2025",
"hostname": "HOST-EXAMPLE",
"username": "Администратор",
"signalType": "self_test",
"policyMode": "server",
@@ -1025,7 +1025,7 @@ mod tests {
"id": 11,
"timestamp": "2026-05-15T10:01:00Z",
"data": {
"hostname": "SHARKON2025",
"hostname": "HOST-EXAMPLE",
"username": "Администратор",
"signalType": "print_job",
"source": "endpoint-signals-phase2",
@@ -1034,7 +1034,7 @@ mod tests {
}
}),
];
let lines = build_endpoint_lines("SHARKON2025", &events);
let lines = build_endpoint_lines("HOST-EXAMPLE", &events);
assert!(
lines
.iter()
@@ -1048,13 +1048,13 @@ mod tests {
let item = json!({
"timestamp": "2026-05-15T10:02:00Z",
"data": {
"host": "SHARKON2025",
"host": "HOST-EXAMPLE",
"incident": {"status": "open", "verdict": "incident"},
"sourceBucket": "aw-dlp-endpoint-signals_SHARKON2025",
"sourceBucket": "aw-dlp-endpoint-signals_HOST-EXAMPLE",
"sourceEvent": {
"data": {
"signalType": "print_job",
"hostname": "SHARKON2025",
"hostname": "HOST-EXAMPLE",
"username": "Администратор",
"documentName": "Письмо",
"printerName": "HP",
@@ -1063,7 +1063,7 @@ mod tests {
}
}
});
let normalized = normalize_incident(&item, "SHARKON2025");
let normalized = normalize_incident(&item, "HOST-EXAMPLE");
assert_eq!(normalized.signal_type, "print_job");
assert_eq!(normalized.username, "Администратор");
assert_eq!(normalized.action, "incident");
@@ -1074,7 +1074,7 @@ mod tests {
fn case_lines_emit_case_state() {
let cases = vec![json!({
"id": 28,
"host": "SHARKON2025",
"host": "HOST-EXAMPLE",
"status": "open",
"severity": "medium",
"assignee": null,
@@ -1084,7 +1084,7 @@ mod tests {
"forensics": null,
"updated_at": "2026-05-15T10:03:00+00:00"
})];
let lines = build_case_lines("SHARKON2025", &cases);
let lines = build_case_lines("HOST-EXAMPLE", &cases);
assert_eq!(lines.len(), 1);
assert!(lines[0].starts_with("aw_dlp_case,"));
}
@@ -8,9 +8,9 @@ use chrono::Local;
use clap::Parser;
use serde::Serialize;
const DEFAULT_SERVER_HOST: &str = "10.10.10.13";
const DEFAULT_SERVER_HOST: &str = "192.0.2.13";
const DEFAULT_SERVER_USER: &str = "igor";
const DEFAULT_LEGACY_DB: &str = "/root/.local/share/activitywatch/aw-server-rust/sqlite.db";
const DEFAULT_LEGACY_DB: &str = "/var/lib/activitywatch/aw-server-rust/sqlite.db";
const DEFAULT_TARGET_DB: &str =
"/var/lib/activitywatch/.local/share/activitywatch/aw-server-rust/sqlite.db";
const DEFAULT_REMOTE_MERGE_BIN: &str = "/tmp/merge-aw-server-dbs";
@@ -454,7 +454,7 @@ mod tests {
dir.path(),
&dir.path().join("private-config/runtime.env"),
&env,
"10.10.10.13",
"192.0.2.13",
"igor",
"20260602-000000",
"/var/lib/activitywatch/backups/prod-restore-20260602-000000",
@@ -10,8 +10,8 @@ use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
use serde_json::Value;
const DEFAULT_AW_BASE_URL: &str = "http://10.10.10.13:5600/api/0";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_AW_BASE_URL: &str = "http://192.0.2.13:5600/api/0";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
const DEFAULT_SAMPLE_SECONDS: f64 = 30.0;
const DEFAULT_MAX_SAMPLE_SECONDS: f64 = 300.0;
const DEFAULT_OUT_DIR: &str = "reports";
@@ -550,7 +550,7 @@ mod tests {
];
let rows = build_rows(
events,
"SHARKON2025",
"HOST-EXAMPLE",
NaiveDate::from_ymd_opt(2026, 6, 1).unwrap(),
NaiveDate::from_ymd_opt(2026, 6, 1).unwrap(),
30.0,
@@ -563,7 +563,7 @@ mod tests {
assert_eq!(rows[0].sessions_count, 2);
assert_eq!(rows[0].samples_count, 3);
assert_eq!(rows[0].active_samples, 2);
assert_eq!(rows[0].user_id, "SHARKON2025\\user5");
assert_eq!(rows[0].user_id, "HOST-EXAMPLE\\user5");
}
#[test]
@@ -388,7 +388,7 @@ fn suggestions_from_failures(failures: &[String]) -> Vec<String> {
}
if text.contains("pfsense_web") {
suggestions
.push("Проверить доступность pfSense 10.10.10.1:8443, перезапустить WebGUI/nginx.");
.push("Проверить доступность pfSense 192.0.2.1:8443, перезапустить WebGUI/nginx.");
}
if text.contains("pfsense_mcp") {
suggestions.push("Проверить локальный pfsense-mcp-server.service, bearer token и endpoint 127.0.0.1:3010/mcp.");
@@ -397,18 +397,18 @@ fn suggestions_from_failures(failures: &[String]) -> Vec<String> {
suggestions.push("Проверить контейнер InfluxDB и restart сервиса influxdb.");
}
if text.contains("grafana") {
suggestions.push("Проверить grafana-server и NO_PROXY для 10.10.10.0/24.");
suggestions.push("Проверить grafana-server и NO_PROXY для 192.0.2.0/24.");
}
if text.contains("loki") || text.contains("alloy") {
suggestions.push("Проверить LXC логов и restart сервисов loki/alloy.");
}
if text.contains("aw-rus:watcher-") || text.contains("aw-rus:worktime:") {
suggestions.push("Проверить Windows collector recovery: worktime-session-collector, ActivityWatch Recovery и Launch tasks на 192.168.100.18.");
suggestions.push("Проверить Windows collector recovery: worktime-session-collector, ActivityWatch Recovery и Launch tasks на 198.51.100.18.");
suggestions.push("После Windows recovery проверить server-side aw-worktime-autoheal/ui-bridge для пересборки afk/window bucket'ов.");
}
if text.contains("aw-rus:dlp-") {
suggestions.push(
"Проверить DLP endpoint/fileops collectors и server-side DLP transport на 10.10.10.13.",
"Проверить DLP endpoint/fileops collectors и server-side DLP transport на 192.0.2.13.",
);
}
if text.contains("filesystem_usage") {
@@ -11,7 +11,8 @@ use serde::Serialize;
const DEFAULT_HEARTBEAT_FILE: &str = "/opt/infra-admin/.state/tsj_guardian_heartbeat";
const DEFAULT_SERVICE_NAME: &str = "tsj-guardian-bot.service";
const DEFAULT_GOST_SERVICE_NAME: &str = "gost-tg.service";
const DEFAULT_GOST_PATTERN: &str = "/usr/local/bin/gost -L http+socks5://127.0.0.1:11090 -F socks5+wss://gw.example.local:4443";
const DEFAULT_GOST_PATTERN: &str =
"/usr/local/bin/gost -L http+socks5://127.0.0.1:11090 -F socks5+wss://gw.example.local:4443";
#[derive(Debug, Parser)]
#[command(about = "TSJ Guardian bot heartbeat watchdog and gost duplicate guard.")]
+2 -2
View File
@@ -22,7 +22,7 @@ use tiny_http::{Header, Method, Request, Response, Server, StatusCode};
use url::form_urlencoded;
const DEFAULT_AW_URL: &str = "http://127.0.0.1:5600";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
#[derive(Debug, Parser)]
#[command(about = "AW Worktime Report API")]
@@ -2536,7 +2536,7 @@ mod tests {
],
start,
end,
"SHARKON2025",
"HOST-EXAMPLE",
false,
);
assert_eq!(rows[0]["active_seconds"], 60);
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
const DEFAULT_AW_URL: &str = "http://127.0.0.1:5600";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
const AUTOHEAL_SOURCE: &str = "aw-worktime-autoheal";
#[derive(Debug, Parser)]
@@ -11,7 +11,7 @@ const DEFAULT_AW_BASE: &str = "http://127.0.0.1:5600";
const DEFAULT_WORKTIME_REPORT_BASE: &str = "http://127.0.0.1:5610";
const DEFAULT_INFLUX_ORG: &str = "proxmox";
const DEFAULT_INFLUX_BUCKET: &str = "aw_metrics";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
const DEFAULT_DAYS: &str = "today,yesterday";
#[derive(Debug, Parser)]
@@ -993,14 +993,14 @@ mod tests {
fn heartbeat_line_uses_current_exporter_timestamp() {
let out = line(
"aw_worktime_exporter_heartbeat",
vec![("host", "SHARKON2025".to_string())],
vec![("host", "HOST-EXAMPLE".to_string())],
vec![("run", FieldValue::Int(1))],
42,
)
.unwrap();
assert_eq!(
out,
"aw_worktime_exporter_heartbeat,host=SHARKON2025 run=1i 42"
"aw_worktime_exporter_heartbeat,host=HOST-EXAMPLE run=1i 42"
);
}
@@ -1023,11 +1023,11 @@ mod tests {
}
})];
let daily = aggregate_daily_rows(&events, start, end_exclusive, "SHARKON2025", &config);
let hourly = aggregate_hourly_rows(&events, end_exclusive, "SHARKON2025", &config);
let daily = aggregate_daily_rows(&events, start, end_exclusive, "HOST-EXAMPLE", &config);
let hourly = aggregate_hourly_rows(&events, end_exclusive, "HOST-EXAMPLE", &config);
assert_eq!(daily.len(), 1);
assert_eq!(daily[0].user_id, "SHARKON2025\\user5");
assert_eq!(daily[0].user_id, "HOST-EXAMPLE\\user5");
assert_eq!(daily[0].active_seconds, 300);
assert_eq!(daily[0].active_samples, 1);
assert_eq!(hourly.len(), 1);
+1 -1
View File
@@ -7,7 +7,7 @@ use reqwest::blocking::Client;
use serde::Serialize;
const DEFAULT_BASE_URL: &str = "http://127.0.0.1:5610";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
#[derive(Debug, Parser)]
#[command(about = "AW Worktime report cache prewarm")]
@@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
const DEFAULT_AW_URL: &str = "http://127.0.0.1:5600";
const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_HOST: &str = "HOST-EXAMPLE";
const DEFAULT_STATE_PATH: &str = "/var/lib/activitywatch/aw-worktime-ui-bridge-state.json";
const BRIDGE_SOURCE: &str = "aw-worktime-ui-bridge";
+12 -12
View File
@@ -451,9 +451,9 @@
- { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" }
- name: Создать каталог /root/bootstrap для apply_webui_ru_patch.sh
- name: Создать каталог /opt/detmir/bootstrap для apply_webui_ru_patch.sh
ansible.builtin.file:
path: /root/bootstrap
path: /opt/detmir/bootstrap
state: directory
mode: "0755"
@@ -463,10 +463,10 @@
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
loop:
- { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/root/bootstrap/aw-ru-patch.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/root/bootstrap/aw-sw-cleanup.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/root/bootstrap/aw-worktime-panel.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/root/bootstrap/aw-host-groups.json", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/opt/detmir/bootstrap/aw-ru-patch.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/opt/detmir/bootstrap/aw-sw-cleanup.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/opt/detmir/bootstrap/aw-worktime-panel.js", mode: "0644" }
- { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/opt/detmir/bootstrap/aw-host-groups.json", mode: "0644" }
- name: Скопировать apply_webui_ru_patch.sh скрипт
ansible.builtin.copy:
@@ -655,14 +655,14 @@
AW_WORKTIME_INFLUX_URL={{ aw_worktime_influx_url | default('') }}
AW_WORKTIME_INFLUX_ORG={{ aw_worktime_influx_org | default('proxmox') }}
AW_WORKTIME_INFLUX_BUCKET={{ aw_worktime_influx_bucket | default('aw_metrics') }}
AW_WORKTIME_INFLUX_HOSTS={{ aw_worktime_influx_hosts | default('SHARKON2025') }}
AW_WORKTIME_INFLUX_HOSTS={{ aw_worktime_influx_hosts | default('HOST-EXAMPLE') }}
AW_WORKTIME_INFLUX_DAYS={{ aw_worktime_influx_days | default('today,yesterday') }}
AW_WORKTIME_INFLUX_TOKEN={{ aw_effective_worktime_influx_token | default('') }}
AW_DLP_INFLUX_ENABLED={{ 'true' if (aw_dlp_influx_enabled | default(false) | bool) else 'false' }}
AW_DLP_INFLUX_URL={{ aw_dlp_influx_url | default('') }}
AW_DLP_INFLUX_ORG={{ aw_dlp_influx_org | default('proxmox') }}
AW_DLP_INFLUX_BUCKET={{ aw_dlp_influx_bucket | default('aw_metrics') }}
AW_DLP_INFLUX_HOSTS={{ aw_dlp_influx_hosts | default('SHARKON2025') }}
AW_DLP_INFLUX_HOSTS={{ aw_dlp_influx_hosts | default('HOST-EXAMPLE') }}
AW_DLP_INFLUX_LOOKBACK_DAYS={{ aw_dlp_influx_lookback_days | default(30) }}
AW_DLP_INFLUX_EVENT_LIMIT={{ aw_dlp_influx_event_limit | default(2000) }}
AW_DLP_INFLUX_TOKEN={{ aw_effective_dlp_influx_token | default('') }}
@@ -1748,7 +1748,7 @@
- name: Проверить наличие legacy root DB
ansible.builtin.stat:
path: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
path: /var/lib/activitywatch/aw-server-rust/sqlite.db
register: aw_legacy_root_db
- name: Проверить наличие target DB
@@ -1791,7 +1791,7 @@
- name: Backup legacy root DB перед merge
ansible.builtin.copy:
remote_src: true
src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
src: /var/lib/activitywatch/aw-server-rust/sqlite.db
dest: "{{ aw_server_data_dir }}/backups/db/legacy-root-{{ ansible_date_time.iso8601_basic_short }}.sqlite.db"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
@@ -1805,7 +1805,7 @@
argv:
- /usr/local/bin/merge-aw-server-dbs
- --base
- /root/.local/share/activitywatch/aw-server-rust/sqlite.db
- /var/lib/activitywatch/aw-server-rust/sqlite.db
- --overlay
- "{{ aw_server_db_path }}"
- --output
@@ -1843,7 +1843,7 @@
- name: Скопировать legacy root DB в target DB если target ещё не существует
ansible.builtin.copy:
remote_src: true
src: /root/.local/share/activitywatch/aw-server-rust/sqlite.db
src: /var/lib/activitywatch/aw-server-rust/sqlite.db
dest: "{{ aw_server_db_path }}"
owner: "{{ aw_server_user }}"
group: "{{ aw_server_group }}"
+1 -1
View File
@@ -13,7 +13,7 @@
aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
aw_windows_domain: "HOST-EXAMPLE"
aw_windows_builtin_administrator_name: "Администратор"
aw_windows_users:
- Администратор
+3 -3
View File
@@ -42,8 +42,8 @@
DETMIR_PORTAL_STATUS_CMD=detmir-status --json
DETMIR_PORTAL_CHECK_CMD=detmir-check --json
DETMIR_PORTAL_FAILED_UNITS_CMD=systemctl --failed --no-pager
DETMIR_PORTAL_WORKTIME_URL=http://10.10.10.13:5610
DETMIR_PORTAL_ONE_C_URL=http://10.10.10.2:8710
DETMIR_PORTAL_WORKTIME_URL=http://192.0.2.13:5610
DETMIR_PORTAL_ONE_C_URL=http://192.0.2.2:8710
DETMIR_PORTAL_TIMEOUT_SECONDS=10
DETMIR_PORTAL_STATE_DIR=/var/lib/detmir-portal
DETMIR_PORTAL_DLP_DB_PATH=/var/lib/activitywatch/dlp_warehouse.sqlite
@@ -117,7 +117,7 @@
vars:
aw_repo_root: "{{ playbook_dir | dirname }}"
aw_rust_release_dir: "{{ (lookup('env', 'CARGO_TARGET_DIR') | default(aw_repo_root + '/adk-rust/target', true)) + '/release' }}"
detmir_evidence_bind: "{{ detmir_evidence_bind_override | default('10.10.10.13:8721') }}"
detmir_evidence_bind: "{{ detmir_evidence_bind_override | default('192.0.2.13:8721') }}"
detmir_evidence_env_path: "/etc/detmir-portal-evidence.env"
detmir_evidence_upload_token_path: "/var/lib/activitywatch/dlp-evidence/upload-token"
+1 -1
View File
@@ -9,7 +9,7 @@
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_evidence_sync_task_name: "ActivityWatch DLP Evidence Sync"
aw_windows_evidence_sync_interval_minutes: 5
aw_windows_evidence_sync_api_url: "http://10.10.10.13:8721/api/dlp/evidence/upload"
aw_windows_evidence_sync_api_url: "http://192.0.2.13:8721/api/dlp/evidence/upload"
aw_windows_evidence_sync_script: "{{ aw_windows_state_root }}\\sync-dlp-evidence-artifacts.ps1"
aw_windows_evidence_sync_token_path: "{{ aw_windows_state_root }}\\dlp-evidence-upload-token.txt"
aw_windows_evidence_sync_state_path: "{{ aw_windows_state_root }}\\dlp-evidence-sync-state.json"
+1 -1
View File
@@ -16,7 +16,7 @@
aw_file_1c_clickhouse_native_port: 9000
aw_file_1c_company_api_host: "{{ ansible_host | default('127.0.0.1') }}"
aw_file_1c_company_api_port: 8710
aw_file_1c_manager_brief_grafana_url: "http://10.10.10.11:3000/d/1c-file-mgmt/1c-file-management-board?orgId=1"
aw_file_1c_manager_brief_grafana_url: "http://192.0.2.11:3000/d/1c-file-mgmt/1c-file-management-board?orgId=1"
aw_file_1c_manager_brief_state_dir: /opt/activitywatch/clickhouse-1c/state/manager-brief
aw_file_1c_manager_brief_model: gpt-5.3-codex
aw_file_1c_manager_brief_codex_user: igor
+3 -3
View File
@@ -134,9 +134,9 @@
$user = Split-Path $profile.LocalPath -Leaf
$sshDir = Join-Path $profile.LocalPath '.ssh'
$dst = Join-Path $sshDir 'awops_ed25519'
$grantDir = 'SHARKON2025\' + $user + ':(OI)(CI)(F)'
$grantRead = 'SHARKON2025\' + $user + ':(R)'
$grantModify = 'SHARKON2025\' + $user + ':(M)'
$grantDir = 'HOST-EXAMPLE\' + $user + ':(OI)(CI)(F)'
$grantRead = 'HOST-EXAMPLE\' + $user + ':(R)'
$grantModify = 'HOST-EXAMPLE\' + $user + ':(M)'
New-Item -ItemType Directory -Path $sshDir -Force | Out-Null
& icacls.exe $sshDir /grant $grantDir | Out-Null
+1 -1
View File
@@ -14,7 +14,7 @@
grafana_check_dashboard_uid: "{{ detmir_grafana_check_dashboard_uid | default('detmir-aw-main') }}"
grafana_check_dashboard_file_src: "{{ aw_repo_root }}/grafana/detmir-aw-main-dashboard.json"
grafana_check_dashboard_file_dest: /etc/grafana/provisioning/dashboards/aw/detmir-aw-main.json
grafana_check_host: "{{ detmir_grafana_check_host | default('SHARKON2025') }}"
grafana_check_host: "{{ detmir_grafana_check_host | default('HOST-EXAMPLE') }}"
grafana_check_max_freshness_minutes: "{{ detmir_grafana_check_max_freshness_minutes | default(360) }}"
grafana_check_on_calendar: "{{ detmir_grafana_check_on_calendar | default('*:0/15') }}"
+15 -15
View File
@@ -23,84 +23,84 @@
title: "Proxmox VE"
category: "Host"
description: "Основная панель управления Proxmox VE."
target_url: "https://10.10.10.2:8006/"
target_url: "https://192.0.2.2:8006/"
external_enabled: false
- slug: "file1c-brief"
title: "1C Executive Brief"
category: "Management"
description: "Сводка по предприятиям и рискам 1С."
target_url: "http://10.10.10.2:8710/manager/brief"
target_url: "http://192.0.2.2:8710/manager/brief"
proxy_path: "/r/file1c/brief"
proxy_target_url: "http://10.10.10.2:8710/manager/brief"
proxy_target_url: "http://192.0.2.2:8710/manager/brief"
external_enabled: true
- slug: "file1c-actions"
title: "1C Management Actions"
category: "Management"
description: "Очередь действий по предприятиям в 1С."
target_url: "http://10.10.10.2:8710/manager/actions"
target_url: "http://192.0.2.2:8710/manager/actions"
proxy_path: "/r/file1c/actions"
proxy_target_url: "http://10.10.10.2:8710/manager/actions"
proxy_target_url: "http://192.0.2.2:8710/manager/actions"
external_enabled: true
- slug: "file1c-finance"
title: "1C Financial Reporting"
category: "Management"
description: "Первый financial board по файловой 1С с разделением ledger/proxy."
target_url: "http://10.10.10.11:3000/d/1c-file-finance/1c-file-financial-reporting?orgId=1"
target_url: "http://192.0.2.11:3000/d/1c-file-finance/1c-file-financial-reporting?orgId=1"
proxy_path: "/d/1c-file-finance/1c-file-financial-reporting?orgId=1"
external_enabled: true
- slug: "file1c-telemetry"
title: "1C Telemetry Board"
category: "Dashboards"
description: "Read-only telemetry экран по состоянию файловых баз, reglog и host."
target_url: "http://10.10.10.11:3000/d/1c-file-telemetry/1c-file-telemetry-board?orgId=1"
target_url: "http://192.0.2.11:3000/d/1c-file-telemetry/1c-file-telemetry-board?orgId=1"
proxy_path: "/d/1c-file-telemetry/1c-file-telemetry-board?orgId=1"
external_enabled: true
- slug: "grafana-1c"
title: "Grafana 1C"
category: "Dashboards"
description: "Рабочий file-1c dashboard contour в внешней Grafana."
target_url: "http://10.10.10.11:3000/d/1c-file-mgmt/1c-file-management-board?orgId=1"
target_url: "http://192.0.2.11:3000/d/1c-file-mgmt/1c-file-management-board?orgId=1"
proxy_path: "/d/1c-file-mgmt/1c-file-management-board?orgId=1"
external_enabled: true
- slug: "clickhouse-http"
title: "ClickHouse HTTP"
category: "Data"
description: "HTTP endpoint ClickHouse для file-1C analytics."
target_url: "http://10.10.10.2:8123/"
target_url: "http://192.0.2.2:8123/"
external_enabled: false
- slug: "influxdb"
title: "InfluxDB"
category: "Data"
description: "InfluxDB LXC на CT 200."
target_url: "http://10.10.10.10:8086/"
target_url: "http://192.0.2.10:8086/"
external_enabled: false
- slug: "grafana-core"
title: "Grafana Core"
category: "Dashboards"
description: "Отдельный Grafana CT 201."
target_url: "http://10.10.10.11:3000/"
target_url: "http://192.0.2.11:3000/"
proxy_path: "/dashboards"
external_enabled: true
- slug: "loki-alloy"
title: "Grafana Alloy"
category: "Logs"
description: "Web UI Alloy на CT 202."
target_url: "http://10.10.10.12:12345/"
target_url: "http://192.0.2.12:12345/"
external_enabled: false
- slug: "aw-ui"
title: "AW-rus UI"
category: "Operations"
description: "Основной ActivityWatch-Russian Web UI на CT 203."
target_url: "http://10.10.10.13:5600/"
target_url: "http://192.0.2.13:5600/"
proxy_path: "/r/aw/"
external_enabled: true
- slug: "aw-worktime"
title: "AW-rus Management Report"
category: "Operations"
description: "Управленческий worktime/report API на CT 203."
target_url: "http://10.10.10.13:5610/reports/worktime/management?format=html&host=SHARKON2025"
target_url: "http://192.0.2.13:5610/reports/worktime/management?format=html&host=HOST-EXAMPLE"
proxy_path: "/r/aw-worktime"
proxy_target_url: "http://10.10.10.13:5610/reports/worktime/management?format=html&host=SHARKON2025"
proxy_target_url: "http://192.0.2.13:5610/reports/worktime/management?format=html&host=HOST-EXAMPLE"
external_enabled: true
- slug: "detmir-portal"
title: "DetMir Portal"
+25 -25
View File
@@ -201,11 +201,11 @@
TELEGRAM_DEFAULT_CHAT_ID={{ tsj_bot_default_chat_id }}
HTTPS_PROXY={{ tsj_bot_https_proxy_url | default(tsj_bot_telegram_proxy_url | default('http://127.0.0.1:11090')) }}
HTTP_PROXY={{ tsj_bot_http_proxy_url | default(tsj_bot_telegram_proxy_url | default('http://127.0.0.1:11090')) }}
NO_PROXY={{ tsj_bot_no_proxy | default('localhost,127.0.0.1,10.10.10.0/24') }}
NODE_13_HOST={{ tsj_bot_node_13_host | default('10.10.10.13') }}
NODE_16_HOST={{ tsj_bot_node_16_host | default('10.10.10.16') }}
NODE_13_URL={{ tsj_bot_node_13_url | default('http://10.10.10.13:5600/api/0/info') }}
NODE_16_URL={{ tsj_bot_node_16_url | default('http://10.10.10.16/') }}
NO_PROXY={{ tsj_bot_no_proxy | default('localhost,127.0.0.1,192.0.2.0/24') }}
NODE_13_HOST={{ tsj_bot_node_13_host | default('192.0.2.13') }}
NODE_16_HOST={{ tsj_bot_node_16_host | default('192.0.2.16') }}
NODE_13_URL={{ tsj_bot_node_13_url | default('http://192.0.2.13:5600/api/0/info') }}
NODE_16_URL={{ tsj_bot_node_16_url | default('http://192.0.2.16/') }}
NODE_16_ENABLED={{ tsj_bot_node_16_enabled | default('false') }}
INFRA_ADMIN_ROOT={{ tsj_bot_runtime_root }}
CHECK_SCRIPT={{ tsj_bot_check_script | default(tsj_bot_runtime_root + '/scripts/system_self_support.sh --check') }}
@@ -254,24 +254,24 @@
UPDATE_TARGETS={{ tsj_bot_update_targets | default('auto') }}
DETMIR_AI_STATE_FILE={{ tsj_bot_detmir_ai_state_file | default('/var/lib/detmir-ai/latest-state.json') }}
TSJ_GUARDIAN_STATUS_BIN={{ tsj_bot_guardian_status_bin | default('/usr/local/bin/tsj-guardian-status') }}
AW_RUS_API_BASE={{ tsj_bot_aw_rus_api_base | default('http://10.10.10.13:5600/api/0') }}
AW_RUS_WORKTIME_BASE={{ tsj_bot_aw_rus_worktime_base | default('http://10.10.10.13:5610') }}
AW_DLP_POLICY_API_BASE={{ tsj_bot_aw_dlp_policy_api_base | default('http://10.10.10.13:5601/api/0') }}
AW_RUS_API_BASE={{ tsj_bot_aw_rus_api_base | default('http://192.0.2.13:5600/api/0') }}
AW_RUS_WORKTIME_BASE={{ tsj_bot_aw_rus_worktime_base | default('http://192.0.2.13:5610') }}
AW_DLP_POLICY_API_BASE={{ tsj_bot_aw_dlp_policy_api_base | default('http://192.0.2.13:5601/api/0') }}
AW_DLP_POLICY_ACTOR={{ tsj_bot_aw_dlp_policy_actor | default('tsj-guardian-bot') }}
AW_RUS_WORKTIME_HEAL_CMD={{ tsj_bot_aw_rus_worktime_heal_cmd | default("ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@10.10.10.13 'sudo -n /usr/local/bin/aw-worktime-autoheal-rust && sudo -n systemctl reset-failed aw-worktime-ui-bridge.service && sudo -n systemctl start aw-worktime-ui-bridge.service'") }}
AW_RUS_DLP_HEAL_CMD={{ tsj_bot_aw_rus_dlp_heal_cmd | default("ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@10.10.10.13 'sudo -n systemctl restart activitywatch-server.service && (sudo -n systemctl start activitywatch-dlp-aggregator.service || true) && sudo -n /usr/local/bin/aw-health-check && sudo -n /usr/local/bin/dlp-health-check'") }}
AW_RUS_CASE_API_BASE={{ tsj_bot_aw_rus_case_api_base | default('http://10.10.10.13:5602') }}
AW_RUS_WORKTIME_HEAL_CMD={{ tsj_bot_aw_rus_worktime_heal_cmd | default("ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@192.0.2.13 'sudo -n /usr/local/bin/aw-worktime-autoheal-rust && sudo -n systemctl reset-failed aw-worktime-ui-bridge.service && sudo -n systemctl start aw-worktime-ui-bridge.service'") }}
AW_RUS_DLP_HEAL_CMD={{ tsj_bot_aw_rus_dlp_heal_cmd | default("ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@192.0.2.13 'sudo -n systemctl restart activitywatch-server.service && (sudo -n systemctl start activitywatch-dlp-aggregator.service || true) && sudo -n /usr/local/bin/aw-health-check && sudo -n /usr/local/bin/dlp-health-check'") }}
AW_RUS_CASE_API_BASE={{ tsj_bot_aw_rus_case_api_base | default('http://192.0.2.13:5602') }}
AW_RUS_HAYABUSA_ENABLED={{ tsj_bot_aw_rus_hayabusa_enabled | default('true') }}
AW_RUS_HAYABUSA_SSH_CMD={{ tsj_bot_aw_rus_hayabusa_ssh_cmd | default("ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@10.10.10.13") }}
AW_RUS_HOST={{ tsj_bot_aw_rus_host | default('SHARKON2025') }}
AW_RUS_HAYABUSA_SSH_CMD={{ tsj_bot_aw_rus_hayabusa_ssh_cmd | default("ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@192.0.2.13") }}
AW_RUS_HOST={{ tsj_bot_aw_rus_host | default('HOST-EXAMPLE') }}
AW_RUS_PRIMARY_USER={{ tsj_bot_aw_rus_primary_user | default('USER1') }}
AW_RUS_STALE_SEC={{ tsj_bot_aw_rus_stale_sec | default(900) }}
AW_RUS_SLO_ENABLED={{ tsj_bot_aw_rus_slo_enabled | default('true') }}
AW_RUS_SLO_ALERT_WINDOW={{ tsj_bot_aw_rus_slo_alert_window | default('24h') }}
AW_RUS_SLO_MIN_SAMPLES={{ tsj_bot_aw_rus_slo_min_samples | default(4) }}
AW_RUS_SLO_MAX_AGE_SEC={{ tsj_bot_aw_rus_slo_max_age_sec | default(90) }}
AW_RUS_SLO_SUMMARY_CMD={{ tsj_bot_aw_rus_slo_summary_cmd | default("ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@10.10.10.13 'cat /var/lib/activitywatch/slo/aw-slo-summary.json'") }}
AW_RUS_WINDOWS_HOST={{ tsj_bot_aw_rus_windows_host | default(hostvars[(groups['aw_windows'] | first)].ansible_host | default('192.168.100.18')) }}
AW_RUS_SLO_SUMMARY_CMD={{ tsj_bot_aw_rus_slo_summary_cmd | default("ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@192.0.2.13 'cat /var/lib/activitywatch/slo/aw-slo-summary.json'") }}
AW_RUS_WINDOWS_HOST={{ tsj_bot_aw_rus_windows_host | default(hostvars[(groups['aw_windows'] | first)].ansible_host | default('198.51.100.18')) }}
AW_RUS_WINDOWS_SSH_USER={{ tsj_bot_aw_rus_windows_ssh_user | default(hostvars[(groups['aw_windows'] | first)].ansible_user | default('Администратор')) }}
AW_RUS_WINDOWS_SSH_PASSWORD={{ tsj_bot_aw_rus_windows_ssh_password | default(hostvars[(groups['aw_windows'] | first)].ansible_password | default('')) }}
AW_RUS_WINDOWS_CONFIG_PATH={{ tsj_bot_aw_rus_windows_config_path | default('C:\ProgramData\AWatch-rus\deployment-config.json') }}
@@ -301,24 +301,24 @@
group: "{{ tsj_bot_group }}"
mode: "0640"
loop:
- { key: "AW_RUS_API_BASE", value: "{{ tsj_bot_aw_rus_api_base | default('http://10.10.10.13:5600/api/0') }}" }
- { key: "AW_RUS_WORKTIME_BASE", value: "{{ tsj_bot_aw_rus_worktime_base | default('http://10.10.10.13:5610') }}" }
- { key: "AW_DLP_POLICY_API_BASE", value: "{{ tsj_bot_aw_dlp_policy_api_base | default('http://10.10.10.13:5601/api/0') }}" }
- { key: "AW_RUS_API_BASE", value: "{{ tsj_bot_aw_rus_api_base | default('http://192.0.2.13:5600/api/0') }}" }
- { key: "AW_RUS_WORKTIME_BASE", value: "{{ tsj_bot_aw_rus_worktime_base | default('http://192.0.2.13:5610') }}" }
- { key: "AW_DLP_POLICY_API_BASE", value: "{{ tsj_bot_aw_dlp_policy_api_base | default('http://192.0.2.13:5601/api/0') }}" }
- { key: "AW_DLP_POLICY_ACTOR", value: "{{ tsj_bot_aw_dlp_policy_actor | default('tsj-guardian-bot') }}" }
- { key: "AW_RUS_WORKTIME_HEAL_CMD", value: "{{ tsj_bot_aw_rus_worktime_heal_cmd | default(\"ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@10.10.10.13 'sudo -n /usr/local/bin/aw-worktime-autoheal-rust && sudo -n systemctl reset-failed aw-worktime-ui-bridge.service && sudo -n systemctl start aw-worktime-ui-bridge.service'\") }}" }
- { key: "AW_RUS_DLP_HEAL_CMD", value: "{{ tsj_bot_aw_rus_dlp_heal_cmd | default(\"ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@10.10.10.13 'sudo -n systemctl restart activitywatch-server.service && (sudo -n systemctl start activitywatch-dlp-aggregator.service || true) && sudo -n /usr/local/bin/aw-health-check && sudo -n /usr/local/bin/dlp-health-check'\") }}" }
- { key: "AW_RUS_CASE_API_BASE", value: "{{ tsj_bot_aw_rus_case_api_base | default('http://10.10.10.13:5602') }}" }
- { key: "AW_RUS_WORKTIME_HEAL_CMD", value: "{{ tsj_bot_aw_rus_worktime_heal_cmd | default(\"ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@192.0.2.13 'sudo -n /usr/local/bin/aw-worktime-autoheal-rust && sudo -n systemctl reset-failed aw-worktime-ui-bridge.service && sudo -n systemctl start aw-worktime-ui-bridge.service'\") }}" }
- { key: "AW_RUS_DLP_HEAL_CMD", value: "{{ tsj_bot_aw_rus_dlp_heal_cmd | default(\"ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@192.0.2.13 'sudo -n systemctl restart activitywatch-server.service && (sudo -n systemctl start activitywatch-dlp-aggregator.service || true) && sudo -n /usr/local/bin/aw-health-check && sudo -n /usr/local/bin/dlp-health-check'\") }}" }
- { key: "AW_RUS_CASE_API_BASE", value: "{{ tsj_bot_aw_rus_case_api_base | default('http://192.0.2.13:5602') }}" }
- { key: "AW_RUS_HAYABUSA_ENABLED", value: "{{ tsj_bot_aw_rus_hayabusa_enabled | default('true') }}" }
- { key: "AW_RUS_HAYABUSA_SSH_CMD", value: "{{ tsj_bot_aw_rus_hayabusa_ssh_cmd | default(\"ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@10.10.10.13\") }}" }
- { key: "AW_RUS_HOST", value: "{{ tsj_bot_aw_rus_host | default('SHARKON2025') }}" }
- { key: "AW_RUS_HAYABUSA_SSH_CMD", value: "{{ tsj_bot_aw_rus_hayabusa_ssh_cmd | default(\"ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@192.0.2.13\") }}" }
- { key: "AW_RUS_HOST", value: "{{ tsj_bot_aw_rus_host | default('HOST-EXAMPLE') }}" }
- { key: "AW_RUS_PRIMARY_USER", value: "{{ tsj_bot_aw_rus_primary_user | default('USER1') }}" }
- { key: "AW_RUS_STALE_SEC", value: "{{ tsj_bot_aw_rus_stale_sec | default(900) }}" }
- { key: "AW_RUS_SLO_ENABLED", value: "{{ tsj_bot_aw_rus_slo_enabled | default('true') }}" }
- { key: "AW_RUS_SLO_ALERT_WINDOW", value: "{{ tsj_bot_aw_rus_slo_alert_window | default('24h') }}" }
- { key: "AW_RUS_SLO_MIN_SAMPLES", value: "{{ tsj_bot_aw_rus_slo_min_samples | default(4) }}" }
- { key: "AW_RUS_SLO_MAX_AGE_SEC", value: "{{ tsj_bot_aw_rus_slo_max_age_sec | default(90) }}" }
- { key: "AW_RUS_SLO_SUMMARY_CMD", value: "{{ tsj_bot_aw_rus_slo_summary_cmd | default(\"ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@10.10.10.13 'cat /var/lib/activitywatch/slo/aw-slo-summary.json'\") }}" }
- { key: "AW_RUS_WINDOWS_HOST", value: "{{ tsj_bot_aw_rus_windows_host | default(hostvars[(groups['aw_windows'] | first)].ansible_host | default('192.168.100.18')) }}" }
- { key: "AW_RUS_SLO_SUMMARY_CMD", value: "{{ tsj_bot_aw_rus_slo_summary_cmd | default(\"ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new igor@192.0.2.13 'cat /var/lib/activitywatch/slo/aw-slo-summary.json'\") }}" }
- { key: "AW_RUS_WINDOWS_HOST", value: "{{ tsj_bot_aw_rus_windows_host | default(hostvars[(groups['aw_windows'] | first)].ansible_host | default('198.51.100.18')) }}" }
- { key: "AW_RUS_WINDOWS_SSH_USER", value: "{{ tsj_bot_aw_rus_windows_ssh_user | default(hostvars[(groups['aw_windows'] | first)].ansible_user | default('Администратор')) }}" }
- { key: "AW_RUS_WINDOWS_SSH_PASSWORD", value: "{{ tsj_bot_aw_rus_windows_ssh_password | default(hostvars[(groups['aw_windows'] | first)].ansible_password | default('')) }}" }
- { key: "AW_RUS_WINDOWS_CONFIG_PATH", value: "{{ tsj_bot_aw_rus_windows_config_path | default('C:\\ProgramData\\AWatch-rus\\deployment-config.json') }}" }
+4 -4
View File
@@ -16,19 +16,19 @@ aw_worktime_influx_enabled: false
aw_worktime_influx_url: "http://<INFLUXDB_HOST>:8086"
aw_worktime_influx_org: "proxmox"
aw_worktime_influx_bucket: "aw_metrics"
aw_worktime_influx_hosts: "SHARKON2025"
aw_worktime_influx_hosts: "HOST-EXAMPLE"
aw_worktime_influx_days: "today,yesterday"
aw_worktime_influx_token: ""
aw_dlp_influx_enabled: false
aw_dlp_influx_url: "http://<INFLUXDB_HOST>:8086"
aw_dlp_influx_org: "proxmox"
aw_dlp_influx_bucket: "aw_metrics"
aw_dlp_influx_hosts: "SHARKON2025"
aw_dlp_influx_hosts: "HOST-EXAMPLE"
aw_dlp_influx_lookback_days: 30
aw_dlp_influx_event_limit: 2000
aw_dlp_influx_token: ""
aw_monitored_windows_host: "<WINDOWS_HOST>"
aw_monitored_windows_hostname: "SHARKON2025"
aw_monitored_windows_hostname: "HOST-EXAMPLE"
aw_rus_health_worktime_api_base: "http://127.0.0.1:5610"
aw_rus_health_state_dir: "{{ aw_server_data_dir }}/health"
aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation"
@@ -67,7 +67,7 @@ aw_worktime_from: "00:00"
aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
aw_server_always_active_pattern: "aw-watcher-window"
aw_server_landingpage: "/#/activity/SHARKON2025/view/"
aw_server_landingpage: "/#/activity/HOST-EXAMPLE/view/"
aw_health_strict_fileops: 0
aw_dlp_policy_engine_enabled: true
+7 -7
View File
@@ -13,22 +13,22 @@ aw_server_public_host: "{{ (hostvars[aw_server_inventory_host].ansible_host | de
aw_worktime_report_base: "http://{{ aw_server_public_host }}:5610"
aw_worktime_timezone: "Europe/Moscow"
aw_worktime_influx_enabled: true
aw_worktime_influx_url: "http://10.10.10.10:8086"
aw_worktime_influx_url: "http://192.0.2.10:8086"
aw_worktime_influx_org: "proxmox"
aw_worktime_influx_bucket: "aw_metrics"
aw_worktime_influx_hosts: "SHARKON2025"
aw_worktime_influx_hosts: "HOST-EXAMPLE"
aw_worktime_influx_days: "today,yesterday"
aw_worktime_influx_token: "{{ lookup('env', 'AW_WORKTIME_INFLUX_TOKEN') }}"
aw_dlp_influx_enabled: true
aw_dlp_influx_url: "http://10.10.10.10:8086"
aw_dlp_influx_url: "http://192.0.2.10:8086"
aw_dlp_influx_org: "proxmox"
aw_dlp_influx_bucket: "aw_metrics"
aw_dlp_influx_hosts: "SHARKON2025"
aw_dlp_influx_hosts: "HOST-EXAMPLE"
aw_dlp_influx_lookback_days: 30
aw_dlp_influx_event_limit: 2000
aw_dlp_influx_token: "{{ lookup('env', 'AW_DLP_INFLUX_TOKEN') }}"
aw_monitored_windows_host: "192.168.100.18"
aw_monitored_windows_hostname: "SHARKON2025"
aw_monitored_windows_host: "198.51.100.18"
aw_monitored_windows_hostname: "HOST-EXAMPLE"
aw_rus_health_worktime_api_base: "http://127.0.0.1:5610"
aw_rus_health_state_dir: "{{ aw_server_data_dir }}/health"
aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation"
@@ -74,5 +74,5 @@ aw_worktime_from: "00:00"
aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
aw_server_always_active_pattern: "aw-watcher-window"
aw_server_landingpage: "/#/activity/SHARKON2025/view/"
aw_server_landingpage: "/#/activity/HOST-EXAMPLE/view/"
aw_health_strict_fileops: 0
+1 -1
View File
@@ -14,7 +14,7 @@ aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
aw_windows_domain: "HOST-EXAMPLE"
aw_windows_builtin_administrator_name: "Администратор"
aw_windows_users:
- Администратор
+1 -1
View File
@@ -54,7 +54,7 @@ tsj_bot_aw_rus_dlp_heal_cmd: "sshpass -p 'CHANGE_ME' ssh -o PubkeyAuthentication
tsj_bot_aw_rus_case_api_base: "http://<AW_SERVER_HOST>:5602"
tsj_bot_aw_rus_hayabusa_enabled: "true"
tsj_bot_aw_rus_hayabusa_ssh_cmd: "sshpass -p 'CHANGE_ME' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@<AW_SERVER_HOST>"
tsj_bot_aw_rus_host: "SHARKON2025"
tsj_bot_aw_rus_host: "HOST-EXAMPLE"
tsj_bot_aw_rus_primary_user: "USER1"
tsj_bot_aw_rus_stale_sec: 900
tsj_bot_aw_rus_slo_enabled: "true"
+2 -2
View File
@@ -12,7 +12,7 @@ aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload"
aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
aw_windows_domain: "HOST-EXAMPLE"
# Localized name of the built-in local Administrator account (SID ending in -500).
# On the current Russian Windows host this must stay "Администратор";
# do not replace it with "Administrator" unless the target OS account is actually named that way.
@@ -32,7 +32,7 @@ aw_windows_extra_users: []
# Единые Windows/RDP пути: те же, что использует InnoSetup.
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
aw_windows_hostname_override: "" # Например: SHARKON2025
aw_windows_hostname_override: "" # Например: HOST-EXAMPLE
aw_windows_afk_enabled: true
aw_windows_window_enabled: true
aw_windows_file_ops_enabled: true
+2 -2
View File
@@ -1,5 +1,5 @@
[proxmox]
pve-main ansible_host=192.168.10.2 ansible_user=root ansible_port=22
pve-main ansible_host=198.51.100.2 ansible_user=root ansible_port=22
[aw_server]
aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22
@@ -9,7 +9,7 @@ aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22
win-node1 ansible_host=<WINDOWS_HOST> ansible_user=Администратор ansible_password=CHANGE_ME ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore
[aw_pfsense_pollers]
# pfsense-poller1 ansible_host=192.168.100.30 ansible_user=root ansible_port=22
# pfsense-poller1 ansible_host=198.51.100.30 ansible_user=root ansible_port=22
[grafana]
# Для API-import playbook достаточно указать grafana_url.
+5 -5
View File
@@ -127,7 +127,7 @@
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl ca-certificates bash unzip xz-utils jq rsync openssh-server python3
mkdir -p /root/bootstrap/settings /etc/activitywatch
mkdir -p /opt/detmir/bootstrap/settings /etc/activitywatch
systemctl enable ssh || true
systemctl restart ssh || true
register: ct_bootstrap_result
@@ -142,7 +142,7 @@
- push
- "{{ ct_id }}"
- "{{ proxmox_bootstrap_dir }}/{{ item }}"
- "/root/bootstrap/{{ item }}"
- "/opt/detmir/bootstrap/{{ item }}"
loop: "{{ aw_bootstrap_files }}"
- name: Записать AW server env во временный каталог Proxmox host
@@ -196,9 +196,9 @@
- -lc
- |
set -euo pipefail
chmod +x /root/bootstrap/install_aw_server.sh /root/bootstrap/apply_webui_ru_patch.sh
bash /root/bootstrap/install_aw_server.sh
bash /root/bootstrap/apply_webui_ru_patch.sh
chmod +x /opt/detmir/bootstrap/install_aw_server.sh /opt/detmir/bootstrap/apply_webui_ru_patch.sh
bash /opt/detmir/bootstrap/install_aw_server.sh
bash /opt/detmir/bootstrap/apply_webui_ru_patch.sh
systemctl restart activitywatch-server.service
- name: Проверить AW API изнутри CT
@@ -132,7 +132,7 @@
<h1>Proxmox Web Gateway</h1>
<p class="lead">
Единая стартовая точка для web-сервисов контура на Proxmox host
<strong>10.10.10.2</strong>. Внешний вход закрыт gateway-auth,
<strong>192.0.2.2</strong>. Внешний вход закрыт gateway-auth,
а основные операторские страницы идут через reverse proxy без выдачи
внутренних адресов наружу.
</p>
+34 -34
View File
@@ -6,7 +6,7 @@ map $http_upgrade $connection_upgrade {
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name {{ proxmox_web_gateway_public_hostname }} 10.10.10.2 <WAN_OR_EXTERNAL_IP> _;
server_name {{ proxmox_web_gateway_public_hostname }} 192.0.2.2 <WAN_OR_EXTERNAL_IP> _;
access_log /var/log/nginx/proxmox-web-gateway.access.log;
error_log /var/log/nginx/proxmox-web-gateway.error.log;
@@ -17,7 +17,7 @@ server {
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name {{ proxmox_web_gateway_public_hostname }} 10.10.10.2 <WAN_OR_EXTERNAL_IP> _;
server_name {{ proxmox_web_gateway_public_hostname }} 192.0.2.2 <WAN_OR_EXTERNAL_IP> _;
access_log /var/log/nginx/proxmox-web-gateway.access.log;
error_log /var/log/nginx/proxmox-web-gateway.error.log;
@@ -67,32 +67,32 @@ server {
location /api/0/ {
proxy_set_header Origin "";
proxy_set_header Authorization "";
proxy_pass http://10.10.10.13:5600;
proxy_pass http://192.0.2.13:5600;
proxy_redirect off;
}
location /r/grafana/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000/;
proxy_redirect http://10.10.10.11:3000/ /r/grafana/;
proxy_pass http://192.0.2.11:3000/;
proxy_redirect http://192.0.2.11:3000/ /r/grafana/;
}
location = /login {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location = /logout {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /public/ {
proxy_set_header Origin "";
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
@@ -103,138 +103,138 @@ server {
proxy_set_header Authorization "";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /api/ {
proxy_set_header Origin "";
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /apis/ {
proxy_set_header Origin "";
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /d/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /dashboards {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /dashboard/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /avatar/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /profile/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /org/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /user/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /plugins/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /explore {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /alerting/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /connections/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /datasources/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.11:3000;
proxy_pass http://192.0.2.11:3000;
proxy_redirect off;
}
location /r/aw/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.13:5600/;
proxy_redirect http://10.10.10.13:5600/ /r/aw/;
proxy_pass http://192.0.2.13:5600/;
proxy_redirect http://192.0.2.13:5600/ /r/aw/;
}
location /reports/worktime/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.13:5610;
proxy_pass http://192.0.2.13:5610;
proxy_redirect off;
}
location = /dark.css {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.13:5600;
proxy_pass http://192.0.2.13:5600;
proxy_redirect off;
}
location /css/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.13:5600;
proxy_pass http://192.0.2.13:5600;
proxy_redirect off;
}
location = /js/aw-worktime-panel.js {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.13:5600;
proxy_pass http://192.0.2.13:5600;
proxy_redirect off;
sub_filter_once off;
sub_filter_types application/javascript text/javascript;
sub_filter 'http://10.10.10.13:5610' '';
sub_filter 'http://192.0.2.13:5610' '';
}
location /js/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.13:5600;
proxy_pass http://192.0.2.13:5600;
proxy_redirect off;
}
location /img/ {
proxy_set_header Authorization "";
proxy_pass http://10.10.10.13:5600;
proxy_pass http://192.0.2.13:5600;
proxy_redirect off;
}
@@ -242,7 +242,7 @@ server {
auth_basic off;
proxy_set_header Origin "";
proxy_set_header Authorization "";
proxy_pass http://10.10.10.13:5600;
proxy_pass http://192.0.2.13:5600;
proxy_redirect off;
}
@@ -264,7 +264,7 @@ server {
location ^~ /portal/api/dlp/evidence {
proxy_set_header Authorization "";
proxy_set_header X-Remote-User $remote_user;
proxy_pass http://10.10.10.13:8721/api/dlp/evidence;
proxy_pass http://192.0.2.13:8721/api/dlp/evidence;
proxy_redirect off;
}
+4 -4
View File
@@ -14,10 +14,10 @@ SERVER_PUBLIC_HOST="${AW_SERVER_PUBLIC_HOST:-${AW_SERVER_HOST:-$(hostname -f 2>/
REPORT_BASE="${AW_WORKTIME_REPORT_BASE:-http://${SERVER_PUBLIC_HOST}:5610}"
CASE_PORT="${AW_DLP_CASE_PORT:-5602}"
CASE_BASE="${AW_DLP_CASE_PUBLIC_BASE:-}"
PATCH_JS_SRC="/root/bootstrap/aw-ru-patch.js"
SW_CLEANUP_SRC="/root/bootstrap/aw-sw-cleanup.js"
WORKTIME_PANEL_SRC="/root/bootstrap/aw-worktime-panel.js"
HOST_GROUPS_SRC="/root/bootstrap/aw-host-groups.json"
PATCH_JS_SRC="/opt/detmir/bootstrap/aw-ru-patch.js"
SW_CLEANUP_SRC="/opt/detmir/bootstrap/aw-sw-cleanup.js"
WORKTIME_PANEL_SRC="/opt/detmir/bootstrap/aw-worktime-panel.js"
HOST_GROUPS_SRC="/opt/detmir/bootstrap/aw-host-groups.json"
INDEX_HTML="$WEBUI_DIR/index.html"
SERVICE_WORKER="$WEBUI_DIR/service-worker.js"
TS=$(date +%Y%m%d%H%M%S)
+3 -3
View File
@@ -43,10 +43,10 @@ AW_HEALTH_CHECK_ENABLED=true
AW_HEALTH_CHECK_INTERVAL=60
AW_EXPECT_START_OF_DAY=00:00
AW_EXPECT_ALWAYS_ACTIVE_PATTERN=aw-watcher-window
AW_EXPECT_LANDINGPAGE=/#/activity/SHARKON2025/view/
AW_EXPECT_LANDINGPAGE=/#/activity/HOST-EXAMPLE/view/
AW_HEALTH_STRICT_FILEOPS=0
AW_MONITORED_WINDOWS_HOST=<WINDOWS_HOST>
AW_MONITORED_WINDOWS_HOSTNAME=SHARKON2025
AW_MONITORED_WINDOWS_HOSTNAME=HOST-EXAMPLE
AW_RUS_HEALTH_WORKTIME_API=http://127.0.0.1:5610
AW_RUS_HEALTH_STATE_DIR=/var/lib/activitywatch/health
AW_RUS_HEALTH_VALIDATION_DIR=/var/lib/activitywatch/health/windows-validation
@@ -58,7 +58,7 @@ AW_RUS_SLO_WORKTIME_BASE=http://127.0.0.1:5610
AW_RUS_SLO_TARGET_PERCENT=99.97
AW_BROWSER_SMOKE_AW_BASE=http://127.0.0.1:5600
AW_BROWSER_SMOKE_WORKTIME_BASE=http://127.0.0.1:5610
AW_BROWSER_SMOKE_HOST=SHARKON2025
AW_BROWSER_SMOKE_HOST=HOST-EXAMPLE
AW_BROWSER_SMOKE_OUTPUT_DIR=/var/lib/activitywatch/browser-smoke
AW_BROWSER_SMOKE_KEEP_RUNS=24
AW_BROWSER_SMOKE_ENGINE=chromium-cli
+2 -2
View File
@@ -1,12 +1,12 @@
[Unit]
Description=AW Worktime Autoheal (SHARKON2025)
Description=AW Worktime Autoheal (HOST-EXAMPLE)
After=network-online.target activitywatch-server.service
Wants=network-online.target
[Service]
Type=oneshot
Environment=AW_URL=http://127.0.0.1:5600
Environment=AW_WORKTIME_HOST=SHARKON2025
Environment=AW_WORKTIME_HOST=HOST-EXAMPLE
ExecStart=/usr/local/bin/aw-worktime-autoheal-rust
User=root
Group=root
+1 -1
View File
@@ -6,7 +6,7 @@ WORKTIME_PREWARM_TIMEOUT_SECONDS="${WORKTIME_PREWARM_TIMEOUT_SECONDS:-45}"
WORKTIME_PREWARM_HEALTH_TIMEOUT_SECONDS="${WORKTIME_PREWARM_HEALTH_TIMEOUT_SECONDS:-10}"
WORKTIME_PREWARM_READY_TIMEOUT_SECONDS="${WORKTIME_PREWARM_READY_TIMEOUT_SECONDS:-60}"
WORKTIME_PREWARM_READY_INTERVAL_SECONDS="${WORKTIME_PREWARM_READY_INTERVAL_SECONDS:-2}"
WORKTIME_PREWARM_HOST="${WORKTIME_PREWARM_HOST:-${AW_WORKTIME_HOST:-SHARKON2025}}"
WORKTIME_PREWARM_HOST="${WORKTIME_PREWARM_HOST:-${AW_WORKTIME_HOST:-HOST-EXAMPLE}}"
WORKTIME_PREWARM_PROFILE="${WORKTIME_PREWARM_PROFILE:-full}"
log() {
+1 -1
View File
@@ -8,7 +8,7 @@ StartLimitIntervalSec=120
[Service]
Type=simple
Environment=AW_SERVER_URL=http://127.0.0.1:5600
Environment=AW_WORKTIME_HOST=SHARKON2025
Environment=AW_WORKTIME_HOST=HOST-EXAMPLE
ExecStart=/usr/local/bin/aw-worktime-ui-bridge-rust
Restart=on-failure
RestartSec=10
+6 -6
View File
@@ -1,6 +1,6 @@
# aw-rus Hayabusa Server Ops Bundle
This directory is the server-side operational bundle for Hayabusa on `10.10.10.13`.
This directory is the server-side operational bundle for Hayabusa on `192.0.2.13`.
## Goal
@@ -44,7 +44,7 @@ Prerequisites:
- `/opt/activitywatch/aw-rus-ops/venv` contains `ansible` and `pywinrm`
- `/opt/activitywatch/aw-rus-ops/ansible/inventory.ini` contains the live Windows connection details
- WinRM from `10.10.10.13` to the Windows host is reachable
- WinRM from `192.0.2.13` to the Windows host is reachable
Run:
@@ -55,14 +55,14 @@ aw-hayabusa-from-windows --days-back 1 --mode incident --case-id 30
This performs:
- Windows EVTX export via WinRM
- fetch of the newest zip directly onto `10.10.10.13`
- fetch of the newest zip directly onto `192.0.2.13`
- `aw-hayabusa accept`
- `aw-hayabusa process-inbox`
- bounded case linkage via case API
If WinRM from the server to Windows is blocked by network policy, use the drop-zone workflow below instead.
## Drop-zone automation on 10.10.10.13
## Drop-zone automation on 192.0.2.13
The server can auto-process packages dropped into:
@@ -95,9 +95,9 @@ powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-up
- run `C:\ProgramData\AWatch-rus\export-evtx-for-hayabusa.ps1`
- upload matching `.caseid` first when `-CaseId` is specified
- upload the newest zip to `/opt/activitywatch/aw-rus-ops/drop`
- let `aw-hayabusa-drop.path` process the package automatically on `10.10.10.13`
- let `aw-hayabusa-drop.path` process the package automatically on `192.0.2.13`
This path was validated live against case `30` after the `awops` SSH authorization was installed on `10.10.10.13`.
This path was validated live against case `30` after the `awops` SSH authorization was installed on `192.0.2.13`.
Server-side prerequisite for user `awops`:
+2 -2
View File
@@ -21,7 +21,7 @@ required_vars=(
AW_SERVER_GROUP
)
BOOTSTRAP_DIR="/root/bootstrap"
BOOTSTRAP_DIR="/opt/detmir/bootstrap"
VIEWS_JSON="$BOOTSTRAP_DIR/settings/views-default.json"
CLASSES_JSON="$BOOTSTRAP_DIR/settings/classes-worktime.json"
WORKTIME_API_RUST_SRC="$BOOTSTRAP_DIR/worktime-api"
@@ -91,7 +91,7 @@ sed \
-e "s#__AW_SERVER_USER__#$AW_SERVER_USER#g" \
-e "s#__AW_SERVER_GROUP__#$AW_SERVER_GROUP#g" \
-e "s#__AW_SERVER_DATA_DIR__#$AW_SERVER_DATA_DIR#g" \
/root/bootstrap/activitywatch-server.service > /etc/systemd/system/activitywatch-server.service
/opt/detmir/bootstrap/activitywatch-server.service > /etc/systemd/system/activitywatch-server.service
chmod 0644 /etc/systemd/system/activitywatch-server.service
systemctl daemon-reload
@@ -1,6 +1,6 @@
{
"users": {
"SHARKON2025\\USER1": {
"HOST-EXAMPLE\\USER1": {
"display_name": "Иван Петров",
"manager": "Руководитель смены",
"department": "Бухгалтерия",
@@ -8,14 +8,14 @@
"notes": "Дневная смена",
"exclude": false
},
"SHARKON2025\\USER4": {
"HOST-EXAMPLE\\USER4": {
"display_name": "Мария Сидорова",
"manager": "Руководитель смены",
"department": "Бухгалтерия",
"role": "Оператор 1С",
"exclude": false
},
"SHARKON2025\\ADMINISTRATOR": {
"HOST-EXAMPLE\\ADMINISTRATOR": {
"display_name": "Администратор",
"manager": "Ops",
"department": "Инфраструктура",
+11 -11
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# check-aw-full.sh - Полная проверка ActivityWatch: сервер + RDP-хост
# Сервер: 10.10.10.13:5600
# RDP-хост: 192.168.100.18 (SHARKON2025)
# Сервер: 192.0.2.13:5600
# RDP-хост: 198.51.100.18 (HOST-EXAMPLE)
if [[ "${CHECK_AW_FULL_FORCE_LEGACY:-0}" != "1" ]]; then
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -16,9 +16,9 @@ if [[ "${CHECK_AW_FULL_FORCE_LEGACY:-0}" != "1" ]]; then
done
fi
SERVER="http://10.10.10.13:5600"
HOSTNAME_FILTER="SHARKON2025"
RDP_HOST="192.168.100.18"
SERVER="http://192.0.2.13:5600"
HOSTNAME_FILTER="HOST-EXAMPLE"
RDP_HOST="198.51.100.18"
NOW=$(date -u +%s)
HOST_INACTIVE=false
GUARD_HEALTHY=false
@@ -100,7 +100,7 @@ echo ""
# 1. Проверка сервера
echo -e "${CYAN}--- 1. AW Server ($SERVER) ---${NC}"
echo -n " Connectivity... "
RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/info" 2>&1)
RESP=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/info" 2>&1)
if [ $? -eq 0 ] && echo "$RESP" | jq -e '.version' > /dev/null 2>&1; then
VERSION=$(echo "$RESP" | jq -r '.version')
echo -e " ${GREEN}OK${NC} (aw-server $VERSION)"
@@ -110,7 +110,7 @@ else
fi
echo -n " CORS... "
CORS_RESP=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' -H "Origin: http://10.10.10.13:5600" "$SERVER/api/0/settings/" 2>&1)
CORS_RESP=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' -H "Origin: http://192.0.2.13:5600" "$SERVER/api/0/settings/" 2>&1)
if [ "$CORS_RESP" = "200" ]; then
echo -e "${GREEN}OK${NC}"
else
@@ -119,7 +119,7 @@ fi
echo ""
# 1b. Context for inactive/event-driven classification
WORKTIME_EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/aw-worktime-sessions_$HOSTNAME_FILTER/events?limit=1" 2>&1)
WORKTIME_EVENT_DATA=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/aw-worktime-sessions_$HOSTNAME_FILTER/events?limit=1" 2>&1)
WORKTIME_TS=$(echo "$WORKTIME_EVENT_DATA" | jq -r '.[0].timestamp // ""' 2>/dev/null)
WORKTIME_ACTIVE=$(echo "$WORKTIME_EVENT_DATA" | jq -r '.[0].data.active // false' 2>/dev/null)
if [ -n "$WORKTIME_TS" ]; then
@@ -132,7 +132,7 @@ if [ -n "$WORKTIME_TS" ]; then
fi
fi
GUARD_EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/aw-rus-collector-guard_$HOSTNAME_FILTER/events?limit=1" 2>&1)
GUARD_EVENT_DATA=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/aw-rus-collector-guard_$HOSTNAME_FILTER/events?limit=1" 2>&1)
GUARD_TS=$(echo "$GUARD_EVENT_DATA" | jq -r '.[0].timestamp // ""' 2>/dev/null)
GUARD_STATUS=$(echo "$GUARD_EVENT_DATA" | jq -r '.[0].data.status // ""' 2>/dev/null)
GUARD_PROBLEMS=$(echo "$GUARD_EVENT_DATA" | jq -r '([.[0].data.problems[]?] | length) // 0' 2>/dev/null)
@@ -167,7 +167,7 @@ for entry in "${BUCKETS[@]}"; do
label="${entry##*|}"
bucket_full="${bucket}_${HOSTNAME_FILTER}"
EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
EVENT_DATA=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
LAST_ID=$(echo "$EVENT_DATA" | jq '.[0].id // 0')
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
@@ -231,7 +231,7 @@ DEAD_COUNT=0
for entry in "${BUCKETS[@]}"; do
bucket="${entry%%|*}"
bucket_full="${bucket}_${HOSTNAME_FILTER}"
EVENT_DATA=$(no_proxy=10.10.10.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
EVENT_DATA=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then
+1 -1
View File
@@ -7,7 +7,7 @@ CLICKHOUSE_HOST=clickhouse
AW_1C_CLICKHOUSE_RUNTIME_HOST=127.0.0.1
AW_1C_COMPANY_API_HOST=127.0.0.1
AW_1C_COMPANY_API_PORT=8710
AW_1C_MANAGER_BRIEF_GRAFANA_URL=http://10.10.10.11:3000/dashboards/f/file-1c/?orgId=1
AW_1C_MANAGER_BRIEF_GRAFANA_URL=http://192.0.2.11:3000/dashboards/f/file-1c/?orgId=1
AW_1C_COMPANY_LOOKBACK_DAYS=30
AW_1C_COMPANY_MIN_DAYS=1
AW_1C_COMPANY_HORIZONS=7,30
+1 -1
View File
@@ -156,7 +156,7 @@ python ai/generate_manager_brief.py --host localhost --port 8123 --user default
Этот слой делает не raw LLM-чат, а промышленный pipeline:
- строит компактный context из `v_company_portfolio_overview`;
- вызывает локальный `codex exec` на `10.10.10.2`;
- вызывает локальный `codex exec` на `192.0.2.2`;
- валидирует structured output по JSON schema;
- при сбое `codex` отдаёт deterministic fallback, чтобы контур не пустел;
- пишет `latest.json` и `latest.md` в `state/manager-brief/`.
+1 -1
View File
@@ -459,7 +459,7 @@ def build_company_recovery_context(summary_payload: dict[str, Any], infobase: st
def grafana_company_dashboard_url() -> str:
return os.getenv(
"AW_1C_MANAGER_BRIEF_GRAFANA_URL",
"http://10.10.10.11:3000/d/1c-file-mgmt/1c-file-management-board",
"http://192.0.2.11:3000/d/1c-file-mgmt/1c-file-management-board",
)
@@ -17,7 +17,7 @@
},
{
"title": "Manager Brief",
"url": "http://10.10.10.2:8710/manager/brief"
"url": "http://192.0.2.2:8710/manager/brief"
}
],
"panels": [
@@ -9,19 +9,19 @@
"links": [
{
"title": "Manager Brief",
"url": "http://10.10.10.2:8710/manager/brief"
"url": "http://192.0.2.2:8710/manager/brief"
},
{
"title": "Manager Actions",
"url": "http://10.10.10.2:8710/manager/actions"
"url": "http://192.0.2.2:8710/manager/actions"
},
{
"title": "Weekly Digest",
"url": "http://10.10.10.2:8710/manager/digest/weekly"
"url": "http://192.0.2.2:8710/manager/digest/weekly"
},
{
"title": "Recovery",
"url": "http://10.10.10.2:8710/manager/recovery"
"url": "http://192.0.2.2:8710/manager/recovery"
},
{
"title": "Company Intelligence",
@@ -21,7 +21,7 @@
},
{
"title": "Manager Brief",
"url": "http://10.10.10.2:8710/manager/brief"
"url": "http://192.0.2.2:8710/manager/brief"
}
],
"panels": [
+5 -5
View File
@@ -8,14 +8,14 @@ INSERT INTO analytics_1c.postings (ts, infobase, registrar, operation_type, acco
('2026-05-21 10:16:00','ФЕЛИЦТ ГРУПП 2026','DOC-3','adjustment','91.02','62.01',54000.00,'demo-postings.jsonl');
INSERT INTO analytics_1c.reglog_events (ts, infobase, user, host, app, event_name, level, duration_ms, message, source_file) VALUES
('2026-05-21 07:15:00','ТРАНСГАЗ 2026','USER1','SHARKON2025','1cv8c','Login','warn',0,'Вход вне рабочего времени','demo-reglog.jsonl'),
('2026-05-21 10:20:00','ФЕЛИЦТ ГРУПП 2026','USER4','SHARKON2025','1cv8c','PostingError','error',4200,'Ошибка проведения документа','demo-reglog.jsonl'),
('2026-05-21 10:25:00','ФЕЛИЦТ ГРУПП 2026','USER4','SHARKON2025','1cv8c','ExchangeFailure','error',6100,'Ошибка обмена с внешней системой','demo-reglog.jsonl');
('2026-05-21 07:15:00','ТРАНСГАЗ 2026','USER1','HOST-EXAMPLE','1cv8c','Login','warn',0,'Вход вне рабочего времени','demo-reglog.jsonl'),
('2026-05-21 10:20:00','ФЕЛИЦТ ГРУПП 2026','USER4','HOST-EXAMPLE','1cv8c','PostingError','error',4200,'Ошибка проведения документа','demo-reglog.jsonl'),
('2026-05-21 10:25:00','ФЕЛИЦТ ГРУПП 2026','USER4','HOST-EXAMPLE','1cv8c','ExchangeFailure','error',6100,'Ошибка обмена с внешней системой','demo-reglog.jsonl');
INSERT INTO analytics_1c.audit_events (ts, infobase, user, object_type, object_id, action, before_hash, after_hash, risk_tag, source_file) VALUES
('2026-05-21 10:17:00','ФЕЛИЦТ ГРУПП 2026','USER4','document','DOC-3','repost','abc','def','repost','demo-audit.jsonl'),
('2026-05-21 10:18:00','ФЕЛИЦТ ГРУПП 2026','USER4','counterparty','CP-77','change','old','new','critical_ref','demo-audit.jsonl');
INSERT INTO analytics_1c.host_events (ts, host, cpu_pct, ram_pct, disk_free_gb, disk_latency_ms, smb_errors, rdp_sessions, backup_ok, source_file) VALUES
('2026-05-21 10:00:00','SHARKON2025',41.2,68.4,120.0,12.5,0,4,1,'demo-host.jsonl'),
('2026-05-21 11:00:00','SHARKON2025',57.8,72.0,118.0,61.0,2,4,0,'demo-host.jsonl');
('2026-05-21 10:00:00','HOST-EXAMPLE',41.2,68.4,120.0,12.5,0,4,1,'demo-host.jsonl'),
('2026-05-21 11:00:00','HOST-EXAMPLE',57.8,72.0,118.0,61.0,2,4,0,'demo-host.jsonl');
+7 -7
View File
@@ -1,12 +1,12 @@
DETMIR_AW_BASE=http://10.10.10.13:5600
DETMIR_WORKTIME_BASE=http://10.10.10.13:5610
DETMIR_DLP_POLICY_BASE=http://10.10.10.13:5601
DETMIR_DLP_CASE_BASE=http://10.10.10.13:5602
DETMIR_ONEC_BASE=http://10.10.10.2:8710
DETMIR_GRAFANA_URL=http://10.10.10.11:3000
DETMIR_AW_BASE=http://192.0.2.13:5600
DETMIR_WORKTIME_BASE=http://192.0.2.13:5610
DETMIR_DLP_POLICY_BASE=http://192.0.2.13:5601
DETMIR_DLP_CASE_BASE=http://192.0.2.13:5602
DETMIR_ONEC_BASE=http://192.0.2.2:8710
DETMIR_GRAFANA_URL=http://192.0.2.11:3000
DETMIR_GRAFANA_USER=
DETMIR_GRAFANA_PASSWORD=
DETMIR_DEFAULT_HOST=SHARKON2025
DETMIR_DEFAULT_HOST=HOST-EXAMPLE
DETMIR_HTTP_TIMEOUT_SECONDS=10
DETMIR_MCP_TRANSPORT=stdio
DETMIR_MCP_HOST=127.0.0.1
@@ -95,7 +95,7 @@
"tool_name": "worktime_today",
"weight": 3,
"arguments": {
"host": "SHARKON2025",
"host": "HOST-EXAMPLE",
"day": "today",
"top_n": 5
}
@@ -105,7 +105,7 @@
"tool_name": "worktime_management",
"weight": 2,
"arguments": {
"host": "SHARKON2025",
"host": "HOST-EXAMPLE",
"day": "today",
"top_n": 5
}
@@ -115,7 +115,7 @@
"tool_name": "dlp_health_summary",
"weight": 2,
"arguments": {
"host": "SHARKON2025"
"host": "HOST-EXAMPLE"
}
},
{
+7 -7
View File
@@ -26,15 +26,15 @@ def _env_int(name: str, default: int) -> int:
PROJECT_ROOT = Path(__file__).resolve().parent.parent
GRAFANA_DASHBOARDS_DIR = PROJECT_ROOT / "grafana"
AW_BASE = _env("DETMIR_AW_BASE", "http://10.10.10.13:5600").rstrip("/")
WORKTIME_BASE = _env("DETMIR_WORKTIME_BASE", "http://10.10.10.13:5610").rstrip("/")
DLP_POLICY_BASE = _env("DETMIR_DLP_POLICY_BASE", "http://10.10.10.13:5601").rstrip("/")
DLP_CASE_BASE = _env("DETMIR_DLP_CASE_BASE", "http://10.10.10.13:5602").rstrip("/")
ONEC_BASE = _env("DETMIR_ONEC_BASE", "http://10.10.10.2:8710").rstrip("/")
GRAFANA_URL = _env("DETMIR_GRAFANA_URL", "http://10.10.10.11:3000").rstrip("/")
AW_BASE = _env("DETMIR_AW_BASE", "http://192.0.2.13:5600").rstrip("/")
WORKTIME_BASE = _env("DETMIR_WORKTIME_BASE", "http://192.0.2.13:5610").rstrip("/")
DLP_POLICY_BASE = _env("DETMIR_DLP_POLICY_BASE", "http://192.0.2.13:5601").rstrip("/")
DLP_CASE_BASE = _env("DETMIR_DLP_CASE_BASE", "http://192.0.2.13:5602").rstrip("/")
ONEC_BASE = _env("DETMIR_ONEC_BASE", "http://192.0.2.2:8710").rstrip("/")
GRAFANA_URL = _env("DETMIR_GRAFANA_URL", "http://192.0.2.11:3000").rstrip("/")
GRAFANA_USER = os.environ.get("DETMIR_GRAFANA_USER", "")
GRAFANA_PASSWORD = os.environ.get("DETMIR_GRAFANA_PASSWORD", "")
DEFAULT_HOST = _env("DETMIR_DEFAULT_HOST", "SHARKON2025")
DEFAULT_HOST = _env("DETMIR_DEFAULT_HOST", "HOST-EXAMPLE")
HTTP_TIMEOUT_SECONDS = _env_int("DETMIR_HTTP_TIMEOUT_SECONDS", 10)
MCP_TRANSPORT = _env("DETMIR_MCP_TRANSPORT", "stdio")
MCP_HOST = _env("DETMIR_MCP_HOST", "127.0.0.1")
+2 -2
View File
@@ -192,14 +192,14 @@ Grafana уже должна содержать:
Проверенная рабочая учётка:
- `SHARKON2025\Администратор`
- `HOST-EXAMPLE\Администратор`
### 6.2 Команда переключения principal
На `<WINDOWS_HOST>`:
```cmd
schtasks /Change /TN "\ActivityWatch File1C Upload" /RU "SHARKON2025\Администратор" /RP "<LOCAL_ADMIN_PASSWORD>"
schtasks /Change /TN "\ActivityWatch File1C Upload" /RU "HOST-EXAMPLE\Администратор" /RP "<LOCAL_ADMIN_PASSWORD>"
```
### 6.3 Проверка
@@ -25,7 +25,7 @@
### 1. Восстановление ActivityWatch-Russian
Симптом:
- на `<AW_SERVER_HOST>:5600` UI не обновлялся для `SHARKON2025`;
- на `<AW_SERVER_HOST>:5600` UI не обновлялся для `HOST-EXAMPLE`;
- `aw-server` был жив, но stale были `aw-watcher-afk`, `aw-watcher-window`, `aw-worktime-sessions`.
Действия:
@@ -33,7 +33,7 @@
- через WinRM на `<WINDOWS_HOST>` проверены процессы, tasks и recovery-скрипты;
- запущены:
- `ActivityWatch Recovery`
- `ActivityWatch Launch [SHARKON2025_user5]`
- `ActivityWatch Launch [HOST-EXAMPLE_user5]`
- отдельно перезапущен зависший `worktime-session-collector`.
Итог:
+1 -1
View File
@@ -51,7 +51,7 @@
| `<FIREWALL_HOST>` | `pfSense`, firewall, VPN, ACL, OpenVPN export target |
| `<GATEWAY_HOST>` | `Proxmox/DetMirAuto`, web gateway, Telegram bot, operator entrypoint |
| `<AW_SERVER_HOST>` | основной `AW-rus` server, health, worktime/reporting, `Hayabusa` server-side processing |
| `<WINDOWS_HOST>` | `SHARKON2025`, Windows/RDP host, collectors, worktime session path, EVTX export |
| `<WINDOWS_HOST>` | `HOST-EXAMPLE`, Windows/RDP host, collectors, worktime session path, EVTX export |
Практический вывод:
+336
View File
@@ -0,0 +1,336 @@
# Установка экземпляра для эксперта
Документ описывает воспроизводимый путь: чистая VM -> установка -> проверка ->
ожидаемый результат. Он не использует адреса, домены и учетные данные
боевого стенда.
## 1. Цель проверки
Эксперт должен получить развернутый экземпляр DetMir/AWatch-rus, убедиться,
что ПО собирается из исходного кода, устанавливается на чистую Linux VM,
запускает базовые сервисы и проходит smoke-проверки без приватной
инфраструктуры правообладателя.
## 2. Минимальный стенд
Рекомендуемая чистая VM:
- Debian 12 или Ubuntu Server 24.04 LTS;
- 2 vCPU;
- 4 GB RAM для минимальной проверки, 8 GB RAM для проверки Grafana/Prometheus;
- 30 GB свободного диска;
- доступ в интернет для установки пакетов и Rust crates;
- пользователь с правами `sudo`;
- корректные DNS, NTP и системное время.
Опционально для полной проверки контура:
- отдельная Windows VM или физический Windows host для collector toolkit;
- отдельный host или VM для Grafana/Prometheus, если они не ставятся на ту же
Linux VM;
- закрытая тестовая сеть с адресами, заданными в `ansible/inventory.ini`.
## 3. Подготовка чистой VM
Войти на VM под пользователем с `sudo` и установить базовые инструменты:
```bash
sudo apt-get update
sudo apt-get install -y \
ca-certificates curl git jq unzip tar xz-utils \
build-essential pkg-config libssl-dev sqlite3 \
python3 python3-venv python3-pip ansible
```
Проверить версии:
```bash
git --version
python3 --version
ansible --version
```
Ожидаемый результат:
- команды завершаются без ошибок;
- установлен Git, Python 3 и Ansible;
- VM имеет доступ к пакетным репозиториям.
## 4. Установка Rust toolchain
Установить стабильный Rust toolchain:
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. "$HOME/.cargo/env"
rustup default stable
rustc --version
cargo --version
```
Ожидаемый результат:
- `rustc --version` и `cargo --version` выводят стабильную версию;
- `$HOME/.cargo/bin` доступен в текущей shell-сессии.
## 5. Получение исходного кода
Склонировать репозиторий и перейти в рабочую директорию:
```bash
git clone https://github.com/igor04091968/AWatch-rus.git
cd AWatch-rus
git rev-parse --short HEAD
```
Ожидаемый результат:
- репозиторий склонирован;
- команда `git rev-parse --short HEAD` выводит commit id проверяемой версии.
## 6. Проверка публичной гигиены поставки
Перед установкой выполнить быстрый контроль отсутствия приватных маркеров в
публичных документах:
```bash
PRIVATE_MARKERS_REGEX='<PRIVATE_HOSTNAME>|<PRIVATE_PUBLIC_DOMAIN>|<LOCAL_OPERATOR_HOME>|<ROOT_PRIVATE_PATH>'
git grep -n -E "$PRIVATE_MARKERS_REGEX" -- \
README.md docs REGISTER_RU_SOFTWARE.md PRODUCT_DESCRIPTION_RU.md \
SECURITY_OVERVIEW_RU.md adk-rust/RUNBOOK.md || true
```
Ожидаемый результат:
- команда не должна находить реальные имена хостов, личные домены и личные
пути оператора в публичных документах;
- допустимы только нейтральные placeholders вроде `<AW_SERVER_HOST>`,
`<PUBLIC_GATEWAY_FQDN>`, `HOST-EXAMPLE`.
## 7. Подготовка локальной конфигурации
Создать приватные файлы из примеров:
```bash
cp private-config/deploy.env.example private-config/deploy.env
cp ansible/inventory.example.ini ansible/inventory.ini
```
Заполнить в `private-config/deploy.env` и `ansible/inventory.ini` только
тестовые значения:
- адрес Linux VM;
- адрес Windows host, если проверяются Windows collectors;
- адрес Grafana/Prometheus, если они вынесены на отдельную VM;
- тестовые учетные данные;
- тестовые токены Telegram/Webhook, если проверяются уведомления.
Ожидаемый результат:
- приватная конфигурация существует локально;
- приватные файлы не попадают в Git благодаря `.gitignore`;
- в публичных файлах не появляются реальные пароли, IP и домены.
## 8. Сборка Rust-компонентов
Собрать workspace:
```bash
cd adk-rust
cargo build --release --workspace
cd ..
```
Ожидаемый результат:
- сборка завершается кодом `0`;
- release-бинарники появляются в `adk-rust/target/release/`;
- отсутствуют ошибки компиляции Rust-компонентов.
## 9. Локальные тесты до установки
Запустить базовые проверки исходников:
```bash
cd adk-rust
cargo fmt --all -- --check
cargo test --workspace
cd ..
```
Проверить Ansible syntax:
```bash
ansible-playbook --syntax-check -i ansible/inventory.ini ansible/deploy_aw_server.yml
```
Ожидаемый результат:
- `cargo fmt` не сообщает diff;
- `cargo test --workspace` завершается успешно;
- Ansible syntax-check не находит ошибок YAML/playbook.
## 10. Установка минимального серверного экземпляра
Для чистой экспертной VM использовать inventory, где целевой Linux host
указывает на эту же VM или на отдельный тестовый сервер.
Пример команды:
```bash
ansible-playbook -i ansible/inventory.ini ansible/deploy_aw_server.yml
```
Если playbook требует переменные окружения или группы hosts, задать их в
локальном inventory, не меняя публичные файлы репозитория.
Ожидаемый результат:
- playbook завершается без failed tasks;
- systemd units установлены на тестовый Linux host;
- ActivityWatch server и DetMir helper-компоненты доступны локально на
заданных портах;
- конфигурация не содержит боевых адресов правообладателя.
## 11. Проверка сервисов после установки
На тестовом Linux host выполнить:
```bash
systemctl --failed --no-pager
systemctl status activitywatch-server --no-pager
```
Если установлены Rust helper-бинарники:
```bash
detmir-check --json
detmir-status --json
```
Ожидаемый результат:
- `systemctl --failed` не показывает критичных failed units DetMir/AWatch-rus;
- `activitywatch-server` находится в состоянии `active`;
- `detmir-check --json` возвращает машинно-читаемый статус без критичных
ошибок;
- `detmir-status --json` показывает агрегированный статус установленного
экземпляра.
## 12. Проверка HTTP API
Проверить ActivityWatch API:
```bash
curl -fsS http://127.0.0.1:5600/api/0/info | jq .
curl -fsS http://127.0.0.1:5600/api/0/buckets/ | jq 'keys | length'
```
Ожидаемый результат:
- `/api/0/info` возвращает JSON;
- `/api/0/buckets/` возвращает JSON-объект;
- ошибки `connection refused`, `403`, `500` отсутствуют.
## 13. Проверка Grafana/Prometheus
Если в экспертном стенде установлены Grafana/Prometheus:
```bash
curl -fsS http://127.0.0.1:3000/api/health | jq .
curl -fsS http://127.0.0.1:9090/-/ready
```
Ожидаемый результат:
- Grafana health API возвращает статус `ok` или `database: ok`;
- Prometheus ready endpoint возвращает успешный HTTP status;
- DetMir dashboards импортированы или доступны через documented provisioning
path.
## 14. Проверка Windows collectors
Этот шаг нужен только для полной проверки контура.
На Windows host выполнить PowerShell deployment из `windows/` или Ansible
playbook для Windows collectors, используя тестовый domain/hostname:
```powershell
Get-ScheduledTask | Where-Object TaskName -like 'ActivityWatch*'
```
На Linux server проверить появление buckets:
```bash
curl -fsS http://127.0.0.1:5600/api/0/buckets/ | jq 'keys[]' | sort
```
Ожидаемый результат:
- Windows tasks созданы с тестовыми именами host/user;
- ActivityWatch buckets получают события от Windows collectors;
- в bucket names нет приватных имен боевого стенда.
## 15. Smoke-проверка портала оператора
Если установлен portal runtime:
```bash
curl -fsS http://127.0.0.1:8080/health
```
Ожидаемый результат:
- health endpoint возвращает успешный HTTP status;
- карточки портала открываются по локальным адресам стенда;
- ссылки на Grafana/ActivityWatch/incident evidence используют значения из
конфигурации, а не захардкоженные адреса.
## 16. Итоговый критерий приемки
Экземпляр считается установленным корректно, если:
- исходный код собирается командой `cargo build --release --workspace`;
- локальные Rust tests проходят;
- Ansible syntax-check проходит;
- серверный playbook завершается без failed tasks;
- ActivityWatch API отвечает JSON;
- `systemctl --failed` не показывает критичных failures;
- `detmir-check` и `detmir-status` отрабатывают;
- публичные документы не содержат приватных IP, доменов, hostnames и путей;
- third-party license inventory и release/SBOM checklist заполнены.
## 17. Сбор диагностического пакета для эксперта
После проверки сохранить артефакты:
```bash
mkdir -p expert-check-output
git rev-parse HEAD > expert-check-output/commit.txt
systemctl --failed --no-pager > expert-check-output/systemd-failed.txt
curl -fsS http://127.0.0.1:5600/api/0/info > expert-check-output/aw-info.json
detmir-check --json > expert-check-output/detmir-check.json || true
detmir-status --json > expert-check-output/detmir-status.json || true
```
Ожидаемый результат:
- в `expert-check-output/` есть commit id, systemd summary и JSON-проверки;
- пакет не содержит паролей, токенов и приватных доменов.
## 18. Очистка тестового экземпляра
Для удаления тестовой VM достаточно уничтожить саму VM. Если нужно очистить
только сервисы внутри VM, сначала сохранить diagnostic output, затем остановить
установленные units:
```bash
sudo systemctl stop activitywatch-server || true
sudo systemctl list-units 'aw-*' 'detmir-*' --no-pager
```
Ожидаемый результат:
- экспертский стенд можно воспроизвести заново из исходников и локальной
конфигурации;
- очистка не требует доступа к боевому стенду правообладателя.
+277
View File
@@ -0,0 +1,277 @@
# SBOM и release checklist
Документ фиксирует минимальный порядок подготовки публичного релиза
DetMir/AWatch-rus: исходники, сборка, артефакты, сторонние компоненты,
лицензии, публичная гигиена и экспертная проверка.
## 1. Идентификация релиза
Перед сборкой зафиксировать:
- product name: `DetMir, программный комплекс AWatch-rus`;
- repository: `AWatch-rus`;
- release tag: `vX.Y.Z`;
- commit: вывод `git rev-parse HEAD`;
- дата сборки;
- ответственный правообладатель/maintainer;
- состав release assets.
Команды:
```bash
git status --short
git rev-parse HEAD
git tag --points-at HEAD
```
Критерий:
- рабочее дерево не содержит незапланированных tracked-изменений;
- tag указывает на проверенный commit;
- release notes соответствуют фактическому составу поставки.
## 2. Публичная гигиена репозитория
Проверить отсутствие приватных идентификаторов в публичных документах:
```bash
PRIVATE_MARKERS_REGEX='<PRIVATE_HOSTNAME>|<PRIVATE_PUBLIC_DOMAIN>|<LOCAL_OPERATOR_HOME>|<ROOT_PRIVATE_PATH>'
git grep -n -E "$PRIVATE_MARKERS_REGEX" -- \
README.md docs REGISTER_RU_SOFTWARE.md PRODUCT_DESCRIPTION_RU.md \
SECURITY_OVERVIEW_RU.md adk-rust/RUNBOOK.md || true
```
Проверить реальные IP в release-facing документах:
```bash
git grep -n -E '10\\.10\\.10\\.|192\\.168\\.' -- \
README.md docs REGISTER_RU_SOFTWARE.md PRODUCT_DESCRIPTION_RU.md \
SECURITY_OVERVIEW_RU.md adk-rust/RUNBOOK.md || true
```
Критерий:
- в документах нет приватных IP, доменов, usernames и live hostnames;
- допускаются только placeholders: `<AW_SERVER_HOST>`, `<GRAFANA_HOST>`,
`<PUBLIC_GATEWAY_FQDN>`, `HOST-EXAMPLE`, `WINDOWS_USER_EXAMPLE`;
- приватные configs находятся только в ignored-файлах.
## 3. Контроль секретов
Проверить, что в репозиторий не попали secrets:
```bash
git ls-files | grep -E '(^|/)secrets(/|$)|\\.env$|inventory\\.ini$' || true
git grep -n -E 'sk_[A-Za-z0-9]|pk_[A-Za-z0-9]|BEGIN OPENSSH PRIVATE KEY|password\\s*=' -- . || true
```
Рекомендуемый дополнительный контроль:
```bash
gitleaks detect --source . --no-git --redact
```
Критерий:
- директория `secrets/` не отслеживается Git;
- `.env`, `inventory.ini`, tokens, passwords и private keys отсутствуют в
tracked-файлах;
- если секрет ранее был опубликован, он ротируется вне этого checklist.
## 4. Сборка Rust workspace
Собрать Rust-компоненты:
```bash
cd adk-rust
cargo fmt --all -- --check
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo build --release --workspace
cd ..
```
Критерий:
- formatting clean;
- tests green;
- clippy без warnings;
- release-бинарники собраны.
## 5. Проверка Python-исключений
Python в проекте допускается только как:
- Telegram runtime, который решено оставить на Python;
- совместимые legacy fallbacks;
- installer/ops helpers, пока они не являются ядром продукта;
- тестовые и migration utilities.
Проверка:
```bash
git ls-files '*.py'
```
Критерий:
- Python-файлы имеют понятную роль;
- ядро DetMir/AWatch-rus позиционируется как Rust-first;
- README/registry docs не обещают отсутствие Python там, где он еще остается.
## 6. Third-party license inventory
Основной license inventory:
- [`../THIRD_PARTY_LICENSES_RU.md`](../THIRD_PARTY_LICENSES_RU.md)
Проверить минимум:
- ActivityWatch components: MPL-2.0 или лицензии конкретных upstream parts;
- Grafana: AGPL-3.0 или актуальная лицензия используемой версии;
- Prometheus: Apache-2.0;
- Hayabusa: лицензия upstream и правила распространения;
- Ansible: GPL-3.0-or-later;
- Rust crates: по `cargo metadata`, `cargo about`, `cargo deny`;
- Python dependencies: по `pip-licenses` или lock-файлам;
- JavaScript/Node dependencies для Playwright/UI helpers: по `npm ls` и
package metadata.
Критерий:
- для каждого крупного компонента указан upstream, license, роль и риск;
- copyleft-компоненты не скрыты;
- release notes не противоречат лицензиям.
## 7. SBOM
Сформировать машинные перечни зависимостей.
Rust:
```bash
cd adk-rust
cargo metadata --format-version 1 > ../sbom-cargo-metadata.json
cargo tree --workspace > ../sbom-cargo-tree.txt
cd ..
```
Python, если используется виртуальное окружение:
```bash
python3 -m pip freeze > sbom-python-freeze.txt
```
Node/Playwright, если используется frontend smoke tooling:
```bash
npm ls --all --json > sbom-npm-tree.json
```
OS packages на эталонной VM:
```bash
dpkg-query -W -f='${Package}\\t${Version}\\n' > sbom-debian-packages.tsv
```
Критерий:
- SBOM artifacts приложены к release assets или сохранены в build archive;
- SBOM не содержит секретов;
- SBOM соответствует проверяемому commit/tag.
## 8. Release assets
Собрать install-kit и бинарные артефакты только из проверенного commit:
```bash
scripts/rebuild_install_kit.sh
scripts/validate_install_kit.sh
```
Критерий:
- dated zip/tar.gz не лежат в корне tracked-репозитория;
- архивы публикуются как GitHub Release assets;
- checksum каждого asset зафиксирован.
Пример фиксации checksum:
```bash
sha256sum dist/* install-kit-awindows-*.zip install-kit-awindows-*.tar.gz \
> SHA256SUMS
```
## 9. Документы для реестра российского ПО
Проверить наличие и актуальность:
- `REGISTER_RU_SOFTWARE.md`;
- `PRODUCT_DESCRIPTION_RU.md`;
- `THIRD_PARTY_LICENSES_RU.md`;
- `docs/INSTALL_FOR_EXPERT_RU.md`;
- `docs/ARCHITECTURE_RU.md`;
- `docs/ADMIN_GUIDE_RU.md`;
- `docs/OPERATOR_GUIDE_RU.md`;
- `docs/OWNERSHIP_RU.md`;
- `docs/REGISTRY_CHECKLIST_RU.md`.
Критерий:
- документы согласованы по названию продукта;
- не заявляются DLP/SIEM/EDR/XDR/сертифицированная СЗИ как основной класс;
- позиционирование: операционный контроль, технический аудит, автоматизация
эксплуатации ИТ-инфраструктуры.
## 10. Экспертная установка
Проверить релиз на чистой VM по:
- [`INSTALL_FOR_EXPERT_RU.md`](INSTALL_FOR_EXPERT_RU.md)
Критерий:
- чистая VM проходит путь установка -> сборка -> проверка;
- ActivityWatch API отвечает;
- базовые DetMir checks работают;
- diagnostic bundle собран;
- инструкция воспроизводима без доступа к личному стенду разработчика.
## 11. GitHub metadata
Проверить публичную страницу репозитория:
- description заполнен;
- website/homepage заполнен;
- topics заполнены;
- license отображается;
- releases содержат бинарные/install-kit assets;
- root README не содержит приватных адресов и личных путей.
Критерий:
- проект выглядит как поставляемое ПО, а не как личный стенд;
- About/Topics/Website заполнены;
- license detected на GitHub.
## 12. Финальный gate перед публикацией
Команды:
```bash
git diff --check
git status --short
PRIVATE_MARKERS_REGEX='<PRIVATE_HOSTNAME>|<PRIVATE_PUBLIC_DOMAIN>|<LOCAL_OPERATOR_HOME>|<ROOT_PRIVATE_PATH>'
git grep -n -E "$PRIVATE_MARKERS_REGEX" -- \
README.md docs REGISTER_RU_SOFTWARE.md PRODUCT_DESCRIPTION_RU.md \
SECURITY_OVERVIEW_RU.md adk-rust/RUNBOOK.md || true
```
Критерий публикации:
- diff не содержит whitespace errors;
- tracked changes входят в один понятный commit;
- release-facing docs обезличены;
- install-kit artifacts вынесены в Release assets;
- SBOM/license checklist приложен или воспроизводим;
- tag создан только после прохождения проверок.
@@ -10,7 +10,7 @@ This document records the production-verified state of advanced content analysis
- `contentAnalysis.regexPack`
- `contentAnalysis.ocrEnabled`
- `ioc.*`
- Historical incidents in `aw-dlp-incidents_SHARKON2025` already contain enriched fields:
- Historical incidents in `aw-dlp-incidents_HOST-EXAMPLE` already contain enriched fields:
- `dictionaryMatches`
- `regexMatches`
- `ocrRequested`
+2 -2
View File
@@ -27,8 +27,8 @@ Verified on `<AW_SERVER_HOST>`:
- recent journal runs are clean
- current runtime result is `sent=0` / `delivered=0` because no new incidents were generated since the last seen bucket event
- `endpoint -> incident ingest`
- `aw-dlp-endpoint-signals_SHARKON2025` fresh
- `aw-dlp-incidents_SHARKON2025` exists and contains valid historical incidents
- `aw-dlp-endpoint-signals_HOST-EXAMPLE` fresh
- `aw-dlp-incidents_HOST-EXAMPLE` exists and contains valid historical incidents
- `health/admin`
- `/usr/local/bin/dlp-health-check --json` = `ok=true`
- `/usr/local/bin/dlp-admin-cli.py health check` = policy/cases/aw OK
+4 -4
View File
@@ -42,13 +42,13 @@ aw_windows_builtin_administrator_name: "Администратор"
Назначение: явно фиксировать локализованное имя встроенной учетной записи Administrator с SID `*-500`.
Для текущего Windows host `SHARKON2025` task name должен строиться как:
Для текущего Windows host `HOST-EXAMPLE` task name должен строиться как:
```text
ActivityWatch Launch [SHARKON2025_Администратор]
ActivityWatch Launch [HOST-EXAMPLE_Администратор]
```
Если task по `SHARKON2025_Administrator` не найден, recovery/deploy path обязан пробовать кириллическое имя `Администратор`. Это зафиксировано через:
Если task по `HOST-EXAMPLE_Administrator` не найден, recovery/deploy path обязан пробовать кириллическое имя `Администратор`. Это зафиксировано через:
- default vars в `ansible/deploy_aw_windows.yml`;
- `ansible/group_vars/aw_windows.yml`;
@@ -60,7 +60,7 @@ ActivityWatch Launch [SHARKON2025_Администратор]
`ActivityWatch.Windows.Common.psm1` усилил recovery path:
- `Get-ActivityWatchBuiltInAdministratorName` сначала смотрит env override, затем SID-500 lookup, затем host-specific fallback `SHARKON2025 -> Администратор`;
- `Get-ActivityWatchBuiltInAdministratorName` сначала смотрит env override, затем SID-500 lookup, затем host-specific fallback `HOST-EXAMPLE -> Администратор`;
- `Normalize-ActivityWatchUsers` стабилизирован для pipeline/list cases;
- удаление scheduled tasks стало устойчивее к частично удаленным task definitions;
- recovery task может ориентироваться на live interactive session и запускаться в interactive logon context, когда это безопаснее для watcher'ов.
+1 -1
View File
@@ -4,7 +4,7 @@
### 1. Multi-user RDP host
Use this model on `SHARKON2025`-style hosts with multiple user sessions.
Use this model on `HOST-EXAMPLE`-style hosts with multiple user sessions.
- `AWatchRusCollectorGuard` service:
- runs under `LocalSystem`
+2 -2
View File
@@ -191,7 +191,7 @@ Ansible playbook `ansible/deploy_aw_windows.yml` выполняет этот mig
.\windows\deploy-domain-users.ps1 `
-ServerHost <AW_SERVER_HOST> `
-ServerPort 5600 `
-Domain SHARKON2025 `
-Domain HOST-EXAMPLE `
-Users user2,user3,user4,user5 `
-InstallRoot 'C:\Program Files\AWatch-rus\bin' `
-StateRoot 'C:\ProgramData\AWatch-rus' `
@@ -205,7 +205,7 @@ Single-user pilot в таком же стиле:
.\windows\deploy-single-user.ps1 `
-ServerHost <AW_SERVER_HOST> `
-ServerPort 5600 `
-TargetUser 'SHARKON2025\user1' `
-TargetUser 'HOST-EXAMPLE\user1' `
-InstallRoot 'C:\Program Files\AWatch-rus\bin' `
-StateRoot 'C:\ProgramData\AWatch-rus' `
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json `
+1 -1
View File
@@ -20,7 +20,7 @@ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
C:\Program Files\AWatch-rus\windows\deploy-ensemble.ps1 `
-ServerHost <AW_SERVER_HOST> `
-ServerPort 5600 `
-Domain SHARKON2025 `
-Domain HOST-EXAMPLE `
-Users user1,user2,user3,user4,user5 `
-InstallRoot 'C:\Program Files\AWatch-rus\bin' `
-StateRoot 'C:\ProgramData\AWatch-rus' `
+1 -1
View File
@@ -43,7 +43,7 @@ Get-ScheduledTask -TaskName 'ActivityWatch*' |
Точечная проверка:
```powershell
Get-ScheduledTask | Where-Object TaskName -eq 'ActivityWatch Launch [SHARKON2025_user1]'
Get-ScheduledTask | Where-Object TaskName -eq 'ActivityWatch Launch [HOST-EXAMPLE_user1]'
Get-ScheduledTask | Where-Object TaskName -eq 'ActivityWatch Recovery'
```
+8 -8
View File
@@ -13,8 +13,8 @@
Подходит для расчета рабочего времени в толстых клиентах (1С, документы, админка).
```javascript
events = flood(query_bucket("aw-watcher-window_SHARKON2025"));
not_afk = flood(query_bucket("aw-watcher-afk_SHARKON2025"));
events = flood(query_bucket("aw-watcher-window_HOST-EXAMPLE"));
not_afk = flood(query_bucket("aw-watcher-afk_HOST-EXAMPLE"));
not_afk = filter_keyvals(not_afk, "status", ["not-afk"]);
events = filter_period_intersect(events, not_afk);
@@ -41,8 +41,8 @@ RETURN = sort_by_duration(work);
`rootDomain=proxmox-webui`, `categoryGroup=work`, `category=Администрирование`.
```javascript
web = flood(query_bucket("aw-detmir-web-category_SHARKON2025"));
not_afk = flood(query_bucket("aw-watcher-afk_SHARKON2025"));
web = flood(query_bucket("aw-detmir-web-category_HOST-EXAMPLE"));
not_afk = flood(query_bucket("aw-watcher-afk_HOST-EXAMPLE"));
not_afk = filter_keyvals(not_afk, "status", ["not-afk"]);
web = filter_period_intersect(web, not_afk);
@@ -55,8 +55,8 @@ RETURN = sort_by_duration(web);
## Sanity-check: «куда уходит время»
```javascript
events = flood(query_bucket("aw-watcher-window_SHARKON2025"));
not_afk = flood(query_bucket("aw-watcher-afk_SHARKON2025"));
events = flood(query_bucket("aw-watcher-window_HOST-EXAMPLE"));
not_afk = flood(query_bucket("aw-watcher-afk_HOST-EXAMPLE"));
not_afk = filter_keyvals(not_afk, "status", ["not-afk"]);
events = filter_period_intersect(events, not_afk);
@@ -68,7 +68,7 @@ RETURN = sort_by_duration(events);
## Замечания
- Для других хостов замените суффикс `_SHARKON2025` на нужный hostname.
- Для других хостов замените суффикс `_HOST-EXAMPLE` на нужный hostname.
- Если web-поток пустой, рабочее время в браузере корректно посчитать по доменам не получится. Тогда либо:
- чинить/запускать browser collector;
- либо временно считать браузер в `window` как «Интернет/Браузер» без разделения на work/personal.
@@ -82,7 +82,7 @@ RETURN = sort_by_duration(events);
«кто и когда вообще был в активной удалённой сессии».
```javascript
sessions = flood(query_bucket("aw-worktime-sessions_SHARKON2025"));
sessions = flood(query_bucket("aw-worktime-sessions_HOST-EXAMPLE"));
sessions = filter_keyvals(sessions, "active", [true]);
sessions = merge_events_by_keys(sessions, ["username", "sessionName", "state"]);
RETURN = sort_by_duration(sessions);
+2 -2
View File
@@ -5,8 +5,8 @@ GRAFANA_PORT=3000
PROMETHEUS_PORT=9090
SQL_EXPORTER_PORT=9399
AW_EXPORTER_PORT=9398
AW_SERVER_HOST=10.10.10.13
AW_SERVER_HOST=192.0.2.13
AW_SERVER_PORT=5600
AW_SERVER_SCHEME=http
AW_SCRAPE_INTERVAL_SECONDS=30
ONEC_DSN=postgres://onec_reader:change_me@10.10.10.20:5432/onec_db?sslmode=disable
ONEC_DSN=postgres://onec_reader:change_me@192.0.2.20:5432/onec_db?sslmode=disable
+1 -1
View File
@@ -6,7 +6,7 @@ services:
container_name: awrus-aw-exporter
restart: unless-stopped
environment:
- AW_SERVER_HOST=${AW_SERVER_HOST:-10.10.10.13}
- AW_SERVER_HOST=${AW_SERVER_HOST:-192.0.2.13}
- AW_SERVER_PORT=${AW_SERVER_PORT:-5600}
- AW_SERVER_SCHEME=${AW_SERVER_SCHEME:-http}
- EXPORTER_PORT=9398
@@ -16,7 +16,7 @@ logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Configuration
AW_SERVER_HOST = os.getenv("AW_SERVER_HOST", "10.10.10.13")
AW_SERVER_HOST = os.getenv("AW_SERVER_HOST", "192.0.2.13")
AW_SERVER_PORT = int(os.getenv("AW_SERVER_PORT", "5600"))
AW_SERVER_SCHEME = os.getenv("AW_SERVER_SCHEME", "http")
AW_API_BASE = os.getenv(
+2 -2
View File
@@ -362,8 +362,8 @@
{
"current": {
"selected": false,
"text": "SHARKON2025",
"value": "SHARKON2025"
"text": "HOST-EXAMPLE",
"value": "HOST-EXAMPLE"
},
"datasource": {
"type": "influxdb",
@@ -724,8 +724,8 @@
{
"current": {
"selected": false,
"text": "SHARKON2025",
"value": "SHARKON2025"
"text": "HOST-EXAMPLE",
"value": "HOST-EXAMPLE"
},
"datasource": {
"type": "influxdb",
@@ -1,59 +0,0 @@
856d2467afb4b413ae7833ba55340db01bd807f10d5200752b95cc113758c54e install-kit-awindows-20260427-211240/README-INSTALL-KIT.txt
556a9f7524b61309b00b641495b6daa212115411aa5a6fc82d1537658ac0e719 install-kit-awindows-20260427-211240/ansible/README.md
412bb766bbf0791c3593f38daa771d5d0aa58cc1f2d3c9010fcd4588d0fe87df install-kit-awindows-20260427-211240/ansible/deploy_aw_pfsense_poller.yml
ac091618a6793a9be9b52eeb78267068df16d4f57e0051b9d868813c4a641f35 install-kit-awindows-20260427-211240/ansible/deploy_aw_server.yml
cd09574fd0d55bf62d9d7df757bb206b5184ba9ffdcc200e755700e50cfa41f3 install-kit-awindows-20260427-211240/ansible/deploy_aw_windows.yml
4b6f658adb98d548eedd10a0d44ff224b4d46a663d8c11e038f18597503f6c27 install-kit-awindows-20260427-211240/ansible/group_vars/all.example.yml
dbaa55e66c65bf7e4e943c8070e53d002d8a794ffcc0593d7a8a07a5f99aa306 install-kit-awindows-20260427-211240/ansible/group_vars/pfsense-poller.example.yml
c5cab36645065815571c99f6d360f910dcccbb54b780c8bfd526a6cdc3684e19 install-kit-awindows-20260427-211240/ansible/group_vars/proxmox-matrix.example.yml
35a33c8a1c75ded5e85c6b79e0b3efde07959ff61ee5f66d83b7e0c2abe87fc5 install-kit-awindows-20260427-211240/ansible/group_vars/proxmox.example.yml
9e4939d2d951870cacab6e42c5bf0ccd03350089dc0d32b9a681948ff96754d2 install-kit-awindows-20260427-211240/ansible/group_vars/windows.example.yml
7189b5205bd25313db54e5be027b0d066199e6ae34ad74be2095a1691adaf5e3 install-kit-awindows-20260427-211240/ansible/install_full_stack.yml
1abe542724e617c285deaef76514d49c83c148ddbee90bc99b800e9049b70444 install-kit-awindows-20260427-211240/ansible/inventory.example.ini
3adda7422ffbbb76be50858f2f1ed6073b6bf9ee0b57283c41d0fabd3e7c7f9e install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_and_deploy_aw.yml
234932632092226526173b9ef906de2cce961bc95be0f37c878e48d9779e1de7 install-kit-awindows-20260427-211240/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml
a74a49371e889dc3ea404534a939f32f2dac940d8902d20770590951ab67d532 install-kit-awindows-20260427-211240/ansible/tasks/provision_ct_and_deploy_aw.yml
ba16fe9e267194459a6082045a387acb828a1d39a98e66b401ece5069ea62e64 install-kit-awindows-20260427-211240/aw-server/activitywatch-server.service
fea5734c99b516b01bfe8ebbef59972d0ef553c09d7691790b08472ecfb9602d install-kit-awindows-20260427-211240/aw-server/apply_webui_ru_patch.sh
fabcdbb933b1d3b605c5ac1234af4e10495c09f18bd50931bf4a92081fcb5023 install-kit-awindows-20260427-211240/aw-server/aw-browser-smoke.service
e59d58d4cc6b2b0dceef5b1da14ca0a1b24a202ce576ee43028f44ffe43c5ef4 install-kit-awindows-20260427-211240/aw-server/aw-browser-smoke.timer
ba88cc284d047f521427ac038c266624a6fe8493ce3e79bc27c172a2e70ac54a install-kit-awindows-20260427-211240/aw-server/aw-host-groups.json
509759461ce0918a2dc839832812cf5f4c77b1cea1e6ffdcab0146e02598df79 install-kit-awindows-20260427-211240/aw-server/aw-ru-patch.js
699f697143f6be588c698bad9a14a45c736fe74f5e4b0e0ea0f48b569c7df396 install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.service
8ae02b42f4bc33d95268624bae79c2101298b7ec6644553a1e81f7841dda74d8 install-kit-awindows-20260427-211240/aw-server/aw-rus-healthd.timer
7580282be0a3bf057e42ca6f733a64bf0d2f51d096e02a57cde9125064ac04d1 install-kit-awindows-20260427-211240/aw-server/aw-server.env.example
18d038e67b926eca04bec725ed5e6de34b93f8330c3b30a0b07d52eab414bd03 install-kit-awindows-20260427-211240/aw-server/aw-slo-monitor.service
257cdc18f088e8848b1bee69e7f8b99aa860a0f600eebd74365a40ed94aa5ecb install-kit-awindows-20260427-211240/aw-server/aw-slo-monitor.timer
98c0bed353bbda0fa7a69df23f3b008cb0e8e70cdff6cc63330d4caf79fd3280 install-kit-awindows-20260427-211240/aw-server/aw-sw-cleanup.js
0e555853f1cb498e63b70d305ce5facfaa626a2ceee6f7edb854cf37c4371f65 install-kit-awindows-20260427-211240/aw-server/aw-worktime-api.service
afbb4be301b4940ed9b7671be3441a48d53bee968d8870749442f81c6d066650 install-kit-awindows-20260427-211240/aw-server/aw-worktime-panel.js
7e37dd8bbfdbbe661defb9ce69284db8ad339a49b34f8c507060d0dbc75b7aa8 install-kit-awindows-20260427-211240/aw-server/aw-worktime-prewarm.service
0cd38bae7ad1b4718d7decc8ef3ac932939a6e5f8c25e9e27d1d0ecf8cc328b9 install-kit-awindows-20260427-211240/aw-server/aw-worktime-prewarm.timer
6de9b272ad732ad174c32398ab8baf5cc8c83be47efe0d6df46c3190b4010680 install-kit-awindows-20260427-211240/aw-server/install_aw_server.sh
aedffecfa24834968742cb2477faef80bf794345275a9679ac12c5a1f609acc2 install-kit-awindows-20260427-211240/aw-server/settings/classes-worktime.json
47c50054515506b72af2d6bca0ae959ca57861803e528991c230611e6a8893d6 install-kit-awindows-20260427-211240/aw-server/settings/views-default.json
2e728f4632e6564355a580ea1718d41a660dc35595ac38d0ac7fe0dd308dae6a install-kit-awindows-20260427-211240/scripts/aw-webui-browser-smoke.mjs
76a24d19440676d447fac16e4a663f34c3f31d62166a6c326b5bd3754beac0eb install-kit-awindows-20260427-211240/scripts/aw-webui-browser-smoke.sh
6770275fac17607770653a522a64cf5ab31a7a11993b68e7e80a2858c3b8930e install-kit-awindows-20260427-211240/scripts/check_install_kit_vs_repo.sh
4e5bc6ce977e9f5b9e87c393061d5ecac0f90d5e1314e3325093692e5d19965e install-kit-awindows-20260427-211240/scripts/quality-gate.sh
db1a9b0ccd21aec78d8e2aa08dfa28368e5ba6209c57361e6824e85c1af9f6ca install-kit-awindows-20260427-211240/scripts/rebuild_install_kit.sh
38ad2e112796c2fa9bdc1cc2403c679bb896f90ae2b51d6ccc9a9ec103b4dea6 install-kit-awindows-20260427-211240/scripts/validate_install_kit.sh
0b49e6db51d5abcecaeee0b85f186efd4f10d3360cf04e8aea2e4c7d4465dbda install-kit-awindows-20260427-211240/scripts/verify_innosetup_installer.sh
e856d4b411b61dfc11e0a59b59026eff10972a47e7d8b28f6dfe367a2a5e192d install-kit-awindows-20260427-211240/windows/AWatchRusCollectorGuardService.cs
33aa34b89246d6c079ef9afe2f5cd153bd9d5946b69a175ff6fd678c77f61da5 install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psd1
6d5a7dd92619bbf972e17e455a7cda65b53c790f6f0ede47e1196246bfe8b1cf install-kit-awindows-20260427-211240/windows/ActivityWatch.Windows.Common.psm1
f4405a959a52e21185234463887a4fc09ea28e48d2412ac336f9715b46be9cb9 install-kit-awindows-20260427-211240/windows/aw-collector-guard.ps1
81e95c7e4b7336a2e1f0caed3ea99f6cdb9696b99380dc2148667b5fba577f68 install-kit-awindows-20260427-211240/windows/browser-domains-native-collector.ps1
51eb13f689494c279ec321bbb68dfcb9b8ff81f22f11502b72c16895b65e0b65 install-kit-awindows-20260427-211240/windows/deploy-domain-users.ps1
5bacbd48fdfc8d0d4479ba1af54cbbfe6057cc654409b385f487962576f4f1db install-kit-awindows-20260427-211240/windows/deploy-ensemble.ps1
6ab4c496220bb46f064cc927a64ea853526a2c814182a32f39b432be6b32fe89 install-kit-awindows-20260427-211240/windows/deploy-single-user.ps1
73f3328cde22798dafa645cdd78d648ccf6425d6577432ae8c95d24daa17dc94 install-kit-awindows-20260427-211240/windows/dlp-endpoint-signals-collector.ps1
b97dfbd94201c83f3edd87c7940b058bf85ac64794468b44ab3200dcb1bff3c3 install-kit-awindows-20260427-211240/windows/dlp-policy.example.json
0df53518a43bb1f0af7fdff5b44ba859e797090dd08680b239571d2e8a478d47 install-kit-awindows-20260427-211240/windows/dlp-policy.native-cross-os.example.json
863727465497b474d13d2270d443ff96ccb6076f90a5ce3eb270bdf8088e02dc install-kit-awindows-20260427-211240/windows/email-outbound-collector.ps1
2c0e7a96f32d546ce3ff8da2059259e6e5141c97cefc76bccae4c87c502f00cf install-kit-awindows-20260427-211240/windows/hardening-recovery.ps1
ea68b869182587b7be41bf1891401bdf93dda3b9af6028afa0e30d245c77b0f4 install-kit-awindows-20260427-211240/windows/install-collector-guard-service.ps1
5ef21a25d5e2da4eeaef17126e60f96f195f90f9dc17a776f8629334b904d096 install-kit-awindows-20260427-211240/windows/migrate-awatch-rus-paths.ps1
d50228354f4c1f15b8ea9ccfef184a3a2e5cc832259ddbdc7afea4c083d03075 install-kit-awindows-20260427-211240/windows/validate-deployment.ps1
731098681d89b9af6f3872abd586ac3b1faba2d7f9340211e503f52ad0243b3f install-kit-awindows-20260427-211240/windows/web-category-rules.example.json
1b7c337967236474484e781dc8ac37543509b051513f01e1a3145369262f5389 install-kit-awindows-20260427-211240/windows/worktime-session-collector.ps1
@@ -1,14 +0,0 @@
ActivityWatch DetMir Windows Install Kit
Includes:
- windows/* (deploy scripts, collectors, common module, configs/examples)
- ansible/* (Windows and AW server playbooks, examples, inventory, tasks)
- aw-server/* (server installer, health orchestrator, RU patch loader, host groups, default settings)
- scripts/* (install-kit rebuild/validation, quality gates, browser/web smoke checks)
Source:
- Local project snapshot at build time.
Customer-specific deployment configs, inventories, passwords, tokens, domains,
IP addresses and runtime snapshots are intentionally excluded from this public
install-kit.
@@ -1,289 +0,0 @@
# Ansible ensemble for AWatch-rus
Эта директория содержит Ansible-ensemble для полного развёртывания AWatch-rus:
- деплой на уже существующий Debian host/CT;
- полный цикл с нуля в Proxmox: создание CT + bootstrap + установка ActivityWatch + RU patch;
- централизованное развёртывание Windows/RDP collector'ов по WinRM;
- развёртывание внешнего pfSense poller'а на Debian/Ubuntu utility VM.
## Файлы
- `ansible/deploy_aw_server.yml` — основной playbook для уже существующего Debian/CT host.
- `ansible/provision_proxmox_ct_and_deploy_aw.yml` — полный playbook для Proxmox.
- `ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml` — массовый полный playbook (несколько CT).
- `ansible/deploy_aw_windows.yml` — WinRM playbook для развёртывания Windows/RDP collector'ов.
- `ansible/deploy_aw_pfsense_poller.yml` — развёртывание pfSense poller'а.
- `ansible/deploy_grafana_dashboards.yml` — импорт version-controlled Grafana dashboard'ов через HTTP API.
- `ansible/deploy_tsj_guardian_bot_proxmox.yml` — развёртывание TSJ Guardian Telegram Bot на Proxmox host.
- `ansible/install_full_stack.yml` — полный установочный playbook (оркестратор всех этапов).
- `ansible/inventory.example.ini` — шаблон inventory.
- `ansible/group_vars/*.example.yml` — шаблоны переменных.
## Быстрый запуск
1. Скопируйте шаблоны:
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml`
2. Заполните значения в `inventory.ini` и `group_vars/all.yml`.
3. Запустите:
```bash
cd ansible
ansible-playbook -i inventory.ini deploy_aw_server.yml
```
## Секреты (пароли) безопасно
Рекомендуемый способ не хранить пароли в репозитории — перед запуском экспортировать их в переменные окружения:
- Linux `aw_server` (SSH пароль root): `AW_SSH_PASSWORD`
- Windows `aw_windows` (WinRM пароль): `AW_WINRM_PASSWORD`
В `group_vars/aw_server.yml` и `group_vars/windows.yml` они читаются через `lookup('env', ...)`.
## Полный установочный playbook (всё за один запуск)
Если нужно прогнать полный цикл одной командой:
```bash
cd ansible
ansible-playbook -i inventory.ini install_full_stack.yml
```
Что делает:
- `provision_proxmox_ct_and_deploy_aw.yml` (если есть хосты в группе `[proxmox]`);
- `deploy_aw_server.yml` (группа `[aw_server]`);
- `deploy_aw_windows.yml` (группа `[aw_windows]`);
- `deploy_aw_pfsense_poller.yml` (группа `[aw_pfsense_pollers]`);
- `deploy_grafana_dashboards.yml` (группа `[grafana]`).
Пустые группы в `inventory.ini` безопасны: соответствующий play будет пропущен.
## Полный запуск с нуля в Proxmox
1. Подготовьте inventory и vars:
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/all.example.yml ansible/group_vars/all.yml`
- `cp ansible/group_vars/proxmox.example.yml ansible/group_vars/proxmox.yml`
2. Заполните `group_vars/proxmox.yml` и `group_vars/all.yml`.
3. Запустите playbook:
```bash
cd ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml
```
## Массовый запуск (матрица CT)
1. Подготовьте матрицу:
- `cp ansible/group_vars/proxmox-matrix.example.yml ansible/group_vars/proxmox-matrix.yml`
2. Заполните `proxmox-matrix.yml`.
3. Запустите:
```bash
cd ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml
```
## Windows/RDP rollout (WinRM)
Важно:
- `WinRM` здесь остаётся транспортом для `Ansible deploy` и `validation`;
- для интерактивной PowerShell-работы из Linux/Codex по DetMir используйте project MCP-over-SSH путь, а не `WSMan`;
- каноника лежит в `docs/DETMIR_POWERSHELL_MCP_REMOTE_RU.md` и `scripts/install_detmir_powershell_mcp.sh`.
1. Подготовьте inventory и vars:
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/windows.example.yml ansible/group_vars/windows.yml`
2. Заполните `inventory.ini` (секция `[aw_windows]`) и `group_vars/windows.yml`.
- Для русской локализации Windows часто нужен `ansible_user=Администратор` (а не `Administrator`).
- Если WinRM закрыт, playbook не сможет стартовать и нужно сначала открыть `5985/5986` и `wsman`.
3. Запустите:
```bash
cd ansible
AW_WINRM_PASSWORD='...' bash ./run_deploy_aw_windows.sh
```
`run_deploy_aw_windows.sh` автоматически:
- очищает proxy env (`http_proxy/https_proxy/...`), чтобы WinRM не уходил в локальный прокси;
- включает OpenSSL legacy provider, если на хосте отключён `MD4` (нужно для NTLM в pywinrm).
- перезапускает `ansible-playbook` при временных WinRM/NTLM сбоях (по умолчанию 5 попыток, пауза 30 сек).
Параметры retry:
- `AW_DEPLOY_RETRIES` (по умолчанию `5`);
- `AW_DEPLOY_RETRY_DELAY_SEC` (по умолчанию `30`).
Playbook:
- выгружает полный `windows/*` toolkit на целевой хост в InnoSetup-compatible каталог `C:\Program Files\AWatch-rus\windows`, включая DLP и `worktime-session-collector.ps1`;
- если найден legacy config `C:\ProgramData\ActivityWatch-Phase2\deployment-config.json`, выполняет безопасную миграцию через `migrate-awatch-rus-paths.ps1`: backup, остановка задач, перенос данных, переписывание путей, пересоздание scheduled tasks и validation;
- выполняет `deploy-ensemble.ps1` (deploy + hardening/recovery) с policy/rules из AWatch-rus toolkit;
- после deploy принудительно запускает `ActivityWatch Recovery` и managed `ActivityWatch Launch *` задачи;
- включает (`Enable-ScheduledTask`) `ActivityWatch Recovery` и managed `ActivityWatch Launch *` задачи перед запуском (иначе WebUI может показывать `Active time: 0s`);
- оставляет `ActivityWatch Recovery` включённым даже при активном `AWatchRusCollectorGuard`: guard является основным контроллером, recovery остаётся fallback/bootstrap path;
- выполняет API smoke-check bucket `aw-watcher-afk_<COMPUTERNAME>` и ожидает свежие события;
- выполняет API smoke-check bucket `aw-watcher-window_<COMPUTERNAME>` и ожидает свежие события (по умолчанию включено);
- запускает `validate-deployment.ps1`;
- забирает JSON-отчёт в локальную директорию (`/tmp/aw-rus-validation-<USER>` по умолчанию).
- настраивает scheduled task `ActivityWatch Hayabusa Upload` с периодом и lookback по vars.
Дополнительные флаги:
- `aw_windows_afk_enabled: false` — не запускать `aw-watcher-afk`;
- `aw_windows_window_enabled: false` — не запускать `aw-watcher-window`;
- `aw_windows_incident_capture_enabled: false` — отключить блок incidentCapture;
- `aw_windows_incident_screenshot_enabled: false` — не делать скриншот при DLP-инциденте;
- `aw_windows_incident_artifacts_root: 'C:\...\incident-artifacts'` — переопределить путь артефактов;
- `aw_windows_deploy_root: 'C:\Program Files\AWatch-rus'` — каталог toolkit, совпадает с InnoSetup `{app}`;
- `aw_windows_install_root: 'C:\Program Files\AWatch-rus\bin'` — каталог бинарников, совпадает с InnoSetup `AwDefaultInstallRoot`;
- `aw_windows_state_root: 'C:\ProgramData\AWatch-rus'` — каталог состояния/отчётов, совпадает с InnoSetup `AwDefaultStateRoot`;
- `aw_windows_validation_remote_path: '{{ aw_windows_state_root }}\aw_validate_ansible.json'` — отчёт Ansible-валидации хранится рядом с `ensemble-report-*.json`;
- `aw_windows_migration_enabled: true` — включить guard миграции текущего production из `ActivityWatch-Phase2` в единый `AWatch-rus`;
- `aw_windows_legacy_install_root` / `aw_windows_legacy_state_root` — старые production paths, откуда выполняется перенос;
- `aw_windows_migration_report_remote_path` — JSON-отчёт о миграции на Windows-хосте;
- `aw_windows_package_version`, `aw_windows_package_url`, `aw_windows_package_zip_path` — версия и источник Windows-пакета ActivityWatch;
- `aw_windows_api_smoke_check_bucket: ""` — автоматически использовать `aw-watcher-afk_<COMPUTERNAME>`;
- `aw_windows_api_smoke_check_window_enabled: true` — включить дополнительный smoke-check `aw-watcher-window_<COMPUTERNAME>`;
- `aw_windows_api_smoke_check_window_bucket: ""` — переопределить bucket для window smoke-check;
- `aw_windows_api_smoke_check_min_events: 1` — минимум событий, ожидаемых в smoke-check;
- `aw_windows_fail_on_validation_error: true` — завершать playbook ошибкой, если `validate-deployment.ps1` возвращает `overallOk=false`;
- `aw_windows_skip_hardening: true` — пропустить `hardening-recovery.ps1` внутри ensemble-скрипта.
- `aw_windows_hayabusa_auto_upload_enabled: true` — включить авто-upload EVTX на AW-server;
- `aw_windows_hayabusa_auto_upload_interval_hours: 6` — период scheduled task;
- `aw_windows_hayabusa_auto_upload_hours_back: 6` — lookback для каждого запуска;
- `aw_windows_hayabusa_auto_upload_mode: "incident"` — mode для server-side processing;
- `aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload"` — имя scheduled task.
## Server-side Hayabusa auto-case и Telegram alerting
На стороне `deploy_aw_server.yml` теперь есть server-side контур:
- `aw-hayabusa-drop.path`
- `aw-hayabusa-drop.service`
- `aw-hayabusa-autoprocess`
- `aw-hayabusa-case-alert`
Что делает контур:
- автоматически подхватывает `zip` из `/opt/activitywatch/aw-rus-ops/drop`;
- запускает `aw-hayabusa`;
- считает severity/score по `timeline.jsonl`;
- создаёт или обновляет case;
- пишет bounded metadata в `forensics.hayabusa`;
- отправляет Telegram alert.
Основные vars:
- `aw_hayabusa_auto_case_enabled: true`
- `aw_hayabusa_auto_case_min_severity: "medium"`
- `aw_hayabusa_telegram_enabled: true`
- `aw_hayabusa_telegram_min_severity: "high"`
- `aw_hayabusa_telegram_bot_token`
- `aw_hayabusa_telegram_chat_ids`
## Развёртывание pfSense poller
1. Подготовьте vars:
- `cp ansible/group_vars/pfsense-poller.example.yml ansible/group_vars/pfsense-poller.yml`
2. Добавьте inventory group `[aw_pfsense_pollers]`.
3. Запустите:
```bash
cd ansible
ansible-playbook -i inventory.ini deploy_aw_pfsense_poller.yml
```
Playbook:
- ставит `python3`;
- копирует `pfsense-aw-poller.py`;
- пишет `/etc/aw-pfsense/poller.json`;
- поднимает `aw-pfsense-poller.service`.
## Импорт Grafana dashboard'ов
1. Подготовьте inventory и vars:
- `cp ansible/inventory.example.ini ansible/inventory.ini`
- `cp ansible/group_vars/grafana.example.yml ansible/group_vars/grafana.yml`
2. Укажите в inventory группу `[grafana]` и переменную `grafana_url`.
3. Экспортируйте пароль Grafana API:
```bash
export GRAFANA_ADMIN_PASSWORD='...'
```
4. Запустите:
```bash
cd ansible
ansible-playbook -i inventory.ini deploy_grafana_dashboards.yml
```
Playbook:
- проверяет `GET /api/health`;
- создает или актуализирует folder `AWatch-rus` в Grafana;
- импортирует dashboard JSON из каталога `grafana/`;
- верифицирует доступность dashboard'ов по `uid` через Grafana API.
По умолчанию импортируются:
- `DetMir: Работа пользователей в RDP`
- `DetMir: DLP и ИБ обзор`
- `DetMir: ИБ сводка для руководства`
- `AW-rus: DLP обзор`
Подробная документация: `docs/GRAFANA_DASHBOARDS_RU.md`
## Развёртывание TSJ Guardian Bot на Proxmox
1. Подготовьте vars:
- `cp ansible/group_vars/proxmox-bot.example.yml ansible/group_vars/proxmox-bot.yml`
2. Заполните минимум:
- `telegram_bot_token`
- `telegram_allowed_chat_ids`
- `tsj_bot_source_local_path`
3. Убедитесь, что в inventory есть группа `[proxmox]`.
Для текущего контура AW-Rus bot ожидает Proxmox host `<GATEWAY_HOST>`.
Рабочая модель для этого контура: `igor` + `sudo`, а не обязательный `root` login.
4. При необходимости задайте recovery-команды для AW-Rus:
- `tsj_bot_aw_rus_worktime_heal_cmd`
- `tsj_bot_aw_rus_dlp_heal_cmd`
5. Запустите:
```bash
cd ansible
ansible-playbook -i inventory.ini deploy_tsj_guardian_bot_proxmox.yml
```
После актуального production hardening:
- bot различает `worktime idle` и реальную деградацию;
- bot поддерживает отдельный `AW_RUS_DLP_HEAL_CMD`;
- redeploy не должен терять runtime env-ключи, связанные с proxy, FS checks и AI escalation.
## Результат
- Установлен ActivityWatch Server.
- Создан systemd-unit `activitywatch-server.service`.
- Установлен RU Web UI patch.
- Для Web UI используется checksum-based cache-bust для `ru-patch-v5.js` и `sw-cleanup.js`, чтобы браузер не держал старую DLP/русскую статику после деплоя.
- На `#/home` Web UI делит хосты на `Windows RDP` и `Virtual servers + Proxmox`.
- Выполнена валидация API `http://127.0.0.1:5600/api/0/info`.
- Для полного сценария CT создаётся автоматически через `pct create`.
- На Windows/RDP host развёрнуты AFK/window watchers, browser domain collector, DLP endpoint collector и worktime session collector.
- Проверочный JSON-отчёт Windows playbook должен иметь `overallOk=true`.
## Prod rollout одной командой
Для ручного запуска с dry-run и логированием используйте:
```bash
bash scripts/prod_rollout.sh
```
Скрипт попросит `AW_SSH_PASSWORD` и `AW_WINRM_PASSWORD` интерактивно (ввод скрыт) и сложит логи в `.rollout-logs/`.
@@ -1,66 +0,0 @@
---
- name: Развернуть pfSense ActivityWatch poller
hosts: aw_pfsense_pollers
become: true
gather_facts: true
vars:
aw_pfsense_install_root: "/opt/aw-pfsense"
aw_pfsense_config_dir: "/etc/aw-pfsense"
aw_pfsense_service_name: "aw-pfsense-poller.service"
tasks:
- name: Установить обязательные пакеты
ansible.builtin.apt:
name:
- python3
state: present
update_cache: true
- name: Создать каталоги
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0755"
loop:
- "{{ aw_pfsense_install_root }}"
- "{{ aw_pfsense_config_dir }}"
- name: Установить скрипт pfSense poller
ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.py"
dest: "{{ aw_pfsense_install_root }}/pfsense-aw-poller.py"
mode: "0755"
- name: Установить systemd service
ansible.builtin.copy:
src: "{{ aw_repo_root }}/pfsense/pfsense-aw-poller.service"
dest: "/etc/systemd/system/{{ aw_pfsense_service_name }}"
mode: "0644"
notify:
- Перезагрузить systemd
- name: Записать конфигурацию pfSense poller
ansible.builtin.copy:
dest: "{{ aw_pfsense_config_dir }}/poller.json"
mode: "0600"
content: "{{ aw_pfsense_poller_config | to_nice_json }}"
notify:
- Перезапустить pfSense poller
- name: Включить и запустить pfSense poller
ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}"
enabled: true
state: restarted
daemon_reload: true
handlers:
- name: Перезагрузить systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Перезапустить pfSense poller
ansible.builtin.systemd:
name: "{{ aw_pfsense_service_name }}"
state: restarted
File diff suppressed because it is too large Load Diff
@@ -1,559 +0,0 @@
---
- name: Развернуть Windows/RDP collector'ы AWatch-rus
hosts: aw_windows
gather_facts: false
vars:
ansible_winrm_operation_timeout_sec: 120
ansible_winrm_read_timeout_sec: 180
aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http"
aw_windows_server_port: 5600
aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
aw_windows_builtin_administrator_name: "Администратор"
aw_windows_users:
- Администратор
- user1
- user2
- user3
- user4
- user5
aw_windows_extra_users: []
aw_windows_users_effective: "{{ (aw_windows_users + aw_windows_extra_users) | unique }}"
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
aw_windows_policy_mode: "server"
aw_windows_policy_refresh_seconds: 300
aw_windows_policy_engine_enabled: true
aw_windows_policy_engine_port: 5601
aw_windows_policy_engine_scheme: "http"
aw_windows_hayabusa_auto_upload_enabled: true
aw_windows_hayabusa_auto_upload_interval_hours: 6
aw_windows_hayabusa_auto_upload_hours_back: 6
aw_windows_hayabusa_auto_upload_mode: "incident"
aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload"
aw_windows_file_1c_auto_upload_enabled: true
aw_windows_file_1c_auto_upload_interval_hours: 6
aw_windows_file_1c_auto_upload_task_name: "ActivityWatch File1C Upload"
aw_windows_file_1c_target_user: "igor"
aw_windows_file_1c_registry_workbook_path: "E:\\USER1\\СПИСОК ПРЕДПРИЯТИЙ И ИХ РАСПРЕДЕЛЕНИЕ.xlsx"
aw_windows_afk_enabled_default: true
aw_windows_window_enabled_default: true
aw_windows_file_ops_enabled: true
aw_windows_local_agent_logs_enabled: false
aw_windows_incident_capture_enabled: true
aw_windows_incident_screenshot_enabled: true
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
aw_windows_forensics_root: "{{ aw_windows_state_root }}\\forensics\\evtx-exports"
aw_windows_logon_marker_enabled: true
aw_windows_process_events_enabled: false
aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation-{{ lookup('env','USER') | default('ansible', true) }}"
aw_windows_launch_task_pattern: "ActivityWatch Launch *"
aw_windows_recovery_task_name: "ActivityWatch Recovery"
aw_windows_collector_guard_enabled: true
aw_windows_collector_guard_mode: "enforce"
aw_windows_collector_guard_service_name: "AWatchRusCollectorGuard"
aw_windows_collector_guard_loop_seconds: 60
aw_windows_force_task_restart: true
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
aw_windows_api_smoke_check_window_enabled_default: true
aw_windows_api_smoke_check_window_bucket: ""
aw_windows_api_smoke_check_min_events: 1
aw_windows_fail_on_validation_error: true
aw_windows_migration_enabled: true
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
tasks:
- name: Вычислить inventory host AW server по умолчанию
ansible.builtin.set_fact:
aw_server_inventory_host_effective: "{{ (groups['aw_server'] | default([]) | first) | default('', true) }}"
- name: Вычислить inventory host analytics node по умолчанию
ansible.builtin.set_fact:
aw_analytics_inventory_host_effective: "{{ (groups['proxmox'] | default([]) | first) | default('', true) }}"
- name: Вычислить effective host для AW server
ansible.builtin.set_fact:
aw_windows_server_host_effective: >-
{{
aw_windows_server_host
| default(
(
hostvars[aw_server_inventory_host_effective].ansible_host
| default(aw_server_inventory_host_effective, true)
)
if (aw_server_inventory_host_effective | length) > 0
else '',
true
)
}}
- name: Вычислить effective каталог health validation на AW server
ansible.builtin.set_fact:
aw_windows_health_validation_dir_effective: >-
{{
(
hostvars[aw_server_inventory_host_effective].aw_rus_health_validation_dir
| default('/var/lib/activitywatch/health/windows-validation', true)
)
if (aw_server_inventory_host_effective | length) > 0
else ''
}}
- name: Вычислить effective host для policy engine
ansible.builtin.set_fact:
aw_windows_policy_engine_host_effective: >-
{{
aw_windows_policy_engine_host
| default(aw_windows_server_host_effective, true)
}}
- name: Вычислить effective host для file-1C analytics
ansible.builtin.set_fact:
aw_windows_file_1c_target_host_effective: >-
{{
aw_windows_file_1c_target_host
| default(
(
hostvars[aw_analytics_inventory_host_effective].ansible_host
| default(aw_analytics_inventory_host_effective, true)
)
if (aw_analytics_inventory_host_effective | length) > 0
else '',
true
)
}}
- name: Проверить обязательные переменные
ansible.builtin.assert:
that:
- aw_windows_server_host_effective | length > 0
- aw_windows_server_port is defined
- aw_windows_server_scheme is defined
- aw_windows_domain is defined
- aw_windows_builtin_administrator_name is defined
- aw_windows_builtin_administrator_name | length > 0
- aw_windows_users_effective | length > 0
- aw_windows_install_root is defined
- aw_windows_state_root is defined
- (not (aw_windows_file_1c_auto_upload_enabled | bool)) or (aw_windows_file_1c_target_host_effective | length > 0)
fail_msg: "Не заданы обязательные переменные Windows-развёртывания."
- name: Нормализовать effective флаги collector'ов и smoke-check
ansible.builtin.set_fact:
aw_windows_afk_enabled_effective: "{{ (aw_windows_afk_enabled | default(aw_windows_afk_enabled_default)) | bool }}"
aw_windows_window_enabled_effective: "{{ (aw_windows_window_enabled | default(aw_windows_window_enabled_default)) | bool }}"
aw_windows_api_smoke_check_window_enabled_effective: "{{ (aw_windows_api_smoke_check_window_enabled | default(aw_windows_api_smoke_check_window_enabled_default)) | bool }}"
- name: Создать каталоги развёртывания
ansible.windows.win_file:
path: "{{ item }}"
state: directory
loop:
- "{{ aw_windows_deploy_root }}"
- "{{ aw_windows_deploy_root }}\\windows"
- name: Загрузить Windows toolkit развёртывания
ansible.windows.win_copy:
src: "{{ aw_windows_repo_root }}/windows/{{ item }}"
dest: "{{ aw_windows_deploy_root }}\\windows\\{{ item }}"
loop:
- ActivityWatch.Windows.Common.psd1
- ActivityWatch.Windows.Common.psm1
- browser-domains-native-collector.ps1
- dlp-endpoint-signals-collector.ps1
- dlp-policy-client.ps1
- email-outbound-collector.ps1
- file-operations-collector.ps1
- worktime-session-collector.ps1
- export-evtx-for-hayabusa.ps1
- export-upload-hayabusa-to-aw-server.ps1
- export-upload-file-1c-telemetry.ps1
- sync-dlp-evidence-artifacts.ps1
- migrate-awatch-rus-paths.ps1
- deploy-domain-users.ps1
- deploy-ensemble.ps1
- hardening-recovery.ps1
- AWatchRusCollectorGuardService.cs
- aw-collector-guard.ps1
- install-collector-guard-service.ps1
- rebuild-worktime-tasks.ps1
- audit-cryptopro.ps1
- validate-deployment.ps1
- web-category-rules.example.json
- dlp-policy.example.json
- name: Нормализовать кодировку PowerShell файлов (UTF-8 BOM для Windows PowerShell)
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$toolkitDir = "{{ aw_windows_deploy_root }}\windows"
$encIn = New-Object System.Text.UTF8Encoding($false)
$encOut = New-Object System.Text.UTF8Encoding($true)
Get-ChildItem -LiteralPath $toolkitDir -File -Include *.ps1,*.psm1,*.psd1 | ForEach-Object {
$text = [System.IO.File]::ReadAllText($_.FullName, $encIn)
[System.IO.File]::WriteAllText($_.FullName, $text, $encOut)
}
- name: Загрузить список пользователей для доменного развёртывания
ansible.windows.win_copy:
dest: "{{ aw_windows_deploy_root }}\\windows\\users.txt"
content: |
{% for user in aw_windows_users_effective -%}
{{ user }}
{% endfor -%}
- name: Проверить нужен ли migration с legacy ActivityWatch путей
when: aw_windows_migration_enabled | bool
ansible.windows.win_stat:
path: "{{ aw_windows_legacy_state_root }}\\deployment-config.json"
register: aw_windows_legacy_config
- name: Выполнить безопасную migration legacy prod в AWatch-rus
when:
- aw_windows_migration_enabled | bool
- aw_windows_legacy_config.stat.exists | default(false)
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$result = & "{{ aw_windows_deploy_root }}\windows\migrate-awatch-rus-paths.ps1" `
-OldInstallRoot "{{ aw_windows_legacy_install_root }}" `
-OldStateRoot "{{ aw_windows_legacy_state_root }}" `
-NewInstallRoot "{{ aw_windows_install_root }}" `
-NewStateRoot "{{ aw_windows_state_root }}" `
-ToolkitRoot "{{ aw_windows_deploy_root }}\windows"
$result | ConvertTo-Json -Depth 8 | Out-File -FilePath "{{ aw_windows_migration_report_remote_path }}" -Encoding utf8
- name: Запустить Windows/RDP ensemble развёртывание
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$env:AWATCH_RUS_BUILTIN_ADMINISTRATOR_NAME = "{{ aw_windows_builtin_administrator_name }}"
$params = @{
ServerScheme = "{{ aw_windows_server_scheme }}"
ServerHost = "{{ aw_windows_server_host_effective }}"
ServerPort = {{ aw_windows_server_port }}
Version = "{{ aw_windows_package_version }}"
Domain = "{{ aw_windows_domain }}"
UserListPath = "{{ aw_windows_deploy_root }}\windows\users.txt"
InstallRoot = "{{ aw_windows_install_root }}"
StateRoot = "{{ aw_windows_state_root }}"
AfkEnabled = {{ '$true' if (aw_windows_afk_enabled_effective | bool) else '$false' }}
WindowEnabled = {{ '$true' if (aw_windows_window_enabled_effective | bool) else '$false' }}
FileOpsEnabled = {{ '$true' if (aw_windows_file_ops_enabled | bool) else '$false' }}
LocalAgentLogsEnabled = {{ '$true' if (aw_windows_local_agent_logs_enabled | bool) else '$false' }}
IncidentCaptureEnabled = {{ '$true' if (aw_windows_incident_capture_enabled | bool) else '$false' }}
IncidentScreenshotEnabled = {{ '$true' if (aw_windows_incident_screenshot_enabled | bool) else '$false' }}
IncidentArtifactsRoot = "{{ aw_windows_incident_artifacts_root }}"
EvtxExportRoot = "{{ aw_windows_forensics_root }}"
EvtxRetentionDays = {{ aw_windows_evtx_retention_days | int }}
LogonMarkerEnabled = {{ '$true' if (aw_windows_logon_marker_enabled | bool) else '$false' }}
ProcessEventsEnabled = {{ '$true' if (aw_windows_process_events_enabled | bool) else '$false' }}
PolicyMode = "{{ aw_windows_policy_mode }}"
PolicyEngineEnabled = {{ '$true' if (aw_windows_policy_engine_enabled | bool) else '$false' }}
PolicyEngineHost = "{{ aw_windows_policy_engine_host_effective }}"
PolicyEnginePort = {{ aw_windows_policy_engine_port }}
PolicyEngineScheme = "{{ aw_windows_policy_engine_scheme }}"
PolicyRefreshSeconds = {{ aw_windows_policy_refresh_seconds }}
HayabusaAutoUploadEnabled = {{ '$true' if (aw_windows_hayabusa_auto_upload_enabled | bool) else '$false' }}
HayabusaAutoUploadIntervalHours = {{ aw_windows_hayabusa_auto_upload_interval_hours | int }}
HayabusaAutoUploadHoursBack = {{ aw_windows_hayabusa_auto_upload_hours_back | int }}
HayabusaAutoUploadMode = "{{ aw_windows_hayabusa_auto_upload_mode }}"
HayabusaAutoUploadTaskName = "{{ aw_windows_hayabusa_auto_upload_task_name }}"
File1CAutoUploadEnabled = {{ '$true' if (aw_windows_file_1c_auto_upload_enabled | bool) else '$false' }}
File1CAutoUploadIntervalHours = {{ aw_windows_file_1c_auto_upload_interval_hours | int }}
File1CAutoUploadTaskName = "{{ aw_windows_file_1c_auto_upload_task_name }}"
File1CTargetHost = "{{ aw_windows_file_1c_target_host_effective }}"
File1CTargetUser = "{{ aw_windows_file_1c_target_user }}"
File1CRegistryWorkbookPath = "{{ aw_windows_file_1c_registry_workbook_path }}"
CustomRulesPath = "{{ aw_windows_rules_path }}"
CustomPolicyPath = "{{ aw_windows_policy_path }}"
}
{% if (aw_windows_package_url | default('') | string | length) > 0 %}
$params.PackageUrl = "{{ aw_windows_package_url }}"
{% endif %}
{% if (aw_windows_package_zip_path | default('') | string | length) > 0 %}
$params.PackageZipPath = "{{ aw_windows_package_zip_path }}"
{% endif %}
{% if (aw_windows_evtx_channels | default([]) | length) > 0 %}
$params.EvtxChannels = @(
{% for channel in aw_windows_evtx_channels %}
"{{ channel }}"{% if not loop.last %},{% endif %}
{% endfor %}
)
{% endif %}
{% if (aw_windows_hostname_override | default('') | string | length) > 0 %}
$params.AwHostname = "{{ aw_windows_hostname_override }}"
{% endif %}
{% if aw_windows_skip_hardening | bool %}
$params.SkipHardening = $true
{% endif %}
{% if aw_windows_integration_test_enabled | bool %}
$params.IntegrationTestEnabled = $true
{% endif %}
& "{{ aw_windows_deploy_root }}\windows\deploy-ensemble.ps1" @params
- name: Удалить лишние ActivityWatch Launch tasks вне текущего deployment-config
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$config = Get-Content -Raw -LiteralPath "{{ aw_windows_state_root }}\deployment-config.json" | ConvertFrom-Json
$desired = @($config.userTasks | ForEach-Object { [string]$_.LaunchTaskName })
foreach ($task in @(Get-ScheduledTask | Where-Object { $_.TaskName -like 'ActivityWatch Launch *' })) {
if ($desired -notcontains [string]$task.TaskName) {
Unregister-ScheduledTask -TaskName $task.TaskName -Confirm:$false -ErrorAction SilentlyContinue
& cmd.exe /c "schtasks /Delete /TN `"$($task.TaskName)`" /F >nul 2>&1" | Out-Null
}
}
- name: Принудительно запустить ActivityWatch recovery и launch tasks
when: aw_windows_force_task_restart | bool
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
function Get-CollectorKey {
param([string]$CommandLine)
if (-not $CommandLine) { return $null }
$cl = $CommandLine.ToLowerInvariant()
if ($cl -like '*browser-domains-native-collector.ps1*') { return 'browser' }
if ($cl -like '*file-operations-collector.ps1*') { return 'fileops' }
if ($cl -like '*dlp-endpoint-signals-collector.ps1*') { return 'endpoint' }
if ($cl -like '*email-outbound-collector.ps1*') { return 'email' }
if ($cl -like '*worktime-session-collector.ps1*') { return 'worktime' }
return $null
}
$collectorProcs = Get-CimInstance Win32_Process |
Where-Object { $_.Name -eq 'powershell.exe' -and $_.CommandLine } |
ForEach-Object {
$key = Get-CollectorKey -CommandLine $_.CommandLine
if ($key) {
$groupKey = if ($key -eq 'worktime') { 'worktime::global' } else { '{0}::{1}' -f $key, ([int]$_.SessionId) }
[pscustomobject]@{
ProcessId = [int]$_.ProcessId
SessionId = [int]$_.SessionId
CreationDate = $_.CreationDate
CollectorKey = $key
GroupKey = $groupKey
}
}
} |
Where-Object { $_ -ne $null }
# Keep only one process per collector scope: worktime collector is global, others stay per-session.
foreach ($group in ($collectorProcs | Group-Object GroupKey)) {
$ordered = @($group.Group | Sort-Object CreationDate -Descending)
if ($ordered.Count -le 1) { continue }
foreach ($dup in $ordered | Select-Object -Skip 1) {
Stop-Process -Id $dup.ProcessId -Force -ErrorAction SilentlyContinue
}
}
# Force managed collectors/watchers to reload the freshly deployed scripts.
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object {
(
($_.Name -ieq 'powershell.exe' -or $_.Name -ieq 'pwsh.exe') -and
$_.CommandLine -match 'C:\\ProgramData\\AWatch-rus\\' -and
$_.CommandLine -match '(collector|launch-watchers|recovery-loop)\.ps1'
) -or
($_.Name -ieq 'aw-watcher-afk.exe') -or
($_.Name -ieq 'aw-watcher-window.exe')
} |
ForEach-Object {
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
Start-Sleep -Seconds 2
$modulePath = "{{ aw_windows_deploy_root }}\windows\ActivityWatch.Windows.Common.psm1"
Import-Module $modulePath -Force
# Ensure tasks are enabled (some environments keep them disabled, causing "0s" in WebUI).
try {
Enable-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" -ErrorAction SilentlyContinue | Out-Null
} catch {}
$config = Get-Content -Raw -LiteralPath "{{ aw_windows_state_root }}\deployment-config.json" | ConvertFrom-Json
$configPaths = Get-ActivityWatchRecoveryConfigPaths -PrimaryConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
$taskDefs = @(Get-ActivityWatchRecoveryTaskDefinitions -ConfigPaths $configPaths)
$sessionRecords = @(Get-ActivityWatchSessionRecords)
foreach ($taskDef in @($taskDefs)) {
try { Enable-ScheduledTask -TaskName ([string]$taskDef.taskName) -ErrorAction SilentlyContinue | Out-Null } catch {}
}
$recoveryTask = Get-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}" -ErrorAction SilentlyContinue
if ($recoveryTask -and $recoveryTask.State -notin @('Running', 'Queued')) {
Start-ScheduledTask -TaskName "{{ aw_windows_recovery_task_name }}"
}
foreach ($taskDef in @($taskDefs)) {
if (-not (Test-ActivityWatchUserHasManagedSession -UserId ([string]$taskDef.userId) -SessionRecords $sessionRecords -IncludeLive -IncludeDisconnected)) {
continue
}
$launchTask = Get-ScheduledTask -TaskName ([string]$taskDef.taskName) -ErrorAction SilentlyContinue
if ($launchTask -and $launchTask.State -notin @('Running', 'Queued')) {
Start-ScheduledTask -TaskName ([string]$taskDef.taskName) -ErrorAction SilentlyContinue
}
}
- name: Установить session-aware AWatch-rus Collector Guard service
when: aw_windows_collector_guard_enabled | bool
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$guardParams = @{
ConfigPath = "{{ aw_windows_state_root }}\deployment-config.json"
Mode = "{{ aw_windows_collector_guard_mode }}"
ServiceName = "{{ aw_windows_collector_guard_service_name }}"
LoopSeconds = {{ aw_windows_collector_guard_loop_seconds | int }}
}
& "{{ aw_windows_deploy_root }}\windows\install-collector-guard-service.ps1" @guardParams
- name: Получить Windows hostname для AW smoke-check bucket
when:
- aw_windows_api_smoke_check_enabled | bool
ansible.windows.win_command: powershell.exe -NoProfile -Command "$env:COMPUTERNAME"
register: aw_windows_hostname_result
changed_when: false
- name: Вычислить AW worktime smoke-check bucket
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_hostname_result.stdout is defined
ansible.builtin.set_fact:
aw_windows_api_smoke_check_bucket_effective: >-
{{
aw_windows_api_smoke_check_bucket
if (aw_windows_api_smoke_check_bucket | default('') | string | length) > 0
else 'aw-worktime-sessions_' ~ (aw_windows_hostname_result.stdout | trim)
}}
- name: Вычислить AW Window smoke-check bucket
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_api_smoke_check_window_enabled_effective | bool
- aw_windows_window_enabled_effective | bool
- aw_windows_hostname_result.stdout is defined
ansible.builtin.set_fact:
aw_windows_api_smoke_check_window_bucket_effective: >-
{{
aw_windows_api_smoke_check_window_bucket
if (aw_windows_api_smoke_check_window_bucket | default('') | string | length) > 0
else 'aw-watcher-window_' ~ (aw_windows_hostname_result.stdout | trim)
}}
- name: Выполнить AW API smoke-check (worktime bucket должен получать события)
when:
- aw_windows_api_smoke_check_enabled | bool
ansible.builtin.uri:
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host_effective }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET
status_code: 200
return_content: true
register: aw_windows_api_smoke_result
failed_when: false
until: >-
(aw_windows_api_smoke_result.status | default(0)) == 200
and ((aw_windows_api_smoke_result.json | default([])) | length) >= aw_windows_api_smoke_check_min_events
and (
(aw_windows_api_smoke_result.json | default([]))
| selectattr('data.source', 'equalto', 'worktime-session-collector')
| list
| length
) > 0
retries: 12
delay: 5
ignore_errors: "{{ (not aw_windows_fail_on_validation_error | bool) }}"
delegate_to: localhost
changed_when: false
- name: Выполнить AW API smoke-check (Window bucket должен получать события)
when:
- aw_windows_api_smoke_check_enabled | bool
- aw_windows_api_smoke_check_window_enabled_effective | bool
- aw_windows_window_enabled_effective | bool
ansible.builtin.uri:
url: "{{ aw_windows_server_scheme }}://{{ aw_windows_server_host_effective }}:{{ aw_windows_server_port }}/api/0/buckets/{{ aw_windows_api_smoke_check_window_bucket_effective }}/events?limit={{ aw_windows_api_smoke_check_limit }}"
method: GET
status_code: 200
return_content: true
register: aw_windows_api_smoke_window_result
failed_when: false
until: >-
(aw_windows_api_smoke_window_result.status | default(0)) == 200
and ((aw_windows_api_smoke_window_result.json | default([])) | length) >= aw_windows_api_smoke_check_min_events
retries: 12
delay: 5
ignore_errors: "{{ (not aw_windows_fail_on_validation_error | bool) }}"
delegate_to: localhost
changed_when: false
- name: Валидировать развёртывание на эндпоинте
ansible.windows.win_powershell:
script: |
$ErrorActionPreference = 'Stop'
$result = & "{{ aw_windows_deploy_root }}\windows\validate-deployment.ps1" `
-ConfigPath "{{ aw_windows_state_root }}\deployment-config.json"
$result | ConvertTo-Json -Depth 12 | Out-File -FilePath "{{ aw_windows_validation_remote_path }}" -Encoding utf8
return $result
- name: Создать локальную директорию для отчётов валидации
ansible.builtin.file:
path: "{{ aw_windows_validation_local_dir }}"
state: directory
mode: "0755"
delegate_to: localhost
- name: Стянуть отчёт валидации с эндпоинта
ansible.builtin.fetch:
src: "{{ aw_windows_validation_remote_path }}"
dest: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
flat: true
- name: Убедиться, что каталог Windows validation существует на AW server
when:
- aw_server_inventory_host_effective | length > 0
- aw_windows_health_validation_dir_effective | length > 0
ansible.builtin.file:
path: "{{ aw_windows_health_validation_dir_effective }}"
state: directory
mode: "0755"
delegate_to: "{{ aw_server_inventory_host_effective }}"
- name: Опубликовать validation report на AW server для aw-rus-healthd
when:
- aw_server_inventory_host_effective | length > 0
- aw_windows_health_validation_dir_effective | length > 0
ansible.builtin.copy:
src: "{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json"
dest: "{{ aw_windows_health_validation_dir_effective }}/{{ inventory_hostname }}-aw_validate_ansible.json"
mode: "0644"
delegate_to: "{{ aw_server_inventory_host_effective }}"
- name: Проверить статус валидации
ansible.builtin.shell: |
python3 - <<'PY'
import json, sys
with open('{{ aw_windows_validation_local_dir }}/{{ inventory_hostname }}-aw_validate_ansible.json', 'r', encoding='utf-8-sig') as f:
data = json.load(f)
if not data.get('overallOk', False):
failed = ", ".join(data.get("summary", {}).get("failedSections", [])) or "unknown"
print(f"Validation failed for {{ inventory_hostname }}: {failed}")
sys.exit(1)
PY
delegate_to: localhost
when: aw_windows_fail_on_validation_error | bool
@@ -1,86 +0,0 @@
aw_server_version: "v0.13.2"
aw_server_download_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-linux-x86_64.zip"
aw_server_bind_host: "0.0.0.0"
aw_server_port: 5600
aw_server_webui_dir: "/opt/activitywatch/webui-ru"
aw_server_data_dir: "/var/lib/activitywatch"
aw_server_db_path: "/var/lib/activitywatch/.local/share/activitywatch/aw-server-rust/sqlite.db"
aw_server_log_dir: "/var/log/activitywatch"
aw_server_user: "activitywatch"
aw_server_group: "activitywatch"
aw_server_inventory_host: "{{ (groups['aw_server'] | default([]) | first) | default('aw-server', true) }}"
aw_server_public_host: "{{ (hostvars[aw_server_inventory_host].ansible_host | default(aw_server_inventory_host, true)) if (aw_server_inventory_host | length) > 0 else 'aw-server' }}"
aw_worktime_report_base: "http://{{ aw_server_public_host }}:5610"
aw_worktime_timezone: "Europe/Moscow"
aw_worktime_influx_enabled: false
aw_worktime_influx_url: "http://<INFLUXDB_HOST>:8086"
aw_worktime_influx_org: "proxmox"
aw_worktime_influx_bucket: "aw_metrics"
aw_worktime_influx_hosts: "SHARKON2025"
aw_worktime_influx_days: "today,yesterday"
aw_worktime_influx_token: ""
aw_dlp_influx_enabled: false
aw_dlp_influx_url: "http://<INFLUXDB_HOST>:8086"
aw_dlp_influx_org: "proxmox"
aw_dlp_influx_bucket: "aw_metrics"
aw_dlp_influx_hosts: "SHARKON2025"
aw_dlp_influx_lookback_days: 30
aw_dlp_influx_event_limit: 2000
aw_dlp_influx_token: ""
aw_monitored_windows_host: "<WINDOWS_HOST>"
aw_monitored_windows_hostname: "SHARKON2025"
aw_rus_health_worktime_api_base: "http://127.0.0.1:5610"
aw_rus_health_state_dir: "{{ aw_server_data_dir }}/health"
aw_rus_health_validation_dir: "{{ aw_rus_health_state_dir }}/windows-validation"
aw_browser_smoke_enabled: true
aw_browser_smoke_engine: "chromium-cli"
aw_legacy_db_merge_enabled: false
aw_browser_smoke_timeout_ms: 20000
aw_browser_smoke_render_timeout_ms: 15000
aw_hayabusa_auto_case_enabled: true
aw_hayabusa_auto_case_min_severity: "medium"
aw_hayabusa_telegram_enabled: true
aw_hayabusa_telegram_min_severity: "high"
aw_hayabusa_telegram_bot_token: ""
aw_hayabusa_telegram_chat_ids: ""
aw_repo_root: "{{ playbook_dir | dirname }}"
# Применить базовые категории и views для рабочего времени через AW settings API.
# При прод-обновлениях это нужно оставлять включённым, иначе UI остаётся без views/classes.
aw_apply_worktime_settings: true
# Дополнительные origin для aw-server-rust CORS.
# Обязательно включите тот origin, с которого реально открывается Web UI.
aw_server_cors_origins:
- "http://127.0.0.1:5600"
- "http://localhost:5600"
- "http://{{ aw_server_public_host }}:5600"
- "http://aw-server:5600"
# Опциональные значения периода рабочего времени в Web UI.
# startOfDay задаёт границу дня и стартовое время окна отчёта.
# durationDefault задаёт диапазон по умолчанию в секундах.
#
# Рекомендуется явно задать рабочий интервал и дать playbook вычислить duration.
aw_worktime_from: "00:00"
aw_worktime_to: "17:00"
aw_worktime_start_of_day: "{{ aw_worktime_from }}"
aw_server_always_active_pattern: "aw-watcher-window"
aw_server_landingpage: "/#/activity/SHARKON2025/view/"
aw_health_strict_fileops: 0
aw_dlp_policy_engine_enabled: true
aw_dlp_policy_engine_bind_host: "0.0.0.0"
aw_dlp_policy_engine_port: 5601
aw_dlp_policy_engine_db_path: "{{ aw_server_data_dir }}/dlp-policy-engine.sqlite"
aw_dlp_content_analysis_enabled: true
aw_dlp_integrations_enabled: true
aw_dlp_case_management_enabled: true
aw_dlp_case_bind_host: "0.0.0.0"
aw_dlp_case_port: 5602
aw_dlp_case_db_path: "/opt/activitywatch/dlp-case-management/cases.db"
aw_dlp_compliance_enabled: true
aw_dlp_compliance_report_dir: "/opt/activitywatch/dlp-compliance/reports"
aw_dlp_compliance_template_path: "/opt/activitywatch/dlp-compliance/templates/152-fz-report.html"
aw_server_post_deploy_health_check_enabled: true
@@ -1,30 +0,0 @@
aw_pfsense_poller_config:
poll_interval_seconds: 60
aw:
server_host: "<AW_SERVER_HOST>"
server_port: 5600
hostname: "PFSENSE-EDGE01"
pulse_time_seconds: 120
timeout_seconds: 15
pfsense:
name: "pfSense Edge 01"
host: "<FIREWALL_HOST>"
scheme: "https"
verify_tls: false
timeout_seconds: 15
auth:
api_key: "replace-me"
api_secret: "replace-me"
endpoints:
- name: "system-status"
path: "/api/v2/status/system"
bucket_prefix: "aw-pfsense-health"
bucket_type: "aw.pfsense.health"
- name: "interfaces"
path: "/api/v2/interfaces"
bucket_prefix: "aw-pfsense-interfaces"
bucket_type: "aw.pfsense.interfaces"
- name: "gateways"
path: "/api/v2/status/gateways"
bucket_prefix: "aw-pfsense-gateways"
bucket_type: "aw.pfsense.gateways"
@@ -1,38 +0,0 @@
proxmox_ct_matrix:
- id: "203"
hostname: "activitywatch-user1"
storage: "local-lvm"
template: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst"
rootfs_size: "8G"
cores: "2"
memory: "2048"
swap: "512"
bridge: "vmbr10"
ip: "10.20.30.13/24"
gw: "10.20.30.1"
vlan: ""
nameserver: "1.1.1.1 8.8.8.8"
searchdomain: "example.internal"
password: "CHANGE_ME"
unprivileged: "1"
onboot: "1"
features: "nesting=1,keyctl=1"
- id: "204"
hostname: "activitywatch-user2"
storage: "local-lvm"
template: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst"
rootfs_size: "8G"
cores: "2"
memory: "2048"
swap: "512"
bridge: "vmbr10"
ip: "10.20.30.14/24"
gw: "10.20.30.1"
vlan: ""
nameserver: "1.1.1.1 8.8.8.8"
searchdomain: "example.internal"
password: "CHANGE_ME"
unprivileged: "1"
onboot: "1"
features: "nesting=1,keyctl=1"
@@ -1,18 +0,0 @@
proxmox_ct_id: "203"
proxmox_ct_hostname: "activitywatch-server"
proxmox_ct_storage: "local-lvm"
proxmox_ct_template: "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst"
proxmox_ct_rootfs_size: "8G"
proxmox_ct_cores: "2"
proxmox_ct_memory: "2048"
proxmox_ct_swap: "512"
proxmox_ct_bridge: "vmbr10"
proxmox_ct_ip: "10.20.30.13/24"
proxmox_ct_gw: "10.20.30.1"
proxmox_ct_vlan: ""
proxmox_ct_nameserver: "1.1.1.1 8.8.8.8"
proxmox_ct_searchdomain: "example.internal"
proxmox_ct_password: "CHANGE_ME"
proxmox_ct_unprivileged: "1"
proxmox_ct_onboot: "1"
proxmox_ct_features: "nesting=1,keyctl=1"
@@ -1,80 +0,0 @@
aw_windows_repo_root: "{{ playbook_dir | dirname }}"
aw_windows_deploy_root: "C:\\Program Files\\AWatch-rus"
aw_windows_server_scheme: "http"
# Leave empty to derive from the first host in [aw_server] inventory.
aw_windows_server_host: ""
aw_windows_server_port: 5600
aw_windows_hayabusa_auto_upload_enabled: false
aw_windows_hayabusa_auto_upload_interval_hours: 6
aw_windows_hayabusa_auto_upload_hours_back: 6
aw_windows_hayabusa_auto_upload_mode: "incident"
aw_windows_hayabusa_auto_upload_task_name: "ActivityWatch Hayabusa Upload"
aw_windows_package_version: "v0.13.2"
aw_windows_package_url: "https://github.com/ActivityWatch/activitywatch/releases/download/v0.13.2/activitywatch-v0.13.2-windows-x86_64.zip"
aw_windows_package_zip_path: ""
aw_windows_domain: "SHARKON2025"
# Localized name of the built-in local Administrator account (SID ending in -500).
# On the current Russian Windows host this must stay "Администратор";
# do not replace it with "Administrator" unless the target OS account is actually named that way.
aw_windows_builtin_administrator_name: "Администратор"
aw_windows_users:
- Администратор
- user1
- user2
- user3
- user4
- user5
aw_windows_extra_users: []
# Например:
# aw_windows_extra_users:
# - Администратор
# Единые Windows/RDP пути: те же, что использует InnoSetup.
aw_windows_install_root: "C:\\Program Files\\AWatch-rus\\bin"
aw_windows_state_root: "C:\\ProgramData\\AWatch-rus"
aw_windows_hostname_override: "" # Например: SHARKON2025
aw_windows_afk_enabled: true
aw_windows_window_enabled: true
aw_windows_file_ops_enabled: true
aw_windows_local_agent_logs_enabled: false
aw_windows_incident_capture_enabled: true
aw_windows_incident_screenshot_enabled: true
aw_windows_incident_artifacts_root: "{{ aw_windows_state_root }}\\incident-artifacts"
aw_windows_forensics_root: "{{ aw_windows_state_root }}\\forensics\\evtx-exports"
aw_windows_evtx_retention_days: 14
aw_windows_evtx_channels:
- Security
- System
- Application
- Microsoft-Windows-PowerShell/Operational
- Microsoft-Windows-TerminalServices-LocalSessionManager/Operational
- Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational
aw_windows_logon_marker_enabled: true
aw_windows_process_events_enabled: false
aw_windows_skip_hardening: false
aw_windows_rules_path: "{{ aw_windows_deploy_root }}\\windows\\web-category-rules.example.json"
aw_windows_policy_path: "{{ aw_windows_deploy_root }}\\windows\\dlp-policy.example.json"
aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ansible.json"
aw_windows_validation_local_dir: "/tmp/aw-rus-validation-{{ lookup('env','USER') | default('ansible', true) }}"
aw_windows_fail_on_validation_error: true
# Collector Guard supervises collectors, but ActivityWatch Recovery must stay enabled
# as a fallback and as the launch-task bootstrap path for managed RDP sessions.
aw_windows_collector_guard_enabled: true
aw_windows_collector_guard_mode: "enforce"
aw_windows_collector_guard_service_name: "AWatchRusCollectorGuard"
aw_windows_collector_guard_loop_seconds: 60
# Безопасная миграция текущего прода со старых путей в единый профиль AWatch-rus.
aw_windows_migration_enabled: true
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
aw_windows_migration_report_remote_path: "{{ aw_windows_state_root }}\\aw_migration_ansible.json"
# По умолчанию AFK bucket вычисляется как aw-watcher-afk_<COMPUTERNAME>.
# Задайте явное значение только если watcher пишет в нестандартный bucket.
aw_windows_api_smoke_check_enabled: true
aw_windows_api_smoke_check_bucket: ""
aw_windows_api_smoke_check_limit: 10
@@ -1,18 +0,0 @@
---
# Полный установщик AWatch-rus.
# Выполняет развёртывание одной командой:
# 1) создание Proxmox CT + bootstrap AW (если в inventory есть [proxmox])
# 2) развёртывание AW server на хостах [aw_server]
# 3) развёртывание Windows/RDP collector'ов на [aw_windows]
# 4) развёртывание pfSense poller'а на [aw_pfsense_pollers]
# 5) импорт Grafana dashboard'ов на [grafana]
#
# Примечания:
# - Заполняйте только нужные группы inventory для своего окружения.
# - Play без совпадающих host groups Ansible пропускает автоматически.
- import_playbook: provision_proxmox_ct_and_deploy_aw.yml
- import_playbook: deploy_aw_server.yml
- import_playbook: deploy_aw_windows.yml
- import_playbook: deploy_aw_pfsense_poller.yml
- import_playbook: deploy_grafana_dashboards.yml
@@ -1,17 +0,0 @@
[proxmox]
pve-main ansible_host=192.168.10.2 ansible_user=root ansible_port=22
[aw_server]
aw-ct ansible_host=10.20.30.13 ansible_user=root ansible_port=22
[aw_windows]
# Примечание: в русифицированных Windows часто нужен "Администратор", а не "Administrator".
win-node1 ansible_host=<WINDOWS_HOST> ansible_user=Администратор ansible_password=CHANGE_ME ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 ansible_winrm_server_cert_validation=ignore
[aw_pfsense_pollers]
# pfsense-poller1 ansible_host=192.168.100.30 ansible_user=root ansible_port=22
[grafana]
# Для API-import playbook достаточно указать grafana_url.
# SSH-подключение не требуется: playbook работает через HTTP API с control host.
grafana-main grafana_url=http://10.20.30.11:3000

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