chore(1.2): remove per-worker unit templates superseded by consolidation
The batch/cpu supervisor groups + heavy fleet replace 10 per-worker units (reconciler/enrich/orchestrator/mutator/clusterer/campaign-clusterer/ attribution/reuse-correlator/profiler/ttp). Removed their deploy/*.service.j2 templates and rewired decnet.target to the 3 consolidated units. Dropped test_orchestrator_unit.py (tested a removed unit). CLI commands (decnet ttp, mutate, …) stay for manual runs; new units' Conflicts= still name the old units defensively for hosts mid-migration.
This commit is contained in:
@@ -1,45 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET Attribution Engine v0 (per-(identity, primitive) state machine)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#attribution
|
||||
After=network-online.target decnet-bus.service
|
||||
Wants=network-online.target decnet-bus.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.attribution.log
|
||||
# Subscribes to attacker.observation.> and, for each event, ensures a
|
||||
# stub AttackerIdentity row, runs the per-ValueKind merger over the
|
||||
# full identity-keyed observation series, upserts the derived state in
|
||||
# attribution_state, and publishes attribution.profile.state_changed
|
||||
# only on transition. Periodic tick (default 60s) fires
|
||||
# attribution.profile.multi_actor_suspected when >= 2 primitives flag
|
||||
# the same identity. Closes DEBT-051.
|
||||
ExecStart={{ venv_dir }}/bin/decnet attribution
|
||||
StandardOutput=append:/var/log/decnet/decnet.attribution.log
|
||||
StandardError=append:/var/log/decnet/decnet.attribution.log
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,52 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET Campaign Clusterer (identities → campaigns / operations)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#campaign-clusterer
|
||||
After=network-online.target decnet-bus.service decnet-clusterer.service
|
||||
Wants=network-online.target decnet-bus.service decnet-clusterer.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.campaign-clusterer.log
|
||||
# Subscribes to identity.>; falls back to a 60s slow-tick poll when
|
||||
# the bus is idle or unavailable. Reads AttackerIdentity rows,
|
||||
# projects them into the campaign-level similarity graph
|
||||
# (phase-handoff / shared-infra / temporal overlap / cohort), runs
|
||||
# union-find, writes campaigns rows + sets
|
||||
# attacker_identities.campaign_id, and publishes campaign.formed /
|
||||
# campaign.identity.assigned / campaign.merged / campaign.unmerged
|
||||
# plus the cross-family identity.campaign.assigned for identity-side
|
||||
# subscribers.
|
||||
#
|
||||
# Master-only: gated via MASTER_ONLY_COMMANDS in decnet/cli/gating.py.
|
||||
# Sits one layer above decnet-clusterer (the After=/Wants= ensures the
|
||||
# identity layer is up first; the campaign clusterer then wakes on
|
||||
# identity.> events fired by it).
|
||||
ExecStart={{ venv_dir }}/bin/decnet campaign-clusterer
|
||||
StandardOutput=append:/var/log/decnet/decnet.campaign-clusterer.log
|
||||
StandardError=append:/var/log/decnet/decnet.campaign-clusterer.log
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,47 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET Identity Clusterer (per-IP observations → identities)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#identity-clusterer
|
||||
After=network-online.target decnet-bus.service
|
||||
Wants=network-online.target decnet-bus.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.clusterer.log
|
||||
# Subscribes to attacker.observed and attacker.scored; falls back to a
|
||||
# 60s slow-tick poll when the bus is idle or unavailable. Reads
|
||||
# Attacker rows, projects per-IP observations into the similarity
|
||||
# graph (JA3 / HASSH / payload-hash / C2-endpoint), runs union-find,
|
||||
# writes attacker_identities rows + sets attackers.identity_id, and
|
||||
# publishes identity.formed / identity.observation.linked /
|
||||
# identity.merged / identity.unmerged.
|
||||
#
|
||||
# Master-only: gated via MASTER_ONLY_COMMANDS in decnet/cli/gating.py.
|
||||
ExecStart={{ venv_dir }}/bin/decnet clusterer
|
||||
StandardOutput=append:/var/log/decnet/decnet.clusterer.log
|
||||
StandardError=append:/var/log/decnet/decnet.clusterer.log
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,47 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET Threat-Intel Enrichment (GreyNoise + AbuseIPDB + abuse.ch)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#intel-enrichment
|
||||
After=network-online.target decnet-bus.service
|
||||
Wants=network-online.target decnet-bus.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.enrich.log
|
||||
# Subscribes to attacker.observed and attacker.scored; falls back to a 60s
|
||||
# slow-tick poll when the bus is idle or unavailable. Per attacker IP fans
|
||||
# out across the configured intel providers, writes the aggregate verdict
|
||||
# to attacker_intel, and publishes attacker.intel.enriched.
|
||||
#
|
||||
# Free-tier API keys are read from .env.local:
|
||||
# DECNET_GREYNOISE_API_KEY= (optional, lifts rate limit)
|
||||
# DECNET_ABUSEIPDB_API_KEY= (required for AbuseIPDB lookups)
|
||||
# DECNET_THREATFOX_API_KEY= (optional, lifts rate limit)
|
||||
ExecStart={{ venv_dir }}/bin/decnet enrich
|
||||
StandardOutput=append:/var/log/decnet/decnet.enrich.log
|
||||
StandardError=append:/var/log/decnet/decnet.enrich.log
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,41 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET Mutator (runtime fleet mutation watch loop)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#mutator
|
||||
After=network-online.target docker.service decnet-bus.service
|
||||
Wants=network-online.target decnet-bus.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
# Mutator recomposes decky services via docker compose.
|
||||
SupplementaryGroups=docker
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.mutator.log
|
||||
ExecStart={{ venv_dir }}/bin/decnet mutate --watch
|
||||
StandardOutput=append:/var/log/decnet/decnet.mutator.log
|
||||
StandardError=append:/var/log/decnet/decnet.mutator.log
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,50 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET Orchestrator (synthetic life-injection — inter-decky traffic, file plants, email drops)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#orchestrator
|
||||
After=network-online.target decnet-bus.service
|
||||
Wants=network-online.target decnet-bus.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.orchestrator.log
|
||||
# Realism content engine — LLM + persona-pool config used by the
|
||||
# email + (post-stage-6) file-class enrichment paths. See
|
||||
# decnet/realism/llm/factory.py and decnet/realism/personas_pool.py.
|
||||
Environment=DECNET_REALISM_LLM=ollama
|
||||
Environment=DECNET_REALISM_MODEL=llama3.1
|
||||
Environment=DECNET_REALISM_TIMEOUT=60
|
||||
Environment=DECNET_REALISM_PERSONAS=/etc/decnet/email_personas.json
|
||||
ExecStart={{ venv_dir }}/bin/decnet orchestrate
|
||||
StandardOutput=append:/var/log/decnet/decnet.orchestrator.log
|
||||
StandardError=append:/var/log/decnet/decnet.orchestrator.log
|
||||
|
||||
# The orchestrator drives `docker exec` against decky containers, so it
|
||||
# needs membership in the docker group. It does NOT bind to the network,
|
||||
# launch new containers, or write outside its own logs and install dir.
|
||||
SupplementaryGroups=docker
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,38 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET Profiler (attacker profiling and scoring)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#profiler
|
||||
After=network-online.target decnet-bus.service
|
||||
Wants=network-online.target decnet-bus.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.profiler.log
|
||||
ExecStart={{ venv_dir }}/bin/decnet profiler
|
||||
StandardOutput=append:/var/log/decnet/decnet.profiler.log
|
||||
StandardError=append:/var/log/decnet/decnet.profiler.log
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,47 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET Fleet Reconciler (converges decnet-state.json ↔ fleet_deckies DB ↔ docker)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#reconciler
|
||||
After=network-online.target decnet-bus.service
|
||||
Wants=network-online.target decnet-bus.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.reconciler.log
|
||||
ExecStart={{ venv_dir }}/bin/decnet reconcile
|
||||
StandardOutput=append:/var/log/decnet/decnet.reconciler.log
|
||||
StandardError=append:/var/log/decnet/decnet.reconciler.log
|
||||
|
||||
# The reconciler queries the docker daemon (via `docker.from_env()`) to
|
||||
# observe per-container state. Membership in the docker group lets it
|
||||
# read /var/run/docker.sock without root. It does NOT exec into
|
||||
# containers, bind to the network, or spawn new containers.
|
||||
SupplementaryGroups=docker
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
# Read-only access to /var/lib/decnet so we can read decnet-state.json.
|
||||
# Read-write access only to install_dir + log dir.
|
||||
ReadOnlyPaths=/var/lib/decnet
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,41 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET Credential-Reuse Correlator (cross-target secret-reuse detection)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#reuse-correlator
|
||||
After=network-online.target decnet-bus.service
|
||||
Wants=network-online.target decnet-bus.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.reuse-correlator.log
|
||||
# Subscribes to credential.captured and attacker.observed; falls back to
|
||||
# a 60s slow-tick poll when the bus is idle or unavailable. Publishes
|
||||
# credential.reuse.detected once per new/grown finding.
|
||||
ExecStart={{ venv_dir }}/bin/decnet reuse-correlate
|
||||
StandardOutput=append:/var/log/decnet/decnet.reuse-correlator.log
|
||||
StandardError=append:/var/log/decnet/decnet.reuse-correlator.log
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,57 +0,0 @@
|
||||
[Unit]
|
||||
Description=DECNET TTP Tagger (MITRE ATT&CK technique tagging)
|
||||
Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers#ttp-tagger
|
||||
After=network-online.target decnet-bus.service decnet-clusterer.service decnet-enrich.service decnet-reuse-correlator.service
|
||||
Wants=network-online.target decnet-bus.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
Group={{ group }}
|
||||
WorkingDirectory={{ install_dir }}
|
||||
EnvironmentFile=-{{ install_dir }}/.env.local
|
||||
Environment=DECNET_SYSTEM_LOGS=/var/log/decnet/decnet.ttp.log
|
||||
# Subscribes to attacker.session.ended (primary), attacker.observed,
|
||||
# attacker.intel.enriched, identity.formed, identity.merged,
|
||||
# credential.reuse.detected, email.received, and canary.> ; falls back
|
||||
# to a 60s slow-tick poll when the bus is idle or unavailable. Each
|
||||
# event is dispatched through the CompositeTagger (RuleEngine +
|
||||
# Behavioral / Intel / Email / CanaryFingerprint / Identity /
|
||||
# Credential lifters), persisted via the idempotent INSERT OR IGNORE
|
||||
# repo write, and ttp.tagged + ttp.rule.fired.<technique_id> are
|
||||
# published only when the insert returned a non-zero rowcount
|
||||
# (loop-prevention invariant — see TTP_TAGGING.md §"Bus topics").
|
||||
#
|
||||
# Master-only: gated via MASTER_ONLY_COMMANDS in decnet/cli/gating.py.
|
||||
# Sits one layer above the identity / intel / reuse-correlator
|
||||
# workers — the After= dependencies ensure their bus topics are live
|
||||
# before the TTP worker subscribes.
|
||||
ExecStart={{ venv_dir }}/bin/decnet ttp
|
||||
StandardOutput=append:/var/log/decnet/decnet.ttp.log
|
||||
StandardError=append:/var/log/decnet/decnet.ttp.log
|
||||
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
|
||||
# Security Hardening
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=full
|
||||
# Dev installs under /home need ProtectHome=read-only (the worker
|
||||
# reads ./rules/ttp/ from the project root, which lives under /home
|
||||
# on dev boxes — read-only suffices because the FilesystemRuleStore
|
||||
# only reads YAMLs, never writes).
|
||||
ProtectHome=read-only
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
LockPersonality=yes
|
||||
ReadWritePaths={{ install_dir }} /var/log/decnet
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=15
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -5,23 +5,21 @@ Documentation=https://git.resacachile.cl/anti/DECNET/wiki/Workers
|
||||
# heartbeats to it), then the API + data-plane workers. systemd resolves the
|
||||
# actual ordering via each unit's own After=/Wants= on decnet-bus.service —
|
||||
# this target is a convenience grouping, not an ordering primitive.
|
||||
# Consolidated since 1.1/1.2: the batch (reconcile/enrich/orchestrate/mutate)
|
||||
# and cpu (clusterer/campaign-clusterer/attribution/reuse-correlate) supervisor
|
||||
# groups and the heavy (profiler/ttp) prefork fleet replace their per-worker
|
||||
# units. The standalone workers below kept their own units.
|
||||
Wants=decnet-bus.service \
|
||||
decnet-api.service \
|
||||
decnet-web.service \
|
||||
decnet-collector.service \
|
||||
decnet-profiler.service \
|
||||
decnet-sniffer.service \
|
||||
decnet-prober.service \
|
||||
decnet-mutator.service \
|
||||
decnet-reconciler.service \
|
||||
decnet-reuse-correlator.service \
|
||||
decnet-enrich.service \
|
||||
decnet-clusterer.service \
|
||||
decnet-campaign-clusterer.service \
|
||||
decnet-ttp.service \
|
||||
decnet-webhook.service \
|
||||
decnet-canary.service \
|
||||
decnet-orchestrator.service
|
||||
decnet-supervise-batch.service \
|
||||
decnet-supervise-cpu.service \
|
||||
decnet-fleet-heavy.service
|
||||
After=decnet-bus.service
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Smoke tests for the orchestrator systemd unit + decnet.target wiring.
|
||||
|
||||
These don't exercise systemd (the test host wouldn't have it); they
|
||||
just assert the static contents of ``deploy/decnet-orchestrator.service.j2``
|
||||
and ``deploy/decnet.target`` match what ``decnet init`` will install.
|
||||
|
||||
Anti-regressions for two specific failure modes:
|
||||
|
||||
1. After the realism migration (stage 5), ``decnet-emailgen.service``
|
||||
is gone — the orchestrator covers the email branch. A regression
|
||||
that re-introduces the emailgen unit file or the ``decnet.target``
|
||||
entry would only surface on a fresh host install; cheap to catch
|
||||
here.
|
||||
2. The orchestrator unit must ship the ``DECNET_REALISM_*`` env block
|
||||
so the LLM enrichment + persona-pool path are configurable per
|
||||
host without editing the .j2.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent.parent
|
||||
DEPLOY = REPO / "deploy"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unit_text() -> str:
|
||||
return (DEPLOY / "decnet-orchestrator.service.j2").read_text()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def target_text() -> str:
|
||||
return (DEPLOY / "decnet.target").read_text()
|
||||
|
||||
|
||||
# ── orchestrator unit ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_orchestrator_unit_exists():
|
||||
assert (DEPLOY / "decnet-orchestrator.service.j2").exists()
|
||||
|
||||
|
||||
def test_orchestrator_unit_uses_orchestrate_subcommand(unit_text):
|
||||
assert "decnet orchestrate" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_has_docker_supplementary_group(unit_text):
|
||||
"""SSHDriver shells `docker exec` against decky containers — without
|
||||
this group the worker can't reach the docker socket."""
|
||||
assert "SupplementaryGroups=docker" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_orders_after_bus(unit_text):
|
||||
"""Bus must be up first so heartbeats publish from the start."""
|
||||
assert "After=network-online.target decnet-bus.service" in unit_text
|
||||
assert "Wants=network-online.target decnet-bus.service" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_has_security_hardening(unit_text):
|
||||
for directive in (
|
||||
"NoNewPrivileges=yes",
|
||||
"ProtectSystem=full",
|
||||
"ProtectHome=read-only",
|
||||
"PrivateTmp=yes",
|
||||
"ProtectKernelTunables=yes",
|
||||
"ProtectKernelModules=yes",
|
||||
"ProtectControlGroups=yes",
|
||||
"RestrictSUIDSGID=yes",
|
||||
"LockPersonality=yes",
|
||||
):
|
||||
assert directive in unit_text, f"missing {directive}"
|
||||
|
||||
|
||||
def test_orchestrator_unit_writes_to_log_dir(unit_text):
|
||||
assert "/var/log/decnet/decnet.orchestrator.log" in unit_text
|
||||
assert "ReadWritePaths={{ install_dir }} /var/log/decnet" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_restart_on_failure(unit_text):
|
||||
assert "Restart=on-failure" in unit_text
|
||||
|
||||
|
||||
def test_orchestrator_unit_carries_realism_env_block(unit_text):
|
||||
"""Stage 5 + 6 contract: the orchestrator's LLM enrichment and
|
||||
persona-pool path are configured per host via DECNET_REALISM_*
|
||||
env vars. Shipping them in the .j2 means an operator who never
|
||||
drops a .env.local still gets sane defaults."""
|
||||
for var in (
|
||||
"DECNET_REALISM_LLM",
|
||||
"DECNET_REALISM_MODEL",
|
||||
"DECNET_REALISM_TIMEOUT",
|
||||
"DECNET_REALISM_PERSONAS",
|
||||
):
|
||||
assert var in unit_text, f"missing {var} in unit"
|
||||
|
||||
|
||||
def test_orchestrator_unit_does_not_carry_legacy_emailgen_envs(unit_text):
|
||||
"""Pre-v1 clean break per the realism migration: the
|
||||
DECNET_EMAILGEN_* env vars are no longer read. Carrying them in
|
||||
the unit would mislead operators into thinking they still apply."""
|
||||
for legacy in (
|
||||
"DECNET_EMAILGEN_LLM",
|
||||
"DECNET_EMAILGEN_MODEL",
|
||||
"DECNET_EMAILGEN_TIMEOUT",
|
||||
"DECNET_EMAILGEN_PERSONAS",
|
||||
):
|
||||
assert legacy not in unit_text, (
|
||||
f"legacy env {legacy} still referenced; clean-break broken"
|
||||
)
|
||||
|
||||
|
||||
# ── decnet.target ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_target_wants_orchestrator(target_text):
|
||||
assert "decnet-orchestrator.service" in target_text
|
||||
|
||||
|
||||
def test_target_does_not_want_emailgen(target_text):
|
||||
"""Stage 5 of the realism migration deleted decnet-emailgen.service.
|
||||
A fresh `decnet init` against a target file that still mentions it
|
||||
fails systemctl start with `Unit decnet-emailgen.service could not
|
||||
be found`, blocking the whole target. Anti-regression."""
|
||||
assert "decnet-emailgen.service" not in target_text
|
||||
|
||||
|
||||
def test_target_wants_canary(target_text):
|
||||
"""Canary worker is a peer of orchestrator; both are part of the
|
||||
realism + callback story. Bundle check."""
|
||||
assert "decnet-canary.service" in target_text
|
||||
|
||||
|
||||
def test_target_orders_after_bus(target_text):
|
||||
"""Whole target depends on the bus being up."""
|
||||
assert "After=decnet-bus.service" in target_text
|
||||
|
||||
|
||||
# ── unit file no longer exists ───────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_emailgen_unit_template_is_gone():
|
||||
"""The pre-collapse ``deploy/decnet-emailgen.service.j2`` must stay
|
||||
deleted. A future commit that re-creates it (e.g. by accident
|
||||
during a partial revert) would break the realism migration's
|
||||
service-collapse contract."""
|
||||
assert not (DEPLOY / "decnet-emailgen.service.j2").exists(), (
|
||||
"decnet-emailgen.service.j2 reappeared — service collapse undone?"
|
||||
)
|
||||
Reference in New Issue
Block a user