diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bab5a4e..4081f6e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,6 +45,15 @@ jobs: fi - name: Build the Modules for ${{ matrix.nixOpts.tag }} run: nix -L flake check ${{ matrix.nixOpts.flag }} + - name: Build the NixOS installer for Nvidia Jetson Orin Nano devices + run: nix -L build .#packages.x86_64-linux.jetsonOrinNanoInstaller + - name: Upload the installer as artifact + uses: actions/upload-artifact@v6 + with: + name: nvidia-jetson-orin-nano-nixos-${{ github.sha }} + path: result/iso/nixos-minimal-*.iso + retention-days: 7 + if-no-files-found: error - name: Export Nix Store if: always() run: | diff --git a/flake.lock b/flake.lock index c3832e5..0c359d3 100644 --- a/flake.lock +++ b/flake.lock @@ -3,15 +3,15 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1747046372, - "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", - "owner": "edolstra", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", "repo": "flake-compat", - "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { - "owner": "edolstra", + "owner": "NixOS", "repo": "flake-compat", "type": "github" } @@ -21,11 +21,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1762980239, - "narHash": "sha256-8oNVE8TrD19ulHinjaqONf9QWCKK+w4url56cdStMpM=", + "lastModified": 1767609335, + "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "52a2caecc898d0b46b2b905f058ccc5081f842da", + "rev": "250481aafeb741edfe23d29195671c19b36b6dca", "type": "github" }, "original": { @@ -57,11 +57,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1763421233, - "narHash": "sha256-re7SYzc1gs7069DojQiW90fPjNfCUJ9+RUEplQPqxDs=", - "rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648", + "lastModified": 1767767207, + "narHash": "sha256-LmdqDciUNW4a3tFH539SorGLD/5t8PFOXhEQlIxbtww=", + "rev": "5912c1772a44e31bf1c63c0390b90501e5026886", "type": "tarball", - "url": "https://releases.nixos.org/nixos/unstable/nixos-25.11pre897910.89c2b2330e73/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre923638.5912c1772a44/nixexprs.tar.xz" }, "original": { "type": "tarball", @@ -70,11 +70,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1761765539, - "narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=", + "lastModified": 1765674936, + "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "719359f4562934ae99f5443f20aa06c2ffff91fc", + "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", "type": "github" }, "original": { @@ -92,11 +92,11 @@ ] }, "locked": { - "lastModified": 1763319842, - "narHash": "sha256-YG19IyrTdnVn0l3DvcUYm85u3PaqBt6tI6VvolcuHnA=", + "lastModified": 1767281941, + "narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "7275fa67fbbb75891c16d9dee7d88e58aea2d761", + "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa", "type": "github" }, "original": { diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 0000000..64e8ff3 --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,47 @@ +# A `nixpkgs.lib` compatible lib must be provided. +{ lib }: + +let + self = { + /** + Applies the function `fn` on the direct children directories of `dir`. + */ + mapDirs = + fn: dir: + let + dirs = builtins.map (name: fn (dir + "/${name}")) ( + builtins.attrNames (lib.filterAttrs (_name: type: type == "directory") (builtins.readDir dir)) + ); + in + dirs; + + /** + Given a directory where children are structured by vendors and *thing*, + returns an attribute set with combined `"${vendor}-${thing}"` names, + and values representing that subdirectory. + */ + getVendorsModules = + dir: + builtins.listToAttrs ( + builtins.concatLists ( + self.mapDirs ( + vendorDir: + let + vendor = builtins.baseNameOf vendorDir; + in + self.mapDirs ( + thingDir: + let + thing = builtins.baseNameOf thingDir; + in + { + name = "${vendor}-${thing}"; + value = thingDir; + } + ) vendorDir + ) dir + ) + ); + }; +in +self diff --git a/modules/default.nix b/modules/default.nix index 5e2178f..8f272ac 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,4 +1,5 @@ { + hardware = import ./hardware; profiles = import ./profiles; vms = import ./vms.nix; } diff --git a/modules/hardware/default.nix b/modules/hardware/default.nix new file mode 100644 index 0000000..605f052 --- /dev/null +++ b/modules/hardware/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./devices + ]; +} diff --git a/modules/hardware/devices/default.nix b/modules/hardware/devices/default.nix new file mode 100644 index 0000000..9edd234 --- /dev/null +++ b/modules/hardware/devices/default.nix @@ -0,0 +1,41 @@ +{ config, lib, ... }@moduleArgs: + +let + cfg = config.ctrl-os.hardware; + inherit (import ../../../lib { inherit lib; }) + getVendorsModules + ; + deviceModules = getVendorsModules ./.; + devices = builtins.attrNames deviceModules; +in +{ + options.ctrl-os.hardware.device = lib.mkOption { + type = with lib.types; nullOr (enum devices); + description = "Selects a hardware device profile to use by device name."; + default = null; + }; + + imports = builtins.attrValues ( + builtins.mapAttrs ( + device: dir: + let + cfgOrFn = import dir; + appliedConfig = + let + config = cfgOrFn moduleArgs; + in + if config ? config then config else { inherit config; }; + cond = lib.mkIf (cfg.device == device); + in + if builtins.isAttrs cfgOrFn then + { + config = cond (cfgOrFn); + } + else + appliedConfig + // { + config = cond appliedConfig.config; + } + ) deviceModules + ); +} diff --git a/modules/hardware/devices/nvidia/jetson-orin-nano-super/default.nix b/modules/hardware/devices/nvidia/jetson-orin-nano-super/default.nix new file mode 100644 index 0000000..a6b0412 --- /dev/null +++ b/modules/hardware/devices/nvidia/jetson-orin-nano-super/default.nix @@ -0,0 +1,12 @@ +{ + nixpkgs.hostPlatform = "aarch64-linux"; + + boot.initrd.availableKernelModules = [ + # Enable PCIe support at boot time + "phy_tegra194_p2u" + "pcie_tegra194" + # Enable USB support for USB Boot + "xhci-tegra" + "phy-tegra-xusb" + ]; +} diff --git a/packages/flakeModule.nix b/packages/flakeModule.nix index 64ed189..1cdf80e 100644 --- a/packages/flakeModule.nix +++ b/packages/flakeModule.nix @@ -1,4 +1,9 @@ -{ withSystem, inputs, ... }: +{ + withSystem, + inputs, + self, + ... +}: { } // inputs.nixpkgs.lib.optionalAttrs @@ -20,6 +25,11 @@ perSystem = { pkgs, ... }: { + legacyPackages = { + hardware = import ./hardware { + inherit pkgs self; + }; + }; packages = import ./default.nix { inherit pkgs; }; }; } diff --git a/packages/hardware/default.nix b/packages/hardware/default.nix new file mode 100644 index 0000000..2fe0285 --- /dev/null +++ b/packages/hardware/default.nix @@ -0,0 +1,166 @@ +{ self, pkgs }: + +let + inherit (pkgs) lib; + inherit (pkgs.stdenv.hostPlatform) system; + + # Evaluate a NixOS configuration without relying on the Flakes entrypoint. + evalConfig = + { + system ? null, + ... + }@config: + (import (pkgs.path + "/nixos/lib/eval-config.nix")) ( + config + // { + inherit system; + } + ); + + # Borrow an arbitrary NixOS eval for evaluating the final `options` with + # our `hardware` module imported. + inherit + (evalConfig { + modules = [ self.nixosModules.hardware ]; + # The system does not matter, we only need to evaluate up to the options. + inherit (pkgs.stdenv.hostPlatform) system; + }) + options + ; + + devices = + # Get the device profile option + options.ctrl-os.hardware.device + # Unwrap the nullOr + .type.functor.payload + # Dig into the `enum` + .elemType.functor.payload + # And get the values + .values; + + # Evaluate the CTRL-OS device modules for the given `device`. + # This returns the output from the `output` attribute path, with the evaluation + # merged into the attribute set. + # By default, `system.build.toplevel` is returned, which is the "system build". + evaluate = + device: + { + modules ? [ ], + config ? { }, + output ? [ + "system" + "build" + "toplevel" + ], + }: + + let + eval = evalConfig { + modules = modules ++ [ + ( + { config, ... }: + { + imports = [ + self.nixosModules.hardware + self.nixosModules.profiles + ]; + + # Enable the device-specific config. + ctrl-os.hardware.device = device; + ctrl-os.profiles.ctrl-os-system.enable = true; + + # Ensure this system build will use cross-compilation if relevant... + nixpkgs.buildPlatform = system; + # ... and tag the system build as such. + system.nixos.tags = [ + ( + if config.nixpkgs.hostPlatform.system == config.nixpkgs.buildPlatform.system then + "native" + else + "cross-from-${config.nixpkgs.buildPlatform.system}" + ) + ]; + } + ) + config + ]; + }; + in + (lib.getAttrFromPath output eval.config) // { inherit eval; }; + + # For a given `device`, evaluate the installer config from `path`, + # relative to the nixpkgs `nixos/modules/installer` path. + # The output is the (guessed) relevant build output. + # As with `evaluate`, the system config is added to the derivation + # attribute set as the `eval` attribute. + evaluateInstaller = + device: path: + let + installer = evaluate device { + config = + { modulesPath, ... }: + { + imports = [ + "${modulesPath}/installer/${path}" + ]; + + # This is only safe to do when generating images!!! + # `stateVersion` should not otherwise be set by an imported modules in a user's config. + system.stateVersion = pkgs.lib.trivial.release; + + # Make the installer generate the necessary configuration bits. + # FIXME: this is incomplete right now as the whole installer tooling doesn't know about + # adding CTRL-OS modules, and has no way to do that in any generic fashion. + system.nixos-generate-config.desktopConfiguration = + let + # Escape string values using JSON, as they are mostly compatible with Nix strings. + e = builtins.toJSON; + in + [ + '' + # + # WARNING: Using `nixos-generate-config` with CTRL-OS hardware modules is experimental. + # + # This generated configuration DOES NOT yet include the CTRL-OS configuration for the platform. + # You will first need to handle importing the CTRL-OS modules in your configuration. + # + /* + imports = [ + ctrl-os.nixosModules.hardware + ctrl-os.nixosModules.profiles + ]; + + ctrl-os.hardware.device = ${e device}; + ctrl-os.profiles.ctrl-os-system.enable = true; + + */ + '' + ]; + }; + }; + inherit (installer.eval.config.system) build; + in + (build.isoImage or build.sdImage + or (builtins.throw "Unable to guess the artifact type for installer path ${builtins.toJSON path}") + ) + // { + inherit (installer) eval; + }; +in +{ + devices.installers = lib.listToAttrs ( + lib.map ( + device: + let + mkInstaller = evaluateInstaller device; + in + { + name = device; + value = { + iso = mkInstaller "cd-dvd/installation-cd-minimal.nix"; + sd-image-new-kernel = mkInstaller "sd-card/sd-image-aarch64-new-kernel-no-zfs-installer.nix"; + }; + } + ) devices + ); +}