chore: bootstrap lean sysadmin-chronicles repo

Import the runnable game code, content, docs, scripts, and repo guidance while leaving local agent state, dependency installs, build output, and backup copies out of the published tree.
This commit is contained in:
2026-05-02 11:49:07 -04:00
commit 0265afa054
252 changed files with 37574 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Q001-prep.sh — Workstation baseline: SSH key missing
#
# Prepares the workstation VM for Q001 "Welcome Aboard".
# The player's SSH key was never added during provisioning.
#
# What this does:
# - Ensures the player account exists
# - Removes /home/player/.ssh/authorized_keys (key not provisioned)
# - Leaves /var/log/auth.log with a "Permission denied (publickey)" entry
#
# Idempotent: safe to run multiple times.
# AGENT RULES: Never run against a live player session.
set -euo pipefail
export LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-qemu:///system}"
DOMAIN="${1:-sc-workstation}"
DRY_RUN=false
[[ "${2:-}" == "--dry-run" ]] && DRY_RUN=true
SC_SSH_KEY="${SC_SSH_KEY:-${HOME}/.ssh/sc_host_key}"
SSH_USER="${SSH_USER:-opsbridge}"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -o ConnectTimeout=10 -o LogLevel=ERROR -i $SC_SSH_KEY"
get_vm_ip() {
local domain="$1"
local addr=""
addr="$(virsh domifaddr "$domain" --source agent 2>/dev/null | awk '/ipv4/ {print $4}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
if [ -n "$addr" ]; then
printf '%s\n' "$addr"
return 0
fi
local mac=""
mac="$(virsh dumpxml "$domain" 2>/dev/null | sed -n "s/.*<mac address='\\([^']*\\)'.*/\\1/p" | head -n1)"
[ -n "$mac" ] || return 1
addr="$(virsh net-dhcp-leases sc-internal 2>/dev/null | awk -v mac="$mac" '$0 ~ mac {print $5}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
[ -n "$addr" ] || return 1
printf '%s\n' "$addr"
}
VM_IP="$(get_vm_ip "$DOMAIN")"
SSH="ssh $SSH_OPTS $SSH_USER@$VM_IP"
run_in_vm() {
if [ "$DRY_RUN" = "true" ]; then
echo " [DRY-RUN in $DOMAIN] $*"
else
$SSH "sudo $*"
fi
}
echo "Q001-prep: Preparing $DOMAIN for 'Welcome Aboard'..."
run_in_vm "bash -lc 'mkdir -p /home/player/.ssh; touch /var/log/auth.log; ts=\$(date +\"%b %d %H:%M:%S\"); echo \"\$ts ares sshd[1234]: Failed publickey for player from 10.42.0.1 port 22 ssh2\" >> /var/log/auth.log; rm -f /home/player/.ssh/authorized_keys; echo Q001-prep: authorized_keys removed'"
echo "Q001-prep: Done."
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Q002-prep.sh — hermes baseline: nginx config syntax error
#
# Prepares sc-web-server for Q002 "Syntax Error in Aisle Four".
# Introduces a deliberate nginx config syntax error that breaks the service.
#
# What this does:
# - Installs nginx if not present
# - Writes a broken /etc/nginx/sites-enabled/axiomworks.conf
# (missing semicolon on the server_name line)
# - Stops nginx so the player finds it down
# - Adds error log evidence
#
# Idempotent: safe to run multiple times.
set -euo pipefail
export LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-qemu:///system}"
DOMAIN="${1:-sc-web-server}"
DRY_RUN=false
[[ "${2:-}" == "--dry-run" ]] && DRY_RUN=true
get_vm_ip() {
local domain="$1"
local addr=""
addr="$(virsh domifaddr "$domain" --source agent 2>/dev/null | awk '/ipv4/ {print $4}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
if [ -n "$addr" ]; then
printf '%s\n' "$addr"
return 0
fi
local mac=""
mac="$(virsh dumpxml "$domain" 2>/dev/null | sed -n "s/.*<mac address='\\([^']*\\)'.*/\\1/p" | head -n1)"
[ -n "$mac" ] || return 1
addr="$(virsh net-dhcp-leases sc-internal 2>/dev/null | awk -v mac="$mac" '$0 ~ mac {print $5}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
[ -n "$addr" ] || return 1
printf '%s\n' "$addr"
}
SC_SSH_KEY="${SC_SSH_KEY:-${HOME}/.ssh/sc_host_key}"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -o ConnectTimeout=10 -o LogLevel=ERROR -i $SC_SSH_KEY"
VM_IP=$(get_vm_ip "$DOMAIN")
SSH="ssh $SSH_OPTS player@$VM_IP"
run_in_vm() {
if [ "$DRY_RUN" = "true" ]; then
echo " [DRY-RUN in $DOMAIN] $*"
else
printf '%s\n' "$*" | $SSH "sudo bash -se"
fi
}
echo "Q002-prep: Preparing $DOMAIN for 'Syntax Error in Aisle Four'..."
run_in_vm "mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available"
# Write broken nginx config (missing semicolon after server_name)
run_in_vm "cat > /etc/nginx/sites-enabled/axiomworks.conf <<'NGINX_CONF'
server {
listen 80;
server_name axiomworks.internal # <-- MISSING SEMICOLON: this is the bug
root /var/www/axiomworks;
index index.html;
location / {
try_files \$uri \$uri/ =404;
}
}
NGINX_CONF"
# Disable the default site to make this the only relevant config
run_in_vm "rm -f /etc/nginx/sites-enabled/default"
# Stop nginx (it fails to start with bad config)
run_in_vm "systemctl stop nginx || true"
# Populate nginx error log with the kind of evidence a player would find
run_in_vm "mkdir -p /var/log/nginx && echo '[emerg] unexpected \";\" in /etc/nginx/sites-enabled/axiomworks.conf:3' >> /var/log/nginx/error.log"
# Create the web root (nginx would serve from here if config were valid)
run_in_vm "mkdir -p /var/www/axiomworks && echo '<h1>Axiom Works</h1>' > /var/www/axiomworks/index.html"
echo "Q002-prep: Done. nginx is stopped with broken config on $DOMAIN."
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Q003-prep.sh — hermes baseline: logrotate missing, nginx access log ballooning
#
# Prepares sc-web-server for Q003 "The Log That Ate the Disk".
# Assumes Q002 is already resolved (nginx is running, config is clean).
#
# What this does:
# - Removes /etc/logrotate.d/nginx (log rotation not configured)
# - Grows /var/log/nginx/access.log to ~80% disk pressure
# - Disk usage should read >85% on /var so player sees the pressure
#
# Idempotent: safe to run multiple times.
set -euo pipefail
export LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-qemu:///system}"
DOMAIN="${1:-sc-web-server}"
DRY_RUN=false
[[ "${2:-}" == "--dry-run" ]] && DRY_RUN=true
get_vm_ip() {
local domain="$1"
local addr=""
addr="$(virsh domifaddr "$domain" --source agent 2>/dev/null | awk '/ipv4/ {print $4}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
if [ -n "$addr" ]; then
printf '%s\n' "$addr"
return 0
fi
local mac=""
mac="$(virsh dumpxml "$domain" 2>/dev/null | sed -n "s/.*<mac address='\\([^']*\\)'.*/\\1/p" | head -n1)"
[ -n "$mac" ] || return 1
addr="$(virsh net-dhcp-leases sc-internal 2>/dev/null | awk -v mac="$mac" '$0 ~ mac {print $5}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
[ -n "$addr" ] || return 1
printf '%s\n' "$addr"
}
SC_SSH_KEY="${SC_SSH_KEY:-${HOME}/.ssh/sc_host_key}"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -o ConnectTimeout=10 -o LogLevel=ERROR -i $SC_SSH_KEY"
VM_IP=$(get_vm_ip "$DOMAIN")
SSH="ssh $SSH_OPTS player@$VM_IP"
run_in_vm() {
if [ "$DRY_RUN" = "true" ]; then
echo " [DRY-RUN in $DOMAIN] $*"
else
printf '%s\n' "$*" | $SSH "sudo bash -se"
fi
}
echo "Q003-prep: Preparing $DOMAIN for 'The Log That Ate the Disk'..."
# Remove logrotate config for nginx
run_in_vm "rm -f /etc/logrotate.d/nginx"
# Generate a large access log (~500MB of fake log entries, enough to fill a 6GB VM)
# Use truncate for speed rather than generating real content
run_in_vm "mkdir -p /var/log/nginx"
run_in_vm "truncate -s 500M /var/log/nginx/access.log"
# Write real-looking last few lines so tail shows something plausible
run_in_vm "echo '10.42.0.1 - - [\$(date +\"%d/%b/%Y:%H:%M:%S +0000\")] \"GET / HTTP/1.1\" 200 612 \"-\" \"Mozilla/5.0\"' >> /var/log/nginx/access.log"
echo "Q003-prep: Done. /var/log/nginx/access.log inflated on $DOMAIN."
echo " Check disk pressure with: df -h (on the VM)"
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Q004-prep.sh — hermes baseline: web root owned by root, deploy script in place
#
# Prepares sc-web-server for Q004 "Not My Files".
# A bad deploy re-ran as root and chowned the web root to root.
# The deploy script itself is in /opt/deploy/deploy.sh.
#
# What this does:
# - Chowns /var/www/axiomworks and all contents to root:root
# - Places a deploy script at /opt/deploy/deploy.sh (chowned player:player)
# - Ensures nginx is running (deploy will fail but nginx serves stale content)
#
# Idempotent: safe to run multiple times.
set -euo pipefail
export LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-qemu:///system}"
DOMAIN="${1:-sc-web-server}"
DRY_RUN=false
[[ "${2:-}" == "--dry-run" ]] && DRY_RUN=true
get_vm_ip() {
local domain="$1"
local addr=""
addr="$(virsh domifaddr "$domain" --source agent 2>/dev/null | awk '/ipv4/ {print $4}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
if [ -n "$addr" ]; then
printf '%s\n' "$addr"
return 0
fi
local mac=""
mac="$(virsh dumpxml "$domain" 2>/dev/null | sed -n "s/.*<mac address='\\([^']*\\)'.*/\\1/p" | head -n1)"
[ -n "$mac" ] || return 1
addr="$(virsh net-dhcp-leases sc-internal 2>/dev/null | awk -v mac="$mac" '$0 ~ mac {print $5}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
[ -n "$addr" ] || return 1
printf '%s\n' "$addr"
}
SC_SSH_KEY="${SC_SSH_KEY:-${HOME}/.ssh/sc_host_key}"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -o ConnectTimeout=10 -o LogLevel=ERROR -i $SC_SSH_KEY"
VM_IP=$(get_vm_ip "$DOMAIN")
SSH="ssh $SSH_OPTS player@$VM_IP"
run_in_vm() {
if [ "$DRY_RUN" = "true" ]; then
echo " [DRY-RUN in $DOMAIN] $*"
else
printf '%s\n' "$*" | $SSH "sudo bash -se"
fi
}
echo "Q004-prep: Preparing $DOMAIN for 'Not My Files'..."
# Ensure web root exists and is owned by root (the bug)
run_in_vm "mkdir -p /var/www/axiomworks && chown -R root:root /var/www/axiomworks"
# Create the deploy script as player:player (this is correct — player runs it)
run_in_vm "mkdir -p /opt/deploy"
run_in_vm "cat > /opt/deploy/deploy.sh <<'DEPLOY_SCRIPT'
#!/usr/bin/env bash
# deploy.sh — Axiom Works web deploy
# Copies build artifacts to /var/www/axiomworks/
set -e
SRC=\"\${1:-/home/player/build/dist}\"
rsync -av \"\$SRC/\" /var/www/axiomworks/
echo 'Deploy complete.'
DEPLOY_SCRIPT"
run_in_vm "chown player:player /opt/deploy/deploy.sh && chmod 755 /opt/deploy/deploy.sh"
# Ensure nginx is running (serves stale content with root-owned files)
run_in_vm "systemctl start nginx || true"
echo "Q004-prep: Done. /var/www/axiomworks is owned by root on $DOMAIN."
echo " Player must: sudo chown -R player:player /var/www/axiomworks"
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Q006-post-clean.sh — vulcan clean branch state after Q006
#
# Applies the authored clean outcome of Q006 so seed-vms.sh can materialize
# baseline.post-q006 for later quests.
#
# What this does:
# - Enables and starts systemd-timesyncd
# - Verifies archlinux-keyring is installed
# - Replaces pacman.log failure evidence with a healthy update trail
#
# Idempotent: safe to run multiple times.
set -euo pipefail
export LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-qemu:///system}"
DOMAIN="${1:-sc-build-machine}"
DRY_RUN=false
[[ "${2:-}" == "--dry-run" ]] && DRY_RUN=true
get_vm_ip() {
local domain="$1"
local addr=""
addr="$(virsh domifaddr "$domain" --source agent 2>/dev/null | awk '/ipv4/ {print $4}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
if [ -n "$addr" ]; then
printf '%s\n' "$addr"
return 0
fi
local mac=""
mac="$(virsh dumpxml "$domain" 2>/dev/null | sed -n "s/.*<mac address='\\([^']*\\)'.*/\\1/p" | head -n1)"
[ -n "$mac" ] || return 1
addr="$(virsh net-dhcp-leases sc-internal 2>/dev/null | awk -v mac="$mac" '$0 ~ mac {print $5}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
[ -n "$addr" ] || return 1
printf '%s\n' "$addr"
}
SC_SSH_KEY="${SC_SSH_KEY:-${HOME}/.ssh/sc_host_key}"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -o ConnectTimeout=10 -o LogLevel=ERROR -i $SC_SSH_KEY"
VM_IP="$(get_vm_ip "$DOMAIN")"
SSH="ssh $SSH_OPTS player@$VM_IP"
run_in_vm() {
if [ "$DRY_RUN" = "true" ]; then
echo " [DRY-RUN in $DOMAIN] $*"
else
printf '%s\n' "$*" | $SSH "sudo bash -se"
fi
}
echo "Q006-post-clean: Applying clean Q006 outcome on $DOMAIN..."
run_in_vm "pacman -Q archlinux-keyring >/dev/null"
run_in_vm "timedatectl set-ntp true || true"
run_in_vm "systemctl enable --now systemd-timesyncd"
run_in_vm "cat > /var/log/pacman.log <<'PACMAN_LOG'
[2026-04-23T09:02:14-0400] [PACMAN] synchronizing package lists
[2026-04-23T09:02:19-0400] [ALPM] transaction started
[2026-04-23T09:02:19-0400] [ALPM] upgraded archlinux-keyring (20260401-1 -> 20260420-1)
[2026-04-23T09:02:20-0400] [ALPM] transaction completed
PACMAN_LOG"
run_in_vm "cat > /var/log/axiomworks/time-drift.note <<'NOTE'
Time sync restored.
systemd-timesyncd is enabled and active.
archlinux-keyring is present and package operations are healthy.
NOTE"
echo "Q006-post-clean: Done. systemd-timesyncd is active and baseline.post-q006 is ready on $DOMAIN."
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Q006-prep.sh — vulcan baseline: time sync disabled, pacman signature errors logged
#
# Prepares sc-build-machine for Q006 "Time Is A Flat Circle".
# The machine clock is drifting because time sync was disabled, which surfaces
# as pacman signature verification failures.
#
# What this does:
# - Disables and stops common NTP services
# - Seeds pacman.log with realistic signature failure evidence
# - Leaves a small operator note pointing at time drift symptoms
#
# Idempotent: safe to run multiple times.
set -euo pipefail
export LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-qemu:///system}"
DOMAIN="${1:-sc-build-machine}"
DRY_RUN=false
[[ "${2:-}" == "--dry-run" ]] && DRY_RUN=true
get_vm_ip() {
local domain="$1"
local addr=""
addr="$(virsh domifaddr "$domain" --source agent 2>/dev/null | awk '/ipv4/ {print $4}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
if [ -n "$addr" ]; then
printf '%s\n' "$addr"
return 0
fi
local mac=""
mac="$(virsh dumpxml "$domain" 2>/dev/null | sed -n "s/.*<mac address='\\([^']*\\)'.*/\\1/p" | head -n1)"
[ -n "$mac" ] || return 1
addr="$(virsh net-dhcp-leases sc-internal 2>/dev/null | awk -v mac="$mac" '$0 ~ mac {print $5}' | cut -d/ -f1 | grep -v '^127\.' | head -n1 || true)"
[ -n "$addr" ] || return 1
printf '%s\n' "$addr"
}
SC_SSH_KEY="${SC_SSH_KEY:-${HOME}/.ssh/sc_host_key}"
SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -o ConnectTimeout=10 -o LogLevel=ERROR -i $SC_SSH_KEY"
VM_IP="$(get_vm_ip "$DOMAIN")"
SSH="ssh $SSH_OPTS player@$VM_IP"
run_in_vm() {
if [ "$DRY_RUN" = "true" ]; then
echo " [DRY-RUN in $DOMAIN] $*"
else
printf '%s\n' "$*" | $SSH "sudo bash -se"
fi
}
echo "Q006-prep: Preparing $DOMAIN for 'Time Is A Flat Circle'..."
run_in_vm "timedatectl set-ntp false || true"
run_in_vm "systemctl stop systemd-timesyncd ntpd chronyd 2>/dev/null || true"
run_in_vm "systemctl disable systemd-timesyncd ntpd chronyd 2>/dev/null || true"
run_in_vm "mkdir -p /var/log/axiomworks /srv/repo /srv/builds"
run_in_vm "cat > /var/log/pacman.log <<'PACMAN_LOG'
[2026-04-23T08:10:51-0400] [PACMAN] synchronizing package lists
[2026-04-23T08:10:57-0400] [ALPM] transaction started
[2026-04-23T08:10:58-0400] [ALPM] warning: Public keyring not found; have you run 'pacman-key --init'?
[2026-04-23T08:10:58-0400] [ALPM] error: archlinux-keyring: signature from \"Arch Linux Master Key\" is invalid
[2026-04-23T08:10:58-0400] [ALPM] error: failed to commit transaction (invalid or corrupted package (PGP signature))
[2026-04-23T08:10:58-0400] [ALPM] transaction failed
PACMAN_LOG"
run_in_vm "cat > /var/log/axiomworks/time-drift.note <<'NOTE'
Builds started failing after the machine clock fell behind.
Symptoms:
- pacman reports invalid or corrupted package (PGP signature)
- signed packages appear to come from the future
- timedatectl shows NTP inactive
NOTE"
echo "Q006-prep: Done. NTP is disabled and pacman signature failures are seeded on $DOMAIN."