fix(boot): clarify lifecycle orchestration

This commit is contained in:
Guillaume ARM 2026-06-15 20:06:45 +02:00
parent bf92a697c8
commit 41977eaf13
16 changed files with 199 additions and 129 deletions

View File

@ -24,7 +24,7 @@ Use [`docs/README.md`](docs/README.md) as the entrypoint for CC:Tweaked, CraftOS
## Architecture ## Architecture
- `apis/eventloop.lua` is the single-threaded event loop around `os.pullEventRaw`; consider using it everywhere async behavior is needed. A handler that returns `api.STOP` auto-unregisters. - `apis/eventloop.lua` is the single-threaded event loop around `os.pullEventRaw`; consider using it everywhere async behavior is needed. A handler that returns `api.STOP` auto-unregisters.
- `startup/servers.lua` creates a single boot eventloop at `_G.bootEventLoop` and runs it alongside the shell via `parallel.waitForAny`. Servers register handlers on it and return; programs call services via `os.pullEvent` without touching the loop. See [ADR-0002](docs/adrs/adr-0002-eventloop-and-service-bus.md). - `startup/boot.lua` creates a single boot eventloop at `_G.bootEventLoop` and runs it alongside the shell via `parallel.waitForAny`. Servers register handlers on it and return; programs call services via `os.pullEvent` without touching the loop. See [ADR-0002](docs/adrs/adr-0002-eventloop-and-service-bus.md).
- `apis/libtest.lua` is the lightweight test helper used by scripts under `tests/`; `/programs/runtest.lua` discovers tests, renders suite output, and owns the `__TRAPOS_TEST_OK__` success marker. - `apis/libtest.lua` is the lightweight test helper used by scripts under `tests/`; `/programs/runtest.lua` discovers tests, renders suite output, and owns the `__TRAPOS_TEST_OK__` success marker.
- `apis/net.lua` is a service-name bus on a single channel (`10`). `net.serve(name, handler)` registers a server handler; `net.call(name, payload, { destId, timeout })` and `net.send(name, payload, { destId })` are the client surface. `require('/apis/net')()` returns a singleton bound to `_G.bootEventLoop` when present, otherwise an ephemeral instance. - `apis/net.lua` is a service-name bus on a single channel (`10`). `net.serve(name, handler)` registers a server handler; `net.call(name, payload, { destId, timeout })` and `net.send(name, payload, { destId })` are the client surface. `require('/apis/net')()` returns a singleton bound to `_G.bootEventLoop` when present, otherwise an ephemeral instance.
- A router (`/programs/router.lua`) must be running somewhere on the network; it registers a `modem_message` handler that stamps `routerId`, resolves label-addressed packets via a TTL map populated by `servers/net-registrar.lua`, and rebroadcasts. Without it, packets stay unrouted and consumers ignore them. - A router (`/programs/router.lua`) must be running somewhere on the network; it registers a `modem_message` handler that stamps `routerId`, resolves label-addressed packets via a TTL map populated by `servers/net-registrar.lua`, and rebroadcasts. Without it, packets stay unrouted and consumers ignore them.
@ -33,13 +33,13 @@ Use [`docs/README.md`](docs/README.md) as the entrypoint for CC:Tweaked, CraftOS
## Boot And Install ## Boot And Install
- `startup/servers.lua` creates the boot eventloop, runs autostart server files (which register handlers and return), then runs the shell and the eventloop in parallel via `parallel.waitForAny`. - `startup/boot.lua` creates the boot eventloop, runs autostart server files (which register handlers and return), then runs the shell and the eventloop in parallel via `parallel.waitForAny`.
- Preserve `periphemu` guards used for CraftOS-PC emulation; see [`docs/craftos_pc_glossary.md`](docs/craftos_pc_glossary.md) for upstream emulator references. - Preserve `periphemu` guards used for CraftOS-PC emulation; see [`docs/craftos_pc_glossary.md`](docs/craftos_pc_glossary.md) for upstream emulator references.
- Global CraftOS-PC save data lives in `~/Library/Application Support/CraftOS-PC` on macOS and `~/.local/share/craftos-pc` on Linux. Repo launchers use local state instead: `.craftos/` for TrapOS and `.craftos-vanilla/` for vanilla CraftOS-PC. See [`docs/install-craftos-pc.md`](docs/install-craftos-pc.md). - Global CraftOS-PC save data lives in `~/Library/Application Support/CraftOS-PC` on macOS and `~/.local/share/craftos-pc` on Linux. Repo launchers use local state instead: `.craftos/` for TrapOS and `.craftos-vanilla/` for vanilla CraftOS-PC. See [`docs/install-craftos-pc.md`](docs/install-craftos-pc.md).
- TrapOS ships as packages, each described by `packages/<name>/ccpm.json` (`name`, `version`, `dependencies`, `files`, `autostart`); `packages/index.json` lists them. Source files stay in place — descriptors only reference them. To ship a new file, add it to the right package's `files` (and `autostart` if it is a server). `packages/trapos/ccpm.json` is the full OS meta-package. See [ADR-0010](docs/adrs/adr-0010-ccpm-package-manager.md). - TrapOS ships as packages, each described by `packages/<name>/ccpm.json` (`name`, `version`, `dependencies`, `files`, `autostart`); `packages/index.json` lists them. Source files stay in place — descriptors only reference them. To ship a new file, add it to the right package's `files` (and `autostart` if it is a server). `packages/trapos/ccpm.json` is the full OS meta-package. See [ADR-0010](docs/adrs/adr-0010-ccpm-package-manager.md).
- `install-trapos.lua` is the one-time wget bootstrap (public entry point `https://trapos.trapcloud.fr/install`). It installs `trapos-core`/`ccpm`, seeds the default `guillaumearm/cc-libs` registry as a `gitea` registry on `git.trapcloud.fr` tracking `master` (the only channel), then runs `ccpm update` + `ccpm install trapos` and `trapos-postinstall`. Pass `--cpm-only` to stop after the ccpm bootstrap. - `install-trapos.lua` is the one-time wget bootstrap (public entry point `https://trapos.trapcloud.fr/install`). It installs `trapos-core`/`ccpm`, seeds the default `guillaumearm/cc-libs` registry as a `gitea` registry on `git.trapcloud.fr` tracking `master` (the only channel), then runs `ccpm update` + `ccpm install trapos` and `trapos-postinstall`. Pass `--cpm-only` to stop after the ccpm bootstrap.
- `ccpm` (in `trapos-core`) is the package manager: `apis/libccpm.lua` is the testable core (factory with injectable `http`/`stateDir`/`installRoot`), `programs/ccpm.lua` the CLI. State: `/trapos/ccpm.json` (registries), `/trapos/ccpm.lock.json` (installed packages), and `/trapos/ccpm.cache.json` (available packages from `ccpm update`). Never use the word "manifest" in ccpm — it is reserved for the OS manifest. - `ccpm` (in `trapos-core`) is the package manager: `apis/libccpm.lua` is the testable core (factory with injectable `http`/`stateDir`/`installRoot`), `programs/ccpm.lua` the CLI. State: `/trapos/ccpm.json` (registries), `/trapos/ccpm.lock.json` (installed packages), and `/trapos/ccpm.cache.json` (available packages from `ccpm update`). Never use the word "manifest" in ccpm — it is reserved for the OS manifest.
- Add new servers to `startup/servers.lua` as needed. - Add new servers to the relevant package `autostart` list as needed.
## Conventions ## Conventions

