Implements the unattended episode loop described in docs/deploy.md but not yet built. run_campaign.py boots a fresh VM per episode, drives the full phase schedule via the existing EpisodeRunner/VMLoadController stack, writes campaign.json atomically after each episode, and signals completion with campaign_done.marker. shipper.py watches data/episodes/ for done.marker files, tar+zstd-compresses each, and PUTs them to the receiver with exponential backoff on failure. Both support SIGTERM gracefully, finishing the current episode/scan before exiting. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
33 lines
742 B
Desktop File
33 lines
742 B
Desktop File
[Unit]
|
|
Description=CIS490 episode campaign runner
|
|
Documentation=https://maxgit.wg/spectral/CIS490
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=cis490
|
|
Group=cis490
|
|
SupplementaryGroups=kvm
|
|
WorkingDirectory=/opt/cis490
|
|
ExecStart=/opt/cis490/.venv/bin/python tools/run_campaign.py \
|
|
--data-root /var/lib/cis490/data \
|
|
--target 100
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
|
|
# Hardening
|
|
NoNewPrivileges=true
|
|
PrivateTmp=false
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
ReadWritePaths=/var/lib/cis490 /tmp/cis490-vm /dev/kvm
|
|
ProtectKernelTunables=true
|
|
ProtectKernelModules=true
|
|
ProtectControlGroups=true
|
|
LockPersonality=true
|
|
RestrictRealtime=true
|
|
SystemCallArchitectures=native
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|