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
+641
View File
@@ -0,0 +1,641 @@
# Installer & Distribution Plan
> Status: Planning — not yet implemented.
> Covers: installer, uninstaller, VM rebuild, save management, modular script architecture.
---
## Goals
- Download zip from GitHub/Gitea, run `install.sh`, done.
- Friendly tone throughout — this is a game, not a server deployment.
- No jargon (libvirt, pool, domain, NAT) in any user-facing output.
- Power users can follow the Manual Install section in README instead.
- VM images live wherever the user puts the game (portable, large-drive friendly).
- Full uninstall with explicit choices about what gets removed.
- Users can rebuild individual VMs if something goes wrong.
- Save data is resettable; save slots available for experimenting.
---
## `start-game.sh` Fixes
The current launcher works but has two real bugs, several fragile assumptions, and
no user-friendly output. Fix this in the same pass as the rest of the scripts since
it will share `lib/ui.sh` and `lib/config.sh`.
### Bugs to fix
**Orphaned server process**
The script ends with `exec remote-viewer`, which replaces the shell. The `trap`
that was set to kill the server on EXIT disappears with the shell — so when the
player closes the SPICE window, the game server keeps running silently.
Fix: don't `exec`. Run `remote-viewer` normally, capture its PID, wait for it to
exit, then kill the server cleanly.
```bash
# instead of:
exec remote-viewer "$spice_uri"
# do:
remote-viewer "$spice_uri" &
VIEWER_PID=$!
trap 'kill "$SERVER_PID" "$VIEWER_PID" 2>/dev/null || true' EXIT INT TERM
wait "$VIEWER_PID"
```
**`sleep 1` server readiness check**
One second is a race. On a slow machine or if npm install just ran, the server
may not be up. On a fast machine it's wasted time.
Fix: poll in a tight loop with a timeout.
```bash
wait_for_server() {
local port="$1" timeout=15 i=0
while ! ss -tlnp | grep -q ":${port} " 2>/dev/null; do
sleep 0.3
((i++))
[ $i -ge $((timeout * 3)) ] && return 1
done
}
```
### Fragile assumptions to fix
- **`lsof` for port check** — not universal. Replace with `ss -tlnp` (iproute2,
present on all modern Linux).
- **No network check** — if the `sc-internal` libvirt network is inactive, the VM
starts but has no network. The HUD loads but shows nothing. Check the network is
active (and start it if not) before starting the VM.
- **No images-dir check** — once portable installs land, `SC_IMAGES_DIR` might be
on an unmounted game drive. Check it exists before trying virsh ops.
- **Frontend build at launch** — `"Building frontend..."` at game launch is odd UX.
Move this guard to install time. The launcher should only verify `dist/index.html`
exists and fail clearly if it doesn't (don't silently trigger a build).
### UX improvements
- Source `lib/ui.sh` and `lib/config.sh` once they exist.
- Replace raw `echo "ERROR: ..."` with friendly messages. Examples:
| Current | Replacement |
|---|---|
| `ERROR: virsh is required.` | `Your system is missing the virtual machine tools.\nRun install.sh to set up the game.` |
| `ERROR: missing workstation domain: sc-workstation` | `Your game world hasn't been built yet.\nRun install.sh to finish setup.` |
| `ERROR: node is required. Install Node.js 18+.` | `Node.js is required but wasn't found.\nRun install.sh to set up the game.` |
- Show brief startup status so the player isn't staring at a blank terminal:
```
Starting Sysadmin Chronicles...
✓ Game server running
✓ Workstation online
Opening your desk...
```
- Add `--manage-saves` and `--reset-save` flags (forward to `tools/save/manage-saves.sh`).
### New flag: `--stop`
Since the server now outlives the viewer when fixed, add `start-game.sh --stop`
that kills any running game server process. Useful if something gets stuck.
### Summary of changes to `start-game.sh`
| Area | Change |
|---|---|
| Server shutdown | `exec` → normal run + `wait`, trap covers both server and viewer |
| Server readiness | `sleep 1` → poll loop with 15s timeout |
| Port check | `lsof``ss -tlnp` |
| Network check | Add: verify `sc-internal` active, start if not |
| Images dir check | Add: verify `SC_IMAGES_DIR` exists before virsh ops |
| Frontend build | Remove from launcher; fail clearly if dist missing |
| Error messages | Replace all with plain-English + fix instructions |
| Startup output | Add three-line status before opening SPICE |
| New flags | `--manage-saves`, `--reset-save`, `--stop` |
---
## Script Architecture
All user-facing scripts share a common library layer. No logic is duplicated.
```
tools/
lib/
ui.sh # colored output, prompts, spinners, progress bars
deps.sh # distro detection, package name map, dep check/install
libvirt.sh # virsh wrappers: network, pool, domain, snapshot ops
vm.sh # build, rebuild, snapshot, revert per VM
config.sh # read/write install config (~/.config/sysadmin-chronicles/config)
save.sh # save slot management, reset helpers
install.sh # project root — the entry point for new users
uninstall.sh # project root — removal with options
start-game.sh # project root — launcher (checks env, starts server, opens SPICE)
tools/
setup/
check-host.sh # kept, improved UX, used internally by install.sh
first-run-setup.sh # kept as internal lib target or merged into install.sh
seed-vms.sh # kept as internal lib target, called by install.sh and rebuild
vm/
rebuild-vms.sh # new: rebuild all or specific VMs
save/
manage-saves.sh # new: list/switch/reset save slots
```
### `lib/ui.sh`
- `sc_step "label"` — numbered step header
- `sc_ok "msg"`, `sc_warn "msg"`, `sc_fail "msg"` — status lines
- `sc_prompt "question" "default"` — interactive prompt, returns answer
- `sc_confirm "question"` — yes/no, returns 0/1
- `sc_spinner "label"` / `sc_spinner_stop` — background spinner for long ops
- `sc_progress "label" current total` — simple fraction display
### `lib/deps.sh`
- `detect_distro` — sets `$SC_DISTRO` (arch, debian, ubuntu, fedora, opensuse)
- `map_packages` — translates canonical dep names to distro package names
- `check_deps` — returns list of missing deps
- `install_deps "pkg1 pkg2 ..."` — runs the right package manager with sudo, logs what was installed
### `lib/libvirt.sh`
- `ensure_network name xml_path`
- `ensure_pool name path`
- `pool_path name` — returns the pool's target directory
- `domain_exists name`, `domain_state name`
- `snapshot_exists domain name`
- `snapshot_create domain name description`
- `snapshot_revert domain name`
- `snapshot_delete domain name`
### `lib/vm.sh`
- `vm_build profile [--dry-run] [--force]` — wraps `build-vm.sh`
- `vm_rebuild profile [--dry-run]` — destroy + rebuild from cloud image
- `vm_revert vm_id snapshot_name` — revert to named snapshot
- `vm_status vm_id` — running / stopped / missing
- `vm_start vm_id`, `vm_stop vm_id`
### `lib/config.sh`
Config file lives at `~/.config/sysadmin-chronicles/config` (survives game dir moves).
Variables stored:
```bash
SC_GAME_DIR=/home/user/Games/sysadmin-chronicles
SC_IMAGES_DIR=/home/user/Games/sysadmin-chronicles/images
SC_LIBVIRT_URI=qemu:///system
SC_INSTALL_DATE=2026-04-27
SC_INSTALLED_DEPS="libvirt qemu-system-x86 ..." # what we added, for the log
```
- `config_read` — sources the config file
- `config_write key value`
- `config_show` — pretty-prints current config
### `lib/save.sh`
- `save_list` — lists all save slots with name, date, trust score, quest progress
- `save_switch slot_name` — switch active save
- `save_new slot_name` — create a new empty save slot
- `save_reset [slot_name]` — wipe a slot back to new-game state
- `save_export slot_name path` — export save JSON for backup
- `save_import path slot_name` — import a save JSON
---
## Installer Design (`install.sh`)
### Phase 1 — Welcome
```
╔══════════════════════════════════════════╗
║ SYSADMIN CHRONICLES — SETUP ║
╚══════════════════════════════════════════╝
Welcome! This installer will:
• Install a few system tools (KVM, QEMU, libvirt)
• Set up a private virtual network for the game
• Build three virtual machines (~30 minutes, once only)
Where would you like to install the game?
[default: ~/Games/sysadmin-chronicles] >
```
### Phase 2 — System check (silent)
Internally calls `check_deps`. If all present, skip to Phase 4 silently.
### Phase 3 — Dependency install (only if needed)
```
Your system is missing the following tools:
• KVM virtualization support (qemu-system-x86)
• Virtual machine manager (libvirt, virt-install)
• SPICE display viewer (virt-viewer)
• Cloud image tools (cloud-image-utils, genisoimage)
Install them now? You'll be asked for your password. [Y/n]
```
After install:
- Log installed packages to `~/.local/share/sysadmin-chronicles/install.log`
- Format: timestamp, package name, version, distro. Human-readable.
- Note at end: "This log is kept so you know exactly what was added. See it at: ..."
### Phase 4 — One-time network and storage setup
```
── Setting up game network ──────────────────
✓ Private game network created
✓ VM image storage configured at ~/Games/sysadmin-chronicles/images
✓ Game access keys generated
```
User never sees "libvirt", "storage pool", "sc-internal", "sc-images".
### Phase 5 — VM build
```
── Building your game world ─────────────────
This happens once and takes about 30 minutes.
You can leave this running in the background.
Building workstation (1/3) ........... ✓ 8m 14s
Building web server (2/3) ........... ✓ 4m 02s
Building build server (3/3) ........... ✓ 5m 31s
Setting up quest scenarios ........... ✓ 1m 48s
```
### Phase 6 — Desktop entry
```
Create a desktop launcher so the game appears in your app menu? [Y/n]
```
Creates `~/.local/share/applications/sysadmin-chronicles.desktop` if yes.
### Phase 7 — Done
```
╔══════════════════════════════════════════╗
║ SETUP COMPLETE! ║
╚══════════════════════════════════════════╝
Start the game:
bash ~/Games/sysadmin-chronicles/start-game.sh
(or from your app menu if you created a launcher)
If you ever need to rebuild the virtual machines:
bash ~/Games/sysadmin-chronicles/tools/vm/rebuild-vms.sh
Install log saved at:
~/.local/share/sysadmin-chronicles/install.log
```
---
## Uninstaller Design (`uninstall.sh`)
Improved from current: shows sizes, explains consequences, three-tier removal.
### Menu approach
```
╔══════════════════════════════════════════╗
║ SYSADMIN CHRONICLES — UNINSTALL ║
╚══════════════════════════════════════════╝
What would you like to remove?
1) Everything — full uninstall (recommended)
2) Game world only — remove VMs, keep game files
3) Save data only — reset to new game
4) Custom — choose what to remove
q) Cancel
>
```
### "Everything" breakdown (shows before confirming)
```
This will remove:
Game virtual machines (3 VMs + all snapshots) ~38 GB
VM image files on disk ~38 GB ← ask separately
Game network and storage configuration <1 MB
Game access keys (~/.ssh/sc_host_key) <1 KB
Desktop launcher (if created) <1 KB
System packages (libvirt, QEMU, etc.) NOT removed
↑ These were installed by your package manager.
See ~/.local/share/sysadmin-chronicles/install.log
if you want to remove them manually.
Keep VM image files? If you ever reinstall, keeping them
saves the 30-minute rebuild. [Y/n — default: keep]
Type REMOVE to confirm: >
```
### What is never auto-removed
- System packages (libvirt, qemu, virt-viewer, etc.)
- Anything not prefixed with `sc-` in libvirt
- Any other libvirt VMs or networks not owned by this game
---
## VM Rebuild Tool (`tools/vm/rebuild-vms.sh`)
For when something goes wrong with a VM or the user wants a clean reset.
```
Usage:
rebuild-vms.sh Rebuild all VMs from scratch
rebuild-vms.sh --vm workstation Rebuild a single VM
rebuild-vms.sh --revert Revert all VMs to baseline snapshot (fast, ~30s)
rebuild-vms.sh --revert --vm workstation
Menu (interactive):
1) Revert all to last known good (fast — restores baseline snapshot)
2) Rebuild workstation (~8 min — rebuilds from cloud image)
3) Rebuild web server (~4 min)
4) Rebuild build server (~5 min)
5) Rebuild everything (~20 min)
q) Cancel
```
Key behavior:
- Always confirm before destroying a VM
- Show what quest progress will be affected
- Offer to back up save data before proceeding
- After rebuild, re-runs the appropriate quest-prep scripts and re-takes baseline snapshot
---
## User Snapshots
Players can take their own named snapshots of any VM — useful before attempting
something risky, or to bookmark a state they want to return to.
These are distinct from the game's automatic shift checkpoints and baseline
snapshots. User snapshots are never pruned automatically.
### Via `manage-saves.sh` (recommended)
The save management menu will include a **VM Snapshots** section:
```
VM Snapshots
workstation (ares)
1) before-ssh-experiment 2026-05-01 19:14
2) checkpoint.shift-3 2026-05-01 22:00 [auto]
3) baseline.day-one [protected]
web server (hermes)
1) my-nginx-fix 2026-05-02 11:30
2) checkpoint.shift-3 2026-05-01 22:00 [auto]
3) baseline.clean [protected]
Actions: [t]ake snapshot [r]evert [d]elete [q]uit
```
Taking a snapshot prompts for a name (letters, numbers, hyphens only).
Reverting shows a confirmation with the snapshot date.
Protected snapshots (baseline.*, checkpoint.*) cannot be deleted from this menu.
### Via `tools/vm/rebuild-vms.sh --snapshot`
For scripting or quick one-liners:
```bash
rebuild-vms.sh --snapshot --vm workstation --name before-risky-thing
rebuild-vms.sh --snapshot --all --name pre-shift-4
rebuild-vms.sh --revert --vm workstation --name before-risky-thing
```
### Storage note
Each VM snapshot is an internal qcow2 differential — typically 100 MB2 GB
depending on how much disk has changed since the baseline. The uninstaller shows
the total size of user snapshots separately so the user can decide whether to
keep them.
### `lib/vm.sh` additions needed
- `vm_snapshot_create vm_id name` — with name validation
- `vm_snapshot_list vm_id` — returns name, date, size, protection flag
- `vm_snapshot_revert vm_id name`
- `vm_snapshot_delete vm_id name` — refuses if name matches `baseline.*` or `checkpoint.*`
---
## Save Management
### Save file layout
```
~/.local/share/sysadmin-chronicles/
saves/
autosave.json ← always-present auto save (current session)
slot-1.json
slot-2.json
slot-3.json
install.log
```
### Save slot semantics
Save slots store JSON state only:
- Trust score and history
- Quest and ticket state
- World flags
- Inbox
- In-world clock
**VM state is not per-slot.** The shift checkpoint snapshots (checkpoint.shift-N) are the VM save mechanism and are independent of JSON slots. This is a known limitation but keeps disk usage manageable.
When switching slots: if the VM state doesn't match the JSON slot's expected state, warn the user. They may need to revert VMs manually.
### `tools/save/manage-saves.sh`
```
Usage:
manage-saves.sh Show save slot menu
manage-saves.sh --reset Reset current save to new game
manage-saves.sh --reset slot-1 Reset a specific slot
manage-saves.sh --list List all slots
Interactive menu:
Current save: autosave (Day 3, Trust: 67, 4/8 quests)
1) autosave Day 3 Trust 67 Q4/8 [active]
2) slot-1 Day 1 Trust 50 Q1/8
3) slot-2 —empty—
4) slot-3 —empty—
Actions: [s]witch [n]ew [r]eset [e]xport [i]mport [q]uit
```
### Reset save (standalone, accessible from start-game.sh)
The launcher `start-game.sh` should have an escape hatch:
```
start-game.sh --manage-saves → opens save management menu
start-game.sh --reset-save → confirms and resets to new game
```
---
## Launcher Improvements (`start-game.sh`)
Current issues to fix:
- Silently fails if images drive not mounted
- No check that the libvirt network is up before starting
- `sleep 1` to wait for server is fragile
Improvements:
- `config_read` to get `SC_IMAGES_DIR`, check it exists and is writable
- Check libvirt network is active, start it if not (with clear message)
- Poll server readiness on `/healthz` instead of sleeping
- Show a brief status before launching SPICE: "Starting your workstation..."
- On failure, show a plain-English error and the fix
---
## Portable Installation Notes
The `sc-images` libvirt pool target can be any path the host OS can write to. The installer configures it to `$SC_IMAGES_DIR` (inside the game dir by default).
If the user puts the game on a game drive (`/mnt/gamesdrive/sysadmin-chronicles/`):
- `SC_IMAGES_DIR=/mnt/gamesdrive/sysadmin-chronicles/images`
- The libvirt pool points there
- All qcow2 files live on the game drive
- The launcher checks the drive is mounted before starting
If the drive is unmounted:
```
✗ Can't find your game world.
The VM images are stored at /mnt/gamesdrive/sysadmin-chronicles/images
but that location isn't available right now.
Is your game drive plugged in and mounted?
Once it's mounted, run start-game.sh again.
```
---
## Dependency Log Format
`~/.local/share/sysadmin-chronicles/install.log`
```
# Sysadmin Chronicles — Install Log
# Created: 2026-04-27 14:32:01
# Distro: arch (6.19.12-arch1-1)
# Game dir: /home/aaron/Games/sysadmin-chronicles
# Images: /home/aaron/Games/sysadmin-chronicles/images
[INSTALLED] libvirt 12.2.0 via pacman
[INSTALLED] qemu-system-x86 11.0.0 via pacman
[INSTALLED] qemu-hw-display-qxl 11.0.0 via pacman
[INSTALLED] qemu-hw-display-virtio-gpu 11.0.0 via pacman
[INSTALLED] qemu-ui-spice-core 11.0.0 via pacman
[INSTALLED] qemu-chardev-spice 11.0.0 via pacman
[INSTALLED] qemu-audio-spice 11.0.0 via pacman
[INSTALLED] virt-install 5.1.0 via pacman
[INSTALLED] virt-viewer 11.0 via pacman
[INSTALLED] cloud-image-utils 0.33 via pacman
[INSTALLED] cdrtools 3.02a09 via pacman
[INSTALLED] libisoburn 1.5.8 via pacman
[SKIPPED] nodejs already installed
# To remove manually:
# sudo pacman -Rns libvirt qemu-system-x86 qemu-hw-display-qxl ...
```
---
## File Layout After Install
```
~/Games/sysadmin-chronicles/ ← SC_GAME_DIR
install.sh
uninstall.sh
start-game.sh
content/
server/
frontend/
docs/
tools/
lib/
ui.sh
deps.sh
libvirt.sh
vm.sh
config.sh
save.sh
setup/
check-host.sh
first-run-setup.sh
seed-vms.sh
vm/
rebuild-vms.sh
build-vm.sh
...
save/
manage-saves.sh
images/ ← SC_IMAGES_DIR (libvirt pool points here)
sc-workstation.qcow2 (~20 GB)
sc-web-server.qcow2 (~8 GB)
sc-build-machine.qcow2 (~10 GB)
~/.config/sysadmin-chronicles/config ← install config (survives game dir moves)
~/.local/share/sysadmin-chronicles/
saves/
autosave.json
slot-1.json ...
install.log
```
---
## Implementation Order
1. `tools/lib/ui.sh` — all other scripts depend on this
2. `tools/lib/config.sh` — needed by installer and launcher
3. `tools/lib/deps.sh` — needed by installer
4. `tools/lib/libvirt.sh` — needed by installer and rebuild tool
5. `tools/lib/vm.sh` — needed by installer and rebuild tool
6. `tools/lib/save.sh` — needed by save manager
7. `install.sh` — assembles libs 15
8. `tools/vm/rebuild-vms.sh` — assembles libs 1, 3, 4
9. `tools/save/manage-saves.sh` — assembles libs 1, 2, 6
10. `uninstall.sh` — assembles libs 1, 2, 4
11. `start-game.sh` (improved) — assembles libs 1, 2
12. Update `check-host.sh` UX
13. README — manual install section, quick start
---
## README Structure
```markdown
## Quick Install
curl -fsSL .../install.sh | bash
# or
bash install.sh # from downloaded zip
## Manual Install
<details>
<summary>For users who want full control or are troubleshooting</summary>
...per-distro dep tables, step-by-step...
</details>
```