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.
21 KiB
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.
# 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.
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
lsoffor port check — not universal. Replace withss -tlnp(iproute2, present on all modern Linux).- No network check — if the
sc-internallibvirt 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_DIRmight 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 verifydist/index.htmlexists and fail clearly if it doesn't (don't silently trigger a build).
UX improvements
- Source
lib/ui.shandlib/config.shonce 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-savesand--reset-saveflags (forward totools/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 headersc_ok "msg",sc_warn "msg",sc_fail "msg"— status linessc_prompt "question" "default"— interactive prompt, returns answersc_confirm "question"— yes/no, returns 0/1sc_spinner "label"/sc_spinner_stop— background spinner for long opssc_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 namescheck_deps— returns list of missing depsinstall_deps "pkg1 pkg2 ..."— runs the right package manager with sudo, logs what was installed
lib/libvirt.sh
ensure_network name xml_pathensure_pool name pathpool_path name— returns the pool's target directorydomain_exists name,domain_state namesnapshot_exists domain namesnapshot_create domain name descriptionsnapshot_revert domain namesnapshot_delete domain name
lib/vm.sh
vm_build profile [--dry-run] [--force]— wrapsbuild-vm.shvm_rebuild profile [--dry-run]— destroy + rebuild from cloud imagevm_revert vm_id snapshot_name— revert to named snapshotvm_status vm_id— running / stopped / missingvm_start vm_id,vm_stop vm_id
lib/config.sh
Config file lives at ~/.config/sysadmin-chronicles/config (survives game dir moves).
Variables stored:
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 fileconfig_write key valueconfig_show— pretty-prints current config
lib/save.sh
save_list— lists all save slots with name, date, trust score, quest progresssave_switch slot_name— switch active savesave_new slot_name— create a new empty save slotsave_reset [slot_name]— wipe a slot back to new-game statesave_export slot_name path— export save JSON for backupsave_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:
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 MB–2 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 validationvm_snapshot_list vm_id— returns name, date, size, protection flagvm_snapshot_revert vm_id namevm_snapshot_delete vm_id name— refuses if name matchesbaseline.*orcheckpoint.*
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 1to wait for server is fragile
Improvements:
config_readto getSC_IMAGES_DIR, check it exists and is writable- Check libvirt network is active, start it if not (with clear message)
- Poll server readiness on
/healthzinstead 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
tools/lib/ui.sh— all other scripts depend on thistools/lib/config.sh— needed by installer and launchertools/lib/deps.sh— needed by installertools/lib/libvirt.sh— needed by installer and rebuild tooltools/lib/vm.sh— needed by installer and rebuild tooltools/lib/save.sh— needed by save managerinstall.sh— assembles libs 1–5tools/vm/rebuild-vms.sh— assembles libs 1, 3, 4tools/save/manage-saves.sh— assembles libs 1, 2, 6uninstall.sh— assembles libs 1, 2, 4start-game.sh(improved) — assembles libs 1, 2- Update
check-host.shUX - README — manual install section, quick start
README Structure
## 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>