Project 7: Complete custom distro product

Updated

July 30, 2026

Project 7 — Ship a complete NixOS-based distro flake

Goal: Assemble a product-shaped flake: branded identity, at least two editions (minimal + workstation or server), installer ISO, pinned nixpkgs 26.05, documented install path, first-boot defaults, and a demo script—your own distro in engineering terms (not a legal claim on the NixOS trademark).

Field Value
Baseline Nix 2.34, NixOS / nixpkgs 26.05
Time ~4–12 hours (integration)
Depends on Projects 5–6; packaging Projects 1–4 optional but enriching
Artifact Public-ready README + DEMO.md + multi-output flake
Important

Trademark: “NixOS” is a trademark of the NixOS Foundation. Your derivative must use a distinct name and follow NixOS branding guidelines when mentioning the base. This project teaches reproducible OS assembly—not unofficial branding abuse.


Why this project exists

Projects 5–6 create identity and an ISO. Productization means:

  • Editions as module bundles (not copy-pasted hosts).
  • Channels of truth: flake lock + docs, not tribal memory.
  • Install + upgrade story a stranger could follow.
  • Honest boundary: what is your policy vs upstream NixOS.

Treat this like a tiny open-source OS product even if only you use it.


Product definition of done

# Requirement
1 Distinct distro name + version in PRODUCT.md
2 flake.lock pins nixos-26.05 (or documented stable choice)
3 nixosConfigurations for iso and at least one installed host profile
4 Editions: e.g. minimal and desktop or server module bundles
5 Branding on ISO + installed system path (Projects 5–6)
6 Defaults module: flakes-on, explicit unfree policy, nix settings
7 README: build ISO, install notes, upgrade story
8 DEMO.md with commands + evidence list
9 Optional: CI stub (nix flake check / ISO build in GA)

Target tree

my-distro/
  PRODUCT.md
  README.md
  DEMO.md
  LICENSE                 # for *your* modules/assets
  flake.nix
  flake.lock
  branding/
    logo.svg
    README.md
  modules/
    identity.nix
    themes.nix
    defaults-nix.nix
    edition-minimal.nix
    edition-desktop.nix   # or edition-server.nix
  hosts/
    iso.nix
    demo-vm.nix
  packages/
    default.nix           # optional distro toolkit
  checks/                 # optional
    default.nix

Merge everything you already built in Projects 5–6; refactor names for consistency.


Theory: editions vs hosts