View File

@ -70,7 +70,7 @@ supported. State lives in `/trapos/ccpm.json` (registries),
- `/apis/net`: an API to simplify sending and receiving routed messages, based on the `eventloop` library. - `/apis/net`: an API to simplify sending and receiving routed messages, based on the `eventloop` library.
## Servers ## Servers
Servers listed in `manifest.autostart` are launched at boot by `startup/servers.lua`. Servers listed in `manifest.autostart` are launched at boot by `startup/boot.lua`.
- `/servers/ping-server`: allows a machine to respond to a `ping` command. - `/servers/ping-server`: allows a machine to respond to a `ping` command.

View File

@ -18,5 +18,6 @@ Future ADRs can reuse the shape of the existing files when it is useful.
- [`adr-0017-mcp-remote-lua-execution.md`](adr-0017-mcp-remote-lua-execution.md) — MCP `exec-lua` remote execution for linked ComputerCraft computers. - [`adr-0017-mcp-remote-lua-execution.md`](adr-0017-mcp-remote-lua-execution.md) — MCP `exec-lua` remote execution for linked ComputerCraft computers.
- [`adr-0018-justfile-organization.md`](adr-0018-justfile-organization.md) — Root Justfile split with imports while preserving flat recipe names. - [`adr-0018-justfile-organization.md`](adr-0018-justfile-organization.md) — Root Justfile split with imports while preserving flat recipe names.
- [`adr-0019-mcp-over-sandbox-gateway.md`](adr-0019-mcp-over-sandbox-gateway.md) — MCP tools migrated onto the trap-sandbox gateway; reactive `libsandbox.serve`; `traposId` addressing. - [`adr-0019-mcp-over-sandbox-gateway.md`](adr-0019-mcp-over-sandbox-gateway.md) — MCP tools migrated onto the trap-sandbox gateway; reactive `libsandbox.serve`; `traposId` addressing.
- [`adr-0020-boot-lifecycle.md`](adr-0020-boot-lifecycle.md) — TrapOS boot orchestrator, session-end policy, installer reboot path, and stale startup-file self-healing.
Gaps in numbering (0003, 0004, 0006, 0008, 0009, 0012, 0013, 0014, 0015) are records that were either superseded by later decisions or consolidated into the surviving ADRs above. Gaps in numbering (0003, 0004, 0006, 0008, 0009, 0012, 0013, 0014, 0015) are records that were either superseded by later decisions or consolidated into the surviving ADRs above.

