feat(detmir): add rust-first operations tooling
This commit is contained in:
+3
-2
@@ -122,8 +122,9 @@ 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` и все `ActivityWatch Launch *` задачи;
|
||||
- включает (`Enable-ScheduledTask`) `ActivityWatch Recovery` и все `ActivityWatch Launch *` задачи перед запуском (иначе WebUI может показывать `Active time: 0s`);
|
||||
- после 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`;
|
||||
|
||||
+1111
-20
File diff suppressed because it is too large
Load Diff
@@ -50,7 +50,7 @@
|
||||
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: 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"
|
||||
@@ -58,6 +58,10 @@
|
||||
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: ""
|
||||
@@ -181,6 +185,9 @@
|
||||
- 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
|
||||
@@ -371,27 +378,50 @@
|
||||
|
||||
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
|
||||
foreach ($taskDef in @($config.userTasks)) {
|
||||
try { Enable-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue | Out-Null } catch {}
|
||||
$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 @($config.userTasks)) {
|
||||
$launchTask = Get-ScheduledTask -TaskName ([string]$taskDef.launchTaskName) -ErrorAction SilentlyContinue
|
||||
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.launchTaskName) -ErrorAction SilentlyContinue
|
||||
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
|
||||
|
||||
@@ -14,67 +14,94 @@
|
||||
proxmox_web_gateway_tls_cert_path: "{{ proxmox_web_gateway_tls_dir }}/fullchain.pem"
|
||||
proxmox_web_gateway_tls_key_path: "{{ proxmox_web_gateway_tls_dir }}/privkey.pem"
|
||||
proxmox_web_gateway_health_path: "/healthz"
|
||||
proxmox_web_gateway_auth_realm: "DetMir operations gateway"
|
||||
proxmox_web_gateway_auth_user: "detmir"
|
||||
proxmox_web_gateway_auth_file: "/etc/nginx/proxmox-web-gateway.htpasswd"
|
||||
proxmox_web_gateway_credentials_file: "/root/proxmox-web-gateway.credentials"
|
||||
proxmox_web_gateway_routes:
|
||||
- slug: "proxmox-gui"
|
||||
title: "Proxmox VE"
|
||||
category: "Host"
|
||||
description: "Основная панель управления Proxmox VE."
|
||||
target_url: "https://10.10.10.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"
|
||||
proxy_path: "/r/file1c/brief"
|
||||
proxy_target_url: "http://10.10.10.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"
|
||||
proxy_path: "/r/file1c/actions"
|
||||
proxy_target_url: "http://10.10.10.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"
|
||||
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"
|
||||
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"
|
||||
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/"
|
||||
external_enabled: false
|
||||
- slug: "influxdb"
|
||||
title: "InfluxDB"
|
||||
category: "Data"
|
||||
description: "InfluxDB LXC на CT 200."
|
||||
target_url: "http://10.10.10.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/"
|
||||
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/"
|
||||
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/"
|
||||
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?day=today"
|
||||
proxy_path: "/r/aw-worktime"
|
||||
proxy_target_url: "http://10.10.10.13:5610/reports/worktime/management?day=today"
|
||||
external_enabled: true
|
||||
|
||||
tasks:
|
||||
- name: Установить nginx
|
||||
@@ -123,6 +150,50 @@
|
||||
args:
|
||||
creates: "{{ proxmox_web_gateway_tls_cert_path }}"
|
||||
|
||||
- name: Проверить наличие gateway Basic Auth файла
|
||||
ansible.builtin.stat:
|
||||
path: "{{ proxmox_web_gateway_auth_file }}"
|
||||
register: proxmox_web_gateway_auth_stat
|
||||
|
||||
- name: Создать gateway Basic Auth credential при первом запуске
|
||||
ansible.builtin.shell: |
|
||||
set -eu
|
||||
umask 077
|
||||
password="$(openssl rand -base64 24 | tr -d '\n')"
|
||||
hash="$(openssl passwd -apr1 "$password")"
|
||||
printf '%s:%s\n' '{{ proxmox_web_gateway_auth_user }}' "$hash" > '{{ proxmox_web_gateway_auth_file }}'
|
||||
printf 'url=https://{{ proxmox_web_gateway_public_hostname }}/\nuser={{ proxmox_web_gateway_auth_user }}\npassword=%s\n' "$password" > '{{ proxmox_web_gateway_credentials_file }}'
|
||||
args:
|
||||
executable: /bin/sh
|
||||
creates: "{{ proxmox_web_gateway_auth_file }}"
|
||||
no_log: true
|
||||
when: not proxmox_web_gateway_auth_stat.stat.exists
|
||||
|
||||
- name: Зафиксировать права gateway Basic Auth файла
|
||||
ansible.builtin.file:
|
||||
path: "{{ proxmox_web_gateway_auth_file }}"
|
||||
owner: root
|
||||
group: www-data
|
||||
mode: "0640"
|
||||
|
||||
- name: Зафиксировать права файла с gateway credential
|
||||
ansible.builtin.file:
|
||||
path: "{{ proxmox_web_gateway_credentials_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
|
||||
- name: Прочитать gateway credential для локальной проверки
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ proxmox_web_gateway_credentials_file }}"
|
||||
register: proxmox_web_gateway_credentials_slurp
|
||||
no_log: true
|
||||
|
||||
- name: Подготовить gateway auth password для проверок
|
||||
ansible.builtin.set_fact:
|
||||
proxmox_web_gateway_auth_password: "{{ (proxmox_web_gateway_credentials_slurp.content | b64decode).split('password=')[1].split('\n')[0] }}"
|
||||
no_log: true
|
||||
|
||||
- name: Развернуть index.html gateway
|
||||
ansible.builtin.template:
|
||||
src: "templates/proxmox-web-gateway-index.html.j2"
|
||||
@@ -180,25 +251,36 @@
|
||||
- proxmox_web_gateway_http_redirect.location != "https://{{ proxmox_web_gateway_public_hostname }}/"
|
||||
changed_when: false
|
||||
|
||||
- name: Проверить redirect на Proxmox GUI
|
||||
- name: Проверить старый /go путь без auth закрыт
|
||||
ansible.builtin.uri:
|
||||
url: "https://127.0.0.1/go/proxmox-gui"
|
||||
headers:
|
||||
Host: "{{ proxmox_web_gateway_public_hostname }}"
|
||||
validate_certs: false
|
||||
follow_redirects: none
|
||||
status_code: 302
|
||||
status_code: 401
|
||||
register: proxmox_web_gateway_redirect
|
||||
failed_when:
|
||||
- proxmox_web_gateway_redirect.status != 302
|
||||
- proxmox_web_gateway_redirect.location != "https://10.10.10.2:8006/"
|
||||
- proxmox_web_gateway_redirect.status != 401
|
||||
changed_when: false
|
||||
|
||||
- name: Проверить index по public hostname
|
||||
- name: Проверить index без auth закрыт
|
||||
ansible.builtin.uri:
|
||||
url: "https://127.0.0.1/"
|
||||
headers:
|
||||
Host: "{{ proxmox_web_gateway_public_hostname }}"
|
||||
validate_certs: false
|
||||
status_code: 401
|
||||
changed_when: false
|
||||
|
||||
- name: Проверить index по public hostname с auth
|
||||
ansible.builtin.uri:
|
||||
url: "https://127.0.0.1/"
|
||||
headers:
|
||||
Host: "{{ proxmox_web_gateway_public_hostname }}"
|
||||
url_username: "{{ proxmox_web_gateway_auth_user }}"
|
||||
url_password: "{{ proxmox_web_gateway_auth_password }}"
|
||||
force_basic_auth: true
|
||||
return_content: true
|
||||
validate_certs: false
|
||||
register: proxmox_web_gateway_named_index
|
||||
@@ -207,6 +289,69 @@
|
||||
- proxmox_web_gateway_public_hostname not in proxmox_web_gateway_named_index.content
|
||||
changed_when: false
|
||||
|
||||
- name: Проверить reverse proxy к 1C brief с auth
|
||||
ansible.builtin.uri:
|
||||
url: "https://127.0.0.1/r/file1c/brief"
|
||||
headers:
|
||||
Host: "{{ proxmox_web_gateway_public_hostname }}"
|
||||
url_username: "{{ proxmox_web_gateway_auth_user }}"
|
||||
url_password: "{{ proxmox_web_gateway_auth_password }}"
|
||||
force_basic_auth: true
|
||||
validate_certs: false
|
||||
status_code: 200
|
||||
changed_when: false
|
||||
|
||||
- name: Проверить reverse proxy к Grafana health с auth
|
||||
ansible.builtin.uri:
|
||||
url: "https://127.0.0.1/r/grafana/api/health"
|
||||
headers:
|
||||
Host: "{{ proxmox_web_gateway_public_hostname }}"
|
||||
url_username: "{{ proxmox_web_gateway_auth_user }}"
|
||||
url_password: "{{ proxmox_web_gateway_auth_password }}"
|
||||
force_basic_auth: true
|
||||
validate_certs: false
|
||||
status_code: 200
|
||||
changed_when: false
|
||||
|
||||
- name: Проверить reverse proxy к AW info с auth
|
||||
ansible.builtin.uri:
|
||||
url: "https://127.0.0.1/r/aw/api/0/info"
|
||||
headers:
|
||||
Host: "{{ proxmox_web_gateway_public_hostname }}"
|
||||
url_username: "{{ proxmox_web_gateway_auth_user }}"
|
||||
url_password: "{{ proxmox_web_gateway_auth_password }}"
|
||||
force_basic_auth: true
|
||||
validate_certs: false
|
||||
status_code: 200
|
||||
changed_when: false
|
||||
|
||||
- name: Проверить browser-origin AW API query без 403
|
||||
ansible.builtin.uri:
|
||||
url: "https://127.0.0.1/api/0/query/"
|
||||
method: POST
|
||||
headers:
|
||||
Host: "{{ proxmox_web_gateway_public_hostname }}"
|
||||
Origin: "https://{{ proxmox_web_gateway_public_hostname }}"
|
||||
Referer: "https://{{ proxmox_web_gateway_public_hostname }}/r/aw/"
|
||||
Content-Type: "application/json"
|
||||
body_format: json
|
||||
body:
|
||||
timeperiods:
|
||||
- "1970-01-01T00:00:00+00:00/1970-01-01T00:01:00+00:00"
|
||||
query:
|
||||
- "RETURN = [];"
|
||||
url_username: "{{ proxmox_web_gateway_auth_user }}"
|
||||
url_password: "{{ proxmox_web_gateway_auth_password }}"
|
||||
force_basic_auth: true
|
||||
validate_certs: false
|
||||
status_code: 200
|
||||
return_content: true
|
||||
register: proxmox_web_gateway_aw_query
|
||||
failed_when:
|
||||
- proxmox_web_gateway_aw_query.status != 200
|
||||
- proxmox_web_gateway_aw_query.content != "[[]]"
|
||||
changed_when: false
|
||||
|
||||
- name: Проверить локальный HTTPS health endpoint gateway
|
||||
ansible.builtin.uri:
|
||||
url: "https://127.0.0.1{{ proxmox_web_gateway_health_path }}"
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
tsj_bot_script_name: "tsj_guardian_bot.py"
|
||||
tsj_bot_script_dest: "{{ tsj_bot_root }}/{{ tsj_bot_script_name }}"
|
||||
tsj_bot_source_local_path: "{{ aw_repo_root }}/proxmox/tsj_guardian_bot.py"
|
||||
tsj_bot_watchdog_name: "tsj_guardian_watchdog.sh"
|
||||
tsj_bot_watchdog_dest: "{{ tsj_bot_root }}/{{ tsj_bot_watchdog_name }}"
|
||||
tsj_bot_watchdog_source_local_path: "{{ aw_repo_root }}/proxmox/{{ tsj_bot_watchdog_name }}"
|
||||
tsj_bot_watchdog_service_name: "tsj-guardian-watchdog.service"
|
||||
tsj_bot_openvpn_helper_name: "pfsense_openvpn_client_export.php"
|
||||
tsj_bot_openvpn_helper_dest: "{{ tsj_bot_root }}/{{ tsj_bot_openvpn_helper_name }}"
|
||||
tsj_bot_openvpn_helper_source_local_path: "{{ aw_repo_root }}/proxmox/{{ tsj_bot_openvpn_helper_name }}"
|
||||
@@ -20,6 +24,16 @@
|
||||
tsj_bot_state_dir: "{{ tsj_bot_runtime_root }}/.state"
|
||||
tsj_bot_logs_dir: "{{ tsj_bot_runtime_root }}/logs"
|
||||
tsj_bot_default_chat_id: "{{ telegram_default_chat_id | default(telegram_allowed_chat_ids.split(',')[0]) }}"
|
||||
aw_rust_release_dir: "{{ (lookup('env', 'CARGO_TARGET_DIR') | default(aw_repo_root + '/adk-rust/target', true)) + '/release' }}"
|
||||
tsj_guardian_status_required_flags:
|
||||
- "--status-text"
|
||||
- "--incident-suggestions"
|
||||
- "--incident-defer-decision"
|
||||
- "--escalation-decision"
|
||||
- "--operator-action-decision"
|
||||
- "--dlp-policy-decision"
|
||||
- "--confirmation-decision"
|
||||
- "--autoheal-plan-decision"
|
||||
|
||||
pre_tasks:
|
||||
- name: Проверить наличие существующего .env бота на хосте
|
||||
@@ -62,6 +76,42 @@
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Проверить наличие watchdog скрипта на контроллере
|
||||
ansible.builtin.stat:
|
||||
path: "{{ tsj_bot_watchdog_source_local_path }}"
|
||||
register: tsj_bot_watchdog_stat
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Проверить локальный Rust TSJ guardian status helper
|
||||
ansible.builtin.stat:
|
||||
path: "{{ aw_rust_release_dir }}/tsj-guardian-status"
|
||||
register: tsj_guardian_status_rust_binary
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Остановить выполнение если Rust TSJ guardian status helper не найден
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- tsj_guardian_status_rust_binary.stat.exists
|
||||
- tsj_guardian_status_rust_binary.stat.isreg
|
||||
fail_msg: "Rust helper не найден: {{ aw_rust_release_dir }}/tsj-guardian-status. Соберите binary и проверьте CARGO_TARGET_DIR."
|
||||
|
||||
- name: Проверить контракт локального Rust TSJ guardian status helper
|
||||
ansible.builtin.command:
|
||||
cmd: "{{ aw_rust_release_dir }}/tsj-guardian-status --help"
|
||||
register: tsj_guardian_status_help
|
||||
changed_when: false
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Остановить выполнение если Rust TSJ guardian status helper устарел
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item in tsj_guardian_status_help.stdout
|
||||
fail_msg: "Rust helper {{ aw_rust_release_dir }}/tsj-guardian-status не поддерживает {{ item }}. Проверьте CARGO_TARGET_DIR и пересоберите helper."
|
||||
loop: "{{ tsj_guardian_status_required_flags }}"
|
||||
|
||||
- name: Остановить выполнение если helper для OpenVPN не найден
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
@@ -69,6 +119,13 @@
|
||||
- tsj_bot_openvpn_helper_stat.stat.isreg
|
||||
fail_msg: "Файл helper для OpenVPN не найден: {{ tsj_bot_openvpn_helper_source_local_path }}"
|
||||
|
||||
- name: Остановить выполнение если watchdog не найден
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- tsj_bot_watchdog_stat.stat.exists
|
||||
- tsj_bot_watchdog_stat.stat.isreg
|
||||
fail_msg: "Файл watchdog не найден: {{ tsj_bot_watchdog_source_local_path }}"
|
||||
|
||||
tasks:
|
||||
- name: Установить зависимости Python для бота
|
||||
ansible.builtin.package:
|
||||
@@ -99,6 +156,16 @@
|
||||
mode: "0750"
|
||||
notify: Restart tsj bot
|
||||
|
||||
- name: Развернуть Rust helper статуса TSJ guardian
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_rust_release_dir }}/tsj-guardian-status"
|
||||
dest: /usr/local/bin/tsj-guardian-status
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: tsj_guardian_status_rust_binary.stat.exists | default(false)
|
||||
notify: Restart tsj bot
|
||||
|
||||
- name: Развернуть helper для OpenVPN экспорта
|
||||
ansible.builtin.copy:
|
||||
src: "{{ tsj_bot_openvpn_helper_source_local_path }}"
|
||||
@@ -108,12 +175,21 @@
|
||||
mode: "0640"
|
||||
notify: Restart tsj bot
|
||||
|
||||
- name: Развернуть watchdog скрипт бота
|
||||
ansible.builtin.copy:
|
||||
src: "{{ tsj_bot_watchdog_source_local_path }}"
|
||||
dest: "{{ tsj_bot_watchdog_dest }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Сгенерировать полный .env бота
|
||||
when:
|
||||
- telegram_bot_token is defined
|
||||
- (telegram_bot_token | string | length) > 20
|
||||
- telegram_allowed_chat_ids is defined
|
||||
- (telegram_allowed_chat_ids | string | length) > 0
|
||||
no_log: true
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ tsj_bot_env_path }}"
|
||||
owner: "{{ tsj_bot_user }}"
|
||||
@@ -142,6 +218,7 @@
|
||||
LOG_FILE={{ tsj_bot_log_file | default(tsj_bot_runtime_root + '/logs/tsj_guardian_bot.log') }}
|
||||
HEARTBEAT_FILE={{ tsj_bot_heartbeat_file | default(tsj_bot_runtime_root + '/.state/tsj_guardian_heartbeat') }}
|
||||
CHECK_INTERVAL_SEC={{ tsj_bot_check_interval_sec | default(60) }}
|
||||
INCIDENT_FAILURE_QUORUM_CHECKS={{ tsj_bot_incident_failure_quorum_checks | default(2) }}
|
||||
OPERATOR_TIMEOUT_SEC={{ tsj_bot_operator_timeout_sec | default(900) }}
|
||||
RETRY_AUTORECOVERY_EVERY_SEC={{ tsj_bot_retry_autorecovery_every_sec | default(300) }}
|
||||
EXIT_ON_AUTORECOVERY_SUCCESS={{ tsj_bot_exit_on_autorecovery_success | default('true') }}
|
||||
@@ -175,18 +252,25 @@
|
||||
SERVER_FALLBACK_COMMANDS={{ tsj_bot_server_fallback_commands | default(tsj_bot_runtime_root + '/scripts/system_self_support.sh --heal') }}
|
||||
UPDATES_SCRIPT={{ tsj_bot_updates_script | default('/usr/bin/python3 ' + tsj_bot_runtime_root + '/scripts/proxmox_lxc_critical_updates.py') }}
|
||||
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_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("sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S /usr/local/bin/aw-worktime-autoheal.sh && sudo -S systemctl reset-failed aw-worktime-ui-bridge.service && sudo -S systemctl start aw-worktime-ui-bridge.service'") }}
|
||||
AW_RUS_DLP_HEAL_CMD={{ tsj_bot_aw_rus_dlp_heal_cmd | default("sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S systemctl restart activitywatch-server.service && sudo -S systemctl start activitywatch-dlp-aggregator.service || true && sudo -S /usr/local/bin/aw-health-check && sudo -S /usr/local/bin/dlp-health-check'") }}
|
||||
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.sh && 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_HAYABUSA_ENABLED={{ tsj_bot_aw_rus_hayabusa_enabled | default('true') }}
|
||||
AW_RUS_HAYABUSA_SSH_CMD={{ tsj_bot_aw_rus_hayabusa_ssh_cmd | default("sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13") }}
|
||||
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_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_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('')) }}
|
||||
@@ -207,6 +291,7 @@
|
||||
(telegram_allowed_chat_ids | string | length) > 0
|
||||
)
|
||||
- tsj_bot_existing_env.stat.exists | default(false)
|
||||
no_log: true
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ tsj_bot_env_path }}"
|
||||
regexp: "^{{ item.key }}="
|
||||
@@ -220,14 +305,19 @@
|
||||
- { 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_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(\"sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S /usr/local/bin/aw-worktime-autoheal.sh && sudo -S systemctl reset-failed aw-worktime-ui-bridge.service && sudo -S systemctl start aw-worktime-ui-bridge.service'\") }}" }
|
||||
- { key: "AW_RUS_DLP_HEAL_CMD", value: "{{ tsj_bot_aw_rus_dlp_heal_cmd | default(\"sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'sudo -S systemctl restart activitywatch-server.service && sudo -S systemctl start activitywatch-dlp-aggregator.service || true && sudo -S /usr/local/bin/aw-health-check && sudo -S /usr/local/bin/dlp-health-check'\") }}" }
|
||||
- { 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.sh && 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_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(\"sshpass -p '04091968' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13\") }}" }
|
||||
- { 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_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_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('')) }}" }
|
||||
@@ -239,6 +329,9 @@
|
||||
- { key: "AW_RUS_WINDOWS_EMAIL_COLLECTOR_PATH", value: "{{ tsj_bot_aw_rus_windows_email_collector_path | default('C:\\ProgramData\\AWatch-rus\\email-outbound-collector.ps1') }}" }
|
||||
- { key: "AI_CHAT_WORKDIR", value: "{{ tsj_bot_ai_chat_workdir | default('/home/igor') }}" }
|
||||
- { key: "AI_EXEC_USER", value: "{{ tsj_bot_ai_exec_user | default('igor') }}" }
|
||||
- { key: "INCIDENT_FAILURE_QUORUM_CHECKS", value: "{{ tsj_bot_incident_failure_quorum_checks | default(2) }}" }
|
||||
- { key: "DETMIR_AI_STATE_FILE", value: "{{ tsj_bot_detmir_ai_state_file | default('/var/lib/detmir-ai/latest-state.json') }}" }
|
||||
- { key: "TSJ_GUARDIAN_STATUS_BIN", value: "{{ tsj_bot_guardian_status_bin | default('/usr/local/bin/tsj-guardian-status') }}" }
|
||||
- { key: "TMUX_USER", value: "{{ tsj_bot_tmux_user | default('igor') }}" }
|
||||
- { key: "PFSENSE_ENV_PATH", value: "{{ tsj_bot_pfsense_env_path | default('/home/igor/.config/tsj-bot/pfsense.env.readonly') }}" }
|
||||
- { key: "PFSENSE_INVENTORY_PATH", value: "{{ tsj_bot_pfsense_inventory_path | default('/home/igor/.config/tsj-bot/inventory.md') }}" }
|
||||
@@ -272,6 +365,33 @@
|
||||
- Reload systemd
|
||||
- Restart tsj bot
|
||||
|
||||
- name: Установить systemd unit watchdog бота
|
||||
ansible.builtin.copy:
|
||||
dest: "/etc/systemd/system/{{ tsj_bot_watchdog_service_name }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=TSJ Guardian Bot Heartbeat Watchdog
|
||||
After={{ tsj_bot_service_name }} gost-tg.service
|
||||
Wants={{ tsj_bot_service_name }} gost-tg.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/bin/bash -lc 'while true; do {{ tsj_bot_watchdog_dest }}; sleep 60; done'
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
notify:
|
||||
- Reload systemd
|
||||
- Restart tsj watchdog
|
||||
|
||||
- name: Проверить синтаксис Python скрипта бота
|
||||
ansible.builtin.command: "python3 -m py_compile {{ tsj_bot_script_dest }}"
|
||||
changed_when: false
|
||||
@@ -282,12 +402,24 @@
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Включить и запустить watchdog бота
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ tsj_bot_watchdog_service_name }}"
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Проверить что сервис активен
|
||||
ansible.builtin.command: "systemctl is-active {{ tsj_bot_service_name }}"
|
||||
register: tsj_bot_active
|
||||
changed_when: false
|
||||
failed_when: tsj_bot_active.stdout.strip() != "active"
|
||||
|
||||
- name: Проверить что watchdog активен
|
||||
ansible.builtin.command: "systemctl is-active {{ tsj_bot_watchdog_service_name }}"
|
||||
register: tsj_bot_watchdog_active
|
||||
changed_when: false
|
||||
failed_when: tsj_bot_watchdog_active.stdout.strip() != "active"
|
||||
|
||||
handlers:
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
@@ -297,3 +429,8 @@
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ tsj_bot_service_name }}"
|
||||
state: restarted
|
||||
|
||||
- name: Restart tsj watchdog
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ tsj_bot_watchdog_service_name }}"
|
||||
state: restarted
|
||||
|
||||
@@ -32,6 +32,11 @@ 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
|
||||
@@ -62,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/SHARKON2025/view/"
|
||||
aw_health_strict_fileops: 0
|
||||
|
||||
aw_dlp_policy_engine_enabled: true
|
||||
|
||||
@@ -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/SHARKON2025/view/"
|
||||
aw_health_strict_fileops: 0
|
||||
|
||||
@@ -59,7 +59,7 @@ aw_windows_evtx_channels:
|
||||
- Microsoft-Windows-TerminalServices-LocalSessionManager/Operational
|
||||
- Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational
|
||||
aw_windows_logon_marker_enabled: true
|
||||
aw_windows_process_events_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"
|
||||
@@ -69,6 +69,11 @@ aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ans
|
||||
aw_windows_validation_local_dir: "/tmp/aw-rus-validation-{{ lookup('env','USER') | default('ansible', true) }}"
|
||||
aw_windows_fail_on_validation_error: true
|
||||
|
||||
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_migration_enabled: false
|
||||
aw_windows_legacy_install_root: "C:\\Program Files\\ActivityWatch-Phase2"
|
||||
aw_windows_legacy_state_root: "C:\\ProgramData\\ActivityWatch-Phase2"
|
||||
|
||||
@@ -8,6 +8,7 @@ tsj_bot_runtime_root: "/opt/infra-admin"
|
||||
|
||||
# Optional bot tuning
|
||||
tsj_bot_check_interval_sec: 60
|
||||
tsj_bot_incident_failure_quorum_checks: 2
|
||||
tsj_bot_operator_timeout_sec: 900
|
||||
tsj_bot_retry_autorecovery_every_sec: 300
|
||||
tsj_bot_telegram_proxy_url: "http://127.0.0.1:11090"
|
||||
@@ -56,6 +57,11 @@ tsj_bot_aw_rus_hayabusa_ssh_cmd: "sshpass -p 'CHANGE_ME' ssh -o PubkeyAuthentica
|
||||
tsj_bot_aw_rus_host: "SHARKON2025"
|
||||
tsj_bot_aw_rus_primary_user: "USER1"
|
||||
tsj_bot_aw_rus_stale_sec: 900
|
||||
tsj_bot_aw_rus_slo_enabled: "true"
|
||||
tsj_bot_aw_rus_slo_alert_window: "24h"
|
||||
tsj_bot_aw_rus_slo_min_samples: 4
|
||||
tsj_bot_aw_rus_slo_max_age_sec: 90
|
||||
tsj_bot_aw_rus_slo_summary_cmd: "sshpass -p 'CHANGE_ME' ssh -o PubkeyAuthentication=no -o StrictHostKeyChecking=no igor@10.10.10.13 'cat /var/lib/activitywatch/slo/aw-slo-summary.json'"
|
||||
tsj_bot_aw_rus_windows_host: "192.168.100.18"
|
||||
tsj_bot_aw_rus_windows_ssh_user: "Администратор"
|
||||
tsj_bot_aw_rus_windows_ssh_password: "CHANGE_ME"
|
||||
|
||||
@@ -50,7 +50,7 @@ aw_windows_evtx_channels:
|
||||
- Microsoft-Windows-TerminalServices-LocalSessionManager/Operational
|
||||
- Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational
|
||||
aw_windows_logon_marker_enabled: true
|
||||
aw_windows_process_events_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"
|
||||
@@ -60,6 +60,13 @@ aw_windows_validation_remote_path: "{{ aw_windows_state_root }}\\aw_validate_ans
|
||||
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"
|
||||
|
||||
@@ -68,6 +68,117 @@
|
||||
- { src: "webhook-sender.service", dest: "/etc/systemd/system/aw-dlp-webhook-sender.service" }
|
||||
- { src: "webhook-sender.timer", dest: "/etc/systemd/system/aw-dlp-webhook-sender.timer" }
|
||||
|
||||
- name: Check local Rust CEF exporter
|
||||
ansible.builtin.stat:
|
||||
path: "{{ playbook_dir }}/../adk-rust/target/release/dlp-cef-exporter"
|
||||
delegate_to: localhost
|
||||
register: dlp_cef_exporter_rust_binary
|
||||
become: false
|
||||
|
||||
- name: Install Rust CEF exporter
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../adk-rust/target/release/dlp-cef-exporter"
|
||||
dest: /usr/local/bin/dlp-cef-exporter-rust
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: dlp_cef_exporter_rust_binary.stat.exists | default(false)
|
||||
|
||||
- name: Ensure CEF exporter drop-in directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/aw-dlp-cef-exporter.service.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: dlp_cef_exporter_rust_binary.stat.exists | default(false)
|
||||
|
||||
- name: Switch CEF exporter to Rust
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/aw-dlp-cef-exporter.service.d/20-rust-switch.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/usr/local/bin/dlp-cef-exporter-rust
|
||||
when: dlp_cef_exporter_rust_binary.stat.exists | default(false)
|
||||
|
||||
- name: Check local Rust syslog forwarder
|
||||
ansible.builtin.stat:
|
||||
path: "{{ playbook_dir }}/../adk-rust/target/release/dlp-syslog-forwarder"
|
||||
delegate_to: localhost
|
||||
register: dlp_syslog_forwarder_rust_binary
|
||||
become: false
|
||||
|
||||
- name: Install Rust syslog forwarder
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../adk-rust/target/release/dlp-syslog-forwarder"
|
||||
dest: /usr/local/bin/dlp-syslog-forwarder-rust
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: dlp_syslog_forwarder_rust_binary.stat.exists | default(false)
|
||||
|
||||
- name: Ensure syslog forwarder drop-in directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/aw-dlp-syslog-forwarder.service.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: dlp_syslog_forwarder_rust_binary.stat.exists | default(false)
|
||||
|
||||
- name: Switch syslog forwarder to Rust
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/aw-dlp-syslog-forwarder.service.d/20-rust-switch.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/usr/local/bin/dlp-syslog-forwarder-rust
|
||||
when: dlp_syslog_forwarder_rust_binary.stat.exists | default(false)
|
||||
|
||||
- name: Check local Rust webhook sender
|
||||
ansible.builtin.stat:
|
||||
path: "{{ playbook_dir }}/../adk-rust/target/release/dlp-webhook-sender"
|
||||
delegate_to: localhost
|
||||
register: dlp_webhook_sender_rust_binary
|
||||
become: false
|
||||
|
||||
- name: Install Rust webhook sender
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../adk-rust/target/release/dlp-webhook-sender"
|
||||
dest: /usr/local/bin/dlp-webhook-sender-rust
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: dlp_webhook_sender_rust_binary.stat.exists | default(false)
|
||||
|
||||
- name: Ensure webhook sender drop-in directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/aw-dlp-webhook-sender.service.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: dlp_webhook_sender_rust_binary.stat.exists | default(false)
|
||||
|
||||
- name: Switch webhook sender to Rust
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/aw-dlp-webhook-sender.service.d/20-rust-switch.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: |
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/usr/local/bin/dlp-webhook-sender-rust
|
||||
when: dlp_webhook_sender_rust_binary.stat.exists | default(false)
|
||||
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
@@ -78,6 +78,14 @@
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.card-disabled {
|
||||
cursor: default;
|
||||
opacity: 0.72;
|
||||
}
|
||||
.card-disabled:hover {
|
||||
transform: none;
|
||||
border-color: var(--line);
|
||||
}
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -124,9 +132,9 @@
|
||||
<h1>Proxmox Web Gateway</h1>
|
||||
<p class="lead">
|
||||
Единая стартовая точка для web-сервисов контура на Proxmox host
|
||||
<strong>10.10.10.2</strong>. Gateway сознательно работает как
|
||||
безопасный redirector: приложения не ломаются под subpath, а оператор
|
||||
получает один адрес входа.
|
||||
<strong>10.10.10.2</strong>. Внешний вход закрыт gateway-auth,
|
||||
а основные операторские страницы идут через reverse proxy без выдачи
|
||||
внутренних адресов наружу.
|
||||
</p>
|
||||
<p class="meta">
|
||||
Публичное имя gateway: <strong>{{ proxmox_web_gateway_public_hostname }}</strong>
|
||||
@@ -139,12 +147,21 @@
|
||||
|
||||
<section class="grid">
|
||||
{% for route in proxmox_web_gateway_routes %}
|
||||
<a class="card" href="/go/{{ route.slug }}">
|
||||
<span class="badge">{{ route.category }}</span>
|
||||
{% set external_enabled = route.external_enabled | default(false) %}
|
||||
{% if external_enabled and route.proxy_path is defined %}
|
||||
<a class="card" href="{{ route.proxy_path }}">
|
||||
{% else %}
|
||||
<div class="card card-disabled" aria-disabled="true">
|
||||
{% endif %}
|
||||
<span class="badge">{{ route.category }}{% if not external_enabled %} · VPN{% endif %}</span>
|
||||
<h2 class="title">{{ route.title }}</h2>
|
||||
<p class="desc">{{ route.description }}</p>
|
||||
<div class="target">{{ route.target_url }}</div>
|
||||
<div class="target">{% if external_enabled and route.proxy_path is defined %}{{ route.proxy_path }}{% else %}VPN/internal: {{ route.target_url }}{% endif %}</div>
|
||||
{% if external_enabled and route.proxy_path is defined %}
|
||||
</a>
|
||||
{% else %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
@@ -24,25 +29,238 @@ server {
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
auth_basic "{{ proxmox_web_gateway_auth_realm }}";
|
||||
auth_basic_user_file {{ proxmox_web_gateway_auth_file }};
|
||||
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
add_header X-Robots-Tag "noindex, nofollow, noarchive" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
|
||||
root {{ proxmox_web_gateway_root }};
|
||||
index index.html;
|
||||
|
||||
location = {{ proxmox_web_gateway_health_path }} {
|
||||
auth_basic off;
|
||||
default_type text/plain;
|
||||
return 200 "ok\n";
|
||||
}
|
||||
|
||||
location = /robots.txt {
|
||||
auth_basic off;
|
||||
default_type text/plain;
|
||||
return 200 "User-agent: *\nDisallow: /\n";
|
||||
}
|
||||
|
||||
{% for route in proxmox_web_gateway_routes %}
|
||||
location = /go/{{ route.slug }} {
|
||||
return 302 {{ route.target_url }};
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
|
||||
location /api/0/ {
|
||||
proxy_set_header Origin "";
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.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/;
|
||||
}
|
||||
|
||||
location = /login {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location = /logout {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /public/ {
|
||||
proxy_set_header Origin "";
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /api/live/ {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Origin "$scheme://$host";
|
||||
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_redirect off;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_set_header Origin "";
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /apis/ {
|
||||
proxy_set_header Origin "";
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /d/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /dashboards {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /dashboard/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /avatar/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /profile/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /org/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /user/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /plugins/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /explore {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /alerting/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /connections/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.11:3000;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /datasources/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.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/;
|
||||
}
|
||||
|
||||
location /reports/worktime/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.13:5610;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location = /dark.css {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.13:5600;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /css/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.13:5600;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location = /js/aw-worktime-panel.js {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.13:5600;
|
||||
proxy_redirect off;
|
||||
sub_filter_once off;
|
||||
sub_filter_types application/javascript text/javascript;
|
||||
sub_filter 'http://10.10.10.13:5610' '';
|
||||
}
|
||||
|
||||
location /js/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.13:5600;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /img/ {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.13:5600;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location /fonts/ {
|
||||
auth_basic off;
|
||||
proxy_set_header Origin "";
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass http://10.10.10.13:5600;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
{% for route in proxmox_web_gateway_routes %}
|
||||
{% if route.proxy_target_url is defined %}
|
||||
location = {{ route.proxy_path }} {
|
||||
proxy_set_header Authorization "";
|
||||
proxy_pass {{ route.proxy_target_url }};
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
location /go/ {
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user