Collectivenix fleet

30 machines · 4 platform classes · 1 registry

System

Architecture

A machine is a typed value. The flake turns it into a list of modules and a set of special arguments, hands both to its platform's builder, and installs Home Manager inside. Everything else, from packages to live configuration, hangs off that one path.

Two flakes, one repository

FlakeHoldsVisibility
collective (root)The registry and machine directories, private modules for every platform, local packages (cltv, the fleet daemon, login helpers, gateway tooling), overlays, scripts, live configuration in standalone/ and encrypted secrets.Private
collective-public/Reusable modules (supervisord, the Nix-on-Droid service stack, keyboard layouts, handheld daemon), Android helper packages, a Python overlay and collective-lib.Public: collective-public

The public flake lives in the same repository and is consumed as a path input, so a change to both lands in one commit, and cltv public push publishes it to the public mirror as a Git subtree. collective-lib's parser and evaluator moved to their own public flake, nix-reflect. Each of the two takes the other as an input, and follows = "" breaks the cycle.

How a machine is assembled

How a machine is assembledA vertical line runs from a typed registry entry at the top to a builder at the bottom, collecting modules at six stations. One: the machine's own configuration file. Two: tag modules, such as NixOS-WSL for WSL devboxes. Three: secrets, the sops-nix module for its platform. Four: first-party project modules such as the Attic server, finance, Slack, Microsoft 365, cost and agent services. Five: machine-specific modules such as an OCI image or a microVM. Six: the merged module tree, agnostic plus the platform's own plus the public flake's, with x86-only modules masked to stubs elsewhere. Special arguments are added alongside: inputs, constants, the system's name, type, tags and environment, and collective-lib. The builder is lib.nixosSystem, darwinSystem or nixOnDroidConfiguration, with Home Manager inside. NamedSystem name System.NixOS.X86 [WSL] machines/<name>/nixos/configuration.nix only what is this machine's own NixOS-WSL when tagged WSL wsl.enable, a WSL state version sops-nix for the platform system module, plus Home Manager's first-party project modules Attic server, finance, Slack, M365, cost, agents machine-specific modules an OCI image, a microVM agnostic + platform + public modules x86-only modules masked to stubs elsewhere lib.nixosSystem, darwinSystem or nixOnDroidConfiguration special arguments inputs, outputs, constants systemName, systemType systemArchitecture, systemTags hasSystemTag, configType collectiveEnvironment collective-lib, typed, untyped x86Pkgs, pkgs-stable mkHomeManagerSpecialArgs
Assembly.Modules are collected in a fixed order from the machine's type, tags and name; special arguments go alongside.

The function that does this, modulesFor, dispatches on the system type rather than on machine names, apart from a short list of machine-specific modules. Platform trees are attribute sets (name = import ./name.nix), merged with the public flake's and then flattened, so the public and private trees can each define modules without colliding. Two details make the matrix evaluate everywhere:

Special arguments

Every module, system or home, can take these as arguments:

ArgumentIs
inputs, outputsFlake inputs (filtered per architecture) and this flake's outputs
constantsName, username, UI sizes, time zone, locale, Nix features and constants.registry
systemName, systemType, systemArchitecture, systemTagsThe typed identity of this machine
hasSystemTagA predicate: hasSystemTag WSL
configTypeConfigType.System or ConfigType.Home
collectiveEnvironmentThe environment this machine exports
collective-lib, typed, untypedThe library, and its typed and fast untyped views
pkgs-stable, x86PkgsThe stable package set, and x86 packages from any architecture
getHomeConfig, mkHomeManagerSpecialArgsReach the home configuration from system modules, and build Home Manager's arguments

The module tree

DirectoryFilesFor
modules/agnostic6Every system type: the daemon, FlakeHub, fonts, shell, SSH
modules/home-manager103Every home: agents and AI, terminals, editors, window managers, Git and CI runners, local inference, liveness
modules/nixos38NixOS: devboxes, prebuild service, graphics, gaming, audio, firewall, runners, the model gateway
modules/nix-darwin17macOS: Linux VM guests, runners, audio, window manager, gateway
modules/nix-on-droid8Android: bootstrap, SOCKS tunnel, HTTPS listener, VNC
collective-public/modules37Public: supervisord, Termux, keyboards, handheld daemon

Modules can carry their own tests. The trees are merged with tests.modules.withMergedSuites, exported with tests as testedModules, and stripped of them as modules for consumers.

Packages and first-party tools

