5.0 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"), installed first
as a standalone user-facing step. After that, a machine can ccpm update,
ccpm install trapos, ccpm install trapos-net, ccpm uninstall trapos-ui, and manage
where packages come from. TrapOS itself is installed through a trapos meta-package;
the wget run .../install-ccpm.lua bootstrap exists only to install ccpm.
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 |
|---|---|---|
| trapos-core | ccpm, libccpm, eventloop, upgrade, events | — |
| trapos-test | libtest, runtest | trapos-core |
| trapos-boot | motd, servers (startup) | trapos-core |
| trapos-net | net, router, ping, ping-server | trapos-core |
| trapos-ui | libtui, tuidemo | trapos-core |
| trapos-ai | AI client for opencode serve | trapos-core |
| trapos | full TrapOS meta-package | trapos-boot, trapos-net, trapos-ui, trapos-test, trapos-ai |
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.ccpm.cache.json— packages advertised by configured registries, written byccpm updatefrom each registry'spackages/index.json, used byccpm search,ccpm available, andccpm upgrade.
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 installs only ccpm
install-ccpm.lua resolves only the trapos-core package descriptor (pulling any future
dependencies), downloads its files, and writes:
/trapos/manifest.json— the aggregated{ name, version, branch, files, autostart }still consumed bystartup/motd.luaandstartup/servers.luaafter boot packages are installed;/trapos/ccpm.lock.json— so right after a fresh installccpm install trapos-corecorrectly reports "already installed";/trapos/ccpm.json— seeding/refreshing the defaultguillaumearm/cc-libsregistry to track the install branch.
The install path is:
wget run .../install-ccpm.lua— installccpm(trapos-core) and seed the default registry.ccpm update— refresh the local package cache.ccpm install trapos— install the full OS. During beta,traposincludestrapos-testby default.
On a subsequent upgrade, programs/upgrade.lua delegates to ccpm upgrade, which
upgrades installed packages using /trapos/ccpm.cache.json. Users run ccpm update
first to refresh available versions.
Consequences
- The repo gains a
packages/descriptor tree; the flat source layout is untouched. just trapos(formerlyjust craftos; see ADR-0012) no 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, cache update, available status, upgrade, uninstall dependency guard) with an injectedhttpstub — no network in tests.
Future Work
- Version ranges (today a single pinned version per package).
- http/https registries beyond a plain base URL (auth, caching).