Chapters

Nix

Copland OS uses Nix to evaluate its configuration and build the system. It provides its own system modules and activation process rather than using the full upstream NixOS module stack.

Many design decisions and parts of the implementation come from nixpkgs and finix. Both projects deserve credit for that foundation.

OpenRC

Copland OS uses OpenRC for init and service management. We prefer its shell-based service scripts and explicit dependencies because they make service behavior straightforward to inspect and adapt. Services are declared in Nix, and Copland generates the corresponding OpenRC scripts and runlevel entries.

Configurations

The Copland repository provides the operating system itself: its modules, packages and installer. A concrete machine is described in a separate configuration that uses Copland as a pinned source. Accounts, hardware details, selected services, secrets and Home configuration belong there, so installing or customizing a machine does not require changes to Copland.

Layout

The installer creates this configuration in ~/copland-config. Its basic structure looks like this:

copland-config/
├── default.nix
├── justfile
├── npins/
└── hosts/
    └── <host>/
        └── default.nix

npins pins the Copland source the configuration builds against. hosts/<host>/default.nix is the entry point for one machine and imports the modules next to it.

Registering a host

The configuration's default.nix imports the pinned Copland source and registers each machine with copland.evalSystem:

let
  sources = import ./npins;
  copland = import sources.copland-os {
    system = "x86_64-linux";
  };
in
{
  inherit (copland) pkgs sources;
  hosts.laptop = copland.evalSystem { modules = [ ./hosts/laptop ]; };
}

evalSystem loads Copland's module catalog before the host's own modules, so options such as services.iwd or container are available without importing them. Services stay inactive until the host enables them. The result provides the system generation as hosts.laptop.toplevel, the disk layout as hosts.laptop.diskoScript and the Home generations as hosts.laptop.homes.<user>.

Building

The generated justfile provides just build, just switch, just test and just boot for the local machine. They run Copland's deploy.sh with this configuration. Switching and rollback explains what each action does.

Generations

A system generation is a built version of your system configuration. It contains the selected software, generated configuration files, boot files and activation scripts.

Modules

Nix modules describe the desired configuration. For example, this module adds Git to the system packages and selects the system timezone:

{ pkgs, 
... 
}:
{
  environment.systemPackages = [ 
    pkgs.git 
  ];
  time.timeZone = "Europe/Berlin";
}

Evaluation

The module system combines the imported modules, resolves option values and checks their types. Nix uses the resulting configuration to describe the packages, files and scripts to build.

Build

The build produces a system at its own Nix store path, building dependencies or obtaining them from a configured binary cache. The previous generation stays intact, and the running system remains unchanged until activation.

Activation

Activation applies the generation to the machine by preparing system files, accounts, declared storage and secrets. During a live switch, Copland then updates the running OpenRC services to match the new configuration.

Switching and rollback

When switching to another system generation, Copland applies the new configuration and updates the running services. Changed or removed services are stopped using their old definitions. OpenRC then starts the services selected by the new configuration, and Copland checks their status before completing the switch.

Some components cannot be replaced while the system is running, such as the kernel or the session services. If the new configuration changes one of them, Copland refuses the live switch before changing anything. The new generation then has to be selected for the next boot and started with a reboot. Switching and rollback lists these components.

Automatic rollback

If the new configuration cannot be activated or its services fail to start, Copland switches back to the previous generation and starts its services again. This helps keep the system usable when an update fails.

We are aware that not every service failure needs to trigger a rollback. Sometimes it is fine to complete the switch and start a service manually later. We plan to make this configurable.

Generations and snapshots

System generations and ZFS snapshots serve different purposes. A generation keeps the system configuration and software available, while a snapshot preserves the contents of a dataset. Copland does not create or restore ZFS snapshots as part of a system switch, so rolling back a generation leaves application data in place.

ZFS

ZFS is the primary filesystem supported by Copland OS. The native Live ISO includes ZFS support and installs the kernel-matched modules through Nix.

Datasets

ZFS is also used for persistent service state. A stateful service can declare its own dataset, mountpoint, ownership, and quota. During activation, Copland creates missing datasets, mounts them, applies their settings, and verifies that the expected dataset is mounted at the expected path.

The service configuration remains reproducible while its data stays outside the Nix store. The dataset can then use the normal ZFS tools for snapshots, backups, exports, and transfers between systems.

Secrets

Secret management is built into Copland through its native age module. No separate integration such as sops-nix is needed to declare encrypted secrets alongside the system configuration.

Encrypted files can stay in the repository and are decrypted on the target machine during activation. Copland manages their installation paths, ownership, permissions, and lifecycle, with decrypted files normally stored below /run/secrets.

A secret can also declare which OpenRC services should restart when its encrypted source changes. This connects secret updates to the normal system switch without requiring a separate manual restart.

Copland Home

Copland Home covers the same general area as upstream Home Manager, but takes a different approach and uses its own evaluator and activation program instead of importing Home Manager.

Non-editable configuration

Generated configuration and other stable files belong to the "set and forget" side of a Home generation. Their installed files are read-only links into the Nix store. To change them, edit their source or Nix declaration and build and activate a new Home generation.

Editable configuration

Editable files are linked directly to their source outside the Nix store. A Mango or shell configuration can be changed by editing the original file, without translating it into Nix syntax or rebuilding the Home configuration after every change. These links use string sources and depend on the original file being available.

Hybrid configuration

Hybrid files combine direct editing with a stored fallback. With a Nix path and live = true, activation links the editable checkout file when it is available and otherwise links the read-only copy from the Home generation. This keeps the configuration usable on a machine without the checkout.

Home activation

A Home generation also contains managed directories, session variables, and OpenRC user services. Its activation checks for conflicting files before changing anything and restores the previous links and profile if the switch fails.