Compare commits

..
23 changed files with 718 additions and 1173 deletions
-74
View File
@@ -1,74 +0,0 @@
name: rust-binary-build
on:
workflow_dispatch:
pull_request:
branches: [ "main" ]
paths:
- 'rust-toolchain.toml'
- 'adk-rust/**'
- 'scripts/package_rust_release_binaries.py'
- '.github/workflows/rust-binary-build.yml'
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
build-linux-x86_64:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pinned Rust toolchain
run: |
rustup toolchain install 1.94.0 --profile minimal --component rustfmt --component clippy
rustup override set 1.94.0
rustup show active-toolchain
cargo +1.94.0 --version
rustc +1.94.0 --version
- name: Build release binaries
run: cargo +1.94.0 build --manifest-path adk-rust/Cargo.toml --workspace --release
- name: Package release binaries
run: |
python3 scripts/package_rust_release_binaries.py \
--release-dir adk-rust/target/release \
--out-dir dist/awatch-rus-linux-x86_64 \
--archive dist/awatch-rus-linux-x86_64-release-binaries.tar.gz \
--target linux-x86_64 \
--commit "${GITHUB_SHA}" \
--ref "${GITHUB_REF}" \
--run-id "${GITHUB_RUN_ID}"
- name: Upload release binaries artifact
uses: actions/upload-artifact@v4
with:
name: awatch-rus-linux_x86_64-release-binaries
path: |
dist/awatch-rus-linux_x86_64-release-binaries.tar.gz
dist/awatch-rus-linux_x86_64-release-binaries.tar.gz.sha256
dist/awatch-rus-linux_x86_64/BINARIES.txt
dist/awatch-rus-linux_x86_64/SHA256SUMS.txt
dist/awatch-rus-linux_x86_64/BUILD_MANIFEST.json
if-no-files-found: error
retention-days: 30
- name: Publish GitHub Release assets
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
fail_on_unmatched_files: true
files: |
dist/awatch-rus-linux_x86_64-release-binaries.tar.gz
dist/awatch-rus-linux_x86_64-release-binaries.tar.gz.sha256
dist/awatch-rus-linux_x86_64/BINARIES.txt
dist/awatch-rus-linux_x86_64/SHA256SUMS.txt
dist/awatch-rus-linux_x86_64/BUILD_MANIFEST.json
@@ -1,40 +0,0 @@
name: Rust clippy diagnostic
on:
push:
branches:
- codex/rust-professionalization
workflow_dispatch:
jobs:
detmir-portal-clippy-diagnostic:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust 1.85 with rustfmt and clippy
run: |
rustup toolchain install 1.85.0 --profile minimal --component rustfmt --component clippy
rustup default 1.85.0
- name: Capture detmir-portal clippy output
working-directory: adk-rust
run: |
set +e
cargo clippy -p detmir-portal --all-targets -- -D warnings > ../detmir-portal-clippy.log 2>&1
status=$?
echo "clippy_exit_status=${status}" > ../detmir-portal-clippy-status.txt
tail -n 240 ../detmir-portal-clippy.log
exit ${status}
- name: Upload detmir-portal clippy log
if: always()
uses: actions/upload-artifact@v4
with:
name: detmir-portal-clippy-log
path: |
detmir-portal-clippy.log
detmir-portal-clippy-status.txt
@@ -1,73 +0,0 @@
name: Rust professionalization check
on:
pull_request:
branches:
- main
paths:
- 'rust-toolchain.toml'
- 'adk-rust/crates/detmir-core/**'
- 'adk-rust/crates/detmir-portal/**'
- 'scripts/check_private_config_guard.sh'
- 'scripts/check_portal_contract_sync.mjs'
- '.github/workflows/rust-professionalization-check.yml'
workflow_dispatch:
jobs:
rust-check:
name: changed Rust crates smoke
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pinned Rust toolchain
run: |
rustup toolchain install 1.94.0 --profile minimal --component rustfmt --component clippy
rustup override set 1.94.0
rustup show active-toolchain
cargo +1.94.0 --version
rustc +1.94.0 --version
- name: Cargo fmt check
working-directory: adk-rust
run: cargo +1.94.0 fmt --all -- --check
- name: Test detmir-core
working-directory: adk-rust
run: cargo +1.94.0 test -p detmir-core
- name: Test detmir-portal
working-directory: adk-rust
run: cargo +1.94.0 test -p detmir-portal
- name: Clippy detmir-core
working-directory: adk-rust
run: cargo +1.94.0 clippy -p detmir-core --all-targets -- -D warnings
- name: Clippy detmir-portal with captured log
working-directory: adk-rust
run: |
set +e
cargo +1.94.0 clippy -p detmir-portal --all-targets -- -D warnings > ../detmir-portal-clippy.log 2>&1
status=$?
echo "clippy_exit_status=${status}" > ../detmir-portal-clippy-status.txt
tail -n 80 ../detmir-portal-clippy.log
exit ${status}
- name: Upload detmir-portal clippy log
if: always()
uses: actions/upload-artifact@v4
with:
name: detmir-portal-clippy-log
path: |
detmir-portal-clippy.log
detmir-portal-clippy-status.txt
- name: Private config guard
run: bash scripts/check_private_config_guard.sh
- name: Portal contract sync
run: node scripts/check_portal_contract_sync.mjs
+8 -12
View File
@@ -5,7 +5,6 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
jobs:
rust-workspace:
@@ -14,22 +13,19 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install pinned Rust toolchain
run: |
rustup toolchain install 1.94.0 --profile minimal --component rustfmt --component clippy
rustup override set 1.94.0
rustup show active-toolchain
cargo +1.94.0 --version
rustc +1.94.0 --version
- name: Install Rust 1.85
uses: dtolnay/rust-toolchain@1.85.0
with:
components: rustfmt, clippy
- name: Format
run: cargo +1.94.0 fmt --manifest-path adk-rust/Cargo.toml --all -- --check
run: cargo fmt --manifest-path adk-rust/Cargo.toml --all -- --check
- name: Test
run: cargo +1.94.0 test --manifest-path adk-rust/Cargo.toml --workspace
run: cargo test --manifest-path adk-rust/Cargo.toml --workspace
- name: Clippy
run: cargo +1.94.0 clippy --manifest-path adk-rust/Cargo.toml --workspace --all-targets -- -D warnings
run: cargo clippy --manifest-path adk-rust/Cargo.toml --workspace --all-targets -- -D warnings
- name: Release build
run: cargo +1.94.0 build --manifest-path adk-rust/Cargo.toml --workspace --release
run: cargo build --manifest-path adk-rust/Cargo.toml --workspace --release
+79
View File
@@ -38,3 +38,82 @@
Продукт относится к классу средств управления ИТ-службой,
ИТ-инфраструктурой и ИТ-активами. Продукт не заявляется как
сертифицированная DLP, SIEM, EDR/XDR или средство защиты информации.
## Профессиональные сильные стороны проекта
Ниже — краткая и профессиональная сводка ключевых преимуществ AWatch-rus,
предназначенная для реестра программного обеспечения и технического аудитa.
### 1. Архитектура и инженерный дизайн
- Rust-first, модульная архитектура: более 40 специализированных крейтов в
workspace, четкая декомпозиция ответственности и воспроизводимые сборки
(Cargo.lock).
- Разделение на логические уровни: endpoint -> серверные хранилища ->
processing -> operator -> automation, что упрощает тестирование и валидацию.
- Четкие migration-runbooks для последовательного перехода legacy-python → Rust.
### 2. Операционная зрелость
- Полноценный Ansible-ensemble для end-to-end развёртывания (Proxmox, Linux,
централизованный Windows rollout по WinRM), включая retry-политику,
smoke-тесты и валидацию после деплоя.
- Safe-run pattern: dry-run/apply planners, backup-before-delete, atomic
операции и conservative no-restart paths.
- Набор модулей для диагностики и качества (detmir-*, aw-*, quality-gate,
smoke checks) обеспечивает fast feedback при внедрении.
### 3. Безопасность и работа с evidence
- Изолированный путь evidence: opaque IDs, bearer-upload, magic validation
для изображений, размерные лимиты, SHA-256 валидация, atomic write и audit
trails.
- Security hardening guide: TLS по умолчанию, reverse-proxy, firewall
рекомендации, сервисные аккаунты и минимальные привилегии.
- Ясные границы продукта: не позиционируется как сертифицированная СЗИ/DLP;
это снижает юридические риски при регистрации и аудите.
### 4. Production-readiness и валидация пилота
- Полный комплект acceptance и pilot-checklists (30-дневный pilot success
criteria) с метриками доступности, coverage, freshness и времени реакции.
- Runbooks и операционная документация для восстановления, резервного копирования
и отката.
### 5. Наблюдаемость и качество данных
- Встроенные SLO/health monitoring, Prometheus/ Grafana витрины и
детализированные smoke/contour тесты.
- Механизмы объяснимости: UEBA v1 rule-based с reason-codes и объяснениями KPI
coverage/confidence.
### 6. Поддержка Windows/RDP и forensics
- Централизованный Windows rollout и валидация (API smoke-checks для
aw-watcher-afk/aw-watcher-window).
- Серверная автоматическая обработка Hayabusa: auto-upload, severity scoring,
case creation и уведомления (Telegram) с пороговой фильтрацией.
### 7. Документированность и готовность к реестру
- Полный набор документов для регистрации: product passport, architecture,
functional scope, dependency statement, deployment model и readiness checklist.
- Демонстрационные сценарии и фикстуры, исключающие реальные персональные и
сетевые данные — соответствует требованиям для публичных материалов.
### 8. Честное позиционирование и риски
- Прозрачность ограничений (что реализовано, что planned/future) — важный
аспект при подаче в реестр и при взаимодействии с заказчиком.
- Документированная gap-analysis и roadmap-conformance audit облегчают
процессы оценки соответствия и планирования доработок.
---
Если нужно, могу:
- добавить этот раздел как отдельный файл в docs/ (например,
docs/PROFESSIONAL_HIGHLIGHTS_RU.md), либо
- вставить в другой документ (например, в architecture или registry passport),
- сгенерировать краткую выдержку на 1 страницу для подачи в реестр.
-56
View File
@@ -1,37 +1,19 @@
#![deny(unsafe_op_in_unsafe_fn)]
//! Shared production primitives for AWatch-rus.
//!
//! This crate intentionally stays small and dependency-light. It contains the
//! status, exit-code and runtime-configuration guardrails that are reused by
//! operational binaries and health/check tooling. Keep business-specific portal,
//! DLP or workforce logic out of this crate.
use std::fmt;
use anyhow::{Context, Result};
use chrono::{DateTime, SecondsFormat, Utc};
use serde::{Deserialize, Serialize};
/// Normalized health/check status used by CLI tools, probes and JSON payloads.
///
/// CONTRACT: serialized values are uppercase and must remain stable because
/// deployment scripts, smoke checks and dashboards can key off these strings.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum StatusLevel {
/// Component is healthy and the check passed.
Ok,
/// Component works, but a risk or degraded condition needs attention.
Warn,
/// Component check failed or a required dependency is unavailable.
Fail,
/// Component did not provide enough information for a reliable status.
Unknown,
}
impl StatusLevel {
/// Return the stable uppercase representation used in human and JSON output.
pub fn as_str(self) -> &'static str {
match self {
Self::Ok => "OK",
@@ -41,10 +23,6 @@ impl StatusLevel {
}
}
/// Map status to the process exit code expected by operational checks.
///
/// CONTRACT: `WARN` exits as a failed check rather than success so that
/// automation does not silently ignore degraded production state.
pub fn exit_code(self) -> i32 {
match self {
Self::Ok => exit_codes::OK,
@@ -70,39 +48,23 @@ impl From<&str> for StatusLevel {
}
}
/// Stable process exit codes for AWatch-rus operational binaries.
///
/// CONTRACT: keep these numeric values stable. Shell scripts, systemd units,
/// smoke tests and runbooks can depend on them.
pub mod exit_codes {
/// Successful execution.
pub const OK: i32 = 0;
/// Unexpected runtime or IO error.
pub const ERROR: i32 = 1;
/// Health/check policy failed or returned a degraded status.
pub const CHECK_FAILED: i32 = 2;
/// A safety policy denied a requested action.
pub const POLICY_DENIED: i32 = 3;
}
/// Return the current UTC timestamp in compact RFC3339/Zulu format.
pub fn now_utc_rfc3339() -> String {
Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true)
}
/// Parse an RFC3339 timestamp and normalize it to UTC.
pub fn parse_utc_rfc3339(value: &str) -> Result<DateTime<Utc>> {
DateTime::parse_from_rfc3339(value)
.with_context(|| format!("invalid RFC3339 timestamp: {value}"))
.map(|ts| ts.with_timezone(&Utc))
}
/// Runtime configuration guardrails.
///
/// SECURITY: these helpers are deliberately conservative. They reject empty,
/// documentation, TEST-NET and common placeholder values before a component is
/// allowed to run in production mode. This prevents demo-safe examples from
/// accidentally becoming live runtime configuration.
pub mod runtime_guard {
use anyhow::{Result, bail};
@@ -120,11 +82,6 @@ pub mod runtime_guard {
"PASSWORD",
];
/// Return true when a value looks like a public/demo placeholder.
///
/// RATIONALE: AWatch-rus documentation intentionally uses TEST-NET ranges
/// and HOST-EXAMPLE markers. Production binaries should fail closed when
/// such values reach runtime configuration.
pub fn is_runtime_placeholder(value: &str) -> bool {
let trimmed = value.trim();
if trimmed.is_empty() {
@@ -149,7 +106,6 @@ pub mod runtime_guard {
|| (normalized.starts_with('<') && normalized.ends_with('>'))
}
/// Return true when a value is unsafe for a secret-like configuration field.
pub fn is_secret_placeholder(value: &str) -> bool {
is_runtime_placeholder(value)
|| matches!(
@@ -158,10 +114,6 @@ pub mod runtime_guard {
)
}
/// Ensure a required runtime value is not empty or demo-only.
///
/// SECURITY: callers should invoke this before opening network connections,
/// starting ingestion or enabling exporters in production mode.
pub fn ensure_runtime_value(name: &str, value: &str, context: &str) -> Result<()> {
if is_runtime_placeholder(value) {
bail!("{name} contains an empty/example/TEST-NET value while {context}");
@@ -169,7 +121,6 @@ pub mod runtime_guard {
Ok(())
}
/// Ensure a required secret is not empty or an obvious placeholder.
pub fn ensure_secret_value(name: &str, value: &str, context: &str) -> Result<()> {
if is_secret_placeholder(value) {
bail!("{name} contains an empty/example secret value while {context}");
@@ -177,7 +128,6 @@ pub mod runtime_guard {
Ok(())
}
/// Ensure an iterator of runtime values is non-empty and production-safe.
pub fn ensure_runtime_values<'a>(
name: &str,
values: impl IntoIterator<Item = &'a String>,
@@ -194,12 +144,6 @@ pub mod runtime_guard {
Ok(())
}
/// Validate a complete InfluxDB exporter configuration block.
///
/// CONTRACT: when an exporter is enabled, URL, org, bucket, token and host
/// list must all be real runtime values. A partial/demo exporter config is
/// more dangerous than a disabled exporter because it creates false
/// confidence in monitoring readiness.
pub fn ensure_influx_runtime_config(
prefix: &str,
url: &str,
-4
View File
@@ -21,7 +21,3 @@ tiny_http.workspace = true
[dev-dependencies]
tempfile.workspace = true
[lints.clippy]
comparison_chain = "allow"
search_is_some = "allow"
+70 -2
View File
@@ -24,7 +24,6 @@ use sha2::{Digest, Sha256};
use tiny_http::{Header, Method, Request, Response, Server, StatusCode};
mod executive_actions;
mod portal_roles;
mod production;
mod risk_narrative;
mod workforce_kpi_explain;
@@ -32,7 +31,6 @@ mod workforce_kpi_explain;
use executive_actions::{
actions_from_center, build_action_center_from_report, filter_actions_for_role,
};
use portal_roles::PortalRole;
use production::{
build_healthz, build_readyz, build_version, http_request_metadata, is_limited_api_route,
log_http_request, mark_request_started, record_http_metric, record_ingestion_accepted,
@@ -77,6 +75,76 @@ unsafe extern "C" {
type SnapshotCache = Arc<Mutex<Option<CachedSnapshot>>>;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
enum PortalRole {
Executive,
Manager,
Security,
Forensics,
Admin,
}
impl PortalRole {
fn parse(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"executive" | "owner" | "rukovoditel" | "руководитель" => {
Some(Self::Executive)
}
"manager" | "workforce" | "руководитель_подразделения" => {
Some(Self::Manager)
}
"security" | "ib" | "soc" | "безопасность" => Some(Self::Security),
"forensics" | "investigation" | "расследования" => Some(Self::Forensics),
"admin" | "operations" | "operator" | "эксплуатация" => Some(Self::Admin),
_ => None,
}
}
fn as_str(self) -> &'static str {
match self {
Self::Executive => "executive",
Self::Manager => "manager",
Self::Security => "security",
Self::Forensics => "forensics",
Self::Admin => "admin",
}
}
fn label_ru(self) -> &'static str {
match self {
Self::Executive => "Руководитель",
Self::Manager => "Руководитель подразделения",
Self::Security => "Безопасность",
Self::Forensics => "Расследования",
Self::Admin => "Администратор",
}
}
fn allowed_scopes(self) -> &'static [&'static str] {
match self {
Self::Executive => &["executive", "workforce"],
Self::Manager => &["executive", "workforce"],
Self::Security => &["security", "incidents", "ueba", "pfsense"],
Self::Forensics => &["forensics", "incidents", "ueba"],
Self::Admin => &[
"executive",
"workforce",
"security",
"forensics",
"incidents",
"ueba",
"pfsense",
"admin",
],
}
}
fn can_access(self, scope: &str) -> bool {
self.allowed_scopes().contains(&scope)
}
}
#[derive(Clone, Debug)]
struct CachedSnapshot {
created: Instant,
@@ -1,77 +0,0 @@
//! Portal role model and access-scope contract.
//!
//! CONTRACT: role aliases, serialized values and allowed scopes are part of
//! the portal API/security boundary. Keep changes explicit and covered by
//! existing role-gate tests in `main.rs`.
use serde::Serialize;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PortalRole {
Executive,
Manager,
Security,
Forensics,
Admin,
}
impl PortalRole {
pub(crate) fn parse(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"executive" | "owner" | "rukovoditel" | "руководитель" => {
Some(Self::Executive)
}
"manager" | "workforce" | "руководитель_подразделения" => {
Some(Self::Manager)
}
"security" | "ib" | "soc" | "безопасность" => Some(Self::Security),
"forensics" | "investigation" | "расследования" => Some(Self::Forensics),
"admin" | "operations" | "operator" | "эксплуатация" => Some(Self::Admin),
_ => None,
}
}
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Executive => "executive",
Self::Manager => "manager",
Self::Security => "security",
Self::Forensics => "forensics",
Self::Admin => "admin",
}
}
pub(crate) fn label_ru(self) -> &'static str {
match self {
Self::Executive => "Руководитель",
Self::Manager => "Руководитель подразделения",
Self::Security => "Безопасность",
Self::Forensics => "Расследования",
Self::Admin => "Администратор",
}
}
pub(crate) fn allowed_scopes(self) -> &'static [&'static str] {
match self {
Self::Executive => &["executive", "workforce"],
Self::Manager => &["executive", "workforce"],
Self::Security => &["security", "incidents", "ueba", "pfsense"],
Self::Forensics => &["forensics", "incidents", "ueba"],
Self::Admin => &[
"executive",
"workforce",
"security",
"forensics",
"incidents",
"ueba",
"pfsense",
"admin",
],
}
}
pub(crate) fn can_access(self, scope: &str) -> bool {
self.allowed_scopes().contains(&scope)
}
}
@@ -1,8 +1,3 @@
//! Liveness probe payload.
//!
//! CONTRACT: `/healthz` is intentionally shallow. It proves that the portal
//! process can answer HTTP, while dependency checks belong to `/readyz`.
use serde_json::{Value, json};
use crate::now;
@@ -1,10 +1,3 @@
//! Configuration and request-bound validation for production portal routes.
//!
//! RATIONALE: the portal can aggregate reports, evidence and external service
//! payloads. Query and body limits keep pilot installations responsive and make
//! expensive report routes fail closed instead of exhausting memory or blocking
//! the single-process runtime.
use std::collections::BTreeSet;
use anyhow::{Result, anyhow};
@@ -37,10 +30,6 @@ pub(crate) fn validate_portal_config(args: &Cli) -> Result<()> {
if port == 0 {
return Err(anyhow!("invalid config port: expected 1..65535"));
}
// RATIONALE: page and date limits protect heavy report endpoints while
// preserving monthly pilot reporting. Hard upper bounds prevent accidental
// production overrides from turning the portal into an unbounded exporter.
if args.max_page_size == 0 || args.max_page_size > MAX_ALLOWED_PAGE_SIZE {
return Err(anyhow!(
"invalid config max_page_size: expected 1..={MAX_ALLOWED_PAGE_SIZE}"
@@ -77,10 +66,6 @@ pub(crate) fn validate_portal_config(args: &Cli) -> Result<()> {
"invalid config max_request_body_bytes: expected 1024..={MAX_ALLOWED_REQUEST_BODY_BYTES}"
));
}
// SECURITY: environment and module names can reach metrics/log labels.
// Restrict them to short ASCII tokens to avoid label injection and runaway
// cardinality from free-form deployment names.
if !is_safe_environment_name(&args.environment) {
return Err(anyhow!(
"invalid config environment: use 1..32 chars from A-Z, a-z, 0-9, _, -"
@@ -1,10 +1,3 @@
//! Structured HTTP access logging for the portal runtime.
//!
//! CONTRACT: logs are emitted as single-line JSON to stderr so systemd/journald,
//! container runtimes and log forwarders can parse them without scraping free
//! text. Do not log raw request bodies, secrets, evidence bytes or personal
//! payloads here.
use serde_json::{Value, json};
use tiny_http::StatusCode;
@@ -28,10 +21,6 @@ pub(crate) fn log_http_request(
} else {
Value::Null
};
// SECURITY: include routing/correlation fields, but do not include query
// values, request body, headers or tokens. Those can contain employee data,
// screenshots, evidence references or API keys.
eprintln!(
"{}",
json!({
@@ -1,9 +1,3 @@
//! In-process Prometheus-style metrics for the portal.
//!
//! CONTRACT: metric names and label keys are part of the operational contract
//! used by dashboards and smoke checks. Additive metrics are allowed; renaming
//! existing metrics requires synchronized dashboard/documentation changes.
use std::collections::BTreeMap;
use std::fmt::Write as FmtWrite;
use std::sync::{Mutex, OnceLock};
@@ -185,7 +179,5 @@ pub(crate) fn render_prometheus_metrics(args: &Cli) -> String {
}
fn prom_escape(value: &str) -> String {
// SECURITY: metric label values are route/module tokens, but escaping keeps
// the endpoint safe if future callers pass proxy-derived values.
value.replace('\\', "\\\\").replace('"', "\\\"")
}
@@ -1,14 +1,3 @@
//! Production-facing portal runtime support.
//!
//! This module groups the cross-cutting concerns that must stay consistent
//! across all portal routes: health/readiness/version contracts, query and
//! configuration limits, structured logging, Prometheus-style metrics and
//! request correlation metadata.
//!
//! CONTRACT: keep this module free from role-specific business rendering. It is
//! the operational boundary around the portal, not the workforce/security report
//! implementation itself.
pub(crate) mod health;
pub(crate) mod limits;
pub(crate) mod logging;
@@ -1,10 +1,3 @@
//! Readiness probe payload.
//!
//! CONTRACT: `/readyz` checks whether the portal is safe to receive normal
//! traffic. It must remain conservative: configuration errors and broken state
//! storage make the process `not_ready`; optional integrations can report
//! `disabled`, `not_required` or `contract_only` without failing the whole probe.
use std::path::Path;
use serde_json::{Value, json};
@@ -1,8 +1,3 @@
//! Request correlation and route classification for portal observability.
//!
//! CONTRACT: generated route names must not expose volatile identifiers such as
//! case IDs, candidate IDs or evidence IDs; use route templates instead.
use std::cell::RefCell;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
@@ -78,7 +73,7 @@ fn request_header(request: &Request, name: &str) -> Option<String> {
request
.headers()
.iter()
.find(|header| header.field.as_str().as_str().eq_ignore_ascii_case(name))
.find(|header| header.field.to_string().eq_ignore_ascii_case(name))
.map(|header| header.value.as_str().to_string())
}
@@ -1,8 +1,3 @@
//! Build/version probe payload.
//!
//! CONTRACT: `/version` is used by smoke tests, runbooks and release evidence.
//! Keep field names stable and add new fields only in a backward-compatible way.
use serde_json::{Value, json};
use crate::{Cli, PORTAL_SCHEMA_VERSION};
+297 -542
View File
@@ -1,667 +1,422 @@
Полная инструкция по развёртыванию и поддержке AWatch-rus
# Полная инструкция по развёртыванию и поддержке ActivityWatch-Russian
Статус документа
Документ описывает полный цикл: Proxmox/LXC сервер, установка ActivityWatch Server, RU Web UI patch, развёртывание Windows-клиентов в другом AD-домене, валидация, сопровождение и rollback.
Этот документ описывает актуальный **Rust-fiWindows/PowerShell deployment flow больше не считается основным способом развёртывания, патчинга или эксплуатации. Если в репозитории остаются старые ".ps1"-файлы, они рассматриваются как legacy/history или как будущий provider-слой, но не как production runtime.
---
0. Назначение
## 0) Структура проекта (полные пути)
AWatch-rus — программный комплекс операционного контроля, технического аудита, оценки трудоотдачи сотрудников и мониторинга корпоративной ИТ-инфраструктуры на базе:
- `<PROJECT_ROOT>/private-config/deploy.env`
- `<PROJECT_ROOT>/proxmox/create-ct.sh`
- `<PROJECT_ROOT>/proxmox/push-aw-artifacts.sh`
- `<PROJECT_ROOT>/aw-server/install_aw_server.sh`
- `<PROJECT_ROOT>/aw-server/apply_webui_ru_patch.sh`
- `<PROJECT_ROOT>/windows/deploy-single-user.ps1`
- `<PROJECT_ROOT>/windows/deploy-domain-users.ps1`
- `<PROJECT_ROOT>/windows/deploy-ensemble.ps1`
- `<PROJECT_ROOT>/windows/hardening-recovery.ps1`
- `<PROJECT_ROOT>/windows/validate-deployment.ps1`
- `<PROJECT_ROOT>/windows/browser-domains-native-collector.ps1`
- `<PROJECT_ROOT>/windows/dlp-endpoint-signals-collector.ps1`
- `<PROJECT_ROOT>/ansible/deploy_aw_server.yml`
- `<PROJECT_ROOT>/ansible/provision_proxmox_ct_and_deploy_aw.yml`
- `<PROJECT_ROOT>/ansible/provision_proxmox_ct_matrix_and_deploy_aw.yml`
- `<PROJECT_ROOT>/ansible/deploy_aw_windows.yml`
- Rust backend/runtime;
- Rust Agent;
- Rust server-rendered HTML + HTMX-compatible JSON API;
- Grafana/Prometheus-витрин;
- модулей Workforce, Security и Forensics;
- evidence/reporting tooling;
- ActivityWatch-compatible источников данных, где это применимо.
---
Проект не позиционируется как сертифицированная DLP/SIEM/EDR/XDR/СЗИ. DLP, evidence, UEBA и расследовательские функции используются как внутренние аналитические и операционные модули.
## 1) Подготовка
1. Актуальная архитектура
### 1.1 Требования
1.1 Основной runtime
- Proxmox VE 8/9, доступ root (или sudo с правами на `pct`).
- Шаблон Debian 12 LXC на хосте Proxmox.
- Windows хост(ы) с PowerShell 5.1+ и правами локального администратора.
- Сетевой доступ Windows-клиентов до ActivityWatch Server (`5600/tcp`).
Основной production runtime AWatch-rus — Rust-first:
### 1.2 Подготовка единого файла секретов
- backend/runtime — Rust;
- agent — Rust;
- portal — Rust server-rendered HTML + HTMX-compatible JSON API;
- operational status/check — Rust;
- DLP server-side helpers — Rust;
- worktime helpers/exporters/prewarm — Rust;
- SLO/health/readiness helpers — Rust;
- evidence/install-kit tooling — Rust;
- auto-heal helpers — Rust, только в безопасном режиме.
Скопируйте шаблон:
1.2 Что не является основным runtime
```bash
cp <PROJECT_ROOT>/private-config/deploy.env.example \
<PROJECT_ROOT>/private-config/deploy.env
```
Не считать основным production deployment flow:
Заполните в файле `<PROJECT_ROOT>/private-config/deploy.env`:
- PowerShell deployment;
- старые Windows ".ps1" rollout scripts;
- ручное исправление production-файлов без release/backup;
- прямое редактирование Web UI в "/opt" без воспроизводимого патча;
- Python/shell как основной operational runtime, если для компонента уже есть Rust-аналог.
- все `CT_*` параметры контейнера;
- все `AW_SERVER_*` параметры сервера;
- `CT_PASSWORD` (реальный пароль).
Python, shell, Ansible или PowerShell могут оставаться в проекте только как:
Важно: этот файл подхватывается автоматически скриптами Proxmox.
- legacy compatibility;
- вспомогательные dev/test tools;
- миграционные сценарии;
- будущие provider-слои;
- Telegram/OCR/AI/ETL/MCP helpers, если они явно не входят в Rust-first core.
---
2. Типовые роли узлов
## 2) Развёртывание сервера в Proxmox
2.1 Server node
### 2.0 Ansible full-stack (создание CT + установка AW)
Серверный узел содержит:
Подготовьте:
- AWatch-rus backend/runtime;
- portal;
- API;
- exporters;
- health/readiness/status tooling;
- systemd units/timers;
- Grafana/Prometheus integration;
- evidence/reporting storage.
- `<PROJECT_ROOT>/ansible/inventory.ini`
- `<PROJECT_ROOT>/ansible/group_vars/all.yml`
- `<PROJECT_ROOT>/ansible/group_vars/proxmox.yml`
2.2 Agent node
Запуск:
Agent node содержит:
```bash
cd <PROJECT_ROOT>/ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_and_deploy_aw.yml
```
- Rust Agent;
- локальную конфигурацию агента;
- systemd service или другой штатный supervisor;
- локальные логи;
- буфер/очередь, если предусмотрено конфигурацией;
- сетевой доступ до backend/API.
Этот сценарий полностью закрывает:
2.3 Monitoring node
- создание CT в Proxmox;
- bootstrap пакетов в CT;
- установку ActivityWatch Server;
- применение RU Web UI patch;
- проверку API.
Monitoring node может содержать:
Для массового режима (несколько CT):
- Prometheus;
- Grafana;
- dashboards;
- alerting rules;
- external logs/metrics storage.
```bash
cd <PROJECT_ROOT>/ansible
ansible-playbook -i inventory.ini provision_proxmox_ct_matrix_and_deploy_aw.yml
```
Monitoring node может совпадать с server node в пилотной установке.
### 2.1 Создать LXC контейнер
3. Требования
На узле Proxmox:
3.1 Базовые требования
```bash
cd <PROJECT_ROOT>
<PROJECT_ROOT>/proxmox/create-ct.sh
```
- Linux-сервер или LXC/VM.
- Доступ администратора к systemd.
- Rust toolchain для сборочного узла.
- Сетевой доступ между agent node и server node.
- Закрытый доступ к API и порталу через VPN, reverse proxy или внутренний контур.
- Backup/snapshot перед любым production patch.
По умолчанию читается:
3.2 Рекомендуемый production-подход
- `<PROJECT_ROOT>/private-config/deploy.env`
Для production не собирать проект прямо на боевом сервере, если есть отдельный build host.
При необходимости можно передать другой путь:
Рекомендуемый поток:
```bash
<PROJECT_ROOT>/proxmox/create-ct.sh /absolute/path/to/deploy.env
```
git checkout нужного commit/tag
→ cargo fmt / clippy / test / build
→ упаковка release artifacts
→ перенос artifacts на сервер
→ backup/snapshot
→ остановка/перезапуск нужных services
→ smoke tests
→ фиксация версии
### 2.2 Загрузить bootstrap-артефакты и env внутрь CT
4. Основные пути
```bash
cd <PROJECT_ROOT>
<PROJECT_ROOT>/proxmox/push-aw-artifacts.sh
```
Рекомендуемая структура на сервере:
Скрипт загружает в CT:
/opt/awatch-rus/
bin/
etc/
portal/
releases/
evidence/
reports/
logs/
- `<CT_BOOTSTRAP_DIR>/install_aw_server.sh`
- `<CT_BOOTSTRAP_DIR>/apply_webui_ru_patch.sh`
- `<CT_BOOTSTRAP_DIR>/activitywatch-server.service`
- `<CT_BOOTSTRAP_DIR>/aw-ru-patch.js`
- `<CT_BOOTSTRAP_DIR>/aw-sw-cleanup.js`
- `/etc/activitywatch/aw-server.env` (из `AW_SERVER_*`)
/etc/awatch-rus/
awatch-rus.env
agent.env
portal.env
### 2.3 Установить ActivityWatch Server внутри CT
/var/lib/awatch-rus/
data/
state/
cache/
evidence/
reports/
```bash
pct enter <CT_ID>
bash <CT_BOOTSTRAP_DIR>/install_aw_server.sh
```
/var/log/awatch-rus/
backend.log
agent.log
portal.log
exporter.log
### 2.4 Применить RU patch Web UI
Рекомендуемые runtime binaries:
```bash
bash <CT_BOOTSTRAP_DIR>/apply_webui_ru_patch.sh
systemctl restart activitywatch-server.service
```
/usr/local/bin/detmir-status
/usr/local/bin/detmir-check
/usr/local/bin/detmir-dlp
/usr/local/bin/detmir-auto
/usr/local/bin/detmir-heal-safe
/usr/local/bin/aw-rus-healthd
После применения патча доступны:
Имена конкретных бинарников должны соответствовать текущему "Cargo.toml" и фактически собранным artifacts. Если имя binary изменено, документация и systemd unit должны обновляться в том же commit.
- верхнее меню `DLP` в Web UI;
- DLP-страница bucket `aw-dlp-endpoint-signals_<HOST>`;
- встроенный центр `DLP review и правила`;
- служебные buckets `aw-dlp-review_<HOST>` и `aw-dlp-rules_<HOST>`.
5. Конфигурация
### 2.5 Проверка сервера
5.1 Общие правила
В CT:
- Не хранить production secrets в публичном репозитории.
- Не коммитить реальные hostnames, IP, логины, ФИО, токены, пароли.
- Для production использовать "/etc/awatch-rus/*.env".
- Для demo использовать только обезличенные fixtures.
- Все параметры, влияющие на runtime, должны быть описаны в документации.
```bash
systemctl status activitywatch-server.service --no-pager
curl -fsS http://127.0.0.1:5600/api/0/info
ss -ltnp | grep 5600
grep -n 'aw-ru-patch\|aw-sw-cleanup' /opt/activitywatch/webui-ru/index.html
```
5.2 Пример server env
Ожидается:
AWATCH_ENV=production
AWATCH_BIND_ADDR=127.0.0.1
AWATCH_PORT=5600
AWATCH_DATA_DIR=/var/lib/awatch-rus/data
AWATCH_LOG_DIR=/var/log/awatch-rus
AWATCH_EVIDENCE_DIR=/var/lib/awatch-rus/evidence
AWATCH_REPORTS_DIR=/var/lib/awatch-rus/reports
RUST_LOG=info
- сервис `active (running)`;
- API отвечает JSON;
- порт 5600 слушается;
- в `index.html` присутствуют оба скрипта.
5.3 Пример agent env
Дополнительно после первого входа в Web UI:
AWATCH_AGENT_ENV=production
AWATCH_SERVER_URL=https://awatch.example.local
AWATCH_AGENT_HOST_ID=HOSTNAME_OR_NODE_ID
AWATCH_AGENT_DATA_DIR=/var/lib/awatch-rus/agent
AWATCH_AGENT_LOG_DIR=/var/log/awatch-rus
RUST_LOG=info
- `#/home` должен показывать один корректный пункт `DLP`;
- `#/buckets/aw-dlp-endpoint-signals_<HOST>` должен открываться без ошибок;
- сохранение review/rule должно создавать buckets `aw-dlp-review_<HOST>` и `aw-dlp-rules_<HOST>`.
6. Сборка
---
6.1 Проверки перед сборкой
## 3) Развёртывание Windows-клиентов (другой AD-домен)
На build host:
### 3.1 Подготовка на Windows-хосте
cd /path/to/AWatch-rus
Скопируйте каталог:
git status --short
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
- `<PROJECT_ROOT>/windows`
Если в репозитории есть проектные quality gates, выполнить их обязательно:
например в:
bash scripts/check_private_config_guard.sh
bash scripts/quality-gate.sh
- `C:\Program Files\AWatch-rus\windows`
Если какой-то скрипт отсутствует в текущей ветке, не создавать фиктивную замену. Зафиксировать это в release notes.
Откройте **elevated PowerShell**:
6.2 Release build
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
```
cargo build --release --workspace
### 3.2 Массовое доменное развёртывание (рекомендуется)
Проверить artifacts:
Если текущий production ещё работает в старых каталогах
`C:\Program Files\ActivityWatch-Phase2` и `C:\ProgramData\ActivityWatch-Phase2`,
сначала выполните безопасную миграцию:
find target/release -maxdepth 1 -type f -executable -print
```powershell
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1 -WhatIf
C:\Program Files\AWatch-rus\windows\migrate-awatch-rus-paths.ps1
```
6.3 Упаковка artifacts
Скрипт остановит `ActivityWatch Recovery`/`ActivityWatch Launch *`, создаст backup в
`C:\ProgramData\AWatch-rus\migration-backups\...`, перенесёт файлы в единые пути,
пересоздаст `deployment-config.json`/scheduled tasks и запустит validation.
Рекомендуемый вариант:
Пример со списком пользователей:
mkdir -p dist/awatch-rus-release/bin
cp target/release/detmir-status dist/awatch-rus-release/bin/ 2>/dev/null || true
cp target/release/detmir-check dist/awatch-rus-release/bin/ 2>/dev/null || true
cp target/release/detmir-dlp dist/awatch-rus-release/bin/ 2>/dev/null || true
cp target/release/detmir-auto dist/awatch-rus-release/bin/ 2>/dev/null || true
cp target/release/detmir-heal-safe dist/awatch-rus-release/bin/ 2>/dev/null || true
cp target/release/aw-rus-healthd dist/awatch-rus-release/bin/ 2>/dev/null || true
```powershell
C:\Program Files\AWatch-rus\windows\deploy-domain-users.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-Domain CONTOSO `
-UserListPath C:\Deploy\aw-users.txt `
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json
```
tar -C dist -czf awatch-rus-release.tar.gz awatch-rus-release
sha256sum awatch-rus-release.tar.gz > awatch-rus-release.tar.gz.sha256
Поддерживаемые варианты:
Не использовать "cp ... || true" в CI без последующей проверки обязательных binaries. Для ручного production release список обязательных binaries должен быть проверен явно.
- `-Users user01,user02`
- `-Users 'CONTOSO\user01','CONTOSO\user02'`
- `-UserListPath <txt|csv>`
7. Первичное развёртывание server node
### 3.2.1 Ensemble orchestration (рекомендуется для production)
7.1 Создание каталогов
```powershell
C:\Program Files\AWatch-rus\windows\deploy-ensemble.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-Domain CONTOSO `
-Users user1,user2,user3,user4,user5 `
-ValidateAfterDeploy
```
sudo mkdir -p /opt/awatch-rus/bin
sudo mkdir -p /opt/awatch-rus/releases
sudo mkdir -p /etc/awatch-rus
sudo mkdir -p /var/lib/awatch-rus/data
sudo mkdir -p /var/lib/awatch-rus/state
sudo mkdir -p /var/lib/awatch-rus/evidence
sudo mkdir -p /var/lib/awatch-rus/reports
sudo mkdir -p /var/log/awatch-rus
Отчёт сохраняется в:
7.2 Установка binaries
- `C:\ProgramData\AWatch-rus\ensemble-report-YYYYMMDD-HHMMSS.json`
sudo install -m 0755 dist/awatch-rus-release/bin/* /opt/awatch-rus/bin/
### 3.3 Single-user развёртывание
Создать symlink для удобства:
```powershell
C:\Program Files\AWatch-rus\windows\deploy-single-user.ps1 `
-ServerHost aw.example.local `
-ServerPort 5600 `
-TargetUser 'CONTOSO\user01' `
-CustomRulesPath C:\Program Files\AWatch-rus\windows\web-category-rules.example.json
```
sudo ln -sf /opt/awatch-rus/bin/detmir-status /usr/local/bin/detmir-status
sudo ln -sf /opt/awatch-rus/bin/detmir-check /usr/local/bin/detmir-check
sudo ln -sf /opt/awatch-rus/bin/detmir-dlp /usr/local/bin/detmir-dlp
### 3.4 Recovery / hardening
Если binary отсутствует, не создавать пустой symlink. Сначала проверить фактический состав release artifact.
```powershell
C:\Program Files\AWatch-rus\windows\hardening-recovery.ps1 `
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
```
7.3 Конфигурация
### 3.5 Валидация deployment-а (PowerShell report)
sudo install -m 0640 awatch-rus.env /etc/awatch-rus/awatch-rus.env
```powershell
$report = C:\Program Files\AWatch-rus\windows\validate-deployment.ps1 `
-ConfigPath C:\ProgramData\AWatch-rus\deployment-config.json
$report | ConvertTo-Json -Depth 12
```
Проверить права:
---
sudo chown root:root /etc/awatch-rus/awatch-rus.env
sudo chmod 0640 /etc/awatch-rus/awatch-rus.env
## 4) Что должно появиться на Windows после установки
8. systemd units
- `C:\Program Files\AWatch-rus\bin`
- `C:\ProgramData\AWatch-rus\deployment-config.json`
- `C:\ProgramData\AWatch-rus\launch-watchers.ps1`
- `C:\ProgramData\AWatch-rus\recovery-loop.ps1`
- `C:\ProgramData\AWatch-rus\browser-domains-native-collector.ps1`
- `C:\ProgramData\AWatch-rus\web-category-rules.json`
- `C:\ProgramData\AWatch-rus\logs\`
8.1 Пример backend service
Задачи планировщика:
[Unit]
Description=AWatch-rus backend/runtime
After=network-online.target
Wants=network-online.target
- `ActivityWatch Launch [<user>]` (per-user, при логоне)
- `ActivityWatch Recovery` (system-level recovery)
[Service]
Type=simple
EnvironmentFile=/etc/awatch-rus/awatch-rus.env
ExecStart=/opt/awatch-rus/bin/awatch-rus-backend
Restart=on-failure
RestartSec=5
WorkingDirectory=/opt/awatch-rus
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/awatch-rus /var/log/awatch-rus
---
[Install]
WantedBy=multi-user.target
## 5) Полная валидация потока данных
Если фактическое имя backend binary отличается, заменить "awatch-rus-backend" на актуальное имя из release artifact.
### 5.1 На Windows-хосте
8.2 Пример health service
Проверить процессы:
[Unit]
Description=AWatch-rus health daemon
After=network-online.target
Wants=network-online.target
```powershell
Get-Process aw-watcher-afk,aw-watcher-window -ErrorAction SilentlyContinue
Get-CimInstance Win32_Process | ? { $_.CommandLine -like '*browser-domains-native-collector.ps1*' } | select ProcessId,SessionId,CommandLine
```
[Service]
Type=simple
EnvironmentFile=/etc/awatch-rus/awatch-rus.env
ExecStart=/opt/awatch-rus/bin/aw-rus-healthd
Restart=on-failure
RestartSec=5
WorkingDirectory=/opt/awatch-rus
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/awatch-rus /var/log/awatch-rus
Проверить задачи:
[Install]
WantedBy=multi-user.target
```powershell
Get-ScheduledTask | ? { $_.TaskName -like 'ActivityWatch*' } | select TaskName,State
```
8.3 Применение unit files
### 5.2 На сервере ActivityWatch API
sudo systemctl daemon-reload
sudo systemctl enable --now awatch-rus-backend.service
sudo systemctl enable --now aw-rus-healthd.service
```bash
curl -sS http://127.0.0.1:5600/api/0/buckets | jq 'keys'
```
Если конкретный unit не используется в текущей инсталляции, не создавать фиктивный сервис. Документировать фактический набор services.
Ожидаемые bucket'ы:
9. Развёртывание Rust Agent
- `aw-watcher-afk_<HOST>`
- `aw-watcher-window_<HOST>`
- `aw-watcher-web-<browser>_<HOST>`
- `aw-detmir-web-category_<HOST>` (категоризованный поток)
- `aw-dlp-endpoint-signals_<HOST>` (endpoint сигналы)
- `aw-dlp-review_<HOST>` (ручная классификация через UI)
- `aw-dlp-rules_<HOST>` (suppress/rule записи через UI)
9.1 Установка agent binary
Проверка событий браузера:
sudo mkdir -p /opt/awatch-rus/bin
sudo mkdir -p /etc/awatch-rus
sudo mkdir -p /var/lib/awatch-rus/agent
sudo mkdir -p /var/log/awatch-rus
```bash
curl -sS "http://127.0.0.1:5600/api/0/buckets/aw-watcher-web-edge_<HOST>/events?limit=5" | jq
```
sudo install -m 0755 awatch-rus-agent /opt/awatch-rus/bin/awatch-rus-agent
sudo install -m 0640 agent.env /etc/awatch-rus/agent.env
Проверка категоризации:
9.2 Пример agent service
```bash
curl -sS "http://127.0.0.1:5600/api/0/buckets/aw-detmir-web-category_<HOST>/events?limit=5" | jq
```
[Unit]
Description=AWatch-rus Rust Agent
After=network-online.target
Wants=network-online.target
Проверка DLP review/rules:
[Service]
Type=simple
EnvironmentFile=/etc/awatch-rus/agent.env
ExecStart=/opt/awatch-rus/bin/awatch-rus-agent
Restart=on-failure
RestartSec=5
WorkingDirectory=/opt/awatch-rus
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/awatch-rus /var/log/awatch-rus
```bash
curl -sS "http://127.0.0.1:5600/api/0/buckets/aw-dlp-review_<HOST>/events?limit=20" | jq
curl -sS "http://127.0.0.1:5600/api/0/buckets/aw-dlp-rules_<HOST>/events?limit=20" | jq
```
[Install]
WantedBy=multi-user.target
Ожидаемые поля review:
9.3 Запуск agent
- `reviewId`
- `signalType`
- `verdict`
- `category`
- `comment`
- `archived`
sudo systemctl daemon-reload
sudo systemctl enable --now awatch-rus-agent.service
sudo systemctl status awatch-rus-agent.service --no-pager
Ожидаемые поля rules:
10. Развёртывание портала
- `ruleId`
- `signalType`
- `match`
- `category`
- `comment`
- `enabled`
Портальный слой AWatch-rus зафиксирован как Rust server-rendered HTML + HTMX-compatible JSON API.
---
10.1 Общий порядок
## 6) Сопровождение (обязательно)
build portal/backend binary
→ install binary
→ install templates/static assets, если они выделены отдельно
→ update portal env
→ restart portal service
→ smoke check HTTP/API routes
### 6.1 Backup перед любыми изменениями
10.2 Проверка портала
curl -fsS http://127.0.0.1:5600/healthz
curl -fsS http://127.0.0.1:5600/readyz
curl -fsS http://127.0.0.1:5600/version
Если конкретные endpoints в текущей версии отличаются, использовать фактически реализованные health/readiness/version endpoints и обновить этот документ в том же commit.
11. Патчи в развернутой среде
11.1 Правило
Любой production patch применяется только через контролируемый цикл:
определить commit/tag
→ собрать release artifact
→ выполнить локальные проверки
→ сделать backup/snapshot
→ установить новые binaries/configs
→ restart/reload services
→ smoke tests
→ зафиксировать результат
→ сохранить rollback path
11.2 Перед патчем
git rev-parse HEAD
git status --short
Сохранить:
дата/время
commit/tag
кто применяет
какие services затрагиваются
какой rollback path
11.3 Backup перед патчем
Если используется Proxmox/LXC:
На Proxmox:
```bash
vzdump <CT_ID> --mode snapshot --compress zstd --storage <BACKUP_STORAGE>
```
Внутри сервера:
Конфиги внутри CT:
sudo tar -C / -czf /root/awatch-rus-backup-$(date +%Y%m%d-%H%M%S).tgz \
etc/awatch-rus \
opt/awatch-rus \
var/lib/awatch-rus \
var/log/awatch-rus
```bash
pct exec <CT_ID> -- tar -C / -czf <PRIVATE_BACKUP_DIR>/activitywatch-config-backup.tgz \
etc/activitywatch \
etc/systemd/system/activitywatch-server.service \
opt/activitywatch/webui-ru \
opt/activitywatch/releases
```
Если данные большие, backup "/var/lib/awatch-rus" выполнять отдельной процедурой согласно backup policy.
### 6.2 Обновление сервера
11.4 Установка нового binary
1. Обновить `AW_SERVER_VERSION` и `AW_SERVER_DOWNLOAD_URL` в
`<PROJECT_ROOT>/private-config/deploy.env`
2. Выполнить:
Сохранить предыдущую версию:
```bash
<PROJECT_ROOT>/proxmox/push-aw-artifacts.sh
pct enter <CT_ID>
bash <CT_BOOTSTRAP_DIR>/install_aw_server.sh
bash <CT_BOOTSTRAP_DIR>/apply_webui_ru_patch.sh
systemctl restart activitywatch-server.service
```
sudo mkdir -p /opt/awatch-rus/releases/previous
sudo cp -a /opt/awatch-rus/bin /opt/awatch-rus/releases/previous/bin-$(date +%Y%m%d-%H%M%S)
3. Повторить валидацию API/UI.
Установить новый artifact:
### 6.3 Rollback
sudo install -m 0755 dist/awatch-rus-release/bin/* /opt/awatch-rus/bin/
RU patch rollback:
11.5 Restart services
```bash
cp /opt/activitywatch/webui-ru/index.html.bak.<timestamp> /opt/activitywatch/webui-ru/index.html
systemctl restart activitywatch-server.service
```
sudo systemctl daemon-reload
sudo systemctl restart awatch-rus-backend.service
sudo systemctl restart aw-rus-healthd.service
Полный rollback:
Если патч касается только agent:
- восстановить CT из snapshot/backup;
- проверить API и Web UI;
- проверить доступность для Windows-клиентов.
sudo systemctl restart awatch-rus-agent.service
---
Если сервис в текущем контуре называется иначе, использовать фактическое имя systemd unit.
## 7) Безопасность
12. Smoke-тесты после патча
- Не хранить реальные приватные параметры вне `<PROJECT_ROOT>/private-config/deploy.env`.
- Не открывать `5600/tcp` в интернет напрямую.
- Публиковать через VPN или reverse proxy с ограничением доступа.
- Перед изменениями всегда делать backup.
12.1 Systemd
---
systemctl --failed --no-pager
systemctl status awatch-rus-backend.service --no-pager
systemctl status aw-rus-healthd.service --no-pager
## 8) Короткий чек-лист ввода в эксплуатацию
12.2 Rust operational checks
detmir-status --json
detmir-check --json
detmir-dlp --json
Если отдельная команда не установлена в данном контуре, это не считается ошибкой только при наличии документированного исключения.
12.3 HTTP/API
curl -fsS http://127.0.0.1:5600/healthz
curl -fsS http://127.0.0.1:5600/readyz
curl -fsS http://127.0.0.1:5600/version
12.4 Portal smoke
Проверить в браузере:
/portal
/portal/reports
/portal/architecture
Для Pilot v1 проверить роли:
executive
manager
security
forensics
admin
12.5 Data freshness
Проверить, что витрины и отчёты не пустые из-за сбоя сбора:
последние события поступают
worktime reports обновляются
DLP/security events отображаются, если включены
evidence/reporting не падает
Grafana dashboards открываются
13. Rollback
13.1 Быстрый rollback binary
Найти предыдущий backup:
ls -lah /opt/awatch-rus/releases/previous/
Восстановить:
sudo rsync -a --delete /opt/awatch-rus/releases/previous/bin-YYYYMMDD-HHMMSS/ /opt/awatch-rus/bin/
sudo systemctl restart awatch-rus-backend.service
sudo systemctl restart aw-rus-healthd.service
13.2 Rollback конфигурации
sudo cp /etc/awatch-rus/awatch-rus.env.bak /etc/awatch-rus/awatch-rus.env
sudo systemctl restart awatch-rus-backend.service
13.3 Rollback CT/VM
Если повреждение затрагивает runtime, данные или systemd-конфигурацию:
остановить сервисы
восстановить snapshot/backup
проверить health/readiness/version
проверить портал
проверить поступление данных
зафиксировать incident note
14. Monitoring
14.1 Что должно контролироваться
- service status;
- process uptime;
- API health/readiness;
- latency;
- error rate;
- freshness данных;
- заполненность диска;
- размер логов;
- успешность exporters;
- SLO status;
- agent coverage;
- отсутствие failed systemd units.
14.2 Grafana
В Grafana должны быть разделены витрины:
- executive dashboard;
- security dashboard;
- operations dashboard;
- RDP/user activity dashboard;
- data quality/freshness dashboard;
- DLP/evidence dashboard, если модуль включён.
14.3 Prometheus
Prometheus scrape должен быть доступен только из внутреннего контура мониторинга. Не открывать metrics endpoints наружу.
15. Security hardening
Обязательные правила:
- не публиковать API напрямую в интернет;
- использовать VPN/reverse proxy/access control;
- закрыть лишние порты;
- хранить secrets вне git;
- ограничить права systemd services;
- использовать отдельного service user, если это поддерживается текущей установкой;
- включить backup;
- проверять логи после каждого патча;
- не использовать demo fixtures как production data;
- не смешивать реальные ФИО/IP/hostname с публичными demo screenshots.
16. Проверка перед вводом в эксплуатацию
Минимальный checklist:
[ ] выбран commit/tag release
[ ] cargo fmt прошёл
[ ] cargo clippy прошёл
[ ] cargo test прошёл
[ ] cargo build --release прошёл
[ ] private config guard прошёл
[ ] backup/snapshot создан
[ ] binaries установлены
[ ] systemd services запущены
[ ] health/readiness/version отвечают
[ ] detmir-status/check/dlp работают
[ ] portal открывается
[ ] роли Pilot v1 проверены
[ ] Grafana dashboards открываются
[ ] данные поступают
[ ] rollback path известен
[ ] дата/commit/оператор зафиксированы
17. Что больше не использовать как основной путь
Не использовать как основной production flow:
windows/deploy-single-user.ps1
windows/deploy-domain-users.ps1
windows/deploy-ensemble.ps1
windows/validate-deployment.ps1
windows/hardening-recovery.ps1
windows/browser-domains-native-collector.ps1
windows/dlp-endpoint-signals-collector.ps1
Если эти файлы физически остаются в репозитории, они должны быть явно помечены как:
legacy
planned provider
migration-only
dev/test helper
Они не должны описываться в основном deployment manual как обязательный production-путь.
18. Короткий production runbook
18.1 Развернуть
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build --release --workspace
sudo install -m 0755 target/release/<binary> /opt/awatch-rus/bin/<binary>
sudo systemctl daemon-reload
sudo systemctl restart <service>.service
18.2 Проверить
systemctl --failed --no-pager
detmir-status --json
detmir-check --json
curl -fsS http://127.0.0.1:5600/healthz
curl -fsS http://127.0.0.1:5600/readyz
curl -fsS http://127.0.0.1:5600/version
18.3 Откатить
sudo rsync -a --delete /opt/awatch-rus/releases/previous/bin-YYYYMMDD-HHMMSS/ /opt/awatch-rus/bin/
sudo systemctl restart <service>.service
19. Правило актуализации этого документа
Если меняется:
- имя binary;
- имя systemd unit;
- порт;
- endpoint;
- путь хранения данных;
- способ сборки;
- способ доставки artifacts;
- smoke-test;
- rollback procedure;
то этот файл должен обновляться в том же commit, что и изменение кода или deployment-конфигурации.
1. Заполнен `<PROJECT_ROOT>/private-config/deploy.env`.
2. Выполнен `<PROJECT_ROOT>/proxmox/create-ct.sh`.
3. Выполнен `<PROJECT_ROOT>/proxmox/push-aw-artifacts.sh`.
4. В CT выполнены `<CT_BOOTSTRAP_DIR>/install_aw_server.sh` и `<CT_BOOTSTRAP_DIR>/apply_webui_ru_patch.sh`.
5. Сервер API/порт/UI проверены.
6. На Windows выполнен `deploy-domain-users.ps1`.
7. Проверены процессы, задачи и bucket'ы.
8. Зафиксированы параметры и дата ввода.
-78
View File
@@ -1,78 +0,0 @@
# GitHub-сборка Rust-бинарников AWatch-rus
## Принятое решение
Для проекта AWatch-rus каноническая release-сборка Rust-бинарников выполняется в GitHub Actions.
Локальная сборка используется для разработки и предварительной проверки. Официальным источником release-бинарников считаются только artifacts, полученные из GitHub Actions на конкретном commit или tag.
## Toolchain
Версия Rust/Cargo фиксируется в `rust-toolchain.toml`:
```toml
[toolchain]
channel = "1.94.0"
profile = "minimal"
components = ["rustfmt", "clippy"]
```
Workflow должны запускать Cargo явно:
```bash
cargo +1.94.0 --version
rustc +1.94.0 --version
cargo +1.94.0 fmt --manifest-path adk-rust/Cargo.toml --all -- --check
cargo +1.94.0 test --manifest-path adk-rust/Cargo.toml --workspace --no-fail-fast
cargo +1.94.0 clippy --manifest-path adk-rust/Cargo.toml --workspace --all-targets -- -D warnings
cargo +1.94.0 build --manifest-path adk-rust/Cargo.toml --workspace --release
```
Это исключает ситуацию, когда GitHub runner использует старый системный Cargo.
## Workflow
Основные workflow:
- `.github/workflows/rust-workspace.yml` — fmt, tests, clippy, release build всего workspace.
- `.github/workflows/rust-professionalization-check.yml` — PR smoke для изменяемых Rust-крейтов.
- `.github/workflows/rust-binary-build.yml` — сборка release-бинарников Linux x86_64 и публикация GitHub Actions artifact.
## rust-binary-build
Workflow `rust-binary-build` запускается:
- вручную через GitHub Actions -> rust-binary-build -> Run workflow;
- автоматически при push tag вида `v*`.
Внутри workflow выполняется:
1. checkout repository;
2. установка Rust/Cargo 1.94.0;
3. вывод версий `cargo` и `rustc`;
4. format check;
5. workspace tests;
6. workspace clippy;
7. workspace release build;
8. upload artifact `awatch-rus-linux-x86_64-release-binaries`.
## Правило проекта
Перед передачей бинарников на пилот, демонстрацию или релиз нужно использовать GitHub Actions artifact, а не локально собранный файл.
Минимальные признаки корректного artifact:
- workflow завершился успешно;
- в логах указан Rust/Cargo 1.94.0;
- build выполнен из нужного commit или tag;
- artifact скачан из GitHub Actions.
## Дальнейшие улучшения
Отдельными PR можно добавить:
- SHA256SUMS для каждого бинарника;
- автоматическую публикацию в GitHub Release при tag `v*`;
- Windows x86_64 build для endpoint-компонентов;
- Linux static/musl build при необходимости;
- подпись release artifacts.
+89
View File
@@ -0,0 +1,89 @@
# Профессиональные сильные стороны проекта AWatch-rus
Ниже — структурированная, формальная сводка ключевых преимуществ AWatch-rus,
подходит для включения в пакет документов при регистрации в реестре ПО и для
технического аудита.
## 1. Архитектура и инженерный дизайн
- Rust-first, модульная архитектура: более 40 специализированных крейтов в
workspace, четкая декомпозиция ответственности и воспроизводимые сборки
(Cargo.lock).
- Разделение на логические уровни: endpoint → серверные хранилища → processing
→ operator → automation, что упрощает тестирование, валидацию и аудит.
- Workspace resolver v3 и требуемая версия rust (1.85) — современный базис для
поддержки и сопровождения.
- Четкие migration-runbooks для последовательного и безопасного перехода
legacy Python → Rust.
## 2. Операционная зрелость и автоматизация
- Полноценный Ansible-ensemble для end-to-end развёртывания (Proxmox, Linux,
централизованный Windows rollout по WinRM) с retry-политикой, smoke-тестами
и пост-deploy валидацией.
- Safe-run pattern: dry-run/apply planners, backup-before-delete, atomic
операции, conservative no-restart paths — минимизация риска при операциях.
- Набор диагностических модулей (detmir-*, aw-*, quality-gate, smoke checks)
обеспечивает быстрый feedback и ускоряет внедрение.
## 3. Безопасность и обращение с evidence
- Изолированный путь evidence: opaque IDs, bearer-upload, magic validation для
PNG/JPEG, ограничение размера, SHA-256 валидация, atomic write и детальные
audit trails.
- Security hardening guide: обязательное TLS, reverse-proxy, firewall rules,
минимальные привилегии для сервисных аккаунтов и контроль доступа к
evidence-материалам.
- Позиционирование: продукт не позиционируется как сертифицированная DLP/SIEM/
EDR — это уменьшает юридические риски при регистрации и определяет четкие
ожидания заказчика.
## 4. Production-readiness и критерии приёмки
- Полный комплект acceptance и pilot-checklists (30-дневный Pilot Success
Criteria) с метриками: доступность портала, coverage источников, freshness
данных, время загрузки сценариев Executive/Reporting, доля подтверждённых
incident candidates.
- Runbooks для восстановления, backup & restore сценарии и documented rollback
steps.
## 5. Наблюдаемость, качество данных и объяснимость
- Встроенные SLO/health monitoring, Prometheus + Grafana витрины и
детализированные smoke/contour тесты для проверки консистентности данных.
- UEBA v1 — прозрачная rule-based модель с reason-codes; KPI показывают
coverage и confidence, что повышает доверие заказчика.
## 6. Поддержка Windows/RDP и forensic pipelines
- Централизованный Windows rollout: deploy-скрипты, winrm retry logic, API
smoke-checks (aw-watcher-afk / aw-watcher-window).
- Серверная Hayabusa pipeline: auto-upload EVTX, severity scoring, automatic
case creation, bounded metadata storage и Telegram-уведомления с пороговой
фильтрацией.
## 7. Документированность и готовность к реестру
- Набор документов для регистрации: product passport, architecture, functional
scope, dependency statement, deployment model, readiness checklist.
- Demo fixtures и скриншоты анонимизированы (не содержат реальных IP/логинов/ФИО)
— соответствует требованиям для публичных материалов и подачи в реестр.
## 8. Честное позиционирование и управление рисками
- Ясно задокументированы границы продукта и planned/future элементы — это
облегчает аудит и формирует реалистичные expectations у заказчика.
- Наличие gap-analysis и roadmap-conformance audit ускоряет планирование
необходимых доработок перед масштабированием.
---
Файл сохранён в ветке `docs/add-professional-highlights` по пути
`docs/PROFESSIONAL_HIGHLIGHTS_RU.md`.
Далее могу:
- открыть Pull Request в main с этим изменением (рекомендуется для review),
- смержить напрямую в main (если вы хотите немедленно обновить основную ветку),
- или создать краткую 1-страничную выдержку для подачи в реестр.
Как предпочитаете поступить дальше?
+174
View File
@@ -0,0 +1,174 @@
# Резолюция по проекту AWatch-rus
## 📋 РЕЗОЛЮЦИЯ ПО ПРОЕКТУ AWatch-rus
### 1. ОБЗОР ПРОЕКТА
**Название:** AWatch-rus — платформа операционного контроля и технического аудита
**Статус:** Активный проект в стадии Pilot v1.0
**Язык реализации:** Rust (основной backend) + Python (вспомогательные компоненты)
**Лицензия:** Apache License 2.0
**Видимость:** Открытый репозиторий
---
### 2. НАЗНАЧЕНИЕ И ЦЕННОСТЬ
Проект предназначен для:
- Workforce Analytics — мониторинг активности сотрудников, загруженности, использования приложений
- Security Operations — DLP-сигналы, detection событий, управление инцидентами
- Forensics & Incident Response — сбор evidence, offline-анализ через Hayabusa, расследования
**Целевая аудитория:** руководители, операторы ИБ, администраторы ИТ-инфраструктуры, forensics-специалисты
---
### 3. КЛЮЧЕВЫЕ КОМПОНЕНТЫ АРХИТЕКТУРЫ
| Компонент | Технология | Назначение |
|-----------|------------|-----------|
| Rust Backend | Rust runtime | Основной сервер, SLO, DLP-обработка, evidence, auto-heal |
| Windows Collector | PowerShell/Rust | Сбор данных: AFK, window-tracking, RDP-сессии, DLP-события |
| Grafana/Prometheus | Dashboards | Витрины для админов, операторов, руководителей |
| ActivityWatch | Modified base | Основа для сбора telemetry и worktime |
| Portal UI | Rust server-rendered HTML + HTMX | Веб-интерфейс на основе server-side rendering |
---
### 4. ФУНКЦИОНАЛЬНЫЕ ВОЗМОЖНОСТИ (Implemented)
✅ Workforce Module:
- Отслеживание активности: рабочее время, простои, переключение окон
- RDP-сессии с детализацией
- Профилирование приложений и сайтов
- KPI-отчёты для руководства
✅ Security Module:
- DLP-детектирование (копирование, печать, USB)
- UEBA v1 (прозрачная rule-based модель, без ML)
- Управление очередью инцидентов
- Audit действий оператора
✅ Forensics Module:
- Hayabusa integration для EVTX-анализа
- Offline investigation packs
- Timeline и Evidence-галереи
- Расследование инцидентов
✅ Operations:
- Role-based access (executive, manager, security, forensics, admin)
- Pilot v1.0 validation
- Deployment topologies & sizing guide
- Production hardening
---
### 5. ПЛАНЫ И РАСШИРЕНИЕ
Planned:
- PowerShell Provider для мониторинга
- SSH Provider
- Syslog Provider
- 1C Integration Provider
- Russian OS support validation
Future:
- Extended Enterprise connectors
- SCUD/VPN integrations
- React/TypeScript Enterprise UI
- Tauri Desktop Forensics
---
### 6. ТЕХНИЧЕСКОЕ СОСТОЯНИЕ
| Метрика | Значение |
|---------|----------|
| Open Issues | 1 |
| Forks | 2 |
| Stars | 2 |
| Repository Size | ~10.5 MB |
| Last Push | 2026-06-12T19:17:35Z |
| Default Branch | main |
---
### 7. DEPLOYMENT И PRODUCTION-READINESS
Инструменты развёртывания:
- Ansible playbooks (полный automation stack)
- Proxmox provisioning (CT creation)
- Windows WinRM rollout (centralized deployment)
- Docker/CT topologies (multi-node)
Документация и валидация:
- Pilot v1.0 демо-сценарии
- Enterprise deployment guide
- Security hardening & backup/recovery
- Sizing guide
- Registry readiness документы
---
### 8. ТЕХНИЧЕСКИЕ ПРЕИМУЩЕСТВА
- Rust-first approach — производительность, безопасность памяти, надёжность
- Server-side rendering — снижение нагрузки на клиент
- API-first — OpenAPI contracts, TypeScript declarations
- Observability — Prometheus metrics, Grafana dashboards
- Security by default — read-only по умолчанию, безопасные mutation paths
- Modular Rust workspace — четкая декомпозиция модулей
---
### 9. ОГРАНИЧЕНИЯ И ПОЗИЦИОНИРОВАНИЕ
Не позиционируется как:
- Сертифицированная DLP/SIEM/EDR/XDR
- ML-based UEBA
- Юридически гарантированная неизменность evidence
Позиционируется как:
- Операционная платформа контроля (Workforce + Security + Forensics)
- Pilot-ready решение для технического аудита
- Расширяемая архитектура для агентных и agentless-источников
---
### 10. РЕКОМЕНДАЦИИ
Для потенциальных пользователей:
1. Начать с Pilot v1 demo
2. Пройти Pilot validation checklist
3. Использовать Ansible automation для развёртывания
4. Ознакомиться с Security hardening guide
5. Планировать интеграции через role-based contracts
Для разработчиков:
1. Контрибутировать через PR согласно guidelines
2. Использовать migration runbook для изменений
3. Поддерживать quality gates и smoke tests
---
### 11. ИТОГОВАЯ ОЦЕНКА
- Качество кода: высокое (Rust-first, модульная архитектура)
- Документация: полная, пригодна для реестра
- Production-readiness: готов к пилоту, требуется валидация
- Сообщество: ранняя стадия
- Расширяемость: высокая
---
## ✅ ИТОГОВЫЙ ВЕРДИКТ
AWatch-rus — профессиональный, хорошо структурированный проект для операционного контроля корпоративной ИТ-инфраструктуры. Проект готов к оценке и пилотному развёртыванию; перед production рекомендуется пройти валидацию по чеклистам.
---
*Файл добавлён в ветку `docs/add-professional-highlights` как `docs/RELEASE_RESOLUTION_RU.md`.*
-4
View File
@@ -1,4 +0,0 @@
[toolchain]
channel = "1.94.0"
profile = "minimal"
components = ["rustfmt", "clippy"]
-143
View File
@@ -1,143 +0,0 @@
#!/usr/bin/env python3
"""Create a GitHub Actions release package from Rust release binaries."""
from __future__ import annotations
import argparse
import hashlib
import json
import shutil
import stat
import tarfile
from datetime import datetime, timezone
from pathlib import Path
SKIP_DIRS = {"deps", "build", "examples", "incremental"}
SKIP_SUFFIXES = {".d", ".rlib", ".rmeta"}
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
while True:
chunk = handle.read(1024 * 1024)
if not chunk:
break
digest.update(chunk)
return digest.hexdigest()
def is_binary(path: Path) -> bool:
if not path.is_file():
return False
if path.name in SKIP_DIRS:
return False
if path.suffix in SKIP_SUFFIXES:
return False
return bool(path.stat().st_mode & stat.S_IXUSR)
def collect(release_dir: Path) -> list[Path]:
items = [item for item in sorted(release_dir.iterdir()) if is_binary(item)]
if not items:
raise SystemExit(f"No release binaries found in {release_dir}")
return items
def write(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def copy_release_file(src: Path, dst: Path) -> Path:
"""Copy file contents without preserving metadata that some mounts reject."""
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(src, dst)
try:
dst.chmod(src.stat().st_mode & 0o777)
except PermissionError:
# Some removable/network filesystems reject chmod/utime metadata changes.
# The package remains valid because the archive manifest/checksums are
# based on file contents, not filesystem timestamps.
pass
return dst
def write_archive_checksum(archive: Path) -> None:
write(archive.with_suffix(archive.suffix + ".sha256"), f"{sha256(archive)} {archive.name}\n")
def create_compatibility_aliases(out_dir: Path, archive: Path) -> None:
"""Create both linux-x86_64 and linux_x86_64 artifact paths."""
out_alias = Path(str(out_dir).replace("linux-x86_64", "linux_x86_64"))
if out_alias != out_dir:
if out_alias.exists():
shutil.rmtree(out_alias)
out_alias.mkdir(parents=True)
for item in out_dir.iterdir():
if item.is_file():
copy_release_file(item, out_alias / item.name)
archive_alias = Path(str(archive).replace("linux-x86_64", "linux_x86_64"))
if archive_alias != archive:
copy_release_file(archive, archive_alias)
write_archive_checksum(archive_alias)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--release-dir", type=Path, required=True)
parser.add_argument("--out-dir", type=Path, required=True)
parser.add_argument("--archive", type=Path, required=True)
parser.add_argument("--target", default="linux-x86_64")
parser.add_argument("--commit", default="unknown")
parser.add_argument("--ref", default="unknown")
parser.add_argument("--run-id", default="unknown")
args = parser.parse_args()
release_dir = args.release_dir.resolve()
out_dir = args.out_dir.resolve()
archive = args.archive.resolve()
if out_dir.exists():
shutil.rmtree(out_dir)
out_dir.mkdir(parents=True)
binaries = collect(release_dir)
for binary in binaries:
copy_release_file(binary, out_dir / binary.name)
names = [binary.name for binary in binaries]
write(out_dir / "BINARIES.txt", "\n".join(names) + "\n")
checksum_lines = []
manifest_binaries = []
for name in names:
packaged = out_dir / name
digest = sha256(packaged)
checksum_lines.append(f"{digest} {name}")
manifest_binaries.append(
{"name": name, "size_bytes": packaged.stat().st_size, "sha256": digest}
)
write(out_dir / "SHA256SUMS.txt", "\n".join(checksum_lines) + "\n")
manifest = {
"project": "AWatch-rus",
"target": args.target,
"commit": args.commit,
"ref": args.ref,
"run_id": args.run_id,
"build_time_utc": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"binaries": manifest_binaries,
}
write(out_dir / "BUILD_MANIFEST.json", json.dumps(manifest, ensure_ascii=False, indent=2) + "\n")
archive.parent.mkdir(parents=True, exist_ok=True)
with tarfile.open(archive, "w:gz") as tar:
tar.add(out_dir, arcname=out_dir.name)
write_archive_checksum(archive)
create_compatibility_aliases(out_dir, archive)
if __name__ == "__main__":
main()