The flake has about seventy inputs. Roughly thirty are the owner's own projects, among them the agent orchestrator, its node daemon, the shared context store, the session host, desktop control, terminal tools and work integrations. Each reaches a machine by the same route.

How a first-party tool reaches a machineA horizontal line with five stations. A project repository is pushed. Its flake input in Collective's lock file is advanced, for marked inputs by cltv nix update-inputs. An overlay maps the input's package into the configured package set. A bootstrap category selects it for the architectures it supports. The operator's next switch installs it. A branch from the overlay station leads to cltv run, which runs the package from the configured set without installing it. project repo push to main flake input flake.lock overlay configured pkgs bootstrap category + archs switch installed cltv run pkg#bin no install, no switch cltv nix update-inputs operator
The tool path.One input, one overlay entry, one category entry. A pin update is enough for a new version; nothing is installed imperatively.
  1. Flake input

    The project is an input that follows Collective's nixpkgs where it can. Inputs marked cltv:always-update in flake.nix (31 at this revision) are advanced together or one at a time:

    cltv nix update-inputs --input NAME --no-publish# update only NAME, leave flake.lock uncommitted for review
  2. Overlay

    overlays/default.nix maps the input's package into the configured set, adapting it where needed: one tool is rebuilt against Android's pinned nixpkgs because a newer glibc breaks terminal calls under proot.

  3. Bootstrap category

    modules/home-manager/bootstrap.nix selects about two hundred packages in named categories, each with an architecture selector: _all, _x86, _desktop, _mobile, _nonMac, _mac, and flags such as _workOnly and _bareMetal.

  4. Try it, then switch

    cltv run runs any package from the configured set at the checkout's pins without installing it. The operator's next switch installs it.

    cltv run tools#aztts -- --helpcltv run tools -b aztts -- --help

Several nixpkgs pins coexist, each with a reason recorded beside it: nixos-unstable for the system; a forward pin for packages that should update without a system update; a separate pin for the GitHub Actions runner, which goes stale faster than everything else; release-25.05 for cherry-picks; a fixed revision for Nix-on-Droid; one for the model gateway's Python package; and one for downgrades.

Live configuration outside the store

Home Manager normally copies configuration into the Nix store, so every change needs a switch. Configuration that agents and tools edit while running lives in standalone/ instead (35 directories) and is linked with mkOutOfStoreSymlink, about fifty links from nine modules:

home.file.".pi/agent/settings.json".source = mkOutOfStoreSymlink "${standaloneDir}/settings.json";home.file.".pi/agent/skills".source = mkOutOfStoreSymlink "${standaloneDir}/skills";

Edits take effect at once and are committed like any other change. The coding agent's settings, skills, extensions and prompts, the model gateway's configuration, account maps for GitHub tokens, and per-tool configuration for the workspace manager, chat bridges and agent services all live there.

Trade-offAn out-of-store link points into the checkout, so a machine needs the checkout at ~/collective. Everything that must work without one stays in the store.

The environment every machine exports

Each configuration exports its identity, so scripts never guess what they are running on:

VariableValue
__COLLECTIVE_SYSTEM_NAMEThe machine's registry name
__COLLECTIVE_SYSTEM_TYPEnixos, nix-on-droid or nix-darwin
__COLLECTIVE_SYSTEM_ARCHITECTUREx86_64-linux, aarch64-linux or aarch64-darwin
__COLLECTIVE_HOME_ONLY1 for standalone Home Manager, else 0
__COLLECTIVE_FLAKE_LOCAL_PATH$HOME/collective#name
__COLLECTIVE_FLAKE_STORE_PATHThe same flake pinned in the store
__COLLECTIVE_FLAKE_GITHUB_PATHThe same flake on GitHub
__COLLECTIVE_NIX_REBUILD_CMDThe platform's rebuild tool, with sudo --preserve-env=SSH_AUTH_SOCK where needed
__COLLECTIVE_NIX_SWITCH_CMDThe full switch command, with --option accept-flake-config true
COLLECTIVE_DIR, COLLECTIVE_STANDALONEThe checkout and its standalone/
COLLECTIVE_SECRETS_YAMLThe encrypted secrets file
COLLECTIVE_SYSTEM_SECRETS, COLLECTIVE_HOME_SECRETS/var/run/secrets and $HOME/.config/sops-nix/secrets
__COLLECTIVE_DEBUGSet at run time by cltv debug: verbose cltv logging

A few network constants for well-known hosts are exported too. SSH_AUTH_SOCK survives sudo because private inputs are fetched over SSH during evaluation, and the accepted flake configuration means a switch can use the caches it is about to install.