# Deploy Two roles. One install command each. ## Roles | Role | Where it runs | What it does | |---|---|---| | `lab-host` | any KVM-capable Linux box on WG | runs episodes, ships completed episodes to the receiver | | `receiver` | Pi5 (or any always-on WG node) | accepts ship uploads, stores tarballs + `index.jsonl` | ## Lab host install ```sh git clone https://maxgit.wg/spectral/CIS490.git cd CIS490 ./scripts/install-lab-host.sh ``` The installer: 1. Verifies KVM (`/dev/kvm` exists, user in `kvm` group). 2. Installs system deps via the host package manager (qemu, tcpdump, linux-tools/perf, zstd, python ≥ 3.11). 3. Bootstraps a [`uv`](https://github.com/astral-sh/uv)-managed venv at `.venv/` and installs the pinned Python deps from `uv.lock`. 4. Drops two systemd units into `/etc/systemd/system/`: - `cis490-orchestrator.service` — runs the episode loop on a queue - `cis490-shipper.service` — watches `data/episodes/` and ships completed episodes 5. Writes a config template to `/etc/cis490/lab-host.toml` (idempotent — only on first install). You finish by editing `/etc/cis490/lab-host.toml` to point at your receiver and to enroll your lab host's WG-issued client cert, then: ```sh sudo systemctl enable --now cis490-orchestrator cis490-shipper ``` ### `lab-host.toml` ```toml host_id = "lab-host-1" [paths] data_root = "/var/lib/cis490/data" samples_store = "/var/lib/cis490/samples/store" qcow_image = "/var/lib/cis490/vm/images/metasploitable2.qcow2" [receiver] url = "https://collector.wg" client_cert = "/etc/cis490/certs/lab-host-1.pem" client_key = "/etc/cis490/certs/lab-host-1.key" ca_bundle = "/etc/cis490/certs/wg-ca.pem" [episode] baseline_seconds = 30 infected_seconds = 90 dormant_seconds = 60 [retention] keep_local_for_days = 7 prune_at_disk_pct = 80 ``` ## Receiver install On the Pi5 (or designated central node): ```sh git clone https://maxgit.wg/spectral/CIS490.git cd CIS490 ./scripts/install-receiver.sh ``` The installer: 1. Installs Python ≥ 3.11 + zstd + a tiny WSGI runner (uvicorn). 2. Bootstraps the same `uv`-managed venv. 3. Drops `cis490-receiver.service` listening on `127.0.0.1:8443` (TLS terminated by the existing Caddy in `spectral/caddy`, which already binds `*.wg`). 4. Writes a config template to `/etc/cis490/receiver.toml`. Caddy block (added to your `spectral/caddy` config) for the receiver: ```caddy collector.wg { tls internal reverse_proxy 127.0.0.1:8443 { transport http { tls tls_client_auth /etc/cis490/certs/wg-ca.pem } } } ``` (mTLS terminates at the receiver, not Caddy — so the receiver sees the client cert and can enforce per-host policies later.) ### `receiver.toml` ```toml listen_addr = "127.0.0.1:8443" store_root = "/var/lib/cis490/episodes" incoming_root = "/var/lib/cis490/incoming" index_path = "/var/lib/cis490/index.jsonl" ca_bundle = "/etc/cis490/certs/wg-ca.pem" [limits] max_episode_bytes = 268_435_456 # 256 MiB ``` ## Day-2 operations ```sh # How many episodes have been shipped? ssh collector.wg 'wc -l /var/lib/cis490/index.jsonl' # What's in the outbox on a lab host? (failed/pending shipments) ls /var/lib/cis490/data/outbox/ # Tail the orchestrator log journalctl -u cis490-orchestrator -f # Tail the shipper log journalctl -u cis490-shipper -f ``` ## Updating ```sh git pull ./scripts/install-lab-host.sh # idempotent; re-syncs deps and units sudo systemctl restart cis490-orchestrator cis490-shipper ```