Files
Nomarchy/flake.nix
Bernardo Magri 37819bb50f feat: v1 release branch — generated flakes and template track ?ref=v1
Installed machines and the downstream template now resolve nomarchy from
git+https://git.bemagri.xyz/bernardo/nomarchy.git?ref=v1 (the lock's
original node carries the ref too), so `nix flake update` follows the
release branch instead of the forge's default branch. README quickstart
points at the real forge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:50:09 +01:00

323 lines
15 KiB
Nix

{
description = "Nomarchy the rock-solid reproducibility of NixOS, the polish of an opinionated desktop";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
home-manager = {
url = "github:nix-community/home-manager/release-26.05";
inputs.nixpkgs.follows = "nixpkgs";
};
stylix = {
url = "github:danth/stylix";
inputs.nixpkgs.follows = "nixpkgs";
inputs.home-manager.follows = "home-manager";
};
# Pinned by Nomarchy so distro + hardware quirks are tested together
# (consumed by lib.mkFlake's hardwareProfile). Its nixpkgs input is
# only used by its own CI — follow ours to keep locks/ISO lean.
nixos-hardware = {
url = "github:NixOS/nixos-hardware/master";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, home-manager, stylix, nixos-hardware, ... }:
let
system = "x86_64-linux";
username = "nomarchy"; # reference host only; downstream picks its own
pkgs = import nixpkgs {
inherit system;
overlays = [ self.overlays.default ];
};
# The guided installer — live-ISO only (not in the overlay): it bakes
# in the downstream template, this checkout's flake.lock, and the
# source identity of this very revision so installs work offline.
gitUrl = "https://git.bemagri.xyz/bernardo/nomarchy.git";
nomarchyInstall = pkgs.callPackage ./pkgs/nomarchy-install {
templateDir = ./templates/downstream;
nomarchyLock = ./flake.lock;
hardwareModuleNames = pkgs.writeText "nixos-hardware-modules.txt"
(nixpkgs.lib.concatStringsSep "\n"
(builtins.attrNames nixos-hardware.nixosModules));
nixpkgsPath = nixpkgs.outPath;
# v1 is the release branch: installed machines track it on
# `nix flake update` instead of the forge's default branch.
flakeUrl = "git+${gitUrl}?ref=v1";
# Future updates re-resolve from the forge (original); the install
# itself resolves from the ISO store (locked = path) — fully
# offline, even from a dirty-tree ISO.
originalNode = { type = "git"; url = gitUrl; ref = "v1"; };
lockedNode = {
type = "path";
path = self.outPath;
narHash = self.narHash;
lastModified = self.lastModified or 0;
};
rev = self.rev or null;
};
in
{
# ─── Downstream API ────────────────────────────────────────────
# A user's machine flake imports these and layers its own
# system.nix / home.nix on top (see templates/downstream).
# Their theme-state.json lives in THEIR flake and is wired up via
# the `nomarchy.stateFile` option — reading it stays pure.
overlays.default = final: prev: {
nomarchy-theme-sync = final.callPackage ./pkgs/nomarchy-theme-sync {
# Presets baked into the package, so `nomarchy-theme-sync list`
# works on machines that don't check out this repo.
themesDir = ./themes;
};
};
nixosModules.nomarchy = {
imports = [ ./modules/nixos ];
nixpkgs.overlays = [ self.overlays.default ];
};
homeModules.nomarchy = {
# stylix's home module is injected here because it needs the
# flake input; modules/home/stylix.nix only configures it.
imports = [ stylix.homeModules.stylix ./modules/home ];
};
# One-call downstream wrapper — see templates/downstream/flake.nix.
# The generated flake is never hand-edited; machines live in
# system.nix / home.nix.
lib = import ./lib.nix {
inherit nixpkgs home-manager nixos-hardware;
nomarchy = self;
};
templates.default = {
path = ./templates/downstream;
description = "Nomarchy machine configuration (downstream flake)";
};
packages.${system} = {
nomarchy-theme-sync = pkgs.nomarchy-theme-sync;
nomarchy-install = nomarchyInstall;
default = pkgs.nomarchy-theme-sync;
};
# ─── Checks ────────────────────────────────────────────────────
# `nix flake check` never evaluates templates/, so exercise the
# downstream template through mkFlake directly (this bypasses the
# template's own three-line flake.nix, which only resolves once
# instantiated — everything else is covered here, including the
# hardwareProfile → nixos-hardware mapping).
checks.${system} =
let
downstream = self.lib.mkFlake {
src = ./templates/downstream;
username = "me";
hardwareProfile = "framework-13-7040-amd"; # exercises the mapping
};
in
{
downstream-template-system =
downstream.nixosConfigurations.default.config.system.build.toplevel;
downstream-template-home =
downstream.homeConfigurations.me.activationPackage;
};
# ─── Reference host ────────────────────────────────────────────
# Manually wired rather than via lib.mkFlake: the repo-root
# theme-state.json is load-bearing (live ISO + in-repo CLI dev),
# which doesn't match mkFlake's src-layout convention.
# Deliberately split rebuild paths:
# system → sudo nixos-rebuild switch --flake .#nomarchy (rare)
# desktop → home-manager switch --flake .#nomarchy (no sudo;
# this is what `nomarchy-theme-sync apply` runs)
# Theme changes never touch the system layer, so they never need
# root and never rebuild the world.
nixosConfigurations.nomarchy = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit username; };
modules = [
self.nixosModules.nomarchy
./hosts/default/configuration.nix
];
};
homeConfigurations.${username} = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
modules = [
self.homeModules.nomarchy
{
# The single source of truth — pure read: the file is part
# of this flake's source.
nomarchy.stateFile = ./theme-state.json;
home = {
inherit username;
homeDirectory = "/home/${username}";
};
}
];
};
# ─── Live ISO ──────────────────────────────────────────────────
# Full desktop on a bootable stick, no installation:
# nix build .#nixosConfigurations.nomarchy-live.config.system.build.isoImage
# Test in QEMU with tools/test-live-iso.sh. See docs/TESTING.md.
nixosConfigurations.nomarchy-live = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit username; nomarchySrc = self; };
modules = [
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
self.nixosModules.nomarchy
./hosts/live.nix
# The ISO is a sealed artifact, so the desktop is baked in via
# the HM NixOS module (the installed-system split into
# nixos-rebuild vs home-manager switch doesn't apply here —
# though the seeded ~/.nomarchy flake still allows standalone
# `home-manager switch` for theme testing).
home-manager.nixosModules.home-manager
{
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
users.${username} = {
imports = [ self.homeModules.nomarchy ];
nomarchy.stateFile = ./theme-state.json;
};
};
# The standalone CLI that `nomarchy-theme-sync apply` runs for
# theme switching, from the same pinned input — plus the
# guided installer.
environment.systemPackages = [
home-manager.packages.${system}.home-manager
nomarchyInstall
];
# Keep the locked flake inputs in the ISO store so theme
# switching on the live system works without a network: the
# lockfile's narHashes resolve against these paths instead
# of fetching from GitHub. Must be *transitive* — evaluating
# the seeded flake also fetches stylix's own inputs.
system.extraDependencies =
let
collectInputs = input:
[ input.outPath ] ++ builtins.concatMap collectInputs
(builtins.attrValues (input.inputs or { }));
in
collectInputs self ++ (
# Pre-build a system shaped like what nomarchy-install
# GENERATES (snapper, auto-login, LUKS initrd, swapfile +
# resume — not the bare template!) plus the template
# desktop, so the offline install never builds packages
# from source: a custom username/hostname then changes
# only a handful of derivations. No hardware profile —
# matches a VM install; profiled installs share most of it.
let
template = self.lib.mkFlake {
src = ./templates/downstream;
username = username;
};
representativeInstall = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit username; };
modules = [
self.nixosModules.nomarchy
{ environment.systemPackages = [ home-manager.packages.${system}.home-manager ]; }
# The installer's autodetection emits these on real
# machines (and the intel CPU module pulls the whole
# iGPU stack) — without them an offline install on
# common hardware builds graphics drivers from
# source. Model-specific profiles may still need
# network; the common ground is covered.
nixos-hardware.nixosModules.common-cpu-intel
nixos-hardware.nixosModules.common-cpu-amd
nixos-hardware.nixosModules.common-gpu-amd
nixos-hardware.nixosModules.common-pc-laptop
nixos-hardware.nixosModules.common-pc-ssd
nixos-hardware.nixosModules.common-pc
(./templates/downstream + "/hardware-configuration.nix")
(./templates/downstream + "/system.nix")
({ lib, ... }: {
nomarchy.system.snapper.enable = true;
nomarchy.system.greeter.autoLogin = username;
swapDevices = [{ device = "/swap/swapfile"; }];
boot.resumeDevice = "/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000";
boot.kernelParams = [ "resume_offset=1" ];
boot.initrd.luks.devices.crypted.device = "/dev/disk/by-partlabel/disk-main-root";
# The real install is BTRFS with a vfat ESP — the
# placeholder's bare ext4 would leave fs-gated bits
# out of this pin (snapper via btrfs; mtools and
# dosfstools enter systemPackages via vfat!).
fileSystems."/" = lib.mkForce {
device = "/dev/mapper/crypted";
fsType = "btrfs";
options = [ "subvol=@" "compress=zstd" "noatime" ];
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/0000-0000";
fsType = "vfat";
options = [ "umask=0077" ];
};
})
];
};
in [
representativeInstall.config.system.build.toplevel
template.homeConfigurations.${username}.activationPackage
# A real install rebuilds the drvs that embed the
# user's hostname/username/UUIDs (etc, initrd, units,
# toplevel, HM activation). inputDerivation pins each
# one's direct build inputs…
representativeInstall.config.system.build.toplevel.inputDerivation
representativeInstall.config.system.build.etc.inputDerivation
representativeInstall.config.system.build.initialRamdisk.inputDerivation
template.homeConfigurations.${username}.activationPackage.inputDerivation
] ++ [
# …and the second-level build tools those rebuilds need
# (their parents are themselves rebuilt, so parent
# inputDerivations don't reach them): kernel-module
# shrinking, unit-dir assembly, dbus config, /etc.
pkgs.kmod.dev
pkgs.nukeReferences
pkgs.xorg.lndir
pkgs.libxslt.bin
pkgs.python3Minimal
# system-path is rebuilt on EVERY machine: buildEnv's
# package-list order follows module-graph order, and any
# hardware profile/import reshuffles it. Its builder
# wants these.
pkgs.texinfo
pkgs.getconf
# greetd.toml embeds the auto-login user and is generated
# by remarshal — without this pin an offline install
# tries to BUILD remarshal, whose test closure pulls
# matplotlib/ffmpeg/x265 (the ~1900-drv cascade).
pkgs.remarshal
# …and one representative disko script: its tool closure
# (file, which, wrapper hooks, …) isn't otherwise on the
# ISO, and offline nix would try to build it from source.
# The user's actual args produce a different script drv,
# but with identical inputs — built offline in seconds.
(import "${pkgs.disko}/share/disko/cli.nix" {
inherit pkgs;
mode = "destroy,format,mount";
diskoFile = ./pkgs/nomarchy-install/disko-config.nix;
mainDrive = "/dev/vda";
withLuks = true;
swapSize = "2G";
})
]
);
}
];
};
};
}