modules/edition-*.nix   =  product SKUs (what kind of system)
hosts/*.nix             =  concrete machines / images (where it runs)
Layer Example
Edition minimal SSH appliance defaults
Host demo-vm imports minimal + disk/boot for QEMU
Image iso imports minimal + installer profile + themes

Do not put disk device paths inside edition modules.


Step 1 — Defaults module (flakes-first)

modules/defaults-nix.nix:

{ lib, ... }: {
  nix = {
    settings = {
      experimental-features = [ "nix-command" "flakes" ];
      # trusted-users: keep tight on real products
      # substituters / trusted-public-keys: document if you run a cache
      auto-optimise-store = lib.mkDefault true;
    };
    # gc.automatic is a product policy — enable only with thought
  };

  # Product policy: be explicit
  nixpkgs.config.allowUnfree = lib.mkDefault false;

  # Prefer flake-pinned nixpkgs over channels in docs and defaults
  # (channel-less mental model for users of your distro)
}

Document in README: “This product is flakes-first; classic channels are not the support path.”


Step 2 — Edition modules

Minimal

modules/edition-minimal.nix:

{ lib, pkgs, ... }: {
  imports = [
    ./identity.nix
    ./defaults-nix.nix
  ];

  myDistro.enable = lib.mkDefault true;

  services.openssh.enable = lib.mkDefault true;
  networking.firewall.enable = lib.mkDefault true;

  environment.systemPackages = with pkgs; [
    vim
    git
    curl
  ];

  # Users: force consumers to define real users on hosts
}

Desktop (or server)

modules/edition-desktop.nix:

{ lib, pkgs, ... }: {
  imports = [
    ./edition-minimal.nix
    ./themes.nix
  ];

  # Pick ONE desktop stack and stick to it for the lab
  services.xserver.enable = true;
  services.displayManager.gdm.enable = true;   # verify attr path on 26.05
  services.desktopManager.gnome.enable = true; # or plasma6 / something you know

  networking.networkmanager.enable = true;

  environment.systemPackages = with pkgs; [
    firefox
  ];
}
Warning

Display manager option paths have moved across releases (services.xserver.displayManager vs services.displayManager, GNOME/Plasma module names). Eval on your pin and adjust. The product lesson is edition composition, not memorizing one GNOME attr forever.

Server alternative: skip GUI; add fail2ban posture notes, unattended-upgrade policy or flake-based upgrade docs only.


Step 3 — Hosts

Demo VM / installed profile

hosts/demo-vm.nix:

{ lib, modulesPath, ... }: {
  imports = [
    (modulesPath + "/virtualisation/qemu-vm.nix") # if using build-vm patterns
    ../modules/edition-minimal.nix
    # ../modules/edition-desktop.nix
  ];

  myDistro.enable = true;
  myDistro.name = "MyLabOS";
  myDistro.version = "26.05.0-prod1";

  networking.hostName = "mylabos-demo";

  users.users.demo = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
    # initialPassword only for disposable lab VMs — never for public ISOs if weak
    initialPassword = "demo"; # LAB ONLY
  };

  # boot.loader + fileSystems: required for real metal; qemu-vm module helps for demos
}

ISO host

Keep Project 6 hosts/iso.nix importing identity, themes, installer profile, and edition-minimal (not full desktop unless you accept huge ISOs).


Step 4 — Flake product surface

{
  description = "MyLabOS — NixOS-based lab distro";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      nixosConfigurations = {
        iso = nixpkgs.lib.nixosSystem {
          inherit system;
          modules = [ ./hosts/iso.nix ];
        };
        demo-vm = nixpkgs.lib.nixosSystem {
          inherit system;
          modules = [ ./hosts/demo-vm.nix ];
        };
      };

      packages.${system} = {
        # optional toolkit
        mylabos-tools = pkgs.callPackage ./packages/default.nix { };
      };

      # formatter, checks, devShells — add as you harden the product
      checks.${system}.demo-vm-eval =
        self.nixosConfigurations.demo-vm.config.system.build.toplevel;
    };
}
nix flake show
nix build -L .#nixosConfigurations.demo-vm.config.system.build.toplevel
nix build -L .#nixosConfigurations.iso.config.system.build.isoImage
nix flake check   # if checks are wired

Step 5 — Optional metapackage (toolkit)

packages/default.nix:

{ symlinkJoin, hello, git, vim }:

symlinkJoin {
  name = "mylabos-tools";
  paths = [ hello git vim ];
  meta.description = "Convenience toolkit metapackage for MyLabOS demos";
}

Ship only if it clarifies the product; empty packages/ is fine.


Step 6 — Install story (document, then test once)

Write this in README, then execute once on a disposable VM disk:

  1. Boot ISO (Project 6).
  2. Partition (manual or Disko—advanced).
  3. Install from your flake:
# Conceptual — exact installer flow varies by profile and version.
# Prefer copying the flake onto the live system or using a reachable git remote.

sudo nixos-install --flake /path/to/my-distro#demo-vm

# or, if published:
# sudo nixos-install --flake github:you/my-distro#demo-vm
  1. Reboot into installed system.
  2. Verify branding (os-release, issue) and flakes defaults.
  3. Exercise upgrade path:
# on a machine that tracks your flake
cd /etc/nixos   # or your flake path
nix flake update  # policy: when and how you allow this
sudo nixos-rebuild switch --flake .#demo-vm

Document failure modes: wrong host attr, missing boot loader, network for first download.


Step 7 — README + DEMO.md (required)

README minimum: what it is (NixOS-based, trademark-safe), quick start, build ISO, QEMU, install, editions, upgrade policy, branding/license, upstream vs yours, limitations. Paste the exact commands that worked.

DEMO.md minimum:

  1. Build ISO command
  2. QEMU one-liner
  3. Evidence list (boot, issue, os-release, optional fetch)
  4. Upgrade: bump flake input → rebuild
  5. Limitations (hardware, Secure Boot, blobs, not a Foundation release)

Run the demo once end-to-end before calling Project 7 green.


Step 8 — Channels of truth

Source Content
flake.lock Exact nixpkgs (and other inputs)
PRODUCT.md Name, version, release notes
README.md Operator docs
modules/* Behavior
CI (optional) Prevents silent breakage

Avoid a second brain of classic channels. Binary caches are optimization, not identity.


Pitfalls

Symptom Likely cause Fix
ISO branded, install not Host forgot identity import Fix import chain
Desktop never evals DM option names on 26.05 Align to pin
nixos-install misses flake Path/remote Copy flake into live image
Weak password on public ISO Lab password leaked into iso host Demo-only secrets
Scope explosion Out-featuring NixOS Two editions only

Acceptance criteria

  • Two editions as modules (minimal + desktop/server).
  • ISO and demo host configs build.
  • Branding on install path or dual proof (live ISO + demo VM) with evidence.
  • README + DEMO.md + PRODUCT.md complete.
  • Trademark-safe naming; flakes on; unfree policy explicit.
  • flake.lock committed.
  • Note: what you would “sell” vs what remains upstream NixOS.

Stretch goals

  1. Calamares-branded graphical installer.
  2. Binary cache + documented substituters.
  3. deploy-rs / Colmena for two demo VMs.
  4. nixosTests grepping PRETTY_NAME; optional GA nix flake check.
  5. Ship Project 1–4 packages under packages/.

Capstone reflection (½ page)

In PRODUCT.md / RETRO.md: what is yours vs forever-upstream; what breaks first on the next stable bump; would you trust this on a travel laptop tomorrow?


Self-check

  1. Edition vs host vs image in your tree.
  2. Upgrade channel of truth?
  3. How ISO and installed systems avoid drift?
  4. Fifteen-minute demo for a friend?

Done with the projects track

Return to the Projects index and tick the global bar. Optional: fold into capstone days 83–90 or publish the flake as a portfolio piece labeled as a lab derivative.