feat(installer): install full trapos by default

This commit is contained in:
Guillaume ARM 2026-06-15 00:57:10 +02:00
parent f8e15d2267
commit e10a1d5976
16 changed files with 247 additions and 177 deletions

View File

@ -37,13 +37,13 @@ Use [`docs/README.md`](docs/README.md) as the entrypoint for CC:Tweaked, CraftOS
- 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-ccpm.lua` is the one-time wget bootstrap. It installs only `trapos-core`/`ccpm`, seeds the default `guillaumearm/cc-libs` registry as a `gitea` registry on `git.trapcloud.fr` tracking `master` (or `next` with `--beta`), and tells users to run `ccpm update` then `ccpm install trapos`. The legacy `github` registry type still resolves but is deprecated. - `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. The legacy `github` registry type still resolves but is deprecated.
- `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 `startup/servers.lua` as needed.
## Conventions ## Conventions
- Bump the owning `packages/<name>/ccpm.json` version (and mirror it in `packages/index.json`) when changing module behavior; programs report it at runtime via `require('/apis/libversion')().forSelf()`. `install-ccpm.lua` is the only file that still carries its own `_VERSION` because it is the wget bootstrap and lives outside the package system. - Bump the owning `packages/<name>/ccpm.json` version (and mirror it in `packages/index.json`) when changing module behavior; programs report it at runtime via `require('/apis/libversion')().forSelf()`. `install-trapos.lua` is the only file that still carries its own `_VERSION` because it is the wget bootstrap and lives outside the package system.
- Programs support `-version`/`--version` and `-help`/`--help`; router also supports `-silent`/`--silent`. - Programs support `-version`/`--version` and `-help`/`--help`; router also supports `-silent`/`--silent`.
- French or English comments are fine; match surrounding code. - French or English comments are fine; match surrounding code.
- Commit messages use lightweight conventional style: `topic(scope): description` or `topic: description`. - Commit messages use lightweight conventional style: `topic(scope): description` or `topic: description`.

View File

@ -4,52 +4,47 @@ A small in-game operating system for ComputerCraft / CC:Tweaked, built around a
## Installation ## Installation
Install `ccpm` first. This is the only step that needs `wget` with a URL: One command installs everything — it bootstraps `ccpm`, syncs the default
registry (`guillaumearm/cc-libs`), and installs the full TrapOS:
``` ```
wget run https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/master/install-ccpm.lua wget run https://trapos.trapcloud.fr/install
``` ```
Then sync the default registry (`guillaumearm/cc-libs`) and install TrapOS: When it finishes, run `sandbox --help` to get started, or `trapos-upgrade` to
update later.
Install only the package manager (no full OS) with `--cpm-only`, then
cherry-pick packages yourself:
``` ```
wget run https://trapos.trapcloud.fr/install --cpm-only
ccpm update ccpm update
ccpm install trapos ccpm install trapos-net
ccpm install trapos-ui
``` ```
Install individual packages instead if you want to cherry-pick: After install, every boot shows a colored MOTD with the installed version.
```
> ccpm install trapos-net
> ccpm install trapos-ui
```
Install `ccpm` from the beta branch (one-time opt-in, asks for confirmation):
```
wget run https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/install-ccpm.lua --beta
```
Once `ccpm` is installed from beta, the default registry tracks `next`; `ccpm update` and `ccpm upgrade` keep using that branch.
After install, every boot shows a colored MOTD with the installed version and branch (lime for stable, orange + `[BETA]` for beta).
## Packages ## Packages
TrapOS is split into packages, each described by a `packages/<name>/ccpm.json`: TrapOS is split into packages, each described by a `packages/<name>/ccpm.json`:
- `trapos-core`: the package manager (`ccpm`), event loop, `upgrade`, `events`. - `trapos-core`: the package manager (`ccpm`), event loop, `trapos-upgrade`, `trapos-postinstall`, `events`.
- `trapos-test`: the test framework (`libtest`) and suite runner (`runtest`). - `trapos-test`: the test framework (`libtest`) and suite runner (`runtest`).
- `trapos-boot`: the startup MOTD and autostart server launcher. - `trapos-boot`: the startup MOTD and autostart server launcher.
- `trapos-net`: routed modem networking (`net`, `router`, `ping`, `ping-server`). - `trapos-net`: routed modem networking (`net`, `router`, `ping`, `ping-server`).
- `trapos-ui`: the terminal UI toolkit (`libtui`, `tuidemo`). - `trapos-ui`: the terminal UI toolkit (`libtui`, `tuidemo`).
- `trapos-ai`: the AI client for `opencode serve`. - `trapos-ai`: the AI client for `opencode serve`.
- `trapos`: full TrapOS meta-package (`trapos-boot`, `trapos-net`, `trapos-ui`, `trapos-test`, and `trapos-ai` during beta). - `trapos`: full TrapOS meta-package (`trapos-boot`, `trapos-net`, `trapos-ui`, `trapos-test`, `trapos-ai`, `trapos-sandbox`).
The `trapos` meta-package is the user-facing full install. Package descriptors list The `trapos` meta-package is the user-facing full install. Package descriptors list
files and autostart servers; installed state is tracked under `/trapos`. files and autostart servers; installed state is tracked under `/trapos`.
## ccpm ## ccpm
`ccpm` is the TrapOS package manager. `install-ccpm.lua` installs it by installing `ccpm` is the TrapOS package manager. `install-trapos.lua` installs it by installing
the required `trapos-core` package and configures the default registry the required `trapos-core` package and configures the default registry
(`guillaumearm/cc-libs`). (`guillaumearm/cc-libs`), then (unless `--cpm-only`) installs the full `trapos`
meta-package.
``` ```
ccpm install <package> ccpm reinstall <package> ccpm uninstall <package> ccpm install <package> ccpm reinstall <package> ccpm uninstall <package>
@ -82,7 +77,8 @@ Servers listed in `manifest.autostart` are launched at boot by `startup/servers.
- `router`: routes messages. You need to set up a router to use all `apis/net`-based programs and libraries. - `router`: routes messages. You need to set up a router to use all `apis/net`-based programs and libraries.
- `ping`: pings machines using `apis/net`. - `ping`: pings machines using `apis/net`.
- `events`: emits and logs computer events. - `events`: emits and logs computer events.
- `upgrade`: alias for `ccpm upgrade`. - `trapos-upgrade`: runs `ccpm update` then `ccpm upgrade`; pass `--reboot`/`-r` to reboot after upgrading.
- `trapos-postinstall`: prints the post-install welcome and next steps (run automatically by the installer).
## Development ## Development
See [DEVELOPMENT.md](./DEVELOPMENT.md) for local development installation. See [DEVELOPMENT.md](./DEVELOPMENT.md) for local development installation.

View File

@ -56,7 +56,7 @@ Two safe-exec recipes wrap raw `--headless --exec` with `xpcall`, call `os.shutd
- `just craftos-exec '<lua>'` — probe against **vanilla CraftOS-PC**. No mounts, no startup scripts. Use this when the question is purely about CC:Tweaked behavior and TrapOS files would be a distraction, or to confirm a behavior is upstream rather than something the dev env layered on. - `just craftos-exec '<lua>'` — probe against **vanilla CraftOS-PC**. No mounts, no startup scripts. Use this when the question is purely about CC:Tweaked behavior and TrapOS files would be a distraction, or to confirm a behavior is upstream rather than something the dev env layered on.
- `just trapos-install` — drive the full real install (`install-ccpm.lua` → `ccpm update``ccpm install trapos`) on a fresh ephemeral state. This is the probe to run when changing anything in the install path itself. - `just trapos-install` — drive the full real install (`install-trapos.lua`, which bootstraps ccpm then runs `ccpm update` + `ccpm install trapos` itself) on a fresh ephemeral state. This is the probe to run when changing anything in the install path itself.
Conventions: Conventions:
@ -73,7 +73,7 @@ Conventions:
- `just repl` is a human-only interactive wrapper around `just trapos --cli`; automation and LLM agents must use `just trapos-exec '<lua>'` or `just craftos-exec '<lua>'` instead. - `just repl` is a human-only interactive wrapper around `just trapos --cli`; automation and LLM agents must use `just trapos-exec '<lua>'` or `just craftos-exec '<lua>'` instead.
- `craftos` (GUI) now opens a single unlabelled `Computer 0` window with a top modem attached. Cross-machine testing requires an explicit `periphemu create` call from the shell rather than being implicit on boot — one extra command when you need a peer, no surprise windows or persisted ghost VMs. - `craftos` (GUI) now opens a single unlabelled `Computer 0` window with a top modem attached. Cross-machine testing requires an explicit `periphemu create` call from the shell rather than being implicit on boot — one extra command when you need a peer, no surprise windows or persisted ghost VMs.
- `.craftos-vanilla/` is in `.gitignore` alongside `.craftos/`. - `.craftos-vanilla/` is in `.gitignore` alongside `.craftos/`.
- `just trapos-install` is *not* part of `just ci`: it is network-dependent and slower than `just test`. Run it manually when touching `install-ccpm.lua` or ccpm package descriptors. - `just trapos-install` is *not* part of `just ci`: it is network-dependent and slower than `just test`. Run it manually when touching `install-trapos.lua` or ccpm package descriptors.
- Higher CraftOS-PC invocation traffic during agent sessions; cheap enough that this is a good trade. - Higher CraftOS-PC invocation traffic during agent sessions; cheap enough that this is a good trade.
- The harness version becomes a project-level concern. When CC:Tweaked ships breaking changes that require a newer CraftOS-PC build, we bump the minimum version in [`docs/install-craftos-pc.md`](../install-craftos-pc.md) and `check-craftos` keeps contributors honest. - The harness version becomes a project-level concern. When CC:Tweaked ships breaking changes that require a newer CraftOS-PC build, we bump the minimum version in [`docs/install-craftos-pc.md`](../install-craftos-pc.md) and `check-craftos` keeps contributors honest.
- No CI integration yet. Running CraftOS-PC headless in GitHub Actions is feasible (the AppImage works on Ubuntu runners) but is out of scope; the contract is local-only for now. - No CI integration yet. Running CraftOS-PC headless in GitHub Actions is feasible (the AppImage works on Ubuntu runners) but is out of scope; the contract is local-only for now.

View File

@ -8,6 +8,19 @@ Accepted
2026-06-08 2026-06-08
## Update (2026-06-15)
The beta/stable two-channel model has been retired now that `next` is merged into
`master`: `master` is the only channel. The `wget` bootstrap is renamed
`install-ccpm.lua``install-trapos.lua` (public entry point
`https://trapos.trapcloud.fr/install`) and now installs the **full OS** in one shot —
it bootstraps `ccpm`, then runs `ccpm update` + `ccpm install trapos` and a
`trapos-postinstall` welcome. Pass `--cpm-only` to stop after the ccpm bootstrap. The
persisted `branch` field is always `master`; the `--beta` opt-in and its confirmation
prompt are gone. The `upgrade` program is replaced by `trapos-upgrade` (`ccpm update` +
`ccpm upgrade`, with `--reboot`/`-r`). References to `--beta`/`install-ccpm.lua` below
are retained as historical context.
## Context ## Context
The previous install flow (a `LIST_FILES` table inside `install.lua` and a single flat The previous install flow (a `LIST_FILES` table inside `install.lua` and a single flat

View File

@ -7,9 +7,7 @@ Follow this order while playing.
On the ComputerCraft computer: On the ComputerCraft computer:
```sh ```sh
wget run https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/install-ccpm.lua --beta wget run https://trapos.trapcloud.fr/install
ccpm update
ccpm install trapos
``` ```
If the computer asks to reboot, reboot it. If the computer asks to reboot, reboot it.

View File

@ -90,7 +90,7 @@ On macOS, use the `--rom` form shown above if the command fails with `Could not
`just craftos` launches a vanilla CraftOS-PC with no mounts, persistent under `.craftos-vanilla/` (also gitignored). Use it when a probe should not see TrapOS files — for example, to confirm a behavior is upstream rather than TrapOS-specific. `just craftos` launches a vanilla CraftOS-PC with no mounts, persistent under `.craftos-vanilla/` (also gitignored). Use it when a probe should not see TrapOS files — for example, to confirm a behavior is upstream rather than TrapOS-specific.
`just trapos-install` exercises the real ccpm bootstrap (`install-ccpm.lua` → `ccpm update` `ccpm install trapos`) end-to-end on a fresh, ephemeral CraftOS-PC state. Network-dependent and slower than `just test`, so not part of `just ci`. Override the watchdog with `TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS` (default `60`). `just trapos-install` exercises the real one-shot installer (`install-trapos.lua`, which bootstraps ccpm then runs `ccpm update` + `ccpm install trapos`) end-to-end on a fresh, ephemeral CraftOS-PC state. Network-dependent and slower than `just test`, so not part of `just ci`. Override the watchdog with `TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS` (default `60`).
For automated probes, use the safe wrappers. They mount the right environment, For automated probes, use the safe wrappers. They mount the right environment,
shut down the machine after completion or Lua errors, and kill the host process shut down the machine after completion or Lua errors, and kill the host process

View File

@ -14,12 +14,10 @@ CC Computer
## 0. Install TrapOS and the AI package ## 0. Install TrapOS and the AI package
On a fresh CC computer (beta branch): On a fresh CC computer:
``` ```
wget run https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/install-ccpm.lua --beta wget run https://trapos.trapcloud.fr/install
ccpm update
ccpm install trapos
``` ```
## 1. Start `opencode serve` ## 1. Start `opencode serve`

View File

@ -1,18 +1,18 @@
local _VERSION = '5.1.0'; local _VERSION = '6.0.0';
local REPO_BASE = 'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/'; local REPO_BASE = 'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/master/';
local LOCAL_STATE_DIR = '/trapos'; local LOCAL_STATE_DIR = '/trapos';
local LOCAL_MANIFEST_PATH = '/trapos/manifest.json'; local LOCAL_MANIFEST_PATH = '/trapos/manifest.json';
local LOCAL_CONFIG_PATH = '/trapos/ccpm.json'; local LOCAL_CONFIG_PATH = '/trapos/ccpm.json';
local LOCAL_LOCK_PATH = '/trapos/ccpm.lock.json'; local LOCAL_LOCK_PATH = '/trapos/ccpm.lock.json';
local DEFAULT_REGISTRY_NAME = 'guillaumearm/cc-libs'; local DEFAULT_REGISTRY_NAME = 'guillaumearm/cc-libs';
local INSTALL_BRANCH = 'master';
local function printUsage() local function printUsage()
print('install-ccpm usage:'); print('install-trapos usage:');
print(); print();
print('\t\twget run <install-url>'); print('\t\twget run <install-url>');
print('\t\twget run <install-url> --beta'); print('\t\twget run <install-url> --cpm-only');
print('\t\twget run <install-url> --stable');
end end
local function readJsonFile(path) local function readJsonFile(path)
@ -34,17 +34,6 @@ local function writeJsonFile(path, value)
return true; return true;
end end
local function confirmBeta()
print();
print('You are about to install the BETA branch (next).');
print('Beta builds may be unstable. Continue? (y/N)');
write('> ');
local answer = read();
if not answer then return false; end
answer = answer:lower();
return answer == 'y' or answer == 'yes';
end
local function fetchJson(url) local function fetchJson(url)
local response = http.get(url); local response = http.get(url);
if not response then return nil end if not response then return nil end
@ -54,12 +43,12 @@ local function fetchJson(url)
return textutils.unserializeJSON(body); return textutils.unserializeJSON(body);
end end
local function fetchManifest(branch) local function fetchManifest()
return fetchJson(REPO_BASE .. branch .. '/manifest.json'); return fetchJson(REPO_BASE .. 'manifest.json');
end end
local function fetchDescriptor(branch, pkg) local function fetchDescriptor(pkg)
return fetchJson(REPO_BASE .. branch .. '/packages/' .. pkg .. '/ccpm.json'); return fetchJson(REPO_BASE .. 'packages/' .. pkg .. '/ccpm.json');
end end
local function ensureProgramsPath() local function ensureProgramsPath()
@ -76,7 +65,7 @@ end
-- Resolve a list of package names + their dependencies into install order -- Resolve a list of package names + their dependencies into install order
-- (deps first). Returns an ordered list of descriptors or nil, err. -- (deps first). Returns an ordered list of descriptors or nil, err.
local function resolvePackages(branch, names) local function resolvePackages(names)
local ordered = {}; local ordered = {};
local state = {}; -- name -> 'visiting' | 'done' local state = {}; -- name -> 'visiting' | 'done'
@ -86,7 +75,7 @@ local function resolvePackages(branch, names)
return false, 'dependency cycle detected at ' .. name; return false, 'dependency cycle detected at ' .. name;
end end
state[name] = 'visiting'; state[name] = 'visiting';
local desc = fetchDescriptor(branch, name); local desc = fetchDescriptor(name);
if not desc then if not desc then
return false, 'package not found: ' .. name; return false, 'package not found: ' .. name;
end end
@ -107,79 +96,57 @@ local function resolvePackages(branch, names)
end end
-- Seed/refresh the default ccpm registry so it tracks the install branch. -- Seed/refresh the default ccpm registry so it tracks the install branch.
local function seedCcpmConfig(branch) local function seedCcpmConfig()
local cfg = readJsonFile(LOCAL_CONFIG_PATH) or { registries = {} }; local cfg = readJsonFile(LOCAL_CONFIG_PATH) or { registries = {} };
cfg.registries = cfg.registries or {}; cfg.registries = cfg.registries or {};
local found = false; local found = false;
for _, r in ipairs(cfg.registries) do for _, r in ipairs(cfg.registries) do
if r.name == DEFAULT_REGISTRY_NAME then if r.name == DEFAULT_REGISTRY_NAME then
r.type = 'gitea'; r.type = 'gitea';
r.branch = branch; r.branch = INSTALL_BRANCH;
found = true; found = true;
end end
end end
if not found then if not found then
table.insert(cfg.registries, 1, { name = DEFAULT_REGISTRY_NAME, type = 'gitea', branch = branch }); table.insert(cfg.registries, 1, { name = DEFAULT_REGISTRY_NAME, type = 'gitea', branch = INSTALL_BRANCH });
end end
writeJsonFile(LOCAL_CONFIG_PATH, cfg); writeJsonFile(LOCAL_CONFIG_PATH, cfg);
end end
local rawArgs = table.pack(...); local rawArgs = table.pack(...);
local forceBeta, forceStable = false, false; local cpmOnly = false;
for i = 1, rawArgs.n do for i = 1, rawArgs.n do
local a = rawArgs[i]; local a = rawArgs[i];
if a == 'version' or a == '-version' or a == '--version' then if a == 'version' or a == '-version' or a == '--version' then
print('install-ccpm v' .. _VERSION); print('install-trapos v' .. _VERSION);
return; return;
elseif a == 'help' or a == '-help' or a == '--help' then elseif a == 'help' or a == '-help' or a == '--help' then
printUsage(); printUsage();
return; return;
elseif a == '--beta' or a == '-beta' then elseif a == '--cpm-only' or a == '-cpm-only' then
forceBeta = true; cpmOnly = true;
elseif a == '--stable' or a == '-stable' then
forceStable = true;
elseif a ~= nil and a ~= '' then elseif a ~= nil and a ~= '' then
printUsage(); printUsage();
return; return;
end end
end end
local localManifest = readJsonFile(LOCAL_MANIFEST_PATH); print('Fetching manifest from branch: ' .. INSTALL_BRANCH);
local localBranch = localManifest and localManifest.branch or nil; local manifest = fetchManifest();
local branch;
if forceBeta then
branch = 'next';
if localBranch ~= 'next' then
if not confirmBeta() then
print('Aborted.');
return;
end
end
elseif forceStable then
branch = 'master';
else
branch = localBranch or 'master';
end
print('Fetching manifest from branch: ' .. branch);
local manifest = fetchManifest(branch);
if not manifest then if not manifest then
print('Failed to fetch or parse manifest.json from ' .. branch); print('Failed to fetch or parse manifest.json from ' .. INSTALL_BRANCH);
return; return;
end end
local requested = { 'trapos-core' }; local requested = { 'trapos-core' };
local resolved, resolveErr = resolvePackages(branch, requested); local resolved, resolveErr = resolvePackages(requested);
if not resolved then if not resolved then
print('Failed to resolve packages: ' .. resolveErr); print('Failed to resolve packages: ' .. resolveErr);
return; return;
end end
local REPO_PREFIX = REPO_BASE .. branch .. '/';
-- Legacy file cleanup (pre-manifest installs). -- Legacy file cleanup (pre-manifest installs).
fs.delete('ping-server.lua'); fs.delete('ping-server.lua');
fs.delete('ping.lua'); fs.delete('ping.lua');
@ -212,7 +179,7 @@ for _, desc in ipairs(resolved) do
seenFile[filePath] = true; seenFile[filePath] = true;
allFiles[#allFiles + 1] = filePath; allFiles[#allFiles + 1] = filePath;
fs.delete(filePath); fs.delete(filePath);
shell.execute('wget', REPO_PREFIX .. filePath, filePath); shell.execute('wget', REPO_BASE .. filePath, filePath);
end end
end end
for _, srv in ipairs(desc.autostart or {}) do for _, srv in ipairs(desc.autostart or {}) do
@ -234,22 +201,34 @@ end
writeJsonFile(LOCAL_MANIFEST_PATH, { writeJsonFile(LOCAL_MANIFEST_PATH, {
name = manifest.name or 'TrapOS', name = manifest.name or 'TrapOS',
version = manifest.version or '?', version = manifest.version or '?',
branch = branch, branch = INSTALL_BRANCH,
files = allFiles, files = allFiles,
autostart = autostart, autostart = autostart,
}); });
writeJsonFile(LOCAL_LOCK_PATH, { packages = lockPackages }); writeJsonFile(LOCAL_LOCK_PATH, { packages = lockPackages });
seedCcpmConfig(branch); seedCcpmConfig();
ensureProgramsPath(); ensureProgramsPath();
print(); print();
print('=> ccpm installed (branch: ' .. branch .. ')'); print('=> ccpm installed (branch: ' .. INSTALL_BRANCH .. ')');
print('=> Default registry: ' .. DEFAULT_REGISTRY_NAME); print('=> Default registry: ' .. DEFAULT_REGISTRY_NAME);
print('=> Run: ccpm update');
print('=> Run: ccpm install trapos'); if cpmOnly then
print('=> Run: ccpm update');
print('=> Run: ccpm install trapos');
shell.setDir(previousDir);
return;
end
-- Full install: sync the registry and install the TrapOS meta-package.
shell.execute('ccpm', 'update');
shell.execute('ccpm', 'install', 'trapos');
if fs.exists('/startup/servers.lua') then if fs.exists('/startup/servers.lua') then
shell.execute('/startup/servers.lua'); shell.execute('/startup/servers.lua');
end end
shell.execute('trapos-postinstall');
shell.setDir(previousDir); shell.setDir(previousDir);

View File

@ -177,11 +177,10 @@ craftos-exec code:
exit 1 exit 1
fi fi
# End-to-end install probe: drive the real ccpm bootstrap # End-to-end install probe: drive the real one-shot installer
# (install-ccpm.lua -> `ccpm update` -> `ccpm install trapos`) on a fresh, # (install-trapos.lua, which bootstraps ccpm then runs `ccpm update` +
# ephemeral CraftOS-PC state. Reflects the currently checked-out git branch: # `ccpm install trapos` itself) on a fresh, ephemeral CraftOS-PC state.
# `master` -> --stable, `next` -> --beta (confirmation stubbed). Other # Always installs from the published `master` branch (the only channel).
# branches are rejected because install-ccpm only knows master/next.
# Network-dependent and slower than `just test`, so not part of `just ci`. # Network-dependent and slower than `just test`, so not part of `just ci`.
# Override timeout with TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS (default 60). # Override timeout with TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS (default 60).
# See ADR-0005. # See ADR-0005.
@ -191,31 +190,15 @@ trapos-install: check-install
repo='{{justfile_directory()}}' repo='{{justfile_directory()}}'
timeout_seconds="${TRAP_CCLIBS_INSTALL_TIMEOUT_SECONDS:-60}" 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 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=() rom_arg=()
if [ "$(uname -s)" = "Darwin" ]; then if [ "$(uname -s)" = "Darwin" ]; then
rom_arg=(--rom /Applications/CraftOS-PC.app/Contents/Resources) rom_arg=(--rom /Applications/CraftOS-PC.app/Contents/Resources)
fi fi
data_dir="$(mktemp -d)" data_dir="$(mktemp -d)"
stage_dir="$(mktemp -d)" stage_dir="$(mktemp -d)"
cp "$repo/install-ccpm.lua" "$stage_dir/install-ccpm.lua" cp "$repo/install-trapos.lua" "$stage_dir/install-trapos.lua"
tmp="$(mktemp)" 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()" exec_code="shell.run('/staging/install-trapos'); if fs.exists('/trapos/manifest.json') and fs.exists('/trapos/ccpm.lock.json') 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 & craftos --directory "$data_dir" --headless "${rom_arg[@]}" --mount-ro "/staging=$stage_dir" --exec "$exec_code" >"$tmp" 2>&1 &
pid="$!" pid="$!"
( sleep "$timeout_seconds"; kill -TERM "$pid" >/dev/null 2>&1 ) & ( sleep "$timeout_seconds"; kill -TERM "$pid" >/dev/null 2>&1 ) &

View File

@ -1,7 +1,7 @@
{ {
"name": "TrapOS", "name": "TrapOS",
"version": "0.9.3", "version": "0.9.3",
"branch": "next", "branch": "master",
"packages": [ "packages": [
"trapos" "trapos"
] ]

View File

@ -1,6 +1,6 @@
{ {
"packages": { "packages": {
"trapos-core": "0.5.1", "trapos-core": "0.6.0",
"trapos-test": "0.2.1", "trapos-test": "0.2.1",
"trapos-boot": "0.3.3", "trapos-boot": "0.3.3",
"trapos-net": "0.3.0", "trapos-net": "0.3.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "trapos-core", "name": "trapos-core",
"version": "0.5.1", "version": "0.6.0",
"description": "TrapOS base: package manager, event loop, upgrade and event tools", "description": "TrapOS base: package manager, event loop, upgrade and event tools",
"dependencies": [], "dependencies": [],
"files": [ "files": [
@ -8,7 +8,8 @@
"apis/libccpm.lua", "apis/libccpm.lua",
"apis/libversion.lua", "apis/libversion.lua",
"programs/ccpm.lua", "programs/ccpm.lua",
"programs/upgrade.lua", "programs/trapos-upgrade.lua",
"programs/trapos-postinstall.lua",
"programs/events.lua" "programs/events.lua"
], ],
"autostart": [] "autostart": []

View File

@ -0,0 +1,50 @@
local createVersion = require('/apis/libversion');
local function printUsage()
print('trapos-postinstall usage:');
print();
print('\t\ttrapos-postinstall');
print('\t\ttrapos-postinstall version');
print('\t\ttrapos-postinstall help');
end
local command = ...;
if command == 'version' or command == '-version' or command == '--version' then
print('v' .. createVersion().forSelf());
return;
end
if command == 'help' or command == '-help' or command == '--help' then
printUsage();
return;
end
if command ~= nil and command ~= '' then
printUsage();
return;
end
local function readManifestVersion()
if not fs.exists('/trapos/manifest.json') then return nil; end
local f = fs.open('/trapos/manifest.json', 'r');
if not f then return nil; end
local data = f.readAll();
f.close();
local manifest = data and textutils.unserializeJSON(data);
return manifest and manifest.version or nil;
end
local version = readManifestVersion();
print();
if version then
print('=> TrapOS v' .. version .. ' installed.');
else
print('=> TrapOS installed.');
end
print();
print('Get started:');
print(' sandbox --help -- open the sandbox help');
print(' trapos-upgrade -- update and upgrade TrapOS');
print();

View File

@ -0,0 +1,38 @@
local createVersion = require('/apis/libversion');
local function printUsage()
print('trapos-upgrade usage:');
print();
print('\t\ttrapos-upgrade');
print('\t\ttrapos-upgrade --reboot');
print('\t\ttrapos-upgrade version');
print('\t\ttrapos-upgrade help');
end
local rawArgs = table.pack(...);
local reboot = false;
for i = 1, rawArgs.n do
local a = rawArgs[i];
if a == 'version' or a == '-version' or a == '--version' then
print('v' .. createVersion().forSelf());
return;
elseif a == 'help' or a == '-help' or a == '--help' then
printUsage();
return;
elseif a == '--reboot' or a == '-reboot' or a == '-r' then
reboot = true;
elseif a ~= nil and a ~= '' then
printUsage();
return;
end
end
shell.execute('ccpm', 'update');
shell.execute('ccpm', 'upgrade');
if reboot then
print();
print('=> Rebooting...');
os.reboot();
end

View File

@ -1,28 +0,0 @@
local createVersion = require('/apis/libversion');
local function printUsage()
print('upgrade usage:');
print();
print('\t\tupgrade');
print('\t\tupgrade version');
print('\t\tupgrade help');
end
local command = ...;
if command == 'version' or command == '-version' or command == '--version' then
print('v' .. createVersion().forSelf());
return;
end
if command == 'help' or command == '-help' or command == '--help' then
printUsage();
return;
end
if command ~= nil and command ~= '' then
printUsage();
return;
end
shell.execute('ccpm', 'upgrade');

View File

@ -43,31 +43,29 @@ local function fakeHttp(routes)
}; };
end end
testlib.test('programs path resolves the ccpm command', function() local INSTALL_BASE = 'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/master/';
local previous = shell.path();
shell.setPath('/rom/programs:/programs');
local resolved = shell.resolveProgram('ccpm');
shell.setPath(previous);
testlib.assertEquals(resolved, 'programs/ccpm.lua');
end);
testlib.test('fresh beta bootstrap leaves ccpm runnable without trapos-boot', function() local function installRoutes()
local root = '/install-ccpm-test/fresh-beta'; return {
fs.delete(root); [INSTALL_BASE .. 'manifest.json'] = textutils.serializeJSON({ name = 'TrapOS', version = 'test' }),
fs.makeDir(root); [INSTALL_BASE .. 'packages/trapos-core/ccpm.json'] = textutils.serializeJSON({
local base = 'https://git.trapcloud.fr/guillaumearm/cc-libs/raw/branch/next/';
local routes = {
[base .. 'manifest.json'] = textutils.serializeJSON({ name = 'TrapOS', version = 'test' }),
[base .. 'packages/trapos-core/ccpm.json'] = textutils.serializeJSON({
name = 'trapos-core', name = 'trapos-core',
version = '1', version = '1',
dependencies = {}, dependencies = {},
files = { 'apis/libccpm.lua', 'programs/ccpm.lua' }, files = { 'apis/libccpm.lua', 'programs/ccpm.lua' },
}), }),
[base .. 'apis/libccpm.lua'] = 'libccpm-body', [INSTALL_BASE .. 'apis/libccpm.lua'] = 'libccpm-body',
[base .. 'programs/ccpm.lua'] = 'ccpm-body', [INSTALL_BASE .. 'programs/ccpm.lua'] = 'ccpm-body',
}; };
end
-- Run install-trapos.lua inside a sandboxed env. `args` is a list of CLI args.
-- Returns { calls, root } for assertions.
local function runInstall(root, args)
fs.delete(root);
fs.makeDir(root);
local routes = installRoutes();
local calls = { local calls = {
executes = {}, executes = {},
path = '/rom/programs', path = '/rom/programs',
@ -85,9 +83,11 @@ testlib.test('fresh beta bootstrap leaves ccpm runnable without trapos-boot', fu
setDir = function(path) calls.dir = path; end, setDir = function(path) calls.dir = path; end,
path = function() return calls.path; end, path = function() return calls.path; end,
setPath = function(path) calls.path = path; end, setPath = function(path) calls.path = path; end,
execute = function(program, url, target) execute = function(program, ...)
calls.executes[#calls.executes + 1] = { program = program, url = url, target = target }; local rest = { ... };
calls.executes[#calls.executes + 1] = { program = program, args = rest };
if program ~= 'wget' then return true; end if program ~= 'wget' then return true; end
local url, target = rest[1], rest[2];
local body = routes[url]; local body = routes[url];
if not body then return false; end if not body then return false; end
local dir = parentDir(target); local dir = parentDir(target);
@ -100,22 +100,64 @@ testlib.test('fresh beta bootstrap leaves ccpm runnable without trapos-boot', fu
}, },
}, { __index = _G }); }, { __index = _G });
local chunk, loadErr = loadfile('/trapos/install-ccpm.lua', 't', env); local chunk, loadErr = loadfile('/trapos/install-trapos.lua', 't', env);
if not chunk then error(loadErr, 0); end if not chunk then error(loadErr, 0); end
local ok, err = pcall(chunk, '--beta'); local ok, err = pcall(chunk, table.unpack(args));
if not ok then error(err, 0); end if not ok then error(err, 0); end
return calls;
end
local function findExecute(calls, program, firstArg)
for _, call in ipairs(calls.executes) do
if call.program == program then
if firstArg == nil or call.args[1] == firstArg then
return true;
end
end
end
return false;
end
testlib.test('programs path resolves the ccpm command', function()
local previous = shell.path();
shell.setPath('/rom/programs:/programs');
local resolved = shell.resolveProgram('ccpm');
shell.setPath(previous);
testlib.assertEquals(resolved, 'programs/ccpm.lua');
end);
testlib.test('fresh bootstrap seeds a gitea master registry and runs ccpm', function()
local root = '/install-trapos-test/full';
local calls = runInstall(root, {});
testlib.assertTrue(fs.exists(root .. '/programs/ccpm.lua')); testlib.assertTrue(fs.exists(root .. '/programs/ccpm.lua'));
testlib.assertTrue(string.find(calls.path, '/programs', 1, true)); testlib.assertTrue(string.find(calls.path, '/programs', 1, true));
for _, call in ipairs(calls.executes) do
testlib.assertTrue(call.program ~= '/startup/servers.lua');
end
local f = fs.open(root .. '/trapos/ccpm.json', 'r'); local f = fs.open(root .. '/trapos/ccpm.json', 'r');
local config = textutils.unserializeJSON(f.readAll()); local config = textutils.unserializeJSON(f.readAll());
f.close(); f.close();
testlib.assertEquals(config.registries[1].branch, 'next'); testlib.assertEquals(config.registries[1].branch, 'master');
testlib.assertEquals(config.registries[1].type, 'gitea'); testlib.assertEquals(config.registries[1].type, 'gitea');
end); end);
testlib.test('default install runs update, install trapos and postinstall', function()
local root = '/install-trapos-test/default';
local calls = runInstall(root, {});
testlib.assertTrue(findExecute(calls, 'ccpm', 'update'));
testlib.assertTrue(findExecute(calls, 'ccpm', 'install'));
testlib.assertTrue(findExecute(calls, 'trapos-postinstall'));
end);
testlib.test('--cpm-only stops after the ccpm bootstrap', function()
local root = '/install-trapos-test/cpm-only';
local calls = runInstall(root, { '--cpm-only' });
testlib.assertTrue(fs.exists(root .. '/programs/ccpm.lua'));
testlib.assertTrue(not findExecute(calls, 'ccpm', 'update'));
testlib.assertTrue(not findExecute(calls, 'ccpm', 'install'));
testlib.assertTrue(not findExecute(calls, 'trapos-postinstall'));
end);
testlib.run(); testlib.run();