View File

@ -0,0 +1,39 @@
# ADR-0020: TrapOS Boot Lifecycle
## Status
Accepted.
## Context
CraftOS runs `/startup/*` files inside its own shell and expects them to return. TrapOS needs a long-lived init-like process instead: it creates `_G.bootEventLoop`, starts autostart servers, and keeps the user shell and event loop alive together with `parallel.waitForAny`.
The old `startup/servers.lua` name hid that role, and the installer executed it directly after install. That produced a nested boot path where the installer shell, the CraftOS shell, and the TrapOS shell were all active, and where the orchestrator's shutdown behavior could stop the machine before the installer restored its working directory.
## Decision
TrapOS has one startup orchestrator: `startup/boot.lua`. It is treated as the init process for a TrapOS session.
`programs/motd.lua` owns the banner and is invoked by `startup/boot.lua` before autostart servers run. `/startup/` should contain only the boot orchestrator for the TrapOS boot package.
Session-end behavior is controlled by `trapos.on_session_end`:
- `shutdown` is the default and is also used for unset or invalid values.
- `reboot` reboots when the TrapOS shell exits.
- `relaunch` starts a fresh shell when the current one exits, re-reading the setting after each shell session so the policy can be changed before exiting again.
The previous `trapos.shutdown_on_shell_exit` setting is replaced without a migration shim. It was not documented or persisted as package state.
The installer no longer executes the boot orchestrator. Full installs run `ccpm update`, `ccpm install trapos`, `trapos-postinstall`, restore the previous shell directory, print a reboot message, and call `os.reboot()`. `--cpm-only` still returns without rebooting.
`startup/boot.lua` self-heals the known stale startup files left by package upgrades from older versions: `/startup/servers.lua` and `/startup/motd.lua`. This prevents two startup orchestrators from coexisting after `ccpm upgrade` installs the renamed files but does not prune removed package files.
## Consequences
Fresh install and normal power-on use the same boot path.
Exiting the shell never drops to a bare CraftOS prompt with no TrapOS event loop. The default remains shutdown, while reboot and relaunch are explicit local policies.
Upgraded machines converge on the new `/startup/` layout in one boot.
General pruning of removed package files during `ccpm install --force` or `ccpm upgrade` remains future work.

