diff --git a/.vscode/settings.json b/.vscode/settings.json index 0506a7d..99dee5d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -80,6 +80,7 @@ "keystatus", // zfs "kmod", // linux "lazytime", // f2fs + "libblockdev", // package "libubootenv", // package "logbias", // zfs "losetup", // program / function @@ -88,6 +89,7 @@ "luks", // linux "macaddr", // cli arg "mkdir", // program / function + "mkenvimage", // program "mktemp", // program / function "modifyvm", // virtual box "mountpoint", // program / function @@ -109,10 +111,12 @@ "noheadings", // cli arg "nosuid", // mount option "oneshot", // systemd + "optimise", // B/E "ostype", // virtual box "OVMF", // package "partlabel", // linux "partprobe", // program / function + "passthru", // nix "pbkdf", // cli arg "pflash", // cli arg "pkgs", // nix @@ -155,6 +159,7 @@ "typecode", // cli arg "uart", "uarts", // serial protocol "uartmode", // virtual box + "uboot", // program "udev", // program "udevadm", // program "udptunnel", // program diff --git a/README.md b/README.md index 7ee4cde..7891a13 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The modules are inactive by default, and are, where possible, designed to be ind [`patches/`](./patches/) contains patches which are either applied to the flake's inputs in [`flake.nix`](./flake.nix) or to packages in one of the [`overlays/`](./overlays/). [`hosts/`](./hosts/) contains the main NixOS config modules for each host. Generally, there is one file for each host, but the [flake](./flake.nix) can be instructed to reuse the config for multiple hosts (in which case the module should probably interpret the `name` argument passed to it). -Any `preface.*` options have to be set in the first sub-module in these files (`## Hardware` section). \ +Any `wip.preface.*` options have to be set in the first sub-module in these files (`## Hardware` section). \ This flake only defines a single [`example`](./hosts/example.nix.md) host meant to demonstrate how other flakes can use the (NixOS) flake library framework. [`example/`](./example/) contains an example of adjusting the [installation](./example/install.sh.md) script for the hosts and this flake's [default config](./example/defaultConfig/) (see [Namespacing](#namespacing-in-nixos)). diff --git a/flake.lock b/flake.lock index 7f1dce4..0655df1 100644 Binary files a/flake.lock and b/flake.lock differ diff --git a/flake.nix b/flake.nix index f975017..158e97a 100644 --- a/flake.nix +++ b/flake.nix @@ -17,10 +17,10 @@ # ./patches/nixpkgs-fix-systemd-boot-install.patch ]; -}; in (import "${./.}/lib/flakes.nix" "${./.}/lib" inputs).patchFlakeInputsAndImportRepo inputs patches ./. (inputs@ { self, nixpkgs, ... }: repo@{ overlays, lib, ... }: let +}; in (import "${./.}/lib/flakes.nix" "${./.}/lib" inputs).patchFlakeInputsAndImportRepo inputs patches ./. (inputs@{ self, nixpkgs, ... }: repo@{ overlays, lib, ... }: let systemsFlake = lib.wip.mkSystemsFlake (rec { - #systems = { dir = "${inputs.self}/hosts"; exclude = [ ]; }; # (implicit) + #systems = { dir = "${self}/hosts"; exclude = [ ]; }; # (implicit) inherit inputs; scripts = (lib.attrValues lib.wip.setup-scripts) ++ [ ./example/install.sh.md ]; }); @@ -29,8 +29,8 @@ in [ # Run »nix flake show --allow-import-from-derivation« to see what this me repo (if true then systemsFlake else { }) (lib.wip.forEachSystem [ "aarch64-linux" "x86_64-linux" ] (localSystem: { - packages = lib.wip.getModifiedPackages (lib.wip.importPkgs inputs { system = localSystem; }) overlays; + packages = builtins.removeAttrs (lib.wip.getModifiedPackages (lib.wip.importPkgs inputs { system = localSystem; }) overlays) [ "libblockdev" ]; defaultPackage = systemsFlake.packages.${localSystem}.all-systems; })) - { patches = import "${inputs.self}/patches" "${inputs.self}/patches" inputs; } + { patches = (lib.wip.importWrapped inputs "${self}/patches").result; } ]); } diff --git a/hosts/example.nix.md b/hosts/example.nix.md index a5970b3..0e91188 100644 --- a/hosts/example.nix.md +++ b/hosts/example.nix.md @@ -28,12 +28,12 @@ Alternative to running directly as `root` (esp. if `nix` is not installed for ro ```nix #*/# end of MarkDown, beginning of NixOS config flake input: dirname: inputs: { config, pkgs, lib, name, ... }: let inherit (inputs.self) lib; in let - #suffix = builtins.head (builtins.match ''example-(.*)'' name); # make differences in config based on this when using »preface.instances« + #suffix = builtins.head (builtins.match ''example-(.*)'' name); # make differences in config based on this when using »wip.preface.instances« hash = builtins.substring 0 8 (builtins.hashString "sha256" config.networking.hostName); in { imports = [ ({ ## Hardware - preface.instances = [ "example" "example-raidz" ]; + wip.preface.instances = [ "example" "example-minimal" "example-raidz" ]; - preface.hardware = "x86_64"; system.stateVersion = "22.05"; + wip.preface.hardware = "x86_64"; system.stateVersion = "22.05"; ## What follows is a whole bunch of boilerplate-ish stuff, most of which multiple hosts would have in common and which would thus be moved to one or more modules: @@ -81,6 +81,16 @@ in { imports = [ ({ ## Hardware wip.fs.temproot.local.mounts."/var/log" = lib.mkForce null; # example: don't keep logs +}) (lib.mkIf (name == "example-minimal") { ## Minimal automatic FS setup + + wip.fs.boot.enable = true; + wip.fs.temproot.enable = true; + wip.fs.temproot.temp.type = "tmpfs"; + wip.fs.temproot.local.type = "bind"; + wip.fs.temproot.local.bind.base = "f2fs"; + wip.fs.temproot.remote.type = "none"; + + }) (lib.mkIf (name == "example-raidz") { ## Multi-disk ZFS setup #wip.fs.disks.devices.primary.size = "16G"; # (default) @@ -104,7 +114,7 @@ in { imports = [ ({ ## Hardware }) ({ ## Actual Config # Some base config: - wip.base.enable = true; wip.base.includeNixpkgs = inputs.nixpkgs; + wip.base.enable = true; documentation.enable = false; # sometimes takes quite long to build diff --git a/lib/default.nix b/lib/default.nix index 6b71480..fadf9cb 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -3,4 +3,4 @@ dirname: inputs@{ self, nixpkgs, ...}: let #categories = fix (wip: (import "${dirname}/imports.nix" dirname inputs).importAll (inputs // { self = inputs.self // { lib = nixpkgs.lib // { inherit wip; }; }; })) dirname; categories = (import "${dirname}/imports.nix" dirname inputs).importAll inputs dirname; wip = (builtins.foldl' (a: b: a // b) { } (builtins.attrValues (builtins.removeAttrs categories [ "setup-scripts" ]))) // categories; -in nixpkgs.lib // { inherit wip; } +in nixpkgs.lib // { wip = wip // { prefix = inputs.config.prefix; }; } diff --git a/lib/flakes.nix b/lib/flakes.nix index 1a37c65..caa39ef 100644 --- a/lib/flakes.nix +++ b/lib/flakes.nix @@ -3,6 +3,8 @@ dirname: inputs@{ self, nixpkgs, ...}: let inherit (import "${dirname}/vars.nix" dirname inputs) mapMerge mergeAttrsUnique flipNames; inherit (import "${dirname}/imports.nix" dirname inputs) getModifiedPackages getNixFiles importWrapped; inherit (import "${dirname}/scripts.nix" dirname inputs) substituteImplicit; + setup-scripts = (import "${dirname}/setup-scripts" "${dirname}/setup-scripts" inputs); + prefix = inputs.config.prefix; in rec { # Simplified implementation of »flake-utils.lib.eachSystem«. @@ -10,7 +12,7 @@ in rec { # Sooner or later this should be implemented in nix itself, for now require »inputs.nixpkgs« and a system that can run »x86_64-linux« (native or through qemu). patchFlakeInputs = inputs: patches: outputs: let - inherit ((import inputs.nixpkgs { system = "x86_64-linux"; }).pkgs) applyPatches fetchpatch; + inherit ((import inputs.nixpkgs { overlays = [ ]; config = { }; system = "x86_64-linux"; }).pkgs) applyPatches fetchpatch; in outputs (builtins.mapAttrs (name: input: if name != "self" && patches?${name} && patches.${name} != [ ] then (let patched = applyPatches { name = "${name}-patched"; src = input; @@ -26,22 +28,31 @@ in rec { in { inherit (input) inputs; inherit outputs; inherit sourceInfo; } // outputs // sourceInfo) )) else input) inputs); - # Generates implicit flake outputs by importing conventional paths in the local repo. + # Generates implicit flake outputs by importing conventional paths in the local repo. E.g.: + # outputs = inputs@{ self, nixpkgs, wiplib, ... }: wiplib.lib.wip.importRepo inputs ./. (repo@{ overlays, lib, ... }: let ... in [ repo ... ]) importRepo = inputs: repoPath': outputs: let repoPath = builtins.path { path = repoPath'; name = "source"; }; # referring to the current flake directory as »./.« is quite intuitive (and »inputs.self.outPath« causes infinite recursion), but without this it adds another hash to the path (because it copies it) - in let list = (outputs inputs ((if builtins.pathExists "${repoPath}/lib/default.nix" then { - lib = import "${repoPath}/lib" "${repoPath}/lib" inputs; - } else { }) // (if builtins.pathExists "${repoPath}/overlays/default.nix" then rec { - overlays = import "${repoPath}/overlays" "${repoPath}/overlays" inputs; - overlay = final: prev: builtins.foldl' (prev: overlay: prev // (overlay final prev)) prev (builtins.attrValues overlays); - } else { }) // (if builtins.pathExists "${repoPath}/modules/default.nix" then rec { - nixosModules = import "${repoPath}/modules" "${repoPath}/modules" inputs; - nixosModule = { imports = builtins.attrValues nixosModules; }; - } else { }))); in if (builtins.isList list) then mergeOutputs list else list; + in let result = (outputs ( + (let it = importWrapped inputs "${repoPath}/lib"; in if it.exists then rec { + lib = it.result; + } else { }) // (let it = importWrapped inputs "${repoPath}/overlays"; in if it.exists then rec { + overlays = it.result; + overlay = final: prev: builtins.foldl' (prev: overlay: prev // (overlay final prev)) prev (builtins.attrValues overlays); + } else { }) // (let it = importWrapped inputs "${repoPath}/modules"; in if it.exists then rec { + nixosModules = it.result; + nixosModule = { imports = builtins.attrValues nixosModules; }; + } else { }) + )); in if (builtins.isList result) then mergeOutputs result else result; - # Combines »patchFlakeInputs« and »importRepo« in a single call. + # Combines »patchFlakeInputs« and »importRepo« in a single call. E.g.: + # outputs = inputs: let patches = { + # nixpkgs = [ + # # remote: { url = "https://github.com/NixOS/nixpkgs/pull/###.diff"; sha256 = inputs.nixpkgs.lib.fakeSha256; } + # # local: ./overlays/patches/nixpkgs-###.patch # (use long native path to having the path change if any of the other files in ./. change) + # ]; # ... + # }; in inputs.wiplib.lib.wip.patchFlakeInputsAndImportRepo inputs patches ./. (inputs@{ self, nixpkgs, ... }: repo@{ nixosModules, overlays, lib, ... }: let ... in [ repo ... ]) patchFlakeInputsAndImportRepo = inputs: patches: repoPath: outputs: ( - patchFlakeInputs inputs patches (inputs: importRepo inputs repoPath outputs) + patchFlakeInputs inputs patches (inputs: importRepo inputs repoPath (outputs inputs)) ); # Merges a list of flake output attribute sets. @@ -53,11 +64,11 @@ in rec { # Given a path to a host config file, returns some properties defined in its first inline module (to be used where accessing them via »nodes.${name}.config...« isn't possible). getSystemPreface = inputs: entryPath: args: let - imported = (importWrapped inputs entryPath) ({ config = null; pkgs = null; lib = null; name = null; nodes = null; } // args); - module = builtins.elemAt imported.imports 0; props = module.preface; + imported = (importWrapped inputs entryPath).required ({ config = null; pkgs = null; lib = null; name = null; nodes = null; } // args); + module = builtins.elemAt imported.imports 0; props = module.${prefix}.preface; in if ( - imported?imports && (builtins.isList imported.imports) && (imported.imports != [ ]) && module?preface && props?hardware - ) then (props) else throw "File ${entryPath} must fulfill the structure: dirname: inputs: { ... }: { imports = [ { preface = { hardware = str; ... } } ]; }"; + imported?imports && (builtins.isList imported.imports) && (imported.imports != [ ]) && module?${prefix} && module.${prefix}?preface && props?hardware + ) then (props) else throw "File ${entryPath} must fulfill the structure: dirname: inputs: { ... }: { imports = [ { ${prefix}.preface = { hardware = str; ... } } ]; }"; # Builds the System Configuration for a single host. Since each host depends on the context of all other host (in the same "network"), this is essentially only callable through »mkNixosConfigurations«. # See »mkSystemsFlake« for documentation of the arguments. @@ -72,13 +83,13 @@ in rec { in { inherit preface; } // (nixosSystem { system = targetSystem; modules = [ ( - { _file = entryPath; imports = [ (importWrapped inputs entryPath) ]; } # (preserve the location of reported errors) + (importWrapped inputs entryPath).module ) { # The system architecture (often referred to as »system«). - options.preface.hardware = lib.mkOption { type = lib.types.str; readOnly = true; }; + options.${prefix}.preface.hardware = lib.mkOption { type = lib.types.str; readOnly = true; }; } { # List of host names to instantiate this host config for, instead of just for the file name. - options.preface.instances = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ name ]; }; + options.${prefix}.preface.instances = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ name ]; }; } ({ config, ... }: { imports = modules; nixpkgs = { inherit overlays; } @@ -95,13 +106,13 @@ in rec { }); # Given either a list (or attr set) of »files« (paths to ».nix« or ».nix.md« files for dirs with »default.nix« files in them) or a »dir« path (and optionally a list of file names to »exclude« from it), this builds the NixOS configuration for each host (per file) in the context of all configs provided. - # If »files« is an attr set, exactly one host with the attribute's name as hostname is built for each attribute. Otherwise the default is to build for one host per configuration file, named as the file name without extension or the sub-directory name. Setting »preface.instances« can override this to build the same configuration for those multiple names instead (the specific »name« is passed as additional »specialArgs« to the modules and can thus be used to adjust the config per instance). + # If »files« is an attr set, exactly one host with the attribute's name as hostname is built for each attribute. Otherwise the default is to build for one host per configuration file, named as the file name without extension or the sub-directory name. Setting »${prefix}.preface.instances« can override this to build the same configuration for those multiple names instead (the specific »name« is passed as additional »specialArgs« to the modules and can thus be used to adjust the config per instance). # All other arguments are as specified by »mkSystemsFlake« and are passed to »mkNixosConfiguration«. mkNixosConfigurations = args: let # { files, dir, exclude, ... } files = args.files or (getNixFiles args.dir (args.exclude or [ ])); files' = if builtins.isAttrs files then files else (builtins.listToAttrs (map (entryPath: let stripped = builtins.match ''^(.*)[.]nix[.]md$'' (builtins.baseNameOf entryPath); - name = if stripped != null then (builtins.elemAt stripped 0) else (builtins.baseNameOf entryPath); + name = builtins.unsafeDiscardStringContext (if stripped != null then (builtins.elemAt stripped 0) else (builtins.baseNameOf entryPath)); in { inherit name; value = entryPath; }) files)); configs = mapMerge (name: entryPath: (let @@ -118,7 +129,7 @@ in rec { ids = mapMerge (name: node: { "${toString node.preface.id}" = name; }) withId; duplicate = builtins.removeAttrs withId (builtins.attrValues ids); in if duplicate != { } then ( - throw "»my.system.id«s are not unique! The following hosts share their IDs with some other host: ${builtins.concatStringsSep ", " (builtins.attrNames duplicate)}" + throw "»${prefix}.preface.id«s are not unique! The following hosts share their IDs with some other host: ${builtins.concatStringsSep ", " (builtins.attrNames duplicate)}" ) else configs; # Builds a system of NixOS hosts and exports them plus managing functions as flake outputs. @@ -129,7 +140,7 @@ in rec { # Root path of the NixOS configuration. »./.« in the »flake.nix« configPath ? inputs.self.outPath, # Arguments »{ files, dir, exclude, }« to »mkNixosConfigurations«, see there for details. May also be a list of those attrsets, in which case those multiple sets of hosts will be built separately by »mkNixosConfigurations«, allowing for separate sets of »peers« passed to »mkNixosConfiguration«. Each call will receive all other arguments, and the resulting sets of hosts will be merged. - systems ? ({ dir = "${configPath}/hosts/"; exclude = [ ]; }), + systems ? ({ dir = "${configPath}/hosts"; exclude = [ ]; }), # List of overlays to set as »config.nixpkgs.overlays«. Defaults to the ».overlay(s)« of all »overlayInputs«/»inputs« (incl. »inputs.self«). overlays ? (builtins.concatLists (map (input: if input?overlay then [ input.overlay ] else if input?overlays then builtins.attrValues input.overlays else [ ]) (builtins.attrValues overlayInputs))), # (Subset of) »inputs« that »overlays« will be used from. Example: »{ inherit (inputs) self flakeA flakeB; }«. @@ -140,11 +151,11 @@ in rec { moduleInputs ? (builtins.removeAttrs inputs [ "nixpkgs" ]), # Additional arguments passed to each module evaluated for the host config (if that module is defined as a function). specialArgs ? { }, - # List of bash scripts defining functions that do installation and maintenance operations. See »apps« below for more information. - scripts ? [ ], - # The function of that name as defined in »/flake.nix«, or equivalent. + # Optional list of bash scripts defining functions that do installation and maintenance operations. See »./setup-scripts/README.md« below for more information. To define additional functions (or overwrite individual default ones), use »(lib.attrValues lib.wip.setup-scripts) ++ [ ]« and place any extra scripts in the array. + scripts ? (lib.attrValues setup-scripts), + # The »nixosSystem« function defined in »/flake.nix«, or equivalent. nixosSystem ? inputs.nixpkgs.lib.nixosSystem, - # If provided, then cross compilation is enabled for all hosts whose target architecture is different from this. Since cross compilation currently fails for (some stuff in) NixOS, better don't set »localSystem«. Without it, building for other platforms works fine (just slowly) if »boot.binfmt.emulatedSystems« is configured on the building system for the respective target(s). + # If provided, then cross compilation is enabled for all hosts whose target architecture is different from this. Since cross compilation currently fails for (some stuff in) NixOS, better don't set »localSystem«. Without it, building for other platforms works fine (just slowly) if »boot.binfmt.emulatedSystems« on the building system is configured for the respective target(s). localSystem ? null, ... }: let otherArgs = (builtins.removeAttrs args [ "systems" ]) // { inherit systems overlays modules specialArgs scripts inputs configPath nixosSystem localSystem; }; @@ -155,7 +166,7 @@ in rec { pkgs = (import inputs.nixpkgs { inherit overlays; system = localSystem; }); nix = if lib.versionOlder pkgs.nix.version "2.4" then pkgs.nix_2_4 else pkgs.nix; nix_wrapped = pkgs.writeShellScriptBin "nix" ''exec ${nix}/bin/nix --extra-experimental-features nix-command "$@"''; - in (if scripts == [ ] then { } else { + in (if scripts == [ ] || scripts == null then { } else { # E.g.: $ nix run .#$target -- install-system /tmp/system-$target.img # E.g.: $ nix run /etc/nixos/#$(hostname) -- sudo diff --git a/lib/imports.nix b/lib/imports.nix index 0f26441..a46f840 100644 --- a/lib/imports.nix +++ b/lib/imports.nix @@ -28,11 +28,28 @@ in rec { # Any function with two (usually unnamed) arguments returning an attrset could be an overlay, so that's rather vague. couldBeOverlay = thing: let result1 = thing (builtins.functionArgs thing); result2 = result1 (builtins.functionArgs result1); in builtins.isFunction thing && builtins.isFunction result1 && builtins.isAttrs result2; - # Builds an attrset that, for each folder or ».nix« or ».nix.md« file (other than »default.nix«) in this folder, as the name of that folder or the name of the file without extension(s), exports the result of importing that file/folder. + # Builds an attrset that, for each folder (containing a »default.nix«) or ».nix« or ».nix.md« file (other than »./default.nix«) in this folder, as the name of that folder or the name of the file without extension(s), exports the result of importing that file/folder. importAll = inputs: dir: builtins.mapAttrs (name: path: import path (if endsWith "/default.nix" path then "${dir}/${name}" else dir) inputs) (getNamedNixFiles dir [ "default.nix" ]); - # Import a Nix file that expects the standard `dirname: inputs: ` arguments. - importWrapped = inputs: path: import path (if (builtins.match ''^(.*)[.]nix([.]md)?$'' path) != null then builtins.dirOf path else path) inputs; + # Import a Nix file that expects the standard `dirname: inputs: ` arguments, providing some additional information and error handling. + importWrapped = inputs: path: rec { + # Whether the file is imported by an explicit full path (or one omitting ».nix« or »/default.nix«): + isExplicit = (builtins.match ''^(.*)[.]nix([.]md)?$'' path) != null; + # Whether the import path _implicitly_ refers to the »/default.nix« in a directory: + isImplicitDir = !isExplicit && builtins.pathExists "${path}/default.nix"; + # The resolved path that will be imported: + fullPath = if isImplicitDir then "${path}/default.nix" else if isExplicit then path else "${path}.nix"; + # The imported nix value: + result = import fullPath (if isImplicitDir then path else builtins.dirOf path) inputs; + # Whether the import path points to an existing file: + exists = isImplicitDir || (builtins.pathExists (if isExplicit then path else "${path}.nix")); + # Return »null« if not ».exists«: + optional = if exists then result else null; + # Throw if not ».exists«: + required = if exists then result else throw (if isExplicit then "File ${path} does not exist" else "Neither ${path}/default.nix nor ${path}.nix exist"); + # ».result« interpreted as NixOS module, wrapped to preserve the import path: + module = { _file = fullPath; imports = [ required ]; }; + }; ## Returns an attrset that, for each file in »dir« (except »default.nix« and as filtered and named by »getNamedNixFiles dir except«), imports that file and exposes only if the result passes »filter«. If provided, the imported value is »wrapped« after filtering. # If a file/folder' import that is rejected by »filter« is an attrset (for example because it results from a call to this function), then all attributes whose values pass »filter« are prefixed with the file/folders name plus a slash and merged into the overall attrset. @@ -72,7 +89,7 @@ in rec { # Given a list of »overlays« and »pkgs« with them applied, returns the subset of »pkgs« that was directly modified by the overlays. getModifiedPackages = pkgs: overlays: let names = builtins.concatLists (map (overlay: builtins.attrNames (overlay { } { })) (builtins.attrValues overlays)); - in mapMerge (name: { ${name} = pkgs.${name}; }) names; + in mapMerge (name: if lib.isDerivation pkgs.${name} then { ${name} = pkgs.${name}; } else { }) names; ## Given a path to a module in »nixpkgs/nixos/modules/«, when placed in another module's »imports«, this adds an option »disableModule.${modulePath}« that defaults to being false, but when explicitly set to »true«, disables all »config« values set by the module. # Every module should, but not all modules do, provide such an option themselves. @@ -101,7 +118,7 @@ in rec { moduleArgs = { utils = import "${specialArgs.inputs.nixpkgs.outPath}/nixos/lib/utils.nix" { inherit (specialArgs) lib config pkgs; }; } // specialArgs; module = import fullPath moduleArgs; in { _file = fullPath; imports = [ - (mergeAttrsRecursive (([ { imports = module.imports or [ ]; options = module.options or { }; config = module.config or { }; } ]) ++ (lib.toList (override module)))) + (mergeAttrsRecursive ([ { imports = module.imports or [ ]; options = module.options or { }; config = module.config or { }; } ] ++ (lib.toList (override module)))) { disabledModules = [ modulePath ]; } ]; }; } diff --git a/lib/misc.nix b/lib/misc.nix index ffe0ec3..c0775ae 100644 --- a/lib/misc.nix +++ b/lib/misc.nix @@ -20,19 +20,7 @@ in rec { <<<${lib.escapeShellArg text} cat >$out/${lib.escapeShellArg "/etc/systemd/system/${unit}.d/override.conf"} ''); - # Given »config.ids« (or equivalent) and a user name, returns the users numeric »uid:gid« pair as string. - getOwnership = { gids, uids, ... }: user: "${toString uids.${user}}:${toString gids.${user}}"; - - # Given »from« and »to« as »config.my.network.spec.hosts.*«, - # picks the first of »to«'s IPs whose required subnet is either empty/any, or a prefix to any of the subnets in »from«: - # ip = preferredRoute self.subNets other.routes; - # ip6 = preferredRoute self.subNets (builtins.filter (r: r.is6) other.routes); - # to.find(({ ip, prefix }) => from.any(_=>_.startsWith(prefix))).ip - preferredRoute = from: to: (lib.findFirst ({ prefix, ip, ... }: prefix == "" || (builtins.any (fromSub: startsWith prefix fromSub) from)) { ip = ""; } to).ip; - # Given a message and any value, traces both the message and the value, and returns the value. trace = message: value: (builtins.trace (message +": "+ (lib.generators.toPretty { } value)) value); - rpoolOf = hostName: "rpool-${builtins.substring 0 8 (builtins.hashString "sha256" hostName)}"; - } diff --git a/lib/setup-scripts/README.md b/lib/setup-scripts/README.md index c22a2ec..5169e5a 100644 --- a/lib/setup-scripts/README.md +++ b/lib/setup-scripts/README.md @@ -3,7 +3,7 @@ This is a library of bash functions, mostly for NixOS system installation. -The (paths to these) scripts are meant to me passed in the `scripts` argument to [`mkSystemsFlake`](../flakes.nix#mkSystemsFlake) (see [`flake.nix`](../../flake.nix) for an example), which makes their functions available in the per-host `devShells`/`apps`. +The (paths to these) scripts are meant to be passed in the `scripts` argument to [`mkSystemsFlake`](../flakes.nix#mkSystemsFlake) (see [`flake.nix`](../../flake.nix) for an example), which makes their functions available in the per-host [`devShells`/`apps`](../flakes.nix#mkSystemsFlake). Host-specific nix variables are available to the bash functions as `@{...}` through [`substituteImplicit`](../scripts.nix#substituteImplicit) with the respective host as root context. Any script passed later in `scripts` can overwrite the functions of these (earlier) default scripts. @@ -21,16 +21,16 @@ function install-system {( set -eu # 1: diskPaths # `install-system` Documentation The above function performs the mostly automated installation of any `$HOST` from [`../../hosts/`](../../hosts/) to the local disk(s) (or image file(s)) `$DISK`. -On a NixOS host, this script can be run by root as: `#` `( cd /etc/nixos/ && nix run .#"$HOST" -- install-system "$DISK" )`. +On a NixOS host, this can be run by root as: `#` `nix run .#"$HOST" -- install-system "$DISK"`. Doing an installation on non-NixOS (but Linux), where nix isn't installed for root, is a bit of a hack, but works as well. In this case, all `nix` commands will be run as `$SUDO_USER`, but this script and some other user-owned (or user-generated) code will (need to) be run as root. -If that is acceptable, run with `sudo` as first argument: `$` `( cd /etc/nixos/ && nix run .#"$HOST" -- sudo install-system "$DISK" )` (And then maybe `sudo bash -c 'chown $SUDO_USER: '"$DISK"` afterwards.) +If that is acceptable, run with `sudo` as first argument: `$` `nix run .#"$HOST" -- sudo install-system "$DISK"` (And then maybe `sudo bash -c 'chown $SUDO_USER: '"$DISK"` afterwards.) -The `nix run` in the above commands substitutes a number of `@{`-prefixed variables based on the `$HOST` name and its configuration from [`../hosts/`](../hosts/), and then sources this script and calls the `install-system` function. If `$DISK` points to something in `/dev/`, then it is directly formatted and written to as block device, otherwise `$DISK` is (re-)created as raw image and then used as loop device. -For hosts that install to multiple disks, pass a `:`-separated list of `=` pairs (the name may be omitted only for the `default` disk). +For hosts that install to multiple disks, pass a `:`-separated list of `=` pairs (the name may be omitted only for the "`default`" disk). Once done, the disk can be transferred -- or the image be copied -- to the final system, and should boot there. -If the host's hardware target allows, a resulting image can also be passed to [`register-vbox`](../lib/setup-scripts/maintenance.sh#register-vbox) to create a bootable VirtualBox instance for the current user. +If the host's hardware target allows, a resulting image can also be passed to [`register-vbox`](../maintenance.sh#register-vbox) to create a bootable VirtualBox instance for the current user, or to [`run-qemu`](../maintenance.sh#run-qemu) to start it in a qemu VM. + The "Installation" section of each host's documentation should contain host specific details, if any. diff --git a/lib/setup-scripts/disk.sh b/lib/setup-scripts/disk.sh index e56b5df..a2b65ed 100644 --- a/lib/setup-scripts/disk.sh +++ b/lib/setup-scripts/disk.sh @@ -73,7 +73,7 @@ function partition-disks { { # 1: diskPaths if [[ ${disk[serial]} != "$actual" ]] ; then echo "Block device $blockDev's serial ($actual) does not match the serial (${disk[serial]}) declared for ${disk[name]}" ; exit 1 ; fi fi # can (and probably should) restore the backup: - ( PATH=@{native.gptfdisk}/bin ; set -x ; sgdisk --load-backup=@{config.wip.fs.disks.partitioning}/"${disk[name]}".backup "${blockDevs[${disk[name]}]}" >$beQuiet ) + ( PATH=@{native.gptfdisk}/bin ; set -x ; sgdisk --zap-all --load-backup=@{config.wip.fs.disks.partitioning}/"${disk[name]}".backup "${blockDevs[${disk[name]}]}" >$beQuiet ) #partition-disk "${disk[name]}" "${blockDevs[${disk[name]}]}" done @{native.parted}/bin/partprobe "${blockDevs[@]}" @@ -92,7 +92,7 @@ function partition-disk {( set -eu # 1: name, 2: blockDev, 3?: devSize declare -a sgdisk=( --zap-all ) # delete existing part tables if [[ ${disk[gptOffset]} != 0 ]] ; then sgdisk+=( --move-main-table=$(( 2 + ${disk[gptOffset]} )) ) # this is incorrectly documented as --adjust-main-table in the man pages (at least versions 1.05 to 1.09 incl) - sgdisk+=( --move-secondary-table=$(( devSize/512 - 1 - 32 - ${disk[gptOffset]} )) ) + sgdisk+=( --move-backup-table=$(( devSize/512 - 1 - 32 - ${disk[gptOffset]} )) ) fi sgdisk+=( --disk-guid="${disk[guid]}" ) @@ -124,13 +124,13 @@ function partition-disk {( set -eu # 1: name, 2: blockDev, 3?: devSize # move the selected »mbrParts« to slots 1[2[3]] instead of 2[3[4]] (by re-creating part1 in the last sector, then sorting) n;p;1 # new ; primary ; part1 - $(( ($devSize/512) - 1)) # start (size 1sec) + $(( ($devSize/512) - 1)) # start (size 1sec) x;f;r # expert mode ; fix order ; return d;$(( (${#disk[mbrParts]} + 1) / 2 + 1 )) # delete ; part(last) - # create GPT part (spanning primary GPT area) as last part + # create GPT part (spanning primary GPT area and its padding) as last part n;p;4 # new ; primary ; part4 - 1;33 # start ; end + 1;$(( 33 + ${disk[gptOffset]} )) # start ; end t;4;ee # type ; part4 ; GPT ${disk[extraFDiskCommands]} diff --git a/lib/setup-scripts/install.sh b/lib/setup-scripts/install.sh index 4427f80..84ad8e4 100644 --- a/lib/setup-scripts/install.sh +++ b/lib/setup-scripts/install.sh @@ -69,6 +69,7 @@ function install-system-to {( set -eu # 1: mnt, 2?: topLevel mkdir -p -m 755 $mnt/nix/var/nix ; mkdir -p -m 1775 $mnt/nix/store if [[ ${SUDO_USER:-} ]] ; then chown -R $SUDO_USER: $mnt/nix/store $mnt/nix/var ; fi ( set -x ; time nix copy --no-check-sigs --to $mnt ${topLevel:-$targetSystem} ) ; rm -rf $mnt/nix/var/nix/gcroots + # TODO: if the target has @{config.nix.autoOptimiseStore} and the host doesn't (there is no .links dir?), optimize now if [[ ${SUDO_USER:-} ]] ; then chown -R root:root $mnt/nix $mnt/nix/var ; chown :30000 $mnt/nix/store ; fi # Link/create files that some tooling expects: @@ -83,9 +84,9 @@ function install-system-to {( set -eu # 1: mnt, 2?: topLevel mkdir -p -m 755 $mnt/nix/var/nix/profiles ; ln -sT $(realpath $targetSystem) $mnt/nix/var/nix/profiles/system-1-link ; ln -sT system-1-link $mnt/nix/var/nix/profiles/system # Support cross architecture installation (not sure if this is actually required) - if [[ $(cat /run/current-system/system 2>/dev/null || echo "x86_64-linux") != "@{config.preface.hardware}"-linux ]] ; then - mkdir -p $mnt/run/binfmt ; cp -a {,$mnt}/run/binfmt/"@{config.preface.hardware}"-linux || true - # Ubuntu (by default) expects the "interpreter" at »/usr/bin/qemu-@{config.preface.hardware}-static«. + if [[ $(cat /run/current-system/system 2>/dev/null || echo "x86_64-linux") != "@{config.wip.preface.hardware}"-linux ]] ; then + mkdir -p $mnt/run/binfmt ; cp -a {,$mnt}/run/binfmt/"@{config.wip.preface.hardware}"-linux || true + # Ubuntu (by default) expects the "interpreter" at »/usr/bin/qemu-@{config.wip.preface.hardware}-static«. fi # Run the main install command (primarily for the bootloader): diff --git a/lib/setup-scripts/maintenance.sh b/lib/setup-scripts/maintenance.sh index f4b04a5..ac5eaa8 100644 --- a/lib/setup-scripts/maintenance.sh +++ b/lib/setup-scripts/maintenance.sh @@ -51,12 +51,12 @@ function run-qemu {( set -eu # 1: diskImages diskImages=${argv[0]} if [[ ${args[debug]:-} ]] ; then set -x ; fi - qemu=( @{native.qemu_full}/bin/qemu-system-@{config.preface.hardware} ) + qemu=( @{native.qemu_full}/bin/qemu-system-@{config.wip.preface.hardware} ) qemu+=( -m ${args[mem]:-2048} -smp ${args[smp]:-4} ) - if [[ @{config.preface.hardware}-linux == "@{native.system}" && ! ${args[no-kvm]:-} ]] ; then + if [[ @{config.wip.preface.hardware}-linux == "@{native.system}" && ! ${args[no-kvm]:-} ]] ; then qemu+=( -cpu host -enable-kvm ) # For KVM to work vBox may not be running anything at the same time (and vBox hangs on start if qemu runs). Pass »--no-kvm« and accept ~10x slowdown, or stop vBox. - elif [[ @{config.preface.hardware} == aarch64 ]] ; then # assume it's a raspberry PI (or compatible) + elif [[ @{config.wip.preface.hardware} == aarch64 ]] ; then # assume it's a raspberry PI (or compatible) # TODO: this does not work yet: qemu+=( -machine type=raspi3b -m 1024 ) ; args[no-nat]=1 # ... and neither does this: @@ -74,7 +74,7 @@ function run-qemu {( set -eu # 1: diskImages if [[ ! -e /tmp/qemu-@{config.networking.hostName}-VARS.fd ]] ; then cat @{pkgs.OVMF.fd}/FV/OVMF_VARS.fd > /tmp/qemu-@{config.networking.hostName}-VARS.fd ; fi # https://lists.gnu.org/archive/html/qemu-discuss/2018-04/msg00045.html fi - if [[ @{config.preface.hardware} == aarch64 ]] ; then + if [[ @{config.wip.preface.hardware} == aarch64 ]] ; then qemu+=( -kernel @{config.system.build.kernel}/Image -initrd @{config.system.build.initialRamdisk}/initrd -append "$(echo -n "@{config.boot.kernelParams[@]}")" ) fi diff --git a/lib/vars.nix b/lib/vars.nix index 6f8715b..a509af4 100644 --- a/lib/vars.nix +++ b/lib/vars.nix @@ -105,7 +105,7 @@ in rec { parseSizeSuffix = decl: let match = builtins.match ''^([0-9]+)(K|M|G|T|P)?(i)?(B)?$'' decl; num = lib.toInt (builtins.head match); unit = builtins.elemAt match 1; - exponent = if unit == null then 0 else { K = 1; M = 2; G = 3; t = 4; P = 5; }.${unit}; + exponent = if unit == null then 0 else { K = 1; M = 2; G = 3; T = 4; P = 5; }.${unit}; base = if (builtins.elemAt match 3) == null || (builtins.elemAt match 2) != null then 1024 else 1000; in if builtins.isInt decl then decl else if match != null then num * (pow base exponent) else throw "${decl} is not a number followed by a size suffix"; diff --git a/modules/base.nix.md b/modules/base.nix.md index cf0329a..03dd4d1 100644 --- a/modules/base.nix.md +++ b/modules/base.nix.md @@ -9,16 +9,21 @@ Things that really should be (more like) this by default. ```nix #*/# end of MarkDown, beginning of NixOS module: -dirname: inputs: specialArgs@{ config, pkgs, lib, ... }: let inherit (inputs.self) lib; in let +dirname: inputs: specialArgs@{ config, pkgs, lib, name, ... }: let inherit (inputs.self) lib; in let prefix = inputs.config.prefix; cfg = config.${prefix}.base; in { options.${prefix} = { base = { enable = lib.mkEnableOption "saner defaults"; - includeNixpkgs = lib.mkOption { description = "»nixpkgs« to include in the system build."; type = lib.types.nullOr lib.types.package; default = null; }; + includeInputs = lib.mkOption { description = "Whether to include all build inputs to the configuration in the final system, such that they are available for self-rebuilds, in the flake registry, and on the »NIX_PATH« entry (e.g. as »pkgs« on the CLI)."; type = lib.types.bool; default = specialArgs?inputs && specialArgs.inputs?self && specialArgs.inputs?nixpkgs; }; }; }; + # Bugfix: + imports = [ (lib.wip.overrideNixpkgsModule ({ inherit inputs; } // specialArgs) "misc/extra-arguments.nix" (old: { config._module.args.utils = old._module.args.utils // { + escapeSystemdPath = s: builtins.replaceStrings [ "/" "-" " " "." ] [ "-" "\\x2d" "\\x20" "\\x2e" ] (lib.removePrefix "/" s); # The original function does not escape ».«, resulting in mismatching names with units generated from paths with ».« in them. + }; })) ]; + config = let hash = builtins.substring 0 8 (builtins.hashString "sha256" config.networking.hostName); implied = true; # some mount points are implied (and forced) to be »neededForBoot« in »specialArgs.utils.pathsNeededForBoot« (this marks those here) @@ -34,23 +39,41 @@ in { }) ({ # Robustness/debugging: - boot.kernelParams = [ "panic=10" "boot.panic_on_fail" ]; # Reboot on kernel panic, panic if boot fails. + boot.kernelParams = [ "panic=10" "boot.panic_on_fail" ]; # Reboot on kernel panic (showing the printed messages for 10s), panic if boot fails. # might additionally want to do this: https://stackoverflow.com/questions/62083796/automatic-reboot-on-systemd-emergency-mode systemd.extraConfig = "StatusUnitFormat=name"; # Show unit names instead of descriptions during boot. - }) (lib.mkIf (cfg.includeNixpkgs != null) { + }) (lib.mkIf cfg.includeInputs { # non-flake - nix.registry.nixpkgs.flake = cfg.includeNixpkgs; - environment.etc."nix/channels/nixpkgs".source = cfg.includeNixpkgs.outPath; - nix.nixPath = [ "nixpkgs=/etc/nix/channels/nixpkgs" "nixos-config=/etc/nixos" ]; + # Importing »« as non-flake returns a lambda returning the evaluated Nix Package Collection (»pkgs«). The most accurate representation of what that should be on the target host is the »pkgs« constructed when building it: + system.extraSystemBuilderCmds = '' + ln -sT ${pkgs.writeText "pkgs.nix" '' + # Provide the exact same version of (nix)pkgs on the CLI as in the NixOS-configuration (but note that this ignores the args passed to it; and it'll be a bit slower, as it partially evaluates the host's configuration): + args: (builtins.getFlake ${builtins.toJSON specialArgs.inputs.self.outPath}).nixosConfigurations.${name}.pkgs + ''} $out/pkgs # (nixpkgs with overlays) + ''; # (use this indirection so that all open shells update automatically) + + nix.nixPath = [ "nixpkgs=/run/current-system/pkgs" ]; # this intentionally replaces the defaults: nixpkgs is here, /etc/nixos/flake.nix is implicit, channels are impure + # TODO: decide whether to put any other flake inputs also on the nix path: con: they may very well not even have a »./default.nix« + nix.autoOptimiseStore = true; # because why not ... + environment.shellAliases = { "with" = ''nix-shell --run "bash --login" -p''; }; # »with« doesn't seem to be a common linux command yet, and it makes sense here: with $package => do stuff in shell + + }) (lib.mkIf cfg.includeInputs { # flake things + + # "input" to the system build is definitely also a nix version that works with flakes: nix.extraOptions = "experimental-features = nix-command flakes"; # apparently, even nix 2.8 (in nixos-22.05) needs this - environment.shellAliases = { "with" = ''nix-shell --run "bash --login" -p''; }; - system.extraSystemBuilderCmds = (if !specialArgs?inputs && !specialArgs.inputs?self then "" else '' - ln -sT ${specialArgs.inputs.self.outPath} $out/config # (build input for reference) - ''); environment.systemPackages = [ pkgs.git ]; # necessary as external dependency when working with flakes + # »inputs.self« does not have a name (that is known here), so just register it as »/etc/nixos/« system config: + environment.etc.nixos.source = lib.mkDefault "/run/current-system/config"; # (use this indirection to prevent every change in the config to necessarily also change »/etc«) + system.extraSystemBuilderCmds = '' + ln -sT ${specialArgs.inputs.self.outPath} $out/config # (build input for reference) + ''; + + # Add all inputs to the flake registry: + nix.registry = lib.mapAttrs (name: input: lib.mkDefault { flake = input; }) (builtins.removeAttrs specialArgs.inputs [ "self" ]); + }) ({ # Free convenience: @@ -70,10 +93,10 @@ in { fi fi export TERM_RECURSION_DEPTH=$(( 1 + ''${TERM_RECURSION_DEPTH:-0} )) - ''; # The non-interactive version of bash does not remove »\[« and »\]« from PS1, but without those the terminal gets confused about the cursor position after the prompt once one types more than a bit of text there, at least via serial or SSH. + ''; # The non-interactive version of bash does not remove »\[« and »\]« from PS1, but without those the terminal gets confused about the cursor position after the prompt once one types more than a bit of text there (at least via serial or SSH). environment.interactiveShellInit = lib.mkDefault '' - if [[ "$(realpath /dev/stdin)" == /dev/ttyS* && $LINES == 24 && $COLUMNS == 80 ]] ; then + if [[ "$(realpath /dev/stdin)" != /dev/tty[1-8] && $LINES == 24 && $COLUMNS == 80 ]] ; then stty rows 34 cols 145 # Fairly large font on 1080p. Definitely a better default than 24x80. fi ''; diff --git a/modules/fs/patches.nix.md b/modules/fs/patches.nix.md index 234bf91..b73d983 100644 --- a/modules/fs/patches.nix.md +++ b/modules/fs/patches.nix.md @@ -9,9 +9,7 @@ Filesystem related "patches" of options in nixpkgs, i.e. additions of options th ```nix #*/# end of MarkDown, beginning of NixOS module: -dirname: inputs: specialArgs@{ config, pkgs, lib, ... }: let inherit (inputs.self) lib; in let - utils = import "${inputs.nixpkgs.outPath}/nixos/lib/utils.nix" { inherit (specialArgs) lib config pkgs; }; - +dirname: inputs: specialArgs@{ config, pkgs, lib, utils, ... }: let inherit (inputs.self) lib; in let in { options = { diff --git a/modules/fs/temproot.nix.md b/modules/fs/temproot.nix.md index 338f728..e86a1eb 100644 --- a/modules/fs/temproot.nix.md +++ b/modules/fs/temproot.nix.md @@ -95,11 +95,11 @@ On a less beefy system, but also with less data to manage, `tmpfs` works fine fo ```nix #*/# end of MarkDown, beginning of NixOS module: -dirname: inputs: specialArgs@{ config, pkgs, lib, ... }: let inherit (inputs.self) lib; in let +dirname: inputs: specialArgs@{ config, pkgs, lib, utils, ... }: let inherit (inputs.self) lib; in let prefix = inputs.config.prefix; cfg = config.${prefix}.fs.temproot; - utils = import "${inputs.nixpkgs.outPath}/nixos/lib/utils.nix" { inherit (specialArgs) lib config pkgs; }; hash = builtins.substring 0 8 (builtins.hashString "sha256" config.networking.hostName); + keep = if cfg.remote.type == "none" then "local" else "remote"; # preferred place for data that should be kept optionsFor = type: desc: { bind.source = lib.mkOption { description = "Prefix for bind-mount targets."; type = lib.types.str; default = "/.${type}"; }; @@ -151,7 +151,8 @@ in { type = lib.mkOption { description = '' "bind": Expects a filesystem to be mounted at ».bind.target« that gets backed. Bind-mounts any additional mounts to ».bind.source+"/"+« (creating those paths if necessary). "zfs": ... - ''; type = lib.types.enum [ "bind" "zfs" ]; default = "bind"; }; + "none": Don't provide a »/remote«. For hosts that have no secrets or important state. + ''; type = lib.types.enum [ "bind" "zfs" "none" ]; default = "bind"; }; } // (optionsFor "remote" "remotely backed-up"); swap = { @@ -172,9 +173,9 @@ in { "/tmp" = { mode = "1777"; }; }; fs.temproot.local.mounts = { + "/local" = { source = "system"; mode = "755"; }; "/nix" = { zfsProps = zfsNoSyncProps; mode = "755"; }; # this (or /nix/store) is required "/var/log" = { source = "logs"; mode = "755"; }; - "/local" = { source = "system"; mode = "755"; }; # »/swap« is used by »cfg.swap.asPartition = false« }; fs.temproot.remote.mounts = { @@ -187,17 +188,17 @@ in { }) (lib.mkIf cfg.persistenceFixes { # Cope with the consequences of having »/« (including »/{etc,var,root,...}«) cleared on every reboot. + environment.etc.nixos.source = "/local/etc/nixos"; + systemd.tmpfiles.rules = [ # keep in mind: this does not get applied super early ... - # nixos config - "L+ /etc/nixos/ - - - - ../../remote/etc/nixos/" # »/root/.nix-channels« is already being restored. # »/var/lib/nixos« remains ephemeral. Where it matters, explicitly define a U/GID! # root's command history - "f /remote/root/.bash_history 0600 root root -" - "L+ /root/.bash_history - - - - ../remote/root/.bash_history" - "f /remote/root/.local/share/nix/repl-history 0600 root root -" - "L+ /root/.local/share/nix/repl-history - - - - ../../../../remote/root/.local/share/nix/repl-history" + "f /${keep}/root/.bash_history 0600 root root -" + "L+ /root/.bash_history - - - - ../${keep}/root/.bash_history" + "f /${keep}/root/.local/share/nix/repl-history 0600 root root -" + "L+ /root/.local/share/nix/repl-history - - - - ../../../../${keep}/root/.local/share/nix/repl-history" ]; fileSystems = { # this does get applied early # (on systems without hardware clock, this allows systemd to provide an at least monolithic time after restarts) @@ -252,7 +253,7 @@ in { ${prefix} = { fs.keystore.keys."luks/local-${hash}/0" = lib.mkIf (cfg.local.bind.base == "f2fs-encrypted") (lib.mkOptionDefault "random"); fs.disks.partitions."local-${hash}" = { - type = "8300"; order = 1000; disk = "primary"; size = "50%"; + type = "8300"; order = lib.mkDefault 1000; disk = lib.mkDefault "primary"; size = lib.mkDefault (if cfg.remote.type == "none" then null else "50%"); }; }; fileSystems.${cfg.local.bind.source} = { fsType = "f2fs"; device = "/dev/${if useLuks then "mapper" else "disk/by-partlabel"}/local-${hash}"; formatOptions = lib.mkDefault (lib.concatStrings [ diff --git a/modules/services/dropbear.nix.md b/modules/services/dropbear.nix.md index b8126c7..abd1e50 100644 --- a/modules/services/dropbear.nix.md +++ b/modules/services/dropbear.nix.md @@ -42,7 +42,7 @@ in { systemd.services."dropbear" = { description = "dropbear SSH server (listening)"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; - serviceConfig.PreExec = lib.mkIf (cfg.hostKeys == [ ]) "${pkgs.coreutils}/bin/mkdir -p /etc/dropbear/"; + serviceConfig.ExecStartPre = lib.mkIf (cfg.hostKeys == [ ]) "${pkgs.coreutils}/bin/mkdir -p /etc/dropbear/"; serviceConfig.ExecStart = defaultArgs + " -F -E"; # don't fork, use stderr #serviceConfig.PIDFile = "/var/run/dropbear.pid"; serviceConfig.Type = "forking"; after = [ "network.target" ]; # alternative to »-E -F« (?) }; diff --git a/overlays/gptfdisk.nix.md b/overlays/gptfdisk.nix.md index 24622a6..1a10826 100644 --- a/overlays/gptfdisk.nix.md +++ b/overlays/gptfdisk.nix.md @@ -24,4 +24,7 @@ in { ../patches/gptfdisk-move-secondary-table.patch ]; }); + + libblockdev = prev.libblockdev.override { inherit (prev) gptfdisk; }; + } diff --git a/overlays/libubootenv.nix.md b/overlays/libubootenv.nix.md index e52a3f4..55f9c12 100644 --- a/overlays/libubootenv.nix.md +++ b/overlays/libubootenv.nix.md @@ -32,6 +32,7 @@ in { hash = "sha256-6cHkr3s7/2BVXBTn9bUfPFbYAfv9VYh6C9GAbWILNjs="; }; nativeBuildInputs = [ pkgs.cmake pkgs.zlib ]; + outputs = [ "out" "lib" ]; meta = { homepage = "https://github.com/sbabic/libubootenv"; diff --git a/overlays/uboot-with-mmc-env.nix.md b/overlays/uboot-with-mmc-env.nix.md new file mode 100644 index 0000000..05cb77c --- /dev/null +++ b/overlays/uboot-with-mmc-env.nix.md @@ -0,0 +1,55 @@ +/* + +# U-Boot with Env Compiled and on MMC + +A function reconfiguring an u-boot package to save its env on MMC (e.g. internal boot storage or microSD) if it doesn't already, and to compile a custom default env into u-boot itself. + + +## Implementation + +```nix +#*/# end of MarkDown, beginning of NixPkgs overlay: +dirname: inputs: final: prev: let + inherit (final) pkgs; inherit (inputs.self) lib; +in { + + uboot-with-mmc-env = { + base ? null, + + # The (maximum) total size, position (each in in bytes), and MMC device number of the u-boot env that can be set later: + envSize ? base.envSize or 16384, + envOffset ? base.envOffset or 4194304, + envMmcDev ? 1, + # Default boot variables for u-boot. This amends the variables that will be compiled into u-boot as the default env, but also is the base for the variables written by the ».mkEnv« function. As such, this should include everything necessary to boot something: + defaultEnv ? ({ }), # $src/scripts/get_default_env.sh can read this again + # Lines to append to the u-boot config: + extraConfig ? [ ], + + }: base.overrideAttrs (old: let + envTxt = env: pkgs.writeText "uboot-env.txt" "${lib.concatStrings (lib.mapAttrsToList (k: v: if v == null then "" else "${k}=${toString v}\n") env)}"; + defaultEnv' = (base.defaultEnv or { }) // defaultEnv; + in { + passthru = (old.passthru or { }) // { + inherit envSize envOffset; defaultEnv = defaultEnv'; + + # Creates a (user) env blob for this u-boot by merging »env« over its »defaultEnv«. The resulting file can be flashed to »CONFIG_ENV_OFFSET« to replace the default env. + mkEnv = env: pkgs.runCommandLocal "uboot-env.img" { + env = envTxt (defaultEnv' // env); + } "${pkgs.ubootTools}/bin/mkenvimage -p 0x00 -s ${toString envSize} -o $out $env"; + + }; + extraConfig = (old.extraConfig or "") + "${lib.concatStringsSep "\n" ([ + # (these need to be passed as 0x:) + "CONFIG_ENV_OFFSET=0x${lib.concatStrings (map toString (lib.toBaseDigits 16 envOffset))}" + "CONFIG_ENV_SIZE=0x${lib.concatStrings (map toString (lib.toBaseDigits 16 envSize))}" + # Ensure that env is configured to be stored on MMC(/microSD): + "CONFIG_ENV_IS_IN_MMC=y" "CONFIG_SYS_MMC_ENV_DEV=${toString envMmcDev}" # (not sure this is enough) + # CONFIG_EXTRA_ENV_SETTINGS here would be overwritten, and CONFIG_DEFAULT_ENV_FILE replaces some basics that should be kept. + ] ++ extraConfig)}\n"; + CONFIG_EXTRA_ENV_SETTINGS = ''${lib.concatMapStringsSep ''"\0"'' builtins.toJSON (lib.mapAttrsToList (k: v: if v == null then "" else ''${k}=${toString v}'') defaultEnv')}"\0"''; # (this is in addition to whatever u-boot derives from its other CONFIG_*) + postConfigure = (old.postConfigure or "") + '' + # Set CONFIG_EXTRA_ENV_SETTINGS just before it's used, to make sure it actually applies: + printf "%s\n%s\n" "#define CONFIG_EXTRA_ENV_SETTINGS $CONFIG_EXTRA_ENV_SETTINGS" "$(cat include/env_default.h)" >include/env_default.h + ''; + }); +} diff --git a/patches/default.nix b/patches/default.nix index 5a448d5..5eb6c89 100644 --- a/patches/default.nix +++ b/patches/default.nix @@ -3,11 +3,6 @@ dirname: inputs: let getNamedPatchFiles = dir: builtins.removeAttrs (builtins.listToAttrs (map (name: let match = builtins.match ''^(.*)[.]patch$'' name; in if (match != null) then { - name = builtins.head match; value = "${dir}/${name}"; + name = builtins.head match; value = builtins.path { path = "${dir}/${name}"; inherit name; }; # »builtins.path« puts the file in a separate, content-addressed store path, ensuring it's path only changes when the content changes, thus avoiding unnecessary rebuilds. } else { name = ""; value = null; }) (builtins.attrNames (builtins.readDir dir)))) [ "" ]; -in (getNamedPatchFiles dirname) // { - # When referring to the patches by a path derived from »dirname«, then their paths change whenever that changes, which happens when any file in this repo changes. Changing patch paths mean that the derivations the patches are inputs to need to be rebuilt, so using local paths, which put their targets into a new store artifact (i.e. separate input) is much more efficient. - # TODO: automate this, somehow: - nixpkgs-fix-systemd-boot-install = ./nixpkgs-fix-systemd-boot-install.patch; - nixpkgs-test = ./nixpkgs-test.patch; -} +in (getNamedPatchFiles dirname) diff --git a/patches/gptfdisk-move-secondary-table.patch b/patches/gptfdisk-move-secondary-table.patch index 4dd1fb9..e72d250 100644 --- a/patches/gptfdisk-move-secondary-table.patch +++ b/patches/gptfdisk-move-secondary-table.patch @@ -1,5 +1,5 @@ diff --git a/gpt.cc b/gpt.cc -index 76cd9ad..375c216 100644 +index 76cd9ad..d61064f 100644 --- a/gpt.cc +++ b/gpt.cc @@ -1500,7 +1500,7 @@ int GPTData::DestroyGPT(void) { @@ -17,7 +17,7 @@ index 76cd9ad..375c216 100644 +// Change the start sector for the secondary partition table. +// Returns 1 on success, 0 on failure -+int GPTData::MoveSecondaryTable(uint64_t pteSector) { ++int GPTData::MoveSecondTable(uint64_t pteSector) { + uint64_t pteSize = GetTableSizeInSectors(); + int retval = 1; + @@ -30,7 +30,7 @@ index 76cd9ad..375c216 100644 + retval = 0; + } // if/else + return retval; -+} // GPTData::MoveSecondaryTable() ++} // GPTData::MoveSecondTable() + // Blank the partition array void GPTData::BlankPartitions(void) { @@ -66,14 +66,14 @@ index 76cd9ad..375c216 100644 // space on the disk. Returns 0 if there are no available blocks left uint64_t GPTData::FindFirstInLargest(void) { diff --git a/gpt.h b/gpt.h -index 5d19372..c272d65 100644 +index 5d19372..17b3380 100644 --- a/gpt.h +++ b/gpt.h @@ -142,6 +142,7 @@ public: // Adjust GPT structures WITHOUT user interaction... int SetGPTSize(uint32_t numEntries, int fillGPTSectors = 1); int MoveMainTable(uint64_t pteSector); -+ int MoveSecondaryTable(uint64_t pteSector); ++ int MoveSecondTable(uint64_t pteSector); void BlankPartitions(void); int DeletePartition(uint32_t partNum); uint32_t CreatePartition(uint32_t partNum, uint64_t startSector, uint64_t endSector); @@ -95,7 +95,7 @@ index 5d19372..c272d65 100644 uint64_t FindLastAvailable(); uint64_t FindLastInFree(uint64_t start, bool align = false); diff --git a/gptcl.cc b/gptcl.cc -index 34c9421..f6517e4 100644 +index 34c9421..232285a 100644 --- a/gptcl.cc +++ b/gptcl.cc @@ -68,7 +68,7 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) { @@ -107,13 +107,22 @@ index 34c9421..f6517e4 100644 uint64_t temp; // temporary variable; free to use in any case char *device; string cmd, typeGUID, name; +@@ -85,7 +85,7 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) { + {"recompute-chs", 'C', POPT_ARG_NONE, NULL, 'C', "recompute CHS values in protective/hybrid MBR", ""}, + {"delete", 'd', POPT_ARG_INT, &deletePartNum, 'd', "delete a partition", "partnum"}, + {"display-alignment", 'D', POPT_ARG_NONE, NULL, 'D', "show number of sectors per allocation block", ""}, +- {"move-second-header", 'e', POPT_ARG_NONE, NULL, 'e', "move second header to end of disk", ""}, ++ {"move-second-header", 'e', POPT_ARG_NONE, NULL, 'e', "move second/backup header to end of disk", ""}, + {"end-of-largest", 'E', POPT_ARG_NONE, NULL, 'E', "show end of largest free block", ""}, + {"first-in-largest", 'f', POPT_ARG_NONE, NULL, 'f', "show start of the largest free block", ""}, + {"first-aligned-in-largest", 'F', POPT_ARG_NONE, NULL, 'F', "show start of the largest free block, aligned", ""}, @@ -94,7 +94,8 @@ int GPTDataCL::DoOptions(int argc, char* argv[]) { {"hybrid", 'h', POPT_ARG_STRING, &hybrids, 'h', "create hybrid MBR", "partnum[:partnum...][:EE]"}, {"info", 'i', POPT_ARG_INT, &infoPartNum, 'i', "show detailed information on partition", "partnum"}, {"align-end", 'I', POPT_ARG_NONE, NULL, 'I', "align partition end points", ""}, - {"move-main-table", 'j', POPT_ARG_INT, &mainTableLBA, 'j', "adjust the location of the main partition table", "sector"}, + {"move-main-table", 'j', POPT_ARG_INT, &mainTableLBA, 'j', "change the start sector of the main partition table", "sector"}, -+ {"move-secondary-table", 'k', POPT_ARG_INT, &secondTableLBA, 'k', "change the start sector of the secondary partition table", "sector"}, ++ {"move-backup-table", 'k', POPT_ARG_INT, &secondTableLBA, 'k', "change the start sector of the second/backup partition table", "sector"}, {"load-backup", 'l', POPT_ARG_STRING, &backupFile, 'l', "load GPT backup from file", "file"}, {"list-types", 'L', POPT_ARG_NONE, NULL, 'L', "list known partition types", ""}, {"gpttombr", 'm', POPT_ARG_STRING, &mbrParts, 'm', "convert GPT to MBR", "partnum[:partnum...]"}, @@ -144,7 +153,7 @@ index 34c9421..f6517e4 100644 + } // if/else + break; + case 'k': -+ if (MoveSecondaryTable(secondTableLBA)) { ++ if (MoveSecondTable(secondTableLBA)) { + JustLooking(0); + saveData = 1; + } else {