aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-14 00:27:45 +0000
committertv <tv@krebsco.de>2026-11-14 01:46:01 +0100
commita4540a584d762b000b21cb7e61987c414eb72a1d (patch)
tree077bb7dfc4d104e5e133ad96c4485ff99bcd14c5 /pkgs
parent8f08426ae3a2e5f0ecc5d010bc6469b41e3d7678 (diff)
populate: layer populate below krops
populate is a program of its own, with a Nix library, in pkgs/populate, which krops uses, and which doesn't use krops: - default.nix builds the program, with nix-build there or callPackage, as pkgs.populate-unwrapped. - lib is populate's Nix library: the types of sources and the POSIX types they use, evalSource, flakeFollows, hasGeneratedFlake, and derivesFlake, taking nixpkgs' lib. krops' lib takes them from there, so it provides them as before, and so does lib/types. pkgs.populate, the function from populate's arguments to a script running the program, is krops' own, in pkgs/krops/populate.nix, with checks.nix next to it. It takes the program as populate-unwrapped, which callPackage passes from the overlay; the flake, whose nixpkgs lacks it, passes its own.
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/krops/checks.nix (renamed from pkgs/populate/checks.nix)0
-rw-r--r--pkgs/krops/populate.nix84
-rw-r--r--pkgs/overlay.nix5
-rw-r--r--pkgs/populate/default.nix106
-rw-r--r--pkgs/populate/docs/design.md59
-rw-r--r--pkgs/populate/lib/default.nix78
-rw-r--r--pkgs/populate/lib/types/populate.nix323
-rw-r--r--pkgs/populate/lib/types/posix.nix54
-rw-r--r--pkgs/populate/program.nix25
9 files changed, 598 insertions, 136 deletions
diff --git a/pkgs/populate/checks.nix b/pkgs/krops/checks.nix
index 22b45c9..22b45c9 100644
--- a/pkgs/populate/checks.nix
+++ b/pkgs/krops/checks.nix
diff --git a/pkgs/krops/populate.nix b/pkgs/krops/populate.nix
new file mode 100644
index 0000000..74ba04a
--- /dev/null
+++ b/pkgs/krops/populate.nix
@@ -0,0 +1,84 @@
+# pkgs.populate: given populate's arguments, it checks them, and returns a
+# script running populate-unwrapped, the program in ../populate, with them,
+# the sources as JSON.
+{ coreutils, findutils, git, nix, openssh, pass, passage, rsync, writers
+, populate-unwrapped
+, ...
+}@args:
+
+with import ../../lib;
+
+let
+ checks = import ./checks.nix;
+
+ # Arguments populate doesn't use, which overrides written for krops 1 may
+ # set. They come through ..., because callPackage would set named ones.
+ unused = filter (name: args ? ${name}) [ "dash" "implementation" "jq" ];
+ unexpected = attrNames (removeAttrs args ([
+ "coreutils" "findutils" "git" "nix" "openssh" "pass" "passage" "rsync"
+ "writers" "populate-unwrapped"
+ ] ++ unused));
+
+ # The parts of a target string, like mkTarget parses it, without its
+ # defaults, which populate fills in when it runs.
+ parseTarget = s: let
+ parts = match "(([^@]*)@)?(([^:/]+))?(:([^/]+))?(/.*)?" s;
+ in
+ if parts == null then throw "krops: invalid target ${toJSON s}"
+ else { host = elemAt parts 3; path = elemAt parts 6; };
+
+ populate =
+ { backup ? false, force ? false, prune ? false, source, target }:
+ let
+ # A target string goes to populate as it is, so populate decides what
+ # it leaves out when it runs. An attribute set, as mkTarget returns
+ # it, goes part by part, with whether it's local decided now, like the
+ # other functions of krops do.
+ parsed = parseTarget target;
+ path = if isString target then (if parsed.path != null then parsed.path else "/var/src") else target.path;
+ host = if isString target then (if parsed.host != null then parsed.host else "localhost") else target.host;
+ targetArgs =
+ if isString target then [ target ]
+ else [
+ "--user" (if target.user or null == null then "" else target.user)
+ "--host" target.host
+ "--path" target.path
+ ] ++ optionals (target.port or null != null) [
+ "--port" (toString target.port)
+ ] ++ optional (target.sudo or false) "--sudo"
+ ++ concatMap (arg: [ "--ssh-arg" arg ]) (flatten (target.extraOptions or []))
+ ++ [ (if isLocalTarget target then "--local" else "--remote") ];
+ in
+ assert checks.checkTargetPath path;
+ assert all checks.checkSourceName (attrNames source);
+ assert checks.checkInputs source;
+ assert isString target || checks.checkCopyingWithSudo target source;
+ writers.writeDash "populate.${host}" /* sh */ ''
+ set -efu
+ echo + "$0" >&2
+ PATH=${makeBinPath [
+ coreutils findutils git nix openssh pass passage rsync
+ ]}''${PATH:+:$PATH}
+ export PATH
+ # The sources on a file descriptor of their own, so the commands
+ # populate runs keep standard input. Store paths in them stay
+ # dependencies of this script, so building it builds them. The JSON is
+ # a single line, which can't be the delimiter.
+ exec ${populate-unwrapped}/bin/populate ${escapeShellArgs (
+ optional backup "--backup" ++
+ optional force "--force" ++
+ optional prune "--prune"
+ )} /dev/fd/3 ${escapeShellArgs targetArgs} 3<<'EOF'
+ ${toJSON source}
+ EOF
+ '';
+
+in
+
+if unexpected != [] then
+ throw "krops: populate called with unexpected argument ${head unexpected}"
+else
+ foldr
+ (name: warn "krops: populate's argument ${name} is unused")
+ populate
+ unused
diff --git a/pkgs/overlay.nix b/pkgs/overlay.nix
index caffdb5..506b4ad 100644
--- a/pkgs/overlay.nix
+++ b/pkgs/overlay.nix
@@ -1,4 +1,7 @@
self: super: {
krops = self.callPackage ./krops {};
- populate = self.callPackage ./populate {};
+ # populate's Nix API, a function from its arguments to a script running
+ # populate-unwrapped, the program.
+ populate = self.callPackage ./krops/populate.nix {};
+ populate-unwrapped = self.callPackage ./populate {};
}
diff --git a/pkgs/populate/default.nix b/pkgs/populate/default.nix
index bd8e772..0c98b28 100644
--- a/pkgs/populate/default.nix
+++ b/pkgs/populate/default.nix
@@ -1,86 +1,28 @@
-# Populate, which delivers sources to a target directory. Given populate's
-# arguments, it checks them, and returns a script running the program in
-# this directory with them, the sources as JSON. rustProgram is that program, e.g. one
-# built with cargo during development.
-{ coreutils, findutils, git, nix, openssh, pass, passage, rsync, writers
-, rustPlatform ? null
-, rustProgram ? import ./program.nix { inherit rustPlatform; }
-, ...
-}@args:
-
-with import ../../lib;
+# populate, built from the crate in this directory: nix-build here, or
+# callPackage.
+{ rustPlatform ? (import <nixpkgs> {}).rustPlatform }:
let
- checks = import ./checks.nix;
-
- # Arguments populate doesn't use, which overrides written for krops 1 may
- # set. They come through ..., because callPackage would set named ones.
- unused = filter (name: args ? ${name}) [ "dash" "implementation" "jq" ];
- unexpected = attrNames (removeAttrs args ([
- "coreutils" "findutils" "git" "nix" "openssh" "pass" "passage" "rsync"
- "writers" "rustPlatform" "rustProgram"
- ] ++ unused));
-
- # The parts of a target string, like mkTarget parses it, without its
- # defaults, which populate fills in when it runs.
- parseTarget = s: let
- parts = match "(([^@]*)@)?(([^:/]+))?(:([^/]+))?(/.*)?" s;
- in
- if parts == null then throw "krops: invalid target ${toJSON s}"
- else { host = elemAt parts 3; path = elemAt parts 6; };
-
- populate =
- { backup ? false, force ? false, prune ? false, source, target }:
- let
- # A target string goes to populate as it is, so populate decides what
- # it leaves out when it runs. An attribute set, as mkTarget returns
- # it, goes part by part, with whether it's local decided now, like the
- # other functions of krops do.
- parsed = parseTarget target;
- path = if isString target then (if parsed.path != null then parsed.path else "/var/src") else target.path;
- host = if isString target then (if parsed.host != null then parsed.host else "localhost") else target.host;
- targetArgs =
- if isString target then [ target ]
- else [
- "--user" (if target.user or null == null then "" else target.user)
- "--host" target.host
- "--path" target.path
- ] ++ optionals (target.port or null != null) [
- "--port" (toString target.port)
- ] ++ optional (target.sudo or false) "--sudo"
- ++ concatMap (arg: [ "--ssh-arg" arg ]) (flatten (target.extraOptions or []))
- ++ [ (if isLocalTarget target then "--local" else "--remote") ];
- in
- assert checks.checkTargetPath path;
- assert all checks.checkSourceName (attrNames source);
- assert checks.checkInputs source;
- assert isString target || checks.checkCopyingWithSudo target source;
- writers.writeDash "populate.${host}" /* sh */ ''
- set -efu
- echo + "$0" >&2
- PATH=${makeBinPath [
- coreutils findutils git nix openssh pass passage rsync
- ]}''${PATH:+:$PATH}
- export PATH
- # The sources on a file descriptor of their own, so the commands
- # populate runs keep standard input. Store paths in them stay
- # dependencies of this script, so building it builds them. The JSON is
- # a single line, which can't be the delimiter.
- exec ${rustProgram}/bin/populate ${escapeShellArgs (
- optional backup "--backup" ++
- optional force "--force" ++
- optional prune "--prune"
- )} /dev/fd/3 ${escapeShellArgs targetArgs} 3<<'EOF'
- ${toJSON source}
- EOF
- '';
-
+ cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
in
-if unexpected != [] then
- throw "krops: populate called with unexpected argument ${head unexpected}"
-else
- foldr
- (name: warn "krops: populate's argument ${name} is unused")
- populate
- unused
+rustPlatform.buildRustPackage {
+ pname = cargoToml.package.name;
+ inherit (cargoToml.package) version;
+ # Without what cargo doesn't build from, so changes to the Nix files, the
+ # docs, or the tests don't rebuild the program. By names, not by paths
+ # relative to this directory: the paths the filter gets needn't start
+ # with toString ./., e.g. if a symlink leads to this directory.
+ src = builtins.path {
+ name = "populate-src";
+ path = ./.;
+ filter = path: type: let
+ name = baseNameOf path;
+ in
+ !(builtins.elem name [
+ ".gitignore" "README.md" "docs" "lib" "target" "tests"
+ ] ||
+ builtins.match ".*\\.nix" name != null);
+ };
+ cargoLock.lockFile = ./Cargo.lock;
+}
diff --git a/pkgs/populate/docs/design.md b/pkgs/populate/docs/design.md
index cf9d356..940ab5f 100644
--- a/pkgs/populate/docs/design.md
+++ b/pkgs/populate/docs/design.md
@@ -1,15 +1,16 @@
# Populate's design
-Populate delivers sources to a target directory. It consists of two parts:
+Populate delivers sources to a target directory. It consists of:
-- Nix code, which turns populate's arguments into a command line: the
- sources as `evalSource` returns them, as JSON, the target, and the
- options. It checks the arguments during evaluation, so mistakes show
- early.
-- A program, which reads the sources and delivers them to the target its
- command line names. It knows nothing about Nix expressions: whatever
- produces sources as JSON can drive it.
+- A program, which reads the sources as JSON and delivers them to the
+ target its command line names. It knows nothing about Nix expressions:
+ whatever produces sources as JSON can drive it.
+- A Nix library, `lib`, with the types of sources, which describe how to
+ write them in Nix, and `evalSource`, which turns sources written so into
+ the JSON the program reads.
+krops uses both: its `pkgs.populate` turns populate's arguments into a
+command line, and checks them during evaluation, so mistakes show early.
So there is one program for each version of populate, instead of a script
for each set of arguments, and the program can be written in any language.
This directory holds the program in Rust. Another implementation, e.g. in
@@ -18,20 +19,22 @@ the same tests.
## Nix side
-`default.nix` is `pkgs.populate`. Given populate's arguments, `{ backup ?
-false, force ? false, prune ? false, source, target }`, it checks them with
-`checks.nix`, and returns a script running the program with the sources as
-JSON on file descriptor 3, so the commands populate runs keep standard
-input. A target string goes to the program as it is. A target attribute
-set, as `mkTarget` returns it, goes part by part, as options, with
-`--local` or `--remote` as krops' lib's `isLocalTarget` decides during
-evaluation, like the other functions of krops. The script puts the tools
-populate runs in front of `PATH`, and depends on the store paths in the
-sources, so building it builds them. `rustProgram` is the program, by
-default built by `program.nix`.
+`default.nix` builds the program: `nix-build` in this directory, or
+`callPackage`, as krops' `pkgs.populate-unwrapped`. `lib/default.nix` is
+the Nix library, taking nixpkgs' lib.
-`writeDeploy` and the other functions of `pkgs.krops` use `pkgs.populate`,
-so their arguments stay as they are.
+krops' `pkgs.populate`, in its `pkgs/krops/populate.nix`, is populate's Nix
+API there. Given populate's arguments, `{ backup ? false, force ? false,
+prune ? false, source, target }`, it checks them with krops'
+`pkgs/krops/checks.nix`, and returns a script running the program with the
+sources as JSON on file descriptor 3, so the commands populate runs keep
+standard input. A target string goes to the program as it is. A target
+attribute set, as `mkTarget` returns it, goes part by part, as options,
+with `--local` or `--remote` as krops' lib's `isLocalTarget` decides
+during evaluation, like the other functions of krops. The script puts the
+tools populate runs in front of `PATH`, and depends on the store paths in
+the sources, so building it builds them. krops' `writeDeploy` and its
+other functions use `pkgs.populate`, so their arguments stay as they are.
## Invocation
@@ -99,13 +102,13 @@ doesn't go unnoticed. The sources carry no version: this is version 0, and
a later version of populate that reads them differently will mark them.
Store paths in the sources, e.g. of a `file` source that is a derivation,
-must exist when the program runs. The script `default.nix` writes depends
-on them. Something producing sources at run time has to build them first.
+must exist when the program runs. The script krops' `pkgs.populate`
+writes depends on them. Something producing sources at run time has to build them first.
## Checks
-The program checks the sources and the target as `checks.nix` does, with
-the same messages:
+The program checks the sources and the target as krops' `checks.nix` does
+during evaluation, with the same messages:
- The target's host isn't empty, and neither it nor its user starts with
`-`, so ssh can't take them for options. `checks.nix` doesn't check
@@ -122,9 +125,9 @@ the same messages:
since `evalSource` checks the types.
Checks that depend on whether the target is local come on top, e.g.
-refusing to copy flakes to remote targets using sudo. `checks.nix` does
-that during evaluation too, where `default.nix` decides whether the target
-is local.
+refusing to copy flakes to remote targets using sudo. krops' `checks.nix`
+does that during evaluation too, where `pkgs.populate` decides whether the
+target is local.
## Output
diff --git a/pkgs/populate/lib/default.nix b/pkgs/populate/lib/default.nix
new file mode 100644
index 0000000..e9155c5
--- /dev/null
+++ b/pkgs/populate/lib/default.nix
@@ -0,0 +1,78 @@
+# populate's Nix library: the types of sources, which describe how to write
+# them in Nix, and evalSource, which turns sources written so into the
+# sources populate reads as JSON. lib is nixpkgs' lib.
+{ lib ? import <nixpkgs/lib> }:
+
+let
+ # nixpkgs' lib, with what the types use besides.
+ lib' = lib // {
+ test = re: x: lib.isString x && lib.match re x != null;
+ types = lib.types // types;
+ };
+
+ types =
+ import ./types/posix.nix { lib = lib'; } //
+ import ./types/populate.nix { lib = lib'; };
+
+in rec {
+
+ inherit types;
+
+ # The sources, a list of attribute sets of sources by name, or one, with
+ # shorthands expanded and defaults filled in, and without settings that
+ # are null, as populate reads them, e.g. with builtins.toJSON.
+ evalSource = let
+ eval = source: lib.evalModules {
+ modules = lib.singleton {
+ _file = toString ./.;
+ imports = map (source: { inherit source; }) (lib.toList source);
+ options.source = lib.mkOption {
+ default = {};
+ type = lib.types.attrsOf types.source;
+ };
+ };
+ };
+ sanitize = x: lib.getAttr (lib.typeOf x) {
+ bool = x;
+ list = map sanitize x;
+ set = lib.mapAttrs
+ (lib.const sanitize)
+ (lib.filterAttrs
+ (name: value: name != "_module" && value != null) x);
+ string = x;
+ };
+ in
+ source: sanitize (eval source).config.source;
+
+ # Inputs a source draws from other sources, as list of attribute sets like
+ # { path = [ "stockholm" "nixpkgs" ]; source = "nixpkgs"; }.
+ flakeFollows = source: let
+ flatten = prefix: inputs: lib.concatLists (lib.mapAttrsToList
+ (name: input:
+ lib.optional (input.follows or null != null) {
+ path = prefix ++ [ name ];
+ source = input.follows;
+ } ++
+ flatten (prefix ++ [ name ]) (input.inputs or {}))
+ inputs);
+ in
+ flatten [] (source.${source.type}.inputs or {});
+
+ # Tells whether a source's inputs get drawn from other sources by a
+ # generated flake in .populate/flakes, instead of by rewriting the lock
+ # file of the flake in the source. Symlink sources point at other
+ # sources, whose lock files stay as they are.
+ hasGeneratedFlake = source:
+ flakeFollows source != [] &&
+ (source.type == "symlink" || !(source.${source.type}.rewriteLock or true));
+
+ # Tells whether a flake or flake-input source gets delivered as a copy,
+ # which gets patched, gets its lock file rewritten, or is materialized.
+ derivesFlake = source: let
+ source' = source.${source.type};
+ in
+ lib.elem source.type [ "flake" "flake-input" ] &&
+ (source'.materialize or false || source'.patches or [] != [] ||
+ (source'.rewriteLock or true && flakeFollows source != []));
+
+}
diff --git a/pkgs/populate/lib/types/populate.nix b/pkgs/populate/lib/types/populate.nix
new file mode 100644
index 0000000..56b0292
--- /dev/null
+++ b/pkgs/populate/lib/types/populate.nix
@@ -0,0 +1,323 @@
+{ lib }: rec {
+
+ source = lib.types.submodule ({ config, ... }: {
+ options = {
+ type = let
+ known-types = lib.attrNames source-types;
+ type-candidates = lib.filter (k: config.${k} != null) known-types;
+ in lib.mkOption {
+ default = if lib.length type-candidates == 1
+ then lib.head type-candidates
+ else throw "cannot determine type";
+ type = lib.types.enum known-types;
+ };
+ derivation = lib.mkOption {
+ apply = x:
+ if lib.types.str.check x
+ then { text = x; }
+ else x;
+ default = null;
+ type = lib.types.nullOr (lib.types.either lib.types.str source-types.derivation);
+ };
+ file = lib.mkOption {
+ apply = x:
+ if lib.types.absolute-pathname.check x || lib.types.package.check x
+ then { path = x; }
+ else x;
+ default = null;
+ type = lib.types.nullOr (lib.types.oneOf [
+ lib.types.absolute-pathname
+ lib.types.package
+ source-types.file
+ ]);
+ };
+ flake = lib.mkOption {
+ apply = x:
+ if lib.types.str.check x
+ then { url = x; }
+ else x;
+ default = null;
+ type = lib.types.nullOr (lib.types.either lib.types.str source-types.flake);
+ };
+ flake-input = lib.mkOption {
+ default = null;
+ type = lib.types.nullOr source-types.flake-input;
+ };
+ git = lib.mkOption {
+ default = null;
+ type = lib.types.nullOr source-types.git;
+ };
+ pass = lib.mkOption {
+ default = null;
+ type = lib.types.nullOr source-types.pass;
+ };
+ passage = lib.mkOption {
+ apply = x:
+ if lib.types.pathname.check x
+ then { dir = x; }
+ else x;
+ default = null;
+ type = lib.types.nullOr (lib.types.oneOf [
+ lib.types.pathname
+ source-types.passage
+ ]);
+ };
+ pipe = lib.mkOption {
+ apply = x:
+ if lib.types.absolute-pathname.check x
+ then { command = x; }
+ else x;
+ default = null;
+ type = lib.types.nullOr (lib.types.either lib.types.absolute-pathname source-types.pipe);
+ };
+ symlink = lib.mkOption {
+ apply = x:
+ if lib.types.pathname.check x
+ then { target = x; }
+ else x;
+ default = null;
+ type = lib.types.nullOr (lib.types.either lib.types.pathname source-types.symlink);
+ };
+ };
+ });
+
+ # Inputs of the flake in a source that get drawn from other sources, in the
+ # style of flake.nix, e.g. { nixpkgs.follows = "nixpkgs"; }.
+ flake-inputs = lib.types.attrsOf (lib.types.submodule {
+ options = {
+ follows = lib.mkOption {
+ default = null;
+ type = lib.types.nullOr lib.types.str; # TODO lib.types.label
+ };
+ inputs = lib.mkOption {
+ default = {};
+ type = flake-inputs;
+ };
+ };
+ });
+
+ filter = lib.types.submodule {
+ options = {
+ type = lib.mkOption {
+ type = lib.types.enum ["include" "exclude"];
+ default = "exclude";
+ };
+ pattern = lib.mkOption {
+ type = lib.types.str;
+ };
+ };
+ };
+
+ source-types = {
+ derivation = lib.types.submodule {
+ options = {
+ text = lib.mkOption {
+ type = lib.types.str;
+ };
+ };
+ };
+ file = lib.types.submodule {
+ options = {
+ inputs = lib.mkOption {
+ default = {};
+ type = flake-inputs;
+ };
+ path = lib.mkOption {
+ type = lib.types.absolute-pathname;
+ };
+ rewriteLock = lib.mkOption {
+ default = true;
+ type = lib.types.bool;
+ };
+ useChecksum = lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ };
+ exclude = lib.mkOption {
+ apply = x:
+ if x != [] then
+ lib.warn
+ "file.*.exclude is deprecated in favor of file.*.filters"
+ x
+ else
+ x;
+ description = ''
+ DEPRECATED, use `filters`.
+ '';
+ type = lib.types.listOf lib.types.str;
+ default = [];
+ example = [".git"];
+ };
+ filters = lib.mkOption {
+ type = lib.types.listOf filter;
+ default = [];
+ example = [
+ {
+ type = "include";
+ pattern = "*.nix";
+ }
+ {
+ type = "include";
+ pattern = "*/";
+ }
+ {
+ type = "exclude";
+ pattern = "*";
+ }
+ ];
+ };
+ deleteExcluded = lib.mkOption {
+ default = true;
+ type = lib.types.bool;
+ };
+ insecureLinks = lib.mkOption {
+ default = null;
+ type = lib.types.nullOr lib.types.bool;
+ };
+ };
+ };
+ flake = lib.types.submodule {
+ options = {
+ fetchOnTarget = lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ };
+ url = lib.mkOption {
+ type = lib.types.str; # TODO lib.types.flake-ref
+ };
+ inputs = lib.mkOption {
+ default = {};
+ type = flake-inputs;
+ };
+ materialize = lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ };
+ patches = lib.mkOption {
+ apply = map toString;
+ default = [];
+ type = lib.types.listOf lib.types.path;
+ };
+ rewriteLock = lib.mkOption {
+ default = true;
+ type = lib.types.bool;
+ };
+ };
+ };
+ flake-input = lib.types.submodule {
+ options = {
+ fetchOnTarget = lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ };
+ url = lib.mkOption {
+ type = lib.types.str; # TODO lib.types.flake-ref
+ };
+ inputs = lib.mkOption {
+ default = {};
+ type = flake-inputs;
+ };
+ materialize = lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ };
+ patches = lib.mkOption {
+ apply = map toString;
+ default = [];
+ type = lib.types.listOf lib.types.path;
+ };
+ rewriteLock = lib.mkOption {
+ default = true;
+ type = lib.types.bool;
+ };
+ name = lib.mkOption {
+ type = lib.types.str;
+ };
+ requireInputs = lib.mkOption {
+ default = true;
+ type = lib.types.bool;
+ };
+ };
+ };
+ git = lib.types.submodule {
+ options = {
+ clean = {
+ exclude = lib.mkOption {
+ default = [];
+ type = lib.types.listOf lib.types.str;
+ };
+ };
+ fetchAlways = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+ inputs = lib.mkOption {
+ default = {};
+ type = flake-inputs;
+ };
+ ref = lib.mkOption {
+ type = lib.types.str; # TODO lib.types.git.ref
+ };
+ reclone = lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ };
+ rewriteLock = lib.mkOption {
+ default = true;
+ type = lib.types.bool;
+ };
+ url = lib.mkOption {
+ type = lib.types.str; # TODO lib.types.git.url
+ };
+ shallow = lib.mkOption {
+ default = false;
+ type = lib.types.bool;
+ };
+ };
+ };
+ pass = lib.types.submodule {
+ options = {
+ dir = lib.mkOption {
+ type = lib.types.absolute-pathname;
+ };
+ name = lib.mkOption {
+ type = lib.types.pathname; # TODO relative-pathname
+ };
+ };
+ };
+ passage = lib.types.submodule {
+ options = {
+ age = lib.mkOption {
+ default = "age";
+ type = lib.types.pathname;
+ };
+ dir = lib.mkOption {
+ type = lib.types.pathname;
+ };
+ identities_file = lib.mkOption {
+ default = toString ~/.passage/identities;
+ type = lib.types.pathname;
+ };
+ };
+ };
+ pipe = lib.types.submodule {
+ options = {
+ command = lib.mkOption {
+ type = lib.types.absolute-pathname;
+ };
+ };
+ };
+ symlink = lib.types.submodule {
+ options = {
+ inputs = lib.mkOption {
+ default = {};
+ type = flake-inputs;
+ };
+ target = lib.mkOption {
+ type = lib.types.pathname; # TODO relative-pathname
+ };
+ };
+ };
+ };
+
+}
diff --git a/pkgs/populate/lib/types/posix.nix b/pkgs/populate/lib/types/posix.nix
new file mode 100644
index 0000000..e8f464e
--- /dev/null
+++ b/pkgs/populate/lib/types/posix.nix
@@ -0,0 +1,54 @@
+{ lib }: rec {
+
+ # RFC952, B. Lexical grammar, <hname>
+ hostname = lib.mkOptionType {
+ name = "hostname";
+ check = x: lib.isString x && lib.all label.check (lib.splitString "." x);
+ merge = lib.mergeOneOption;
+ };
+
+ # RFC952, B. Lexical grammar, <name>
+ # RFC1123, 2.1 Host Names and Numbers
+ label = lib.mkOptionType {
+ name = "label";
+ # TODO case-insensitive labels
+ check = lib.test "[0-9A-Za-z]([0-9A-Za-z-]*[0-9A-Za-z])?";
+ merge = lib.mergeOneOption;
+ };
+
+ # POSIX.1‐2013, 3.278 Portable Filename Character Set
+ filename = lib.mkOptionType {
+ name = "POSIX filename";
+ check = lib.test "([0-9A-Za-z._])[0-9A-Za-z._-]*";
+ merge = lib.mergeOneOption;
+ };
+
+ # POSIX.1‐2013, 3.2 Absolute Pathname
+ absolute-pathname = lib.mkOptionType {
+ name = "POSIX absolute pathname";
+ check = x: lib.isString x && lib.substring 0 1 x == "/" && pathname.check x;
+ merge = lib.mergeOneOption;
+ };
+
+ # POSIX.1‐2013, 3.267 Pathname
+ pathname = lib.mkOptionType {
+ name = "POSIX pathname";
+ check = x:
+ let
+ # The filter is used to normalize paths, i.e. to remove duplicated and
+ # trailing slashes. It also removes leading slashes, thus we have to
+ # check for "/" explicitly below.
+ xs = lib.filter (s: lib.stringLength s > 0) (lib.splitString "/" x);
+ in
+ lib.isString x && (x == "/" || (lib.length xs > 0 && lib.all filename.check xs));
+ merge = lib.mergeOneOption;
+ };
+
+ # POSIX.1-2013, 3.431 User Name
+ username = lib.mkOptionType {
+ name = "POSIX username";
+ check = filename.check;
+ merge = lib.mergeOneOption;
+ };
+
+}
diff --git a/pkgs/populate/program.nix b/pkgs/populate/program.nix
deleted file mode 100644
index fbb364c..0000000
--- a/pkgs/populate/program.nix
+++ /dev/null
@@ -1,25 +0,0 @@
-# The program delivering sources, built from the crate in this directory.
-{ rustPlatform }:
-
-let
- cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
-in
-
-rustPlatform.buildRustPackage {
- pname = cargoToml.package.name;
- inherit (cargoToml.package) version;
- # Without what cargo doesn't build from, so changes to the Nix files or
- # the docs don't rebuild the program. By names, not by paths relative to
- # this directory: the paths the filter gets needn't start with
- # toString ./., e.g. if a symlink leads to this directory.
- src = builtins.path {
- name = "populate-src";
- path = ./.;
- filter = path: type: let
- name = baseNameOf path;
- in
- !(builtins.elem name [ ".gitignore" "docs" "target" ] ||
- builtins.match ".*\\.nix" name != null);
- };
- cargoLock.lockFile = ./Cargo.lock;
-}