fix(boot): clarify lifecycle orchestration
This commit is contained in:
parent
bf92a697c8
commit
41977eaf13
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
39
docs/adrs/adr-0020-boot-lifecycle.md
Normal file
39
docs/adrs/adr-0020-boot-lifecycle.md
Normal 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.
|
||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "TrapOS",
|
"name": "TrapOS",
|
||||||
"version": "0.9.5",
|
"version": "0.9.6",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"packages": [
|
"packages": [
|
||||||
"trapos"
|
"trapos"
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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": []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
@ -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)
|
||||||
|
repeat
|
||||||
shell.run("shell")
|
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
|
||||||
|
if action == "reboot" then
|
||||||
|
os.reboot()
|
||||||
|
else
|
||||||
os.shutdown()
|
os.shutdown()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
106
tests/boot.lua
106
tests/boot.lua
@ -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();
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
|
||||||
@ -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.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user