# Pass args through to `craftos`. Prefer `just trapos-exec ''` for # automated probes that must not hang the terminal. # Launch the TrapOS dev environment in CraftOS-PC with repo-local data # (.craftos/) and read-only repo mounts. See ADR-0005. [positional-arguments] trapos *args: check-install #!/usr/bin/env bash set -euo pipefail repo='{{justfile_directory()}}' argv=(--directory "$repo/.craftos") if [ "$(uname -s)" = "Darwin" ]; then argv+=(--rom /Applications/CraftOS-PC.app/Contents/Resources) fi argv+=(--mount-ro "/trapos=$repo") for dir in apis programs servers startup tests; do if [ -d "$repo/$dir" ]; then argv+=(--mount-ro "/$dir=$repo/$dir") fi done exec craftos "${argv[@]}" "$@" # Pass args through to a fresh, vanilla `craftos` with no TrapOS mounts. # Persistent state lives under .craftos-vanilla/. Useful for probes that # should not see TrapOS files (e.g. verifying upstream CC:Tweaked behavior) # and as the base for `just trapos-install`. See ADR-0005. [positional-arguments] craftos *args: check-install #!/usr/bin/env bash set -euo pipefail repo='{{justfile_directory()}}' argv=(--directory "$repo/.craftos-vanilla") if [ "$(uname -s)" = "Darwin" ]; then argv+=(--rom /Applications/CraftOS-PC.app/Contents/Resources) fi exec craftos "${argv[@]}" "$@" # Safely run a Lua snippet in the TrapOS dev environment. The wrapper always # shuts the machine down after normal completion or Lua errors, while the host # watchdog catches snippets that block before reaching shutdown. [positional-arguments] trapos-exec code: #!/usr/bin/env bash set -uo pipefail repo='{{justfile_directory()}}' timeout_seconds="${TRAP_CCLIBS_HEADLESS_TIMEOUT_SECONDS:-10}" case "$timeout_seconds" in ''|*[!0-9]*) printf '%s\n' 'TRAP_CCLIBS_HEADLESS_TIMEOUT_SECONDS must be a positive integer' >&2; exit 1 ;; esac if [ "$timeout_seconds" -lt 1 ]; then printf '%s\n' 'TRAP_CCLIBS_HEADLESS_TIMEOUT_SECONDS must be >= 1' >&2; exit 1; fi rom_arg=() if [ "$(uname -s)" = "Darwin" ]; then rom_arg=(--rom /Applications/CraftOS-PC.app/Contents/Resources) fi data_dir="$(mktemp -d)" stage_dir="$(mktemp -d)" tmp="$(mktemp)" output_path="$data_dir/computer/0/headless-output" status_path="$data_dir/computer/0/headless-status" runner="$stage_dir/exec.lua" { printf '%s\n' 'local output = fs.open("/headless-output", "w")' printf '%s\n' 'local function emitLine(...)' printf '%s\n' ' for i = 1, select("#", ...) do' printf '%s\n' ' if i > 1 then output.write("\t") end' printf '%s\n' ' output.write(tostring(select(i, ...)))' printf '%s\n' ' end' printf '%s\n' ' output.write("\n")' printf '%s\n' 'end' printf '%s\n' 'print = emitLine' printf '%s\n' 'write = function(value) output.write(tostring(value)); end' printf '%s\n' 'local function main()' printf '%s\n' "$1" printf '%s\n' 'end' printf '%s\n' 'local ok, err = xpcall(main, debug.traceback)' printf '%s\n' 'if not ok then' printf '%s\n' ' output.writeLine(err)' printf '%s\n' 'end' printf '%s\n' 'output.close()' printf '%s\n' 'local status = fs.open("/headless-status", "w")' printf '%s\n' 'status.writeLine(ok and "OK" or "FAIL")' printf '%s\n' 'status.close()' printf '%s\n' 'os.shutdown()' } > "$runner" mount_arg=(--mount-ro "/trapos=$repo" --mount-ro "/apis=$repo/apis" --mount-ro "/programs=$repo/programs" --mount-ro "/servers=$repo/servers" --mount-ro "/startup=$repo/startup" --mount-ro "/tests=$repo/tests" --mount-ro "/headless=$stage_dir") craftos --directory "$data_dir" --headless "${rom_arg[@]}" "${mount_arg[@]}" --exec "shell.run('/headless/exec.lua')" >"$tmp" 2>&1 & pid="$!" ( sleep "$timeout_seconds"; kill -TERM "$pid" >/dev/null 2>&1 ) & watchdog="$!" wait "$pid" >/dev/null 2>&1 status="$?" kill "$watchdog" >/dev/null 2>&1 || true wait "$watchdog" >/dev/null 2>&1 || true red=$(printf '\033[31m'); reset=$(printf '\033[0m') if [ -f "$status_path" ] && grep -q '^OK$' "$status_path"; then if [ -f "$output_path" ]; then cat "$output_path"; fi rm -f "$tmp"; rm -rf "$data_dir"; rm -rf "$stage_dir" else if [ "$status" -eq 143 ]; then printf '%s\n' "${red}FAIL${reset} TrapOS headless exec timed out after ${timeout_seconds}s" >&2 else printf '%s\n' "${red}FAIL${reset} TrapOS headless exec failed" >&2 fi if [ -f "$output_path" ]; then cat "$output_path" >&2 else cat "$tmp" >&2 fi rm -f "$tmp"; rm -rf "$data_dir"; rm -rf "$stage_dir" exit 1 fi # Safely run a Lua snippet in vanilla CraftOS-PC with no TrapOS mounts. [positional-arguments] craftos-exec code: #!/usr/bin/env bash set -uo pipefail repo='{{justfile_directory()}}' timeout_seconds="${TRAP_CCLIBS_HEADLESS_TIMEOUT_SECONDS:-10}" case "$timeout_seconds" in ''|*[!0-9]*) printf '%s\n' 'TRAP_CCLIBS_HEADLESS_TIMEOUT_SECONDS must be a positive integer' >&2; exit 1 ;; esac if [ "$timeout_seconds" -lt 1 ]; then printf '%s\n' 'TRAP_CCLIBS_HEADLESS_TIMEOUT_SECONDS must be >= 1' >&2; exit 1; fi rom_arg=() if [ "$(uname -s)" = "Darwin" ]; then rom_arg=(--rom /Applications/CraftOS-PC.app/Contents/Resources) fi data_dir="$(mktemp -d)" stage_dir="$(mktemp -d)" tmp="$(mktemp)" output_path="$data_dir/computer/0/headless-output" status_path="$data_dir/computer/0/headless-status" runner="$stage_dir/exec.lua" { printf '%s\n' 'local output = fs.open("/headless-output", "w")' printf '%s\n' 'local function emitLine(...)' printf '%s\n' ' for i = 1, select("#", ...) do' printf '%s\n' ' if i > 1 then output.write("\t") end' printf '%s\n' ' output.write(tostring(select(i, ...)))' printf '%s\n' ' end' printf '%s\n' ' output.write("\n")' printf '%s\n' 'end' printf '%s\n' 'print = emitLine' printf '%s\n' 'write = function(value) output.write(tostring(value)); end' printf '%s\n' 'local function main()' printf '%s\n' "$1" printf '%s\n' 'end' printf '%s\n' 'local ok, err = xpcall(main, debug.traceback)' printf '%s\n' 'if not ok then' printf '%s\n' ' output.writeLine(err)' printf '%s\n' 'end' printf '%s\n' 'output.close()' printf '%s\n' 'local status = fs.open("/headless-status", "w")' printf '%s\n' 'status.writeLine(ok and "OK" or "FAIL")' printf '%s\n' 'status.close()' printf '%s\n' 'os.shutdown()' } > "$runner" craftos --directory "$data_dir" --headless "${rom_arg[@]}" --mount-ro "/headless=$stage_dir" --exec "shell.run('/headless/exec.lua')" >"$tmp" 2>&1 & pid="$!" ( sleep "$timeout_seconds"; kill -TERM "$pid" >/dev/null 2>&1 ) & watchdog="$!" wait "$pid" >/dev/null 2>&1 status="$?" kill "$watchdog" >/dev/null 2>&1 || true wait "$watchdog" >/dev/null 2>&1 || true red=$(printf '\033[31m'); reset=$(printf '\033[0m') if [ -f "$status_path" ] && grep -q '^OK$' "$status_path"; then if [ -f "$output_path" ]; then cat "$output_path"; fi rm -f "$tmp"; rm -rf "$data_dir"; rm -rf "$stage_dir" else if [ "$status" -eq 143 ]; then printf '%s\n' "${red}FAIL${reset} CraftOS headless exec timed out after ${timeout_seconds}s" >&2 else printf '%s\n' "${red}FAIL${reset} CraftOS headless exec failed" >&2 fi if [ -f "$output_path" ]; then cat "$output_path" >&2 else cat "$tmp" >&2 fi rm -f "$tmp"; rm -rf "$data_dir"; rm -rf "$stage_dir" exit 1 fi # End-to-end install probe: drive the real ccpm bootstrap # (install-ccpm.lua -> `ccpm update` -> `ccpm install trapos`) on a fresh, # ephemeral CraftOS-PC state. Reflects the currently checked-out git branch: # `master` -> --stable, `next` -> --beta (confirmation stubbed). Other # branches are rejected because install-ccpm only knows master/next. # Network-dependent and slower than `just test`, so not part of `just ci`. # Override timeout with TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS (default 60). # See ADR-0005. trapos-install: check-install #!/usr/bin/env bash set -uo pipefail repo='{{justfile_directory()}}' timeout_seconds="${TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS:-60}" case "$timeout_seconds" in ''|*[!0-9]*) printf '%s\n' 'TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS must be a positive integer' >&2; exit 1 ;; esac branch="$(git -C "$repo" rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')" case "$branch" in master) install_flag='--stable' stub_read='' ;; next) install_flag='--beta' stub_read="_G.read = function() return 'y' end; " ;; *) printf '%s\n' "trapos-install only supports the master or next branch (current: ${branch:-unknown}). install-ccpm.lua does not accept other branches." >&2 exit 1 ;; esac printf '%s\n' "trapos-install: branch=$branch flag=$install_flag" rom_arg=() if [ "$(uname -s)" = "Darwin" ]; then rom_arg=(--rom /Applications/CraftOS-PC.app/Contents/Resources) fi data_dir="$(mktemp -d)" stage_dir="$(mktemp -d)" cp "$repo/install-ccpm.lua" "$stage_dir/install-ccpm.lua" tmp="$(mktemp)" exec_code="${stub_read}shell.run('/staging/install-ccpm', '$install_flag'); shell.run('/programs/ccpm', 'update'); local ok = shell.run('/programs/ccpm', 'install', 'trapos'); if ok then print('__TRAPOS_INSTALL_OK__') end; os.shutdown()" craftos --directory "$data_dir" --headless "${rom_arg[@]}" --mount-ro "/staging=$stage_dir" --exec "$exec_code" >"$tmp" 2>&1 & pid="$!" ( sleep "$timeout_seconds"; kill -TERM "$pid" >/dev/null 2>&1 ) & watchdog="$!" wait "$pid" >/dev/null 2>&1 status="$?" kill "$watchdog" >/dev/null 2>&1 || true wait "$watchdog" >/dev/null 2>&1 || true red=$(printf '\033[31m'); reset=$(printf '\033[0m') if grep -q __TRAPOS_INSTALL_OK__ "$tmp"; then rm -f "$tmp"; rm -rf "$data_dir"; rm -rf "$stage_dir" printf '%s\n' 'OK: trapos installed end-to-end on fresh CraftOS-PC' else if [ "$status" -eq 143 ]; then printf '%s\n' "${red}FAIL${reset} trapos-install timed out after ${timeout_seconds}s" >&2 else printf '%s\n' "${red}FAIL${reset} trapos-install did not print __TRAPOS_INSTALL_OK__ (status=$status)" >&2 fi cat "$tmp" >&2 rm -f "$tmp"; rm -rf "$data_dir"; rm -rf "$stage_dir" exit 1 fi # Human-only interactive REPL. LLM agents must not execute this command. repl: @just trapos --cli