cc-libs/docs/adrs/adr-0010-ccpm-package-manager.md
2026-06-08 13:52:45 +02:00

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 } } }. type is github (resolves to raw.githubusercontent.com/<name>/<branch>/) or http/https (the name is a base URL).
  • ccpm.lock.json — installed packages { packages = { <name> = { version, registry, files, dependencies, autostart } } }, used by ls, uninstall, and reinstall.

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 by startup/motd.lua, startup/servers.lua, and programs/upgrade.lua (those three are unchanged);
  • /trapos/ccpm.lock.json — so right after a fresh install ccpm install tos-core correctly reports "already installed";
  • /trapos/ccpm.json — seeding/refreshing the default guillaumearm/cc-libs registry to track the install branch.

Two install paths follow:

  • wget run .../install.lua — full OS (all packages in manifest.packages).
  • wget run .../install.lua --core — only tos-core (i.e. just ccpm); the user then cherry-picks with ccpm 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 craftos no longer derives mounts from manifest.json .files (it is now .packages); it mounts a fixed list of top-level dirs instead. just test was 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 injected http stub — 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 run bootstrap.
  • http/https registries beyond a plain base URL (auth, caching).