View File

@ -6,7 +6,7 @@ Upstream: <https://www.craftos-pc.cc/docs/periphemu>. See also the [CraftOS-PC g
## Usage in this repo ## Usage in this repo
`startup/servers.lua` attaches a single top modem when running under CraftOS-PC: `startup/boot.lua` attaches a single top modem when running under CraftOS-PC:
```lua ```lua
if periphemu then if periphemu then
@ -23,7 +23,7 @@ periphemu create 10 computer # spawn router VM (id 10)
periphemu create 1 computer # spawn a client VM periphemu create 1 computer # spawn a client VM
``` ```
Each call opens a new CraftOS-PC window (GUI mode). Keep this out of `startup/servers.lua` — persistent emulated peers clutter the desktop and leave stale state under `~/Library/Application Support/CraftOS-PC/computer/<id>/`. Each call opens a new CraftOS-PC window (GUI mode). Keep this out of `startup/boot.lua` — persistent emulated peers clutter the desktop and leave stale state under `~/Library/Application Support/CraftOS-PC/computer/<id>/`.
## API ## API

View File

@ -225,9 +225,7 @@ end
shell.execute('ccpm', 'update'); shell.execute('ccpm', 'update');
shell.execute('ccpm', 'install', 'trapos'); shell.execute('ccpm', 'install', 'trapos');
shell.execute('trapos-postinstall'); shell.execute('trapos-postinstall');
if fs.exists('/startup/servers.lua') then
shell.execute('/startup/servers.lua');
end
shell.setDir(previousDir); shell.setDir(previousDir);
print();
print('=> Rebooting...');
os.reboot();

View File

