diff --git a/default.nix b/default.nix index 3fec19a..01b949a 100644 --- a/default.nix +++ b/default.nix @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: 2026 Mattias Kockum # # SPDX-License-Identifier: MIT @@ -12,16 +13,6 @@ netbootIP ? "169.254.1.1", }: let - inherit (lib) - concatStringsSep - filter - attrNames - mapAttrs' - isFunction - nameValuePair - removeSuffix - mapAttrs - ; defaultEdition = "acmecorp-bureautix"; @@ -38,220 +29,27 @@ let }; }; - # Default system closure - # This is the system that gets installed by default automatically without any user customization. - defaultSystem = securix.lib.mkTerminal { - name = "default"; - edition = defaultEdition; - userSpecificModule = { }; - vpnProfiles = { }; - modules = [ - ./common - { - securix = { - self = { - mainDisk = "/dev/nvme0n1"; - machine = { - hardwareSKU = "x280"; - serialNumber = "000000"; - }; - }; - graphical-interface.variant = "kde"; - }; - } - ]; - }; - - # CI workflows - nix-reuse = ((import sources.nix-reuse { }).override { input = _: { nixpkgs = pkgs; }; }).output; - nix-actions = import sources.nix-actions { inherit pkgs; }; - - git-checks = (import sources.git-hooks).run { - src = ./.; - - hooks = { - statix = { - enable = true; - stages = [ "pre-push" ]; - settings.ignore = [ - "**/npins/*" - ]; - }; - - nixfmt-rfc-style = { - enable = true; - stages = [ "pre-push" ]; - package = pkgs.nixfmt-rfc-style; - }; - - reuse = nix-reuse.gitHook { }; - }; + defaultSystem = import ./lib/default-system.nix { + inherit securix defaultEdition; }; - reuse = nix-reuse.run { - defaultLicense = "MIT"; - defaultCopyright = "Sécurix project authors"; - - downloadLicenses = true; - generatedPaths = [ - "**/.envrc" - ".gitignore" - "REUSE.toml" - "shell.nix" - "treefmt.toml" - - ".github/workflows/*" - "**/npins/*" - ]; + moduleArgs = { + inherit + sources + pkgs + lib + securix + defaultSystem + netbootIP + ; }; - workflows = nix-actions.install { - src = ./.; - platform = "github"; - - workflows = mapAttrs' ( - name: _: - nameValuePair (removeSuffix ".nix" name) ( - let - w = import ./workflows/${name}; - args = { - inherit nix-actions; - inherit (pkgs) lib; - }; - in - if (isFunction w) then (w args) else w - ) - ) (builtins.readDir ./workflows); - }; + installers = import ./installers moduleArgs; + registry = import ./registry moduleArgs; + dev = import ./dev moduleArgs; in -rec { - # Generic installer for any laptops. - # There's a netboot installer, see `netboot/README.md` for documentation. - # There's an USB generic installer that will install a default system. - - net-installer = securix.lib.buildNetbootInstaller { - # This is the system model that is used for the partitioning. - # We only want to extract the formatting and mounting script, we should not take MORE than that with us. - baseModules = defaultSystem.partitioningModules ++ [ - { - securix = { - self.mainDisk = "/dev/nvme0n1"; - filesystems.layout = "office_v1"; - }; - } - ]; - extraInstallerModules = [ - "${sources.snowboot}/nix-modules/fetch-system-from-binary-cache.nix" - { - boot = { - initrd = { - availableKernelModules = [ - "cdc_ncm" - "virtio-pci" - "virtio-net" - ]; - systemd.enable = true; - }; - snowboot.fetch-system-from-binary-cache.enable = true; - }; - # Use mDNS here instead. - nix.settings.substituters = lib.mkForce [ "http://${netbootIP}:8000?trusted=1" ]; - fileSystems."/" = { - fsType = "tmpfs"; - device = "tmpfs"; - options = [ "mode=0755" ]; - }; - - networking.hostName = "netboot-installer-v1"; - environment.systemPackages = [ - (pkgs.writers.writePython3Bin "nixos-installer" { - flakeIgnore = [ - "E501" - "E302" - "E305" - "E124" - "E265" - "E303" - ]; - } ./pkgs/nixos-installer/installer.py) - ]; - } - ]; - # NOTE: `unsafeDiscardStringContext` is used here to avoid to bring with us the full default system toplevel. - # On a netboot system, you live in RAM and if your default system contains a bunch of things, you can saturate the RAM during the installation. - # This is not a problem on a USB stick. - installScript = '' - nixos-installer --toplevel-registry-uri http://${netbootIP}:8000/snowboot/toplevel/toplevels --default-toplevel ${builtins.unsafeDiscardStringContext defaultSystem.system.toplevel} - ''; - }; - - usb-installer = securix.lib.buildUSBInstallerISO { - # We can include the whole default system in the USB stick to accelerate installation. - inherit (defaultSystem) modules; - - extraInstallerModules = [ - { - networking.hostName = "generic-installer-v1"; - environment.systemPackages = [ - (pkgs.writers.writePython3Bin "nixos-installer" { - flakeIgnore = [ - "E501" - "E302" - "E305" - "E124" - "E265" - "E303" - ]; - } ./pkgs/nixos-installer/installer.py) - ]; - } - ]; - installScript = '' - nixos-installer --default-toplevel ${defaultSystem.system.toplevel} - ''; - }; - - # { , , ... } - terminals = mapAttrs ( - serial: - { machineModule, userModules }: - securix.lib.mkTerminal { - name = serial; - userSpecificModule = { }; - vpnProfiles = { }; - modules = [ - machineModule - ./common - ] - ++ userModules; - } - ) (securix.lib.readInventory2 { dir = ./inventory; }); - - # Toplevel registry: - # iterate over all terminals and perform: $serial $toplevel generation. - # This builds ALL system configurations. - toplevelRegistry = - let - toplevels = map (serial: "${serial} ${terminals.${serial}.system.config.system.build.toplevel}") ( - attrNames terminals - ); - in - pkgs.writeText "toplevels" (concatStringsSep "\n" toplevels); - - shell = pkgs.mkShell { - # Inspired by DGNum's infrastructure. - shellHook = builtins.concatStringsSep "\n" [ - git-checks.shellHook - reuse.shellHook - workflows.shellHook - "unset shellHook # do not contaminate nested shells" - ]; - preferLocalBuild = true; - packages = [ - pkgs.treefmt - pkgs.nixfmt-rfc-style - pkgs.npins - pkgs.reuse - ]; - }; +{ + inherit (installers) net-installer usb-installer; + inherit (registry) terminals toplevelRegistry; + inherit (dev) shell; } diff --git a/dev/ci.nix b/dev/ci.nix new file mode 100644 index 0000000..f955503 --- /dev/null +++ b/dev/ci.nix @@ -0,0 +1,78 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# +# SPDX-License-Identifier: MIT + +# CI workflows +{ pkgs, sources, lib, ... }: +let + inherit (lib) + mapAttrs' + isFunction + nameValuePair + removeSuffix + ; + + nix-reuse = ((import sources.nix-reuse { }).override { input = _: { nixpkgs = pkgs; }; }).output; + nix-actions = import sources.nix-actions { inherit pkgs; }; + + git-checks = (import sources.git-hooks).run { + src = ../.; + + hooks = { + statix = { + enable = true; + stages = [ "pre-push" ]; + settings.ignore = [ + "**/npins/*" + ]; + }; + + nixfmt-rfc-style = { + enable = true; + stages = [ "pre-push" ]; + package = pkgs.nixfmt-rfc-style; + }; + + reuse = nix-reuse.gitHook { }; + }; + }; + + reuse = nix-reuse.run { + defaultLicense = "MIT"; + defaultCopyright = "Sécurix project authors"; + + downloadLicenses = true; + generatedPaths = [ + "**/.envrc" + ".gitignore" + "REUSE.toml" + "shell.nix" + "treefmt.toml" + + ".github/workflows/*" + "**/npins/*" + ]; + }; + + workflows = nix-actions.install { + src = ../.; + platform = "github"; + + workflows = mapAttrs' ( + name: _: + nameValuePair (removeSuffix ".nix" name) ( + let + w = import ../workflows/${name}; + args = { + inherit nix-actions; + inherit (pkgs) lib; + }; + in + if (isFunction w) then (w args) else w + ) + ) (builtins.readDir ../workflows); + }; +in +{ + inherit git-checks reuse workflows; +} diff --git a/dev/default.nix b/dev/default.nix new file mode 100644 index 0000000..2647642 --- /dev/null +++ b/dev/default.nix @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: 2026 Mattias Kockum +# +# SPDX-License-Identifier: MIT + +args: +let + ci = import ./ci.nix args; +in +{ + shell = import ./shell.nix (args // { inherit ci; }); +} diff --git a/dev/shell.nix b/dev/shell.nix new file mode 100644 index 0000000..fd93074 --- /dev/null +++ b/dev/shell.nix @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# +# SPDX-License-Identifier: MIT + +{ pkgs, ci, ... }: +pkgs.mkShell { + # Inspired by DGNum's infrastructure. + shellHook = builtins.concatStringsSep "\n" [ + ci.git-checks.shellHook + ci.reuse.shellHook + ci.workflows.shellHook + "unset shellHook # do not contaminate nested shells" + ]; + preferLocalBuild = true; + packages = [ + pkgs.treefmt + pkgs.nixfmt-rfc-style + pkgs.npins + pkgs.reuse + ]; +} diff --git a/installers/default.nix b/installers/default.nix new file mode 100644 index 0000000..eec704e --- /dev/null +++ b/installers/default.nix @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: 2026 Mattias Kockum +# +# SPDX-License-Identifier: MIT + +# Generic installer for any laptops. +args: +{ + net-installer = import ./netboot.nix args; + usb-installer = import ./usb.nix args; +} diff --git a/installers/netboot.nix b/installers/netboot.nix new file mode 100644 index 0000000..ba0f05d --- /dev/null +++ b/installers/netboot.nix @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# +# SPDX-License-Identifier: MIT + +# There's a netboot installer, see `netboot/README.md` for documentation. +{ lib, pkgs, sources, securix, defaultSystem, netbootIP, ... }: + +securix.lib.buildNetbootInstaller { + # This is the system model that is used for the partitioning. + # We only want to extract the formatting and mounting script, we should not take MORE than that with us. + baseModules = defaultSystem.partitioningModules ++ [ + { + securix = { + self.mainDisk = "/dev/nvme0n1"; + filesystems.layout = "office_v1"; + }; + } + ]; + extraInstallerModules = [ + "${sources.snowboot}/nix-modules/fetch-system-from-binary-cache.nix" + { + boot = { + initrd = { + availableKernelModules = [ + "cdc_ncm" + "virtio-pci" + "virtio-net" + ]; + systemd.enable = true; + }; + snowboot.fetch-system-from-binary-cache.enable = true; + }; + # Use mDNS here instead. + nix.settings.substituters = lib.mkForce [ "http://${netbootIP}:8000?trusted=1" ]; + fileSystems."/" = { + fsType = "tmpfs"; + device = "tmpfs"; + options = [ "mode=0755" ]; + }; + + networking.hostName = "netboot-installer-v1"; + environment.systemPackages = [ + (pkgs.writers.writePython3Bin "nixos-installer" { + flakeIgnore = [ + "E501" + "E302" + "E305" + "E124" + "E265" + "E303" + ]; + } ../pkgs/nixos-installer/installer.py) + ]; + } + ]; + # NOTE: `unsafeDiscardStringContext` is used here to avoid to bring with us the full default system toplevel. + # On a netboot system, you live in RAM and if your default system contains a bunch of things, you can saturate the RAM during the installation. + # This is not a problem on a USB stick. + installScript = '' + nixos-installer --toplevel-registry-uri http://${netbootIP}:8000/snowboot/toplevel/toplevels --default-toplevel ${builtins.unsafeDiscardStringContext defaultSystem.system.toplevel} + ''; +} diff --git a/installers/usb.nix b/installers/usb.nix new file mode 100644 index 0000000..65a97bf --- /dev/null +++ b/installers/usb.nix @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# +# SPDX-License-Identifier: MIT + +# There's an USB generic installer that will install a default system. +{ pkgs, securix, defaultSystem, ... }: + +securix.lib.buildUSBInstallerISO { + # We can include the whole default system in the USB stick to accelerate installation. + inherit (defaultSystem) modules; + + extraInstallerModules = [ + { + networking.hostName = "generic-installer-v1"; + environment.systemPackages = [ + (pkgs.writers.writePython3Bin "nixos-installer" { + flakeIgnore = [ + "E501" + "E302" + "E305" + "E124" + "E265" + "E303" + ]; + } ../pkgs/nixos-installer/installer.py) + ]; + } + ]; + installScript = '' + nixos-installer --default-toplevel ${defaultSystem.system.toplevel} + ''; +} diff --git a/lib/default-system.nix b/lib/default-system.nix new file mode 100644 index 0000000..c8757f5 --- /dev/null +++ b/lib/default-system.nix @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# +# SPDX-License-Identifier: MIT + +# Default system closure +# This is the system that gets installed by default automatically without any user customization. +{ securix, defaultEdition, ... }: + +securix.lib.mkTerminal { + name = "default"; + edition = defaultEdition; + userSpecificModule = { }; + vpnProfiles = { }; + modules = [ + ../common + { + securix = { + self = { + mainDisk = "/dev/nvme0n1"; + machine = { + hardwareSKU = "x280"; + serialNumber = "000000"; + }; + }; + graphical-interface.variant = "kde"; + }; + } + ]; +} diff --git a/registry/default.nix b/registry/default.nix new file mode 100644 index 0000000..90747ec --- /dev/null +++ b/registry/default.nix @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# +# SPDX-License-Identifier: MIT + +{ lib, pkgs, securix, ... }: +let + inherit (lib) mapAttrs attrNames; + inherit (builtins) concatStringsSep; + + # { , , ... } + terminals = mapAttrs ( + serial: + { machineModule, userModules }: + securix.lib.mkTerminal { + name = serial; + userSpecificModule = { }; + vpnProfiles = { }; + modules = [ + machineModule + ../common + ] + ++ userModules; + } + ) (securix.lib.readInventory2 { dir = ../inventory; }); + + # Toplevel registry: + # iterate over all terminals and perform: $serial $toplevel generation. + # This builds ALL system configurations. + toplevelRegistry = + let + toplevels = map (serial: "${serial} ${terminals.${serial}.system.config.system.build.toplevel}") ( + attrNames terminals + ); + in + pkgs.writeText "toplevels" (concatStringsSep "\n" toplevels); +in +{ + inherit terminals toplevelRegistry; +}