Skip to content

Latest commit

 

History

History
700 lines (550 loc) · 24.4 KB

File metadata and controls

700 lines (550 loc) · 24.4 KB

LinuxMissions — Full Implementation Specification

This document is a complete specification for implementing the remaining parts of the LinuxMissions game — a terminal-based Linux learning game modeled after k8smissions and dockermissions.


1. Project Overview

LinuxMissions is a CLI game that drops users into real Linux scenarios. Each level:

  • Sets up a sandbox under /tmp/linuxmissions/<module>/<level>/
  • Presents a mission brief (story, objective, XP, difficulty, concepts)
  • Lets the user run any shell commands
  • Auto-validates after every command using a validate.sh script
  • Awards XP and shows a debrief on success
  • Earns a certificate upon completing a full module

The game runs as a Python TUI (using rich) that wraps real shell execution.


2. Current State — What Is Already Built

Engine (engine/)

All engine files are complete and working:

File Purpose
engine/engine.py Main game loop, level loading, command dispatch, validation
engine/ui.py Rich-based UI: banners, panels, mission briefings, debriefs
engine/player.py Player name prompt
engine/reset.py Sandbox teardown + setup.sh runner, sandbox path = /tmp/linuxmissions/<level_id>/
engine/safety.py Block destructive commands (rm -rf /, fork bomb, etc.)
engine/certificate.py Module completion certificate generator + saver

Modules Built (with all levels complete)

Module Levels Status
module-1-filesystem 10 levels ✅ Complete
module-2-text 8 levels ✅ Complete
module-3-permissions 8 levels ✅ Complete
module-4-processes 2/6 levels ⚠️ Partial (levels 1–2 done)

Missing Top-Level Files

  • play.sh — launcher script
  • install.sh — dependency installer
  • progress.json — initial empty progress file
  • levels.json — level registry (auto-generated by scripts/generate_levels.py)
  • scripts/generate_levels.py — script that walks modules/ and builds levels.json

3. Level File Format (Contract)

Every level directory under modules/<module-name>/<level-name>/ must contain:

mission.yaml      # JSON (despite .yaml extension) — level metadata
setup.sh          # Called with $1=sandbox_path; creates the broken state
validate.sh       # Called with $1=sandbox_path; exits 0=pass, 1=fail
hint-1.txt        # First hint (shown on first `hint` command)
hint-2.txt        # Second hint
hint-3.txt        # Third hint (most explicit / full solution)
debrief.md        # Markdown shown after completing the level

mission.yaml schema

{
  "name": "Human-readable level title",
  "description": "One paragraph story context",
  "objective": "Exactly what the player must do",
  "xp": 100,
  "difficulty": "beginner | intermediate | advanced",
  "expected_time": "5m",
  "concepts": ["cmd1", "cmd2"],
  "module": "module-4-processes",
  "level": "level-3-background-jobs"
}

setup.sh contract

#!/bin/bash
# $1 = absolute sandbox path (e.g. /tmp/linuxmissions/module-4-processes/level-3-background-jobs)
SANDBOX="$1"
# Create files, dirs, start background processes, set permissions, etc.
# All files MUST be created under $SANDBOX

validate.sh contract

#!/bin/bash
# $1 = absolute sandbox path
SANDBOX="$1"
# Print ✅ PASS: ... on success, ❌ FAIL: ... on failure
# Exit 0 = level complete, Exit 1 = not yet

debrief.md conventions

  • Start with # Level Name
  • Section ## What you practiced
  • Section ## Commands to remember with a fenced code block
  • Optional ## Key insight or ## Security note or comparison table

4. How the Engine Works (for context)

  1. Startup: play.sh activates venv → python3 -m engine.engine
  2. Level load: reads levels.json, finds first uncompleted level in selected module
  3. Sandbox setup: engine/reset.py:prepare_level() → runs setup.sh $sandbox
  4. Prompt loop: user types commands, engine runs them via subprocess.run(cmd, shell=True, cwd=sandbox)
  5. After each command: engine calls validate.sh $sandbox → exit 0 triggers level completion
  6. Meta-commands handled by engine (not passed to shell): hint, reset, skip, status, objective, help, quit
  7. On completion: XP added, debrief shown, progress saved to progress.json
  8. Module completion: certificate generated to completion/certificate-<module>.txt

