mouse/cheesed

Blob: PLAN.md

Raw · Blame

# cheesed plan

## Purpose

`cheesed` is MOUSE's PID 1 and boot coordinator. It establishes early userspace,
applies canonical service-enable policy from `/etc/rc.conf`, asks OpenRC to
execute the resulting boot graph, shuts the system down cleanly, and reaps
orphaned children.

The service framework has three deliberately separate owners:

- `cheesed` owns PID 1 duties, configuration translation, boot transitions, and
  final reboot or poweroff.
- OpenRC owns service dependencies and ordered lifecycle actions.
- `supervise-daemon` owns the runtime state and restart policy of each supervised
  service.

OpenRC is not PID 1 and does not remain resident after applying a runlevel.
`cheesed` is not a second implementation of OpenRC's dependency engine or
`supervise-daemon`'s process-monitoring loop.

## Non-negotiable system contract

- `cheesed` is written in Rust and built as a static musl executable.
- Every executable shipped in the MOUSE base image, including the selected
  OpenRC tools, is statically linked against musl. Dynamic linkage is reserved
  for a separately defined ports policy.
- `cheesed` is the only program launched by the kernel as PID 1 in the normal
  system.
- `/etc/rc.conf` is the only persistent service-enable interface.
  `enable_sshd=YES` enables `sshd` at boot; service definitions and generated
  runlevels never record or mutate that policy.
- `cheesed` interprets only strict `enable_${name}=YES|NO` assignments for
  enablement. The first release accepts no OpenRC global settings or other
  assignment namespaces in this file.
- OpenRC runlevel membership generated from `rc.conf` is disposable runtime
  state under `/run` and is rebuilt on every boot.
- MOUSE does not ship `rc-update`.
- `service` is a symlink to `rc-service`. Its start, stop, restart, reload, and
  status actions affect current runtime state only.
- The base system and `cheesed` are released together. Third-party ports may
  supply OpenRC service definitions, but must use the same `rc.conf` enablement
  contract.
- `cheesed` has no network dependency, dynamic-linker dependency, async runtime,
  embedded shell, or package-manager dependency.

## Design shape

### Process model

`cheesed` treats PID 1 as a distinct execution environment: it installs signal
handlers early, reaps every exited child, and never relies on the usual default
signal behaviour. It remains alive after OpenRC has completed the requested
runlevel and while `supervise-daemon` instances manage services.

The initial version is synchronous and event-driven. It may use a small polling
loop around signal delivery and child-status changes, but it must not use Tokio
or a general task runtime. Rust's standard library plus narrowly scoped Unix
bindings are sufficient; unsafe code is allowed only in a small, reviewed
syscall boundary.

`supervise-daemon` is authoritative for the child process it supervises.
`cheesed` may reap unrelated or orphaned descendants, but it must not duplicate
service restart decisions or infer supervised service state from pidfiles.

### Boot stages

The kernel and early userspace provide a usable root filesystem and invoke
`/sbin/cheesed`. Mount topology, root discovery, and an initramfs remain outside
the first service-framework milestone.

After its own early setup, `cheesed` runs these stages:

1. **Bootstrap:** establish a safe environment, open console logging, install
   signal handling, mount a fresh `tmpfs` at `/run`, create OpenRC's runtime
   state directory, and read `/etc/rc.conf`.
2. **Policy translation:** validate `enable_${name}=YES|NO` entries against the
   installed service definitions and generate fresh OpenRC runlevel membership
   under `/run`. Reject duplicates, malformed assignments, invalid service
   names, and enabled services which do not exist.
3. **System boot:** run `/sbin/openrc sysinit`, `/sbin/openrc boot`, and
   `/sbin/openrc default`, in that order. OpenRC resolves dependencies and
   executes service actions.
4. **Login:** after the `default` transition, verify every required service
   through `rc-service <name> status`. Enter recovery if any required service,
   including the console login service, is not started.
5. **Steady state:** reap orphaned children, process shutdown or reboot signals,
   and leave individual service supervision to `supervise-daemon`.

A non-zero `sysinit` or `boot` transition stops the boot path and leaves a
diagnosable console. A non-zero `default` transition is fatal only when a
required service is not started. Otherwise `cheesed` logs each failed optional
service, determined by running `rc-service <name> status` for every enabled
optional service, and proceeds to steady state. An optional service which is a
hard dependency of a required service therefore becomes boot-critical without
requiring `cheesed` to interpret OpenRC's dependency graph.

### OpenRC integration

MOUSE ships the minimum statically linked OpenRC command surface needed for
boot and administration. The initial executable set is `openrc`, `openrc-run`,
`rc-service`, `rc-status`, `supervise-daemon`, `start-stop-daemon`, `checkpath`,
and `rc-sstat`, plus the `runscript` and `service` links. It also ships the
OpenRC POSIX-shell support files required by `openrc-run`. It deliberately omits
`rc-update`, shared OpenRC libraries, and tools not exercised by the C1 service
definitions.

The C1 build produces a manifest of every OpenRC-owned runtime file. Every ELF
in that manifest must pass the base static-link audit, and a clean image build
must prove that every executable or shell helper referenced by the proof
services exists in the image.

Service definitions are ordinary OpenRC service scripts. They declare
dependencies and lifecycle actions using OpenRC's interfaces. A daemon requiring
runtime supervision is launched through `supervise-daemon`, with its restart,
retry, health, and shutdown policy expressed in the service definition rather
than reimplemented in `cheesed`.

OpenRC retains its upstream system runlevel path, `/etc/runlevels`.
`/etc/runlevels` is an immutable image-owned link to
`/run/openrc/runlevels/current`. `cheesed` creates a complete generation before
starting OpenRC:

- Base-owned templates under `/usr/lib/mouse/runlevels` provide required
  `sysinit`, `boot`, `default`, and `shutdown` membership.
- `enable_${name}=YES` adds the matching optional service to the generated
  `default` runlevel. `NO` or an absent assignment leaves it out.
- `cheesed` builds `/run/openrc/runlevels/generations/${generation}`, validates
  every membership link, creates a relative `current.new` symlink to that
  generation, and renames `current.new` over `current` only after the tree is
  complete. A recovery retry creates a new generation rather than modifying the
  active one.
- No tool edits the generated tree after boot, and the whole tree disappears
  when `/run` is recreated.

Required membership is therefore persistent only as a versioned base template;
the effective runlevel tree consumed by OpenRC is always disposable runtime
state.

### Configuration

`/etc/rc.conf` is a small, strict assignment file, not general shell input. The
first-release grammar is deliberately complete:

- Empty lines and lines whose first non-whitespace character is `#` are
  accepted.
- Every other line must be exactly `enable_${name}=YES` or
  `enable_${name}=NO`, with no surrounding whitespace, quotes, escapes, inline
  comments, expansion, or command syntax.
- A service name must match `[a-z][a-z0-9_]*`. MOUSE service definitions and
  ports-provided service definitions must use the same restricted name.
- Each service may appear at most once. A duplicate is an error even if both
  values agree.
- The name must resolve to exactly one installed OpenRC service definition.
  An assignment for a required base service is an error because required
  membership is not administrator-selectable.
- Missing assignments mean `NO` for optional services.
- Any other key is an error. The first release exposes no OpenRC global settings
  and has no `rc.conf.local` or include mechanism.

This grammar is also a security boundary because `openrc-run` sources
`/etc/rc.conf` in a POSIX shell. `cheesed` must validate the entire file before
any OpenRC process is started, so every accepted line is already safe shell
assignment syntax. Later global settings require an explicit grammar extension,
an allowlisted value vocabulary, and parser tests; they must never be accepted
as opaque text.

`cheesed` logs the effective enabled-service set in lexical order. It never logs
unrecognized input values after reporting the line-numbered parse error.

## Command surface

The initial console-oriented command surface is:

- `cheesed`: PID 1 mode, used only by the boot process.
- `openrc`: applies the generated boot or shutdown runlevel.
- `service <name> start|stop|status|restart|reload`: the `rc-service` runtime
  interface.

There is no initial `cheesedctl` protocol or service-control socket. OpenRC and
`supervise-daemon` already own lifecycle state, and adding a parallel control
plane would make the ownership boundary ambiguous.

Enabling or disabling a service is a configuration operation: edit
`enable_${name}` in `/etc/rc.conf` and reboot, or use a later MOUSE-specific
configuration tool which edits that file atomically. The `service` command must
never persist enablement.

### OpenRC transition protocol

`cheesed` has an explicit transition state:
`bootstrap`, `sysinit`, `boot`, `default`, `steady`, `recovery`, or `shutdown`.
For each OpenRC transition it forks a direct child in a new process group,
connects its output to the boot console, waits synchronously while still
handling signals and `SIGCHLD`, and records the command's exit status.

The initial time limits are:

- 120 seconds for each of `sysinit`, `boot`, and `default`;
- 30 seconds for `openrc shutdown`; and
- five seconds after sending `SIGTERM` to an overdue OpenRC process group before
  escalating to `SIGKILL`.

These are release constants in C1 rather than user-controlled `rc.conf`
settings. A timeout is reported with the transition name and is treated like a
non-zero transition result.

If a reboot or poweroff request arrives during boot, `cheesed` latches the first
requested action and ignores later conflicting requests, terminates the active
OpenRC process group using the five-second escalation rule, reaps it, and then
enters shutdown. Shutdown always invokes `/sbin/openrc shutdown`, even after a
partial boot, so OpenRC can stop anything it already marked as started. When
that command exits or reaches its timeout, `cheesed` performs the requested
kernel reboot or poweroff operation. No second boot transition starts after
shutdown has been latched.

## Failure and recovery policy

- On boot-time configuration errors, print a precise console diagnostic and
  spawn a recovery shell. `cheesed` remains PID 1, reaps the shell when it exits,
  and follows an explicit retry, reboot, or poweroff path; PID 1 never returns
  from `main`.
- On OpenRC dependency or required-service failure, retain OpenRC's diagnostic
  result and stop the affected boot path rather than continuing into a partially
  defined system state.
- On a supervised service crash, `supervise-daemon` applies the policy declared
  by its OpenRC service definition. `cheesed` does not apply a competing restart
  policy.
- On `SIGTERM`, `SIGINT`, reboot, or poweroff requests, `cheesed` runs
  `openrc shutdown` under the transition protocol above. OpenRC stops started
  services in reverse dependency order before `cheesed` performs the requested
  system action.
- Reap unknown orphaned children and log them at a low rate; never let zombies
  accumulate.

## Milestones

### C0: executable skeleton

Create the Rust crate, cross/static build configuration, and a QEMU boot path
that starts `cheesed` as PID 1 and reaches an emergency `tcsh` shell. Implement
early console logging, `SIGCHLD` reaping, and signal-driven reboot and poweroff
behaviour. `cheesed` must remain alive if the emergency shell exits or a final
reboot syscall fails.

### C1: rc.conf and OpenRC boot

Statically build the required OpenRC tools. Implement the strict `rc.conf`
enablement parser and ephemeral runlevel generator. Prove one required base
service and one optional service, with the optional service joining the generated
runlevel only when `enable_${name}=YES` is present.

### C2: supervision and administration

Run a non-daemonising test service through `supervise-daemon`, prove its restart
and shutdown policy, and expose accurate runtime state through
`service <name> status`. Confirm that no `service` action changes next-boot
enablement and that `rc-update` is absent from the image.

### C3: base integration

Replace the proof services with console login and the minimal base services
needed by MOUSE. Document the OpenRC service-definition contract for ports and
prove that a ports-provided service can be enabled only through
`/etc/rc.conf`.

## Acceptance gates

- The release build produces statically linked musl binaries for `cheesed` and
  every shipped OpenRC executable.
- A clean QEMU boot shows `cheesed` as PID 1, reaches a `tcsh` login, and leaves
  no zombies after the test workload exits repeatedly.
- An optional service starts only when its matching
  `enable_${name}=YES` assignment is present; removing it prevents the service
  from joining the next boot's generated runlevel.
- Generated OpenRC runlevels live only in runtime state and are recreated from
  `/etc/rc.conf` on every boot.
- The image does not ship `rc-update`.
- `service` resolves to `rc-service`, accurately reports a supervised test
  service, and never changes persistent enablement.
- OpenRC reports missing dependencies and dependency cycles without producing
  an unordered partial start.
- `supervise-daemon` applies the declared crash policy without a competing
  restart decision from `cheesed`.
- Reboot and poweroff stop services in reverse dependency order and respect the
  configured timeout.
- Unit tests cover parsing, enablement validation, and generated membership;
  QEMU integration tests cover boot, optional enablement, supervision, runtime
  service control, and shutdown.

## Explicitly out of scope for the first release

- Shipping `rc-update` or treating persistent runlevel symlinks as policy.
- A custom `cheesed` dependency resolver, service supervisor, control socket, or
  service-file format.
- A systemd compatibility layer, unit-file import, or D-Bus API.
- Parallel service startup before ordered serial boot is correct and observable.
- Socket, timer, path, device, user-session, or container activation.
- Cgroup accounting and resource limits beyond capabilities provided directly
  by the selected OpenRC release.
- Mounting root filesystems, discovering storage, or replacing the initramfs.
- Shell-evaluated `enable_` values or a second persistent enablement database.