@ -1,6 +1,6 @@
{ {
"name": "TrapOS", "name": "TrapOS",
"version": "0.9.5", "version": "0.9.6",
"branch": "master", "branch": "master",
"packages": [ "packages": [
"trapos" "trapos"

View File

@ -2,12 +2,12 @@
"packages": { "packages": {
"trapos-core": "0.6.3", "trapos-core": "0.6.3",
"trapos-test": "0.2.1", "trapos-test": "0.2.1",
"trapos-boot": "0.3.3", "trapos-boot": "0.4.0",
"trapos-net": "0.3.0", "trapos-net": "0.3.0",
"trapos-ui": "0.2.2", "trapos-ui": "0.2.2",
"trapos-ai": "0.7.0", "trapos-ai": "0.7.0",
"trapos-sandbox": "0.3.0", "trapos-sandbox": "0.3.0",
"trapos-sandbox-legacy": "0.3.0", "trapos-sandbox-legacy": "0.3.0",
"trapos": "0.9.5" "trapos": "0.9.6"
} }
} }

View File

@ -1,11 +1,11 @@
{ {
"name": "trapos-boot", "name": "trapos-boot",
"version": "0.3.3", "version": "0.4.0",
"description": "TrapOS boot: startup MOTD and autostart server launcher", "description": "TrapOS boot: startup MOTD and autostart server launcher",
"dependencies": ["trapos-core"], "dependencies": ["trapos-core"],
"files": [ "files": [
"startup/motd.lua", "programs/motd.lua",
"startup/servers.lua" "startup/boot.lua"
], ],
"autostart": [] "autostart": []
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "trapos", "name": "trapos",
"version": "0.9.5", "version": "0.9.6",
"description": "TrapOS full install meta-package", "description": "TrapOS full install meta-package",
"dependencies": [ "dependencies": [
"trapos-boot", "trapos-boot",

View File

@ -1,8 +1,5 @@
local LOCAL_MANIFEST_PATH = "/trapos/manifest.json" local LOCAL_MANIFEST_PATH = "/trapos/manifest.json"
-- I think this should be moved in `programs`
-- then we will run this motd program from startup/boot.lua
local function readLocalManifest() local function readLocalManifest()
if not fs.exists(LOCAL_MANIFEST_PATH) then if not fs.exists(LOCAL_MANIFEST_PATH) then
return nil return nil

View File

@ -1,9 +1,8 @@
local createEventLoop = require("/apis/eventloop") local createEventLoop = require("/apis/eventloop")
local LOCAL_MANIFEST_PATH = "/trapos/manifest.json" local LOCAL_MANIFEST_PATH = "/trapos/manifest.json"
local SHUTDOWN_ON_SHELL_EXIT_SETTING = "trapos.shutdown_on_shell_exit" local SESSION_END_SETTING = "trapos.on_session_end"
local STALE_STARTUP_FILES = { "/startup/servers.lua", "/startup/motd.lua" }
-- OK this file should not be named servers.lua we could rename it boot.lua and this could be the only file in this startup directory so the whole startup process will be orchestrated from there.
local function readLocalManifest() local function readLocalManifest()
if not fs.exists(LOCAL_MANIFEST_PATH) then if not fs.exists(LOCAL_MANIFEST_PATH) then
@ -23,14 +22,25 @@ end
local function init() local function init()
shell.setPath(shell.path() .. ":/programs") shell.setPath(shell.path() .. ":/programs")
for _, path in ipairs(STALE_STARTUP_FILES) do
if fs.exists(path) then
fs.delete(path)
end
end
end end
local function shouldShutdownOnShellExit() local function sessionEndAction()
return settings.get(SHUTDOWN_ON_SHELL_EXIT_SETTING) ~= false local action = settings.get(SESSION_END_SETTING)
if action == "reboot" or action == "relaunch" then
return action
end
return "shutdown"
end end
init() init()
shell.run("/programs/motd.lua")
if periphemu then if periphemu then
periphemu.create("top", "modem") periphemu.create("top", "modem")
end end
@ -38,10 +48,14 @@ end
_G.bootEventLoop = createEventLoop() _G.bootEventLoop = createEventLoop()
local shellExited = false local shellExited = false
local action = "shutdown"
local function shellFn() local function shellFn()
os.sleep(0.1) os.sleep(0.1)
shell.run("shell") repeat
shell.run("shell")
action = sessionEndAction()
until action ~= "relaunch"
shellExited = true shellExited = true
end end
@ -65,6 +79,10 @@ end
parallel.waitForAny(shellFn, eventLoopFn) parallel.waitForAny(shellFn, eventLoopFn)
if shellExited and shouldShutdownOnShellExit() then if shellExited then
os.shutdown() if action == "reboot" then
os.reboot()
else
os.shutdown()
end
end end

View File

@ -1,10 +1,110 @@
-- Basic integration test: prove CraftOS-PC boots and can run a test script.
local createLibTest = require('/apis/libtest'); local createLibTest = require('/apis/libtest');
local testlib = createLibTest({ ... }); local testlib = createLibTest({ ... });
testlib.test('CraftOS-PC boots and runs Lua', function() local function runStartupWithStubs(settingValues)
testlib.assertTrue(true); settingValues = settingValues or {};
local settingIndex = 0;
local calls = {
rebooted = false,
shutdown = false,
shellRuns = {},
prints = {},
};
local env = setmetatable({
require = require,
fs = {
exists = function(path)
return path ~= '/trapos/manifest.json' and fs.exists(path);
end,
delete = function() end,
},
os = {
reboot = function()
calls.rebooted = true;
end,
shutdown = function()
calls.shutdown = true;
end,
sleep = function() end,
},
settings = {
get = function(key)
if key ~= 'trapos.on_session_end' then return nil; end
settingIndex = settingIndex + 1;
if type(settingValues) == 'table' then
return settingValues[settingIndex] or settingValues[#settingValues];
end
return settingValues;
end,
},
parallel = {
waitForAny = function(firstFn)
firstFn();
end,
},
print = function(message)
calls.prints[#calls.prints + 1] = tostring(message);
end,
shell = {
path = function()
return '/rom/programs';
end,
run = function(program)
calls.shellRuns[#calls.shellRuns + 1] = program;
return true;
end,
setPath = function() end,
},
}, { __index = _G });
env._G = env;
local chunk, loadErr = loadfile('/startup/boot.lua', 't', env);
if not chunk then
error(loadErr, 0);
end
local ok, err = pcall(chunk);
if not ok then
error(err, 0);
end
return calls;
end
testlib.test('shell exit shuts down by default', function()
local calls = runStartupWithStubs();
testlib.assertEquals(calls.shellRuns[1], '/programs/motd.lua');
testlib.assertEquals(calls.shellRuns[2], 'shell');
testlib.assertEquals(calls.shutdown, true);
testlib.assertEquals(calls.rebooted, false);
for _, message in ipairs(calls.prints) do
testlib.assertTrue(
not string.find(message, 'Servers stopped', 1, true),
'startup should not print a forced reboot message'
);
end
end);
testlib.test('shell exit reboots when policy is reboot', function()
local calls = runStartupWithStubs('reboot');
testlib.assertEquals(calls.shellRuns[1], '/programs/motd.lua');
testlib.assertEquals(calls.shellRuns[2], 'shell');
testlib.assertEquals(calls.shutdown, false);
testlib.assertEquals(calls.rebooted, true);
end);
testlib.test('shell exit relaunches until policy changes', function()
local calls = runStartupWithStubs({ 'relaunch', 'shutdown' });
testlib.assertEquals(calls.shellRuns[1], '/programs/motd.lua');
testlib.assertEquals(calls.shellRuns[2], 'shell');
testlib.assertEquals(calls.shellRuns[3], 'shell');
testlib.assertEquals(calls.shutdown, true);
testlib.assertEquals(calls.rebooted, false);
end); end);
testlib.run(); testlib.run();

View File

@ -70,11 +70,18 @@ local function runInstall(root, args)
executes = {}, executes = {},
path = '/rom/programs', path = '/rom/programs',
dir = '/', dir = '/',
rebooted = false,
}; };
local fsApi = fakeFs(root); local fsApi = fakeFs(root);
local env = setmetatable({ local env = setmetatable({
fs = fsApi, fs = fsApi,
http = fakeHttp(routes), http = fakeHttp(routes),
os = {
reboot = function()
calls.rebooted = true;
calls.executes[#calls.executes + 1] = { program = '__reboot__', args = {} };
end,
},
print = function() end, print = function() end,
read = function() return 'yes'; end, read = function() return 'yes'; end,
write = function() end, write = function() end,
@ -161,16 +168,16 @@ testlib.test('default install runs update, install trapos and postinstall', func
testlib.assertTrue(findExecute(calls, 'trapos-postinstall')); testlib.assertTrue(findExecute(calls, 'trapos-postinstall'));
end); end);
testlib.test('default install runs postinstall before startup servers', function() testlib.test('default install runs postinstall before reboot', function()
local root = '/install-trapos-test/order'; local root = '/install-trapos-test/order';
local calls = runInstall(root, {}); local calls = runInstall(root, {});
local postinstallIndex = findExecuteIndex(calls, 'trapos-postinstall'); local postinstallIndex = findExecuteIndex(calls, 'trapos-postinstall');
local startupIndex = findExecuteIndex(calls, '/startup/servers.lua'); local rebootIndex = findExecuteIndex(calls, '__reboot__');
testlib.assertTrue(postinstallIndex ~= nil, 'postinstall was not run'); testlib.assertTrue(postinstallIndex ~= nil, 'postinstall was not run');
if startupIndex ~= nil then testlib.assertTrue(rebootIndex ~= nil, 'reboot was not run');
testlib.assertTrue(postinstallIndex < startupIndex, 'postinstall must run before startup servers'); testlib.assertTrue(postinstallIndex < rebootIndex, 'postinstall must run before reboot');
end testlib.assertEquals(calls.rebooted, true);
end); end);
testlib.test('--cpm-only stops after the ccpm bootstrap', function() testlib.test('--cpm-only stops after the ccpm bootstrap', function()
@ -181,6 +188,7 @@ testlib.test('--cpm-only stops after the ccpm bootstrap', function()
testlib.assertTrue(not findExecute(calls, 'ccpm', 'update')); testlib.assertTrue(not findExecute(calls, 'ccpm', 'update'));
testlib.assertTrue(not findExecute(calls, 'ccpm', 'install')); testlib.assertTrue(not findExecute(calls, 'ccpm', 'install'));
testlib.assertTrue(not findExecute(calls, 'trapos-postinstall')); testlib.assertTrue(not findExecute(calls, 'trapos-postinstall'));
testlib.assertEquals(calls.rebooted, false);
end); end);
testlib.run(); testlib.run();

View File

@ -1,91 +0,0 @@
local createLibTest = require('/apis/libtest');
local testlib = createLibTest({ ... });
local function runStartupWithStubs(settingsValues)
settingsValues = settingsValues or {};
local calls = {
rebooted = false,
shutdown = false,
shellRuns = {},
prints = {},
};
local env = setmetatable({
require = require,
fs = {
exists = function(path)
return path ~= '/trapos/manifest.json' and fs.exists(path);
end,
},
os = {
reboot = function()
calls.rebooted = true;
end,
shutdown = function()
calls.shutdown = true;
end,
sleep = function() end,
},
settings = {
get = function(key)
return settingsValues[key];
end,
},
parallel = {
waitForAny = function(firstFn)
firstFn();
end,
},
print = function(message)
calls.prints[#calls.prints + 1] = tostring(message);
end,
shell = {
path = function()
return '/rom/programs';
end,
run = function(program)
calls.shellRuns[#calls.shellRuns + 1] = program;
return true;
end,
setPath = function() end,
},
}, { __index = _G });
env._G = env;
local chunk, loadErr = loadfile('/startup/servers.lua', 't', env);
if not chunk then
error(loadErr, 0);
end
local ok, err = pcall(chunk);
if not ok then
error(err, 0);
end
return calls;
end
testlib.test('shell exit shuts down by default', function()
local calls = runStartupWithStubs();
testlib.assertEquals(calls.shellRuns[1], 'shell');
testlib.assertEquals(calls.shutdown, true);
testlib.assertEquals(calls.rebooted, false);
for _, message in ipairs(calls.prints) do
testlib.assertTrue(
not string.find(message, 'Servers stopped', 1, true),
'startup should not print a forced reboot message'
);
end
end);
testlib.test('shell exit does not shut down when setting is disabled', function()
local calls = runStartupWithStubs({ ['trapos.shutdown_on_shell_exit'] = false });
testlib.assertEquals(calls.shellRuns[1], 'shell');
testlib.assertEquals(calls.shutdown, false);
testlib.assertEquals(calls.rebooted, false);
end);
testlib.run();

View File

@ -1,4 +1,4 @@
-- Full-boot e2e: replicate the real boot path (startup/servers.lua) closely enough -- Full-boot e2e: replicate the real boot path (startup/boot.lua) closely enough
-- to prove the whole stack works end to end. Sets the sandbox.* settings, creates -- to prove the whole stack works end to end. Sets the sandbox.* settings, creates
-- the global event loop, runs the REAL servers/sandbox.lua daemon, then under -- the global event loop, runs the REAL servers/sandbox.lua daemon, then under
-- parallel waits for the connection and runs the REAL `sandbox health` program. -- parallel waits for the connection and runs the REAL `sandbox health` program.