cc-libs/docs/adrs/adr-0010-ccpm-package-manager.md

4.9 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 tos-net, ccpm uninstall tos-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
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
trapos full TrapOS meta-package tos-boot, tos-net, tos-ui, tos-test

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.
  • ccpm.cache.json — packages advertised by configured registries, written by ccpm update from each registry's packages/index.json, used by ccpm search, ccpm available, and ccpm 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 tos-core package descriptor (pulling any future dependencies), downloads its files, and writes:

  • /trapos/manifest.json — the aggregated { name, version, branch, files, autostart } still consumed by startup/motd.lua and startup/servers.lua after boot packages are installed;
  • /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.

The install path is:

  • wget run .../install-ccpm.lua — install ccpm (tos-core) and seed the default registry.
  • ccpm update — refresh the local package cache.
  • ccpm install trapos — install the full OS. During beta, trapos includes tos-test by 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 (formerly just craftos; see ADR-0012) 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, cache update, available status, upgrade, uninstall dependency guard) with an injected http stub — 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).