5. What Needs to Be Implemented

5A. Complete Module 4: Processes (levels 3–6)

Module directory: modules/module-4-processes/ Already done: level-1-ps-find, level-2-kill


level-3-background-jobs

Name: The Background Worker
Description: A data processing script takes 30 seconds. You can't wait — you need to run it in background and check on it.
Objective: Run process.sh in the background, check its status with jobs, bring it to foreground, then let it finish
XP: 150
Difficulty: intermediate
Concepts: &, jobs, fg, bg, Ctrl+Z, disown

setup.sh:

  • Create $SANDBOX/process.sh — a script that sleeps 15 seconds then writes "done" to $SANDBOX/result.txt
  • chmod +x it

validate.sh:

  • Pass if $SANDBOX/result.txt exists and contains "done"

hints:

  1. Run in background: bash $SANDBOX/process.sh &
  2. Check jobs: jobs — see running jobs. Use fg to bring it foreground, or just wait.
  3. The script writes result.txt when finished. Check: cat $SANDBOX/result.txt

debrief.md:

  • Explain &, jobs, fg %1, bg %1, Ctrl+Z (suspend), disown (detach from terminal)
  • Table: job control commands

level-4-nice

Name: Play Nice
Description: A CPU-intensive batch job is slowing down the web server. Launch it with a lower priority.
Objective: Run heavy.sh with niceness 15, save its PID to pid.txt, verify it's running with adjusted priority
XP: 150
Difficulty: intermediate
Concepts: nice, renice, ps -o ni, priority

setup.sh:

  • Create $SANDBOX/heavy.sh — infinite loop: while true; do :; done
  • chmod +x
  • Create $SANDBOX/README with instructions

validate.sh:

  • Check $SANDBOX/pid.txt exists
  • Read PID from pid.txt
  • ps -o ni= -p $PID — check niceness is >= 10
  • Kill the process after checking

hints:

  1. nice -n 15 bash $SANDBOX/heavy.sh &
  2. Save PID: nice -n 15 bash $SANDBOX/heavy.sh & echo $! > $SANDBOX/pid.txt
  3. Verify: ps -o pid,ni,cmd -p $(cat $SANDBOX/pid.txt)

debrief.md:

  • Explain niceness scale: -20 (highest priority) to 19 (lowest)
  • nice for new processes, renice for existing
  • Only root can set negative nice values

level-5-signals

Name: Signal Relay
Description: A daemon is configured to reload its config when it receives SIGHUP. Simulate this.
Objective: Send SIGHUP to the listener.sh process, which will write 'reloaded' to reload.log
XP: 200
Difficulty: intermediate
Concepts: kill -HUP, signals, SIGHUP, trap, kill -l

setup.sh:

  • Create $SANDBOX/listener.sh:
    #!/bin/bash
    trap 'echo "reloaded" >> "$SANDBOX/reload.log"' HUP
    echo $$ > "$SANDBOX/listener.pid"
    while true; do sleep 1; done
    (use actual sandbox path in the trap, interpolated during setup)
  • chmod +x and launch: bash $SANDBOX/listener.sh &

validate.sh:

  • Check $SANDBOX/reload.log exists and contains "reloaded"

hints:

  1. Find the PID: cat $SANDBOX/listener.pid or pgrep -f listener.sh
  2. Send SIGHUP: kill -HUP $(cat $SANDBOX/listener.pid)
  3. Verify: cat $SANDBOX/reload.log

debrief.md:

  • List common signals with real-world use cases
  • Explain trap in bash
  • kill -l to list all signals

level-6-zombie

Name: Zombie Apocalypse
Description: A poorly coded parent process left zombie children behind. Identify them and clean up.
Objective: Find all zombie processes, write their PIDs and names to zombies.txt, then kill the parent to reap them
XP: 200
Difficulty: advanced
Concepts: ps, zombie, Z state, PPID, kill parent, wait

setup.sh:

  • Create $SANDBOX/make_zombies.sh:
    #!/bin/bash
    # Creates zombie children by not calling wait
    for i in 1 2 3; do
      (exit 0) &   # child exits immediately, parent never waits
    done
    echo $$ > "$SANDBOX/parent.pid"
    sleep 3600   # parent stays alive, children become zombies
  • chmod +x and launch it

validate.sh:

  • Check $SANDBOX/zombies.txt exists and has content
  • Check the parent process is no longer running (or zombies are gone)

hints:

  1. Find zombie processes: ps aux | grep Z
  2. Zombies show 'Z' in STAT column. Get their PPID: ps -o pid,ppid,stat,cmd | grep Z
  3. Kill the parent to reap zombies: kill $(cat $SANDBOX/parent.pid) then save info first

debrief.md:

  • What a zombie is (exited child, parent hasn't called wait())
  • They use a PID slot but no memory/CPU
  • Fix: kill parent (init/systemd adopts and reaps) or fix code to call wait()

5B. New Modules to Build


MODULE 5: Shell Scripting (module-5-scripting)

8 levels — teach bash scripting from first principles

Level Name Objective Key Concepts
level-1-variables Speak Variable Write a script greet.sh that takes a name as $1 and prints "Hello, $1!" variables, $1, echo, script args
level-2-conditionals The Gatekeeper Write check_file.sh $1 that prints "EXISTS" or "MISSING" depending on whether $1 exists if/else, -f, -d, test
level-3-loops Bulk Renamer Write rename.sh that renames all .txt files in a dir to .bak using a for loop for loops, glob, mv, basename
level-4-functions DRY Deploy Refactor a repetitive deploy script to use functions function, local, return, $?
level-5-input Config Parser Write a script that reads key=value lines from a file and exports them as env vars while read, IFS, export, source
level-6-error-handling Fail Loudly Add set -euo pipefail and proper error handling to a broken script set -e, set -u, set -o pipefail, trap ERR
level-7-subshells Parallel Launch Use subshells to run 3 tasks in parallel and wait for all to finish ( ), &, wait, subshells
level-8-heredoc Template Engine Use heredoc to generate a config file from variables heredoc, <<EOF, cat, envsubst

Validation patterns for this module:

  • level-1: Run bash $SANDBOX/greet.sh World → check output contains "Hello, World!"
  • level-2: Test both file-exists and file-missing cases
  • level-3: Check .txt files gone, .bak files exist
  • level-4: Run the refactored script, check output
  • level-5: Source the config, check exported vars
  • level-6: Run script with missing var → should fail with clear message not unbound variable
  • level-7: Check all 3 output files exist (created by parallel tasks)
  • level-8: Check generated config has correct substituted values

MODULE 6: Networking (module-6-networking)

8 levels — diagnose and work with Linux networking

Level Name Objective Key Concepts
level-1-ip-addr What's My IP? Find all network interfaces and their IPs, save to interfaces.txt ip addr, ip a, ifconfig
level-2-ping Can You Hear Me? Ping 3 hosts from hosts.txt, record which are reachable in reachable.txt ping -c1, exit codes, loops
level-3-ports What's Listening? List all listening TCP ports, save to listening.txt ss -tlnp, netstat -tlnp, lsof
level-4-curl API Call Fetch a JSON response from a local test file using curl and extract a field curl, -s, -o, jq, pipe
level-5-dns Name Resolution Look up the A record for hostnames in a list, save IPs to resolved.txt dig, nslookup, host, /etc/resolv.conf
level-6-netstat Connection Map Find all ESTABLISHED connections and count unique remote IPs ss -tn, awk, sort, uniq
level-7-firewall Open Sesame Using iptables or nftables rules file, identify which port is blocked (conceptual analysis) iptables -L, rule reading
level-8-tcpdump Packet Sniff Use tcpdump to capture traffic to a file and identify the protocol tcpdump, -w, -r, -i lo

Notes:

  • Networking levels use files to simulate (e.g., a pre-populated hosts.txt, fake JSON files for curl) to avoid needing real network access
  • level-4-curl uses curl file:///path or serves a local file
  • level-7 is analysis-only (read the iptables output, answer a question)

MODULE 7: System Administration (module-7-sysadmin)

8 levels — real sysadmin day-to-day tasks

Level Name Objective Key Concepts
level-1-systemd Service Down Enable and start a systemd unit file for a dummy service systemctl start/enable/status, unit files
level-2-journald Log Hunt Find all log entries from the last hour containing "error" using journalctl journalctl, --since, -p, grep
level-3-cron Scheduled Sweep Write a crontab entry that runs cleanup.sh every day at 3am crontab -e, cron syntax, crontab -l
level-4-users New Hire Create user devuser, set password, add to docker group useradd, passwd, usermod -aG, id
level-5-sudoers Controlled Power Add a sudoers rule allowing devuser to run /usr/bin/systemctl without password visudo, /etc/sudoers.d/, NOPASSWD
level-6-logrotate Log Discipline Write a logrotate config for /var/log/myapp/*.log logrotate config syntax, rotate/compress/daily
level-7-env Env Surgery A script fails because of missing env vars. Set them in .env and source it export, source, .env, env, printenv
level-8-at One-Shot Task Schedule a one-time command to run in 1 minute using at at, atq, atrm

Notes:

  • level-1-systemd: create a dummy unit file at $SANDBOX/myapp.service, copy to /tmp/ location — validation checks systemctl if available, otherwise just checks unit file syntax
  • level-4-users and level-5-sudoers require root — these levels should detect if running as root and skip gracefully if not, OR use a sandbox approach with mock files

MODULE 8: Advanced Text & Data (module-8-advanced-text)

6 levels — power-user text processing

Level Name Objective Key Concepts
level-1-regex-grep Pattern Mastery Extract all IPv4 addresses from a log using grep regex, save to ips.txt grep -E, IPv4 regex, \d, groups
level-2-awk-report Expense Report Use awk to sum expenses by category from CSV, print sorted totals awk arrays, END block, sort
level-3-sed-multiline Config Injector Use sed to insert a new line AFTER a specific pattern in a config file sed /pattern/a, sed address ranges
level-4-jq JSON Surgeon Parse a JSON file, extract nested fields, filter an array jq, ., [], select, map
level-5-xargs Parallel Grep Use xargs to run grep across 50 files in parallel, find the one with a keyword xargs -P, -I{}, parallel execution
level-6-column Pretty Table Format a space-separated report file into aligned columns column -t, printf, awk printf

6. Files That Need to Be Created

play.sh

#!/bin/bash
# LinuxMissions launcher
clear
cd "$(dirname "$0")"

if [[ "$1" == "--reset" ]]; then
  # prompt to reset progress.json (same pattern as k8smissions)
fi

if [ ! -d "venv" ]; then
  echo "❌ Virtual environment not found. Run ./install.sh first."
  exit 1
fi

export PYTHONPATH="$(pwd):$PYTHONPATH"

if [ -f "venv/bin/python3" ]; then
  venv/bin/python3 -m engine.engine
else
  echo "❌ Python interpreter not found inside venv"
  exit 1
fi

install.sh

#!/bin/bash
# LinuxMissions installer
set -euo pipefail

echo "🚀 Installing LinuxMissions..."

# Check Python 3
if ! command -v python3 &>/dev/null; then
  echo "❌ Python 3 required. Install it first."
  exit 1
fi

# Create venv
python3 -m venv venv
venv/bin/pip install --upgrade pip -q
venv/bin/pip install -r requirements.txt -q

# Generate levels.json
venv/bin/python3 scripts/generate_levels.py

# Init progress.json if missing
if [ ! -f "progress.json" ]; then
  echo '{"player_name":"","total_xp":0,"completed_levels":[],"current_module":"","current_level":"","module_certificates":[],"time_per_level":{},"level_start_time":null}' > progress.json
fi

echo "✅ Done! Run: ./play.sh"

scripts/generate_levels.py

This script walks modules/ alphabetically, reads each mission.yaml, and produces levels.json:

#!/usr/bin/env python3
"""Walk modules/ and generate levels.json registry."""
import json
from pathlib import Path
from datetime import datetime, timezone

REPO = Path(__file__).resolve().parent.parent
MODULES_DIR = REPO / "modules"
OUTPUT = REPO / "levels.json"

modules = []
total = 0

for mod_dir in sorted(MODULES_DIR.iterdir()):
    if not mod_dir.is_dir():
        continue
    levels = []
    for lvl_dir in sorted(mod_dir.iterdir()):
        if not lvl_dir.is_dir():
            continue
        mission_file = lvl_dir / "mission.yaml"
        if not mission_file.exists():
            continue
        mission = json.loads(mission_file.read_text())
        level_id = f"{mod_dir.name}/{lvl_dir.name}"
        levels.append({
            "id": level_id,
            "name": lvl_dir.name,
            "path": f"modules/{mod_dir.name}/{lvl_dir.name}",
            "mission": mission,
        })
        total += 1
    if levels:
        modules.append({"name": mod_dir.name, "levels": levels})

OUTPUT.write_text(json.dumps({
    "generated_at": datetime.now(timezone.utc).isoformat(),
    "level_count": total,
    "modules": modules,
}, indent=2))
print(f"✅ levels.json: {total} levels across {len(modules)} modules")

progress.json (initial)

{
  "player_name": "",
  "total_xp": 0,
  "completed_levels": [],
  "current_module": "",
  "current_level": "",
  "module_certificates": [],
  "time_per_level": {},
  "level_start_time": null
}

7. UI Behavior Reference

The engine/ui.py module uses Rich. Here's how each screen looks:

Welcome Screen

  • ASCII banner (BANNER constant in ui.py)
  • Panel with player name, game description, keyboard shortcut hints

Module Selection

  • Numbered list of modules with done/total level count
  • Green ✓ for completed modules
  • Player enters a number

Mission Briefing Panel

  • Title: Mission N/Total: <name> DIFFICULTY +XP ~Xm
  • Body: description paragraph + Objective: line + Concepts: dim line
  • Border color: blue

Hint Panel

  • Yellow border
  • Title: Hint N

Debrief Panel (on level complete)

  • Green border
  • Title: Level Complete! +XP Xm Xs
  • Body: rendered Markdown from debrief.md

Status Panel (on status command)

  • Dim border, simple table
  • Player name, total XP, module progress N/M

Module Certificate

  • Saved to completion/certificate-<module>.txt
  • ASCII art box with player name, module name, XP, date

Prompt

  • > in bold green

Error messages

  • [red] for blocked commands
  • [yellow] for warnings

8. Sandbox Path Convention

The sandbox root for any level is:

/tmp/linuxmissions/<module-name>/<level-name>/

Example:

/tmp/linuxmissions/module-4-processes/level-2-kill/
  • setup.sh receives this as $1 and must create ALL files under it
  • validate.sh receives this as $1 and checks the state under it
  • The engine sets cwd=sandbox when running player commands
  • The engine also sets SANDBOX=<path> in the environment so player scripts can use $SANDBOX
  • Sandbox is rebuilt from scratch on reset command

9. XP Budget per Module

Recommended XP totals:

Module Levels Suggested XP Range
module-1-filesystem 10 100–150 each
module-2-text 8 100–200 each
module-3-permissions 8 100–200 each
module-4-processes 6 100–200 each
module-5-scripting 8 150–250 each
module-6-networking 8 150–250 each
module-7-sysadmin 8 150–300 each
module-8-advanced-text 6 200–300 each

Total: ~62 levels, ~10,000 XP


10. Difficulty Progression Per Module

Each module should follow this arc:

  1. Levels 1–2: beginner — basic command usage
  2. Levels 3–5: intermediate — combining commands, real scenarios
  3. Levels 6+: advanced — edge cases, security hardening, scripting

11. Validation Script Best Practices

  • Always check the primary objective first, print ❌ FAIL and exit 1
  • Check side effects (e.g., other files not accidentally broken)
  • Final echo "✅ PASS: ..." + exit 0
  • Use 2>/dev/null on commands that might fail for non-critical checks
  • For numeric comparisons use -eq, -lt, etc. not =
  • For process checks, always handle missing pid.txt gracefully

12. Common Patterns for setup.sh

# Pattern 1: Create broken file
echo "wrong content" > "$SANDBOX/config.conf"
chmod 600 "$SANDBOX/config.conf"  # too restrictive

# Pattern 2: Start a background process
bash "$SANDBOX/myloop.sh" &
echo $! > "$SANDBOX/myloop.pid"

# Pattern 3: Create directory structure with wrong permissions
mkdir -p "$SANDBOX/app/logs"
chmod 700 "$SANDBOX/app/logs"  # too restrictive for app

# Pattern 4: Create a tar archive to extract
tar -czf "$SANDBOX/backup.tar.gz" -C "$SANDBOX/source" .
rm -rf "$SANDBOX/source"

# Pattern 5: Create a large set of files with some targeting
for i in $(seq 1 20); do
  echo "line $i" > "$SANDBOX/file$i.txt"
done
echo "TARGET_VALUE=secret" > "$SANDBOX/file7.txt"  # the one to find

13. Implementation Order

Recommended order to implement:

  1. scripts/generate_levels.py — needed to test anything
  2. play.sh + install.sh — needed to run the game
  3. progress.json — initial empty file
  4. Module 4 remaining levels (3–6) — finish the incomplete module
  5. Module 5 scripting (8 levels) — most self-contained to test
  6. Module 6 networking (8 levels) — use file-based simulation
  7. Module 7 sysadmin (8 levels) — some require root detection
  8. Module 8 advanced text (6 levels) — most complex, do last

14. Testing a Level

After creating a level's files, test it manually:

# 1. Run setup
bash modules/module-X-foo/level-Y-bar/setup.sh /tmp/test_sandbox/

# 2. Inspect state
ls -la /tmp/test_sandbox/

# 3. Perform the solution manually
# (run the expected commands)

# 4. Run validate
bash modules/module-X-foo/level-Y-bar/validate.sh /tmp/test_sandbox/
# Should print ✅ and exit 0

# 5. Test failure case — don't do the solution, check it fails
bash modules/module-X-foo/level-Y-bar/setup.sh /tmp/test_sandbox2/
bash modules/module-X-foo/level-Y-bar/validate.sh /tmp/test_sandbox2/
# Should print ❌ and exit 1

15. Full Directory Tree (Final State)

linuxmissions/
├── play.sh                          # launcher
├── install.sh                       # setup script
├── requirements.txt                 # rich>=13.0.0
├── progress.json                    # player save file
├── levels.json                      # auto-generated registry
├── SPEC.md                          # this file
├── README.md                        # user-facing docs
├── engine/
│   ├── __init__.py
│   ├── engine.py                    # main game loop
│   ├── ui.py                        # rich UI components
│   ├── player.py                    # name prompt
│   ├── reset.py                     # sandbox setup
│   ├── safety.py                    # blocked command filter
│   └── certificate.py              # module cert generator
├── scripts/
│   └── generate_levels.py          # builds levels.json
├── completion/
│   └── certificate-<module>.txt    # generated on module completion
└── modules/
    ├── module-1-filesystem/         # ✅ 10 levels done
    ├── module-2-text/               # ✅ 8 levels done
    ├── module-3-permissions/        # ✅ 8 levels done
    ├── module-4-processes/          # ⚠️ 2/6 done — need levels 3–6
    ├── module-5-scripting/          # ❌ 8 levels needed
    ├── module-6-networking/         # ❌ 8 levels needed
    ├── module-7-sysadmin/           # ❌ 8 levels needed
    └── module-8-advanced-text/      # ❌ 6 levels needed

Each level directory:

<level-name>/
├── mission.yaml      # metadata (JSON)
├── setup.sh          # creates sandbox state
├── validate.sh       # checks completion
├── hint-1.txt
├── hint-2.txt
├── hint-3.txt
└── debrief.md

Total remaining: ~56 levels + 5 top-level files + 1 generator script