Decision — one repository, install profiles

Status: accepted 2026-10-12; amended 2026-10-12 after review. AGENTS.md §2.3 (identity and metadata), §2.5 (Mix components) and §10 (the checklist) are amended in the same change that adds the profiles field and its gate.

The question

MixOS has two audiences: a desktop (the compositor, the apps and their settings services) and servers (mail, web, blobs, DNS and the mesh daemons on headless nodes). The server programs have not moved into this repository yet. Should they get a repository of their own?

The decision

  • Everything stays in this repository and its one Cargo workspace. Desktop, server, command-line and library code alike. There is no second MixOS source repository for server programs.

  • The split is an install split, not a source split. Every shipping component declares the install profiles that include it:

    ProfileMeaning
    baseOn every MixOS system: the shell and the Bus node
    serverWhat a headless node may run
    desktopThe desktop session: compositor, apps, their services and the session's own wiring
  • Composition. server and desktop each include base: resolving either one yields its own components plus base's. A system may install both. A resolved set is the union of its profiles, each component once.

  • Who declares. A component's principal package declares profiles: the package whose name is its component (the directory under apps/, services/ or cli/), or a Mix component's component.mx. The list is non-empty, has no duplicates and names only the three profiles. The field is absent on libraries, on a component's private crates (whatever kind they inherit from their owner), and on tool and test packages. Libraries are never installed on their own; they are linked into what needs them. The session's targets and wiring (session/) belong to desktop; when the session kind lands, it declares profiles like any other shipping component.

  • Where it is declared. A Rust component adds profiles to its [package.metadata.mixos]:

    [package.metadata.mixos]
    component = "webd"
    kind = "service"
    layer = "core"
    contract = "public"
    profiles = ["server"]
    

    A Mix component adds the same profiles list to its component.mx.

  • Naming. Install profiles are unrelated to Cargo's build profiles (release, release-fast) and to any service's own settings profiles. Prose says "install profile"; tools take --install-profile, beside --cargo-profile and --target, never a bare --profile.

  • The profile gate is new, workspace-wide work. Today's closure and layering gates check compd's own crates only; they do not protect other components. The profile gate:

    • refuses the declaration errors above;
    • refuses a base or server component that itself declares the desktop layer;
    • resolves the normal and build dependency closure of each base and server principal, for every feature set its profiles build, and refuses any desktop-layer workspace package in it, and any upstream GUI crate (smithay, egui, eframe, wgpu, winit, Bevy and the like) even though those carry no MixOS metadata;
    • for a Mix component, which has no Cargo closure, refuses a desktop layer in a base or server component; what it calls on the Bus is checked at review, as AGENTS.md §2.5 already says.

    The component index (/docs/components/) shows each component's profiles.

  • Resolving, installing and activating are separate.

    • Resolve: one tool answers "what is in server?" from the metadata, deterministically. Its output covers the packages to build, the Mix components, and each component's runtime artefacts: units by class (system or session), sysusers and tmpfiles fragments, and assets. Build scripts, images and installers never keep their own lists.
    • Install: places those files. Rust components go to an installer that gives binaries and units the record, rollback and drift check tools/component_install.mix gives Mix components (separate work that follows this decision); Mix components go to that existing installer.
    • Activate: which services a particular node runs, with what configuration, is that node's decision, not the profile's. Installing server makes a service available; it does not start every server on every node.

The first assignment

ComponentProfiles
mix-shell (the mix binary), nodedbase
compd, settingsd, portald, editd, releasesd, busviewer, ced, dopus, prefsdesktop

server starts empty: the server programs join it as they move in (mail, web, blobs and the rest). releasesd is a per-user session service and editd serves the session's editors, so both are desktop. A headless mode for either would be its own decision, with suitable supervision, before it joins server.

Why

  • The two sides share their foundation. The Bus, noded, Mix, the config and props libraries and logging sit under both, and they change almost daily. A second repository could pin them as Git dependencies rather than wait for releases, but every foundation change would still mean a coordinated upgrade on the other side, and consolidating duplicated code across the boundary would mean moving it between repositories.
  • Changes cut across both. The verb registry's move to classes changed the registry, its gate and every application in one commit. Across two repositories that is coordinated pull requests that can drift apart.
  • The separation that matters can be enforced here. Headless code never needing a GUI stack is a property of a dependency closure, and the profile gate checks exactly that for every base and server component. A repository boundary would enforce it less precisely: it says nothing about which features or which programs a node gets.
  • This was decided once already. The source layout decision rejected one repository per component so that one dependency graph can be gated, shared code consolidated, and changes made atomically. A desktop/server split is the same proposal in two pieces, and nothing it relied on has changed.
  • What people actually need separated is the build and the install. A mail server should never compile or install the compositor. Profiles say that precisely, from data, in one place.

Alternatives

  • A second repository for server programs. The strongest case for it: an independent lockfile, toolchain and release pipeline, so desktop dependency churn never touches a server build and a server fix never waits on the desktop. That isolation is real; it loses to the shared, fast-moving foundation and the consolidation still to do. A split would be the right call if the server side ever needed a different licence, private code, its own team or its own release cadence. None of these applies. Private deployment detail already lives outside this repository.
  • Cargo features or a second workspace in this repository. Rejected. A second workspace means two lockfiles and two dependency graphs, which is what AGENTS.md §0 rules out. Features select code inside a crate, not which programs a system gets.
  • A top-level server/ directory. Rejected: the top level is classified by kind only (AGENTS.md §1). A web server is a service like any other; which systems it belongs on is data.

Consequences

  • One Cargo.lock: a dependency bump for the desktop also moves the servers' resolved versions, and a server fix ships from the same tree as that day's desktop work. Each component's tests and the gates catch a break; the alternative was version drift between repositories.
  • Building a server set compiles only that set's closure (cargo build -p), so headless builds stay small. A server build host still checks out the whole tree, desktop vendor forks included.
  • AGENTS.md §2.3 and §2.5 gain the profiles field, and §10's checklist asks for it.

This page as Markdown · This page describes the current main branch of MixOS.