4.4 KiB
ADR 0010: ccpm Package Manager
Status
Accepted
Date
2026-06-08
Context
ADR 0004 made installs manifest-driven: install.lua reads a flat manifest.json
file list from a branch and wgets every file. That is all-or-nothing. There is no
way to install just networking or just the UI, and no way to add or remove pieces of
the OS after the initial install.
We want a package manager, ccpm ("ComputerCraft Package Manager"), shipped as part
of the base OS, so a machine can ccpm install tos-net, ccpm uninstall tos-ui, and
manage where packages come from. TrapOS itself is not becoming a package for now;
the wget run .../install.lua bootstrap stays the recommended entry point.
Decision
Packages are descriptors over the existing tree
Source files stay where they are (apis/, programs/, servers/, startup/); their
install targets remain the same absolute CC paths, so require paths and the dev
mounts are unchanged. A package is a descriptor that references those files:
packages/<name>/ccpm.json with { name, version, description, dependencies, files, autostart }. packages/index.json lists the packages a registry offers (for
ccpm search). There is no ccpm.json at the repo root.
The split is finer-grained than the install examples imply:
| package | contents | deps |
|---|---|---|
| tos-core | ccpm, libccpm, eventloop, upgrade, events | — |
| tos-test | libtest, runtest | tos-core |
| tos-boot | motd, servers (startup) | tos-core |
| tos-net | net, router, ping, ping-server | tos-core |
| tos-ui | libtui, tuidemo | tos-core |
Two files for ccpm, "manifest" reserved for the OS
To avoid colliding with the OS manifest.json, ccpm never uses the word "manifest".
Local state lives under /trapos:
ccpm.json— ordered registry list{ registries = { { name, type, branch } } }.typeisgithub(resolves toraw.githubusercontent.com/<name>/<branch>/) orhttp/https(thenameis a base URL).ccpm.lock.json— installed packages{ packages = { <name> = { version, registry, files, dependencies, autostart } } }, used byls,uninstall, andreinstall.
apis/libccpm.lua is the testable core (a factory; http/stateDir/installRoot
are injectable for tests). programs/ccpm.lua is a thin CLI over it.
The bootstrap is package-aware
manifest.json now lists packages instead of files. install.lua resolves each
package descriptor (pulling dependencies), downloads the union of their files, and
writes:
/trapos/manifest.json— the aggregated{ name, version, branch, files, autostart }still consumed verbatim bystartup/motd.lua,startup/servers.lua, andprograms/upgrade.lua(those three are unchanged);/trapos/ccpm.lock.json— so right after a fresh installccpm install tos-corecorrectly reports "already installed";/trapos/ccpm.json— seeding/refreshing the defaultguillaumearm/cc-libsregistry to track the install branch.
Two install paths follow:
wget run .../install.lua— full OS (all packages inmanifest.packages).wget run .../install.lua --core— onlytos-core(i.e. justccpm); the user then cherry-picks withccpm install ....
On a subsequent upgrade, install.lua prefers the existing lockfile's package set
over manifest.packages, so a cherry-picked machine upgrades only what it actually has.
Consequences
- The repo gains a
packages/descriptor tree; the flat source layout is untouched. just craftosno longer derives mounts frommanifest.json .files(it is now.packages); it mounts a fixed list of top-level dirs instead.just testwas already on fixed mounts and is unaffected.- ccpm logic is covered by
tests/ccpm.lua(URL resolution, dependency ordering, cycle/missing detection, already-installed, registry CRUD, uninstall dependency guard) with an injectedhttpstub — no network in tests.
Future Work
- Version ranges /
ccpm update(today a single pinned version per package). - Making TrapOS self-update through ccpm rather than the
wget runbootstrap. - http/https registries beyond a plain base URL (auth, caching).