aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/populate/README.md620
-rw-r--r--pkgs/populate/docs/design.md93
-rw-r--r--pkgs/populate/src/check.rs14
3 files changed, 635 insertions, 92 deletions
diff --git a/pkgs/populate/README.md b/pkgs/populate/README.md
new file mode 100644
index 0000000..f96ef49
--- /dev/null
+++ b/pkgs/populate/README.md
@@ -0,0 +1,620 @@
+# populate
+
+populate fills a target directory, `/var/src` by default, on the machine it
+runs on or on another one via ssh, with sources: directories and files,
+Git work trees, secrets from [pass](https://www.passwordstore.org/) or
+[passage](https://github.com/FiloSottile/passage), the output of commands,
+Nix derivations, flakes, and inputs of flakes, which can draw their own
+inputs from other sources. Running it again brings the target directory up
+to date with the sources. It doesn't change anything outside the target
+directory.
+
+[krops](../../README.md) deploys NixOS systems with it.
+
+## Building
+
+```sh
+nix-build # in this directory, which builds result/bin/populate
+cargo build # for development, with Rust 1.71 or later
+```
+
+`default.nix` takes nixpkgs' `rustPlatform`, from `<nixpkgs>` by default,
+so `callPackage` builds it as well. It needs nixpkgs 23.11 or later, and
+fetches the program's dependencies from [crates.io](https://crates.io).
+
+
+## Requirements
+
+populate runs rsync, ssh, Git, Nix, pass, and passage as the sources need
+them, found in `PATH`, as well as a POSIX shell, coreutils, and findutils.
+The target needs a POSIX shell, coreutils, findutils, rsync, `patch` for
+sources with `patches`, Git for `git` sources, and Nix, and, if remote,
+ssh access as root, or, with `--sudo`, as a user who may use `sudo`.
+NixOS has all of them but Git, and rsync as long as
+`environment.defaultPackages` contains it. Features that say so need Nix
+with flake support (Nix 2.4 or later) on the machine that uses them.
+
+
+## Command line
+
+```
+populate [OPTION]... SOURCES [TARGET]
+```
+
+populate delivers the sources in the file `SOURCES`, or standard input if
+`SOURCES` is `-`, to `TARGET`, which is `[USER@][HOST][:PORT][PATH]`. The
+user ends at the first `@`, the host at a `:` or a `/`, and the port at a
+`/`. The user defaults to `$LOGNAME`, the host to the name of the machine
+populate runs on, from `$HOSTNAME` or `/etc/hostname`, and the path to
+`/var/src`. A target is local if its user is `$LOGNAME`, and its host is
+`localhost` or the name of the machine; populate reaches other targets via
+ssh, as their user.
+
+| Option | Meaning |
+|-------------------|---------------------------------------------------------|
+| `--backup` | Copy the target directory to a backup first. |
+| `--force` | Create the target directory's sentinel if it's missing. |
+| `--prune` | Remove what isn't a source from the target directory. |
+| `--sudo` | Deliver as root, via sudo on the target. |
+| `--ssh-arg ARG` | Pass `ARG` to ssh; repeatable. |
+| `--user USER` | The target's user; empty for ssh's default. |
+| `--host HOST` | The target's host, e.g. one `TARGET` can't express. |
+| `--port PORT` | The port ssh connects to. |
+| `--path PATH` | The target directory. |
+| `--local` | The target is the machine populate runs on. |
+| `--remote` | The target isn't, even if it looks so. |
+| `-h`, `--help` | Print the usage and exit. |
+| `-V`, `--version` | Print populate's version and exit. |
+
+`--user`, `--host`, `--port`, and `--path` take precedence over `TARGET`'s
+parts. Options take their argument as the next argument or after `=`, and
+may come before, between, or after `SOURCES` and `TARGET`, up to `--`.
+
+Without the sentinel, `.populate` in the target directory, populate refuses
+to touch the target directory, unless `--force` creates it; see
+[The target directory](#the-target-directory).
+
+Exit status:
+
+- 0: populate delivered all sources.
+- 1: populate refused the sources or the target, or delivering failed,
+ with a message of its own.
+- The exit status of the command that failed, if delivering failed with
+ that: e.g. 255 if ssh can't reach the target, or 128 if `git fetch`
+ fails.
+- 2: the command line is wrong, or the sources can't be read, aren't JSON,
+ or aren't sources.
+- 129, 130, 143: populate got SIGHUP, SIGINT, or SIGTERM, and stopped.
+
+
+## Sources
+
+The sources are a JSON object, of sources by name. Each source is an
+object with the field `type`, the source's type, and a field named after
+the type, holding its settings, which [Source types](#source-types)
+describes:
+
+```json
+{
+ "nixpkgs": {
+ "type": "git",
+ "git": { "url": "https://github.com/NixOS/nixpkgs", "ref": "nixos-26.05" }
+ },
+ "config": { "type": "file", "file": { "path": "/home/me/config" } },
+ "nixos-config": {
+ "type": "symlink",
+ "symlink": { "target": "config/configuration.nix" }
+ }
+}
+```
+```sh
+populate --force sources.json root@server
+```
+
+Settings with defaults may be left out. Fields and settings populate
+doesn't know are errors, so a misspelt one 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.
+
+### Sources in Nix
+
+`lib` is populate's Nix library: the types of sources, which describe how
+to write them in Nix, with shorthands, e.g. `config.file = "/home/me/config"`,
+and `evalSource`, which turns sources written so, an attribute set or a list
+of them, into the sources populate reads. It takes nixpkgs' lib, from
+`<nixpkgs/lib>` by default.
+
+```sh
+nix-instantiate --eval --strict --json -E '
+ (import ./lib {}).evalSource {
+ nixpkgs.git = {
+ url = "https://github.com/NixOS/nixpkgs";
+ ref = "nixos-26.05";
+ };
+ config.file = "/home/me/config";
+ nixos-config.symlink = "config/configuration.nix";
+ }
+' | populate --force - root@server
+```
+
+Store paths in the sources, e.g. of a `file` source that is a derivation,
+must exist when populate runs. `nix-instantiate --eval` doesn't build
+them; building a file that contains the sources, e.g. with `pkgs.writeText
+"sources.json" (builtins.toJSON sources)`, builds them too.
+
+
+## The target directory
+
+Populate owns the target directory, `/var/src` by default: it replaces
+whatever is at the target path of a source, and with `prune` it removes
+everything else. It doesn't change or remove anything outside:
+
+* Target paths must be absolute, must not be the root directory, and must not
+ contain `..`. Source names must consist of letters, digits, and the
+ characters `._-`, not start with `-`, and be neither `.`, `..`, nor
+ `.populate`. populate refuses anything else, and so does krops'
+ `pkgs.populate` during evaluation.
+
+* A symlink at the target path of a source gets replaced by what the source
+ delivers, not followed, and populate says so. To keep a source elsewhere,
+ e.g. on another disk or in memory, mount that place at the target path, or
+ make the target directory itself a symlink. A Git work tree whose `.git`
+ is a symlink, or a file naming a repository elsewhere, gets replaced by a
+ `git` source as well.
+
+* A `git` source only uses a work tree whose `.git` directory contains no
+ symlinks, no `commondir` file and no linked work trees, and only the
+ configuration that `git clone` and populate write, and in which each
+ `.git` of a submodule is a file naming a repository in `.git/modules`. Otherwise Git's
+ commands could change what's elsewhere, or run commands, so populating
+ fails, telling what's wrong, unless the source's `reclone` is set. Git
+ runs no hooks.
+
+* A file in the target directory that has other hard links, e.g. one in
+ `/etc/nixos`, gets replaced by a copy of its own before rsync syncs it, so
+ the other ones keep their mode and time. Populate writes its own files,
+ and replaces lock files it rewrites, by renaming new files into place, and
+ only makes directories writable that it removes.
+
+* Populate keeps its state in the sentinel, `.populate`, which becomes a
+ directory once there is state to keep. Populating fails if the sentinel,
+ or a directory populate needs therein, is a symlink.
+
+
+## Source Types
+
+### `derivation`
+
+Nix expression to be built at the target machine.
+
+Supported attributes:
+
+* `text` -
+ Nix expression to be built.
+
+
+### `file`
+
+The file source type transfers local files (and folders) to the target
+using [`rsync`](https://rsync.samba.org/).
+
+Supported attributes:
+
+* `path` -
+ absolute path to files that should by transferred.
+
+* `useChecksum` (optional) -
+ boolean that controls whether file contents should be checked to decide
+ whether a file has changed. This is useful when `path` points at files
+ with mangled timestamps, e.g. the Nix store.
+
+ The default value is `true` if `path` is a derivation, and `false` otherwise.
+
+* `filters` (optional)
+ List of filters that should be passed to [`rsync`](https://rsync.samba.org/).
+ Filters are specified as attribute sets with the attributes `type` and
+ `pattern`. Supported filter types are `include` and `exclude`.
+ Checkout the filter rules section in the
+ [rsync manual](https://download.samba.org/pub/rsync/rsync.html)
+ for further information.
+
+* `deleteExcluded` (optional)
+ boolean that controls whether the excluded directories should be deleted
+ if they exist on the target. This is passed to the `--delete-excluded` option
+ of rsync. Defaults to `true`.
+
+* `insecureLinks` (optional) -
+ what to do if rsync can't read `.rsync-filter` files that apply to `path`:
+ the ones therein, and the ones in the directories above it. rsync 3.5.0
+ and later open them more strictly than the files they transfer, and ignore
+ the ones they can't open, e.g. because a symlink on the way belongs to
+ neither root nor the user deploying, or, with rsync 3.5.0, because a
+ directory on the way may be entered but not read, like a home directory
+ with mode 711. By default, with `null`, populating fails with an error
+ naming the file. With `true`, rsync reads such files anyway
+ (`--insecure-links`), which also lets it pass such symlinks on the way to
+ the target path of a local target; with `false`, rsync syncs without them.
+ Both print a warning. Older versions of rsync aren't affected.
+
+* `inputs` (optional) -
+ inputs of the flake in this source to draw from other sources, see
+ [Flake inputs from sources](#flake-inputs-from-sources).
+
+* `rewriteLock` (optional) -
+ boolean that controls whether inputs get drawn from other sources by
+ rewriting the lock file of the flake in this source, or else through a
+ generated flake, see [Flake inputs from sources](#flake-inputs-from-sources).
+ Defaults to `true`.
+
+
+### `flake`
+
+The flake source type delivers a flake to the target. The flake gets
+archived locally using `nix flake archive`, which includes only files tracked
+by Git, so use a `file` source for work trees with untracked files. The
+flake and its inputs get copied into the target's Nix store using `nix copy`,
+and the target path becomes a symlink to the flake's store path, registered
+as garbage collector root. For flakes in a subdirectory, e.g.
+`git+file:/src/mono?dir=sub`, the symlink points at that subdirectory of the
+store path. The inputs get garbage collector roots in
+`.populate/flake-inputs/<name>`, which turns the sentinel file into a
+directory.
+
+Supported attributes:
+
+* `fetchOnTarget` (optional) -
+ boolean that controls whether the target fetches the flake's inputs itself
+ instead of receiving them from the deploying machine. The target then uses
+ the arguments recorded in the lock file, so inputs that refer to local paths
+ can only be fetched if the target has these paths as well; populating fails
+ otherwise, naming the input. If the flake reference names a scheme that
+ doesn't refer to the deploying machine, e.g. `github:`, the target handles
+ the flake entirely on its own, and the deploying machine doesn't fetch it.
+ Otherwise the target fetches the flake itself as well if its locked
+ reference doesn't refer to the deploying machine, e.g. for indirect
+ references resolving to `github:` flakes, or else the flake gets
+ transferred using `rsync`, unless the target's Nix store has it already,
+ and added to the target's Nix store, where it gets the same store path as
+ on the deploying machine. This requires Nix with flake support (Nix 2.4 or
+ later) on the target, and works for targets using `sudo`. Defaults to
+ `false`.
+
+* `url` -
+ flake reference, e.g. the absolute path of a directory containing a
+ `flake.nix`. Its lock file is used as is; populating fails if the lock
+ file needs to be updated.
+
+* `inputs` (optional) -
+ inputs of the flake in this source to draw from other sources, see
+ [Flake inputs from sources](#flake-inputs-from-sources).
+
+* `materialize` (optional) -
+ boolean that controls whether the target path becomes a writable copy
+ instead of a link into the store. The copy gets made again only if what
+ it derives from changes: the delivered store path, the patches, or which
+ inputs get drawn from which sources. Until then, changes made to it on the
+ target stay. Nix copies it into the store whenever evaluating a flake
+ changed it, which is noticeable for big flakes like nixpkgs. For a flake
+ in a subdirectory, only that subdirectory gets copied. Defaults to
+ `false`.
+
+* `patches` (optional) -
+ list of paths of patches on the deploying machine, which get applied in
+ order with `patch -p1` on the target, in the directory the target path
+ shows. Populating fails if a patch doesn't apply. Defaults to `[]`.
+
+* `rewriteLock` (optional) -
+ boolean that controls whether inputs get drawn from other sources by
+ rewriting the lock file of the flake in this source, or else through a
+ generated flake, see [Flake inputs from sources](#flake-inputs-from-sources).
+ Defaults to `true`.
+
+Instead of an attribute set, the flake reference can be given as a string.
+
+The flake commands on the deploying machine enable flakes themselves, so
+flakes don't need to be enabled there. Without `fetchOnTarget`, the target
+only needs `nix-store`. Because the copied store paths aren't signed, the
+target user must be allowed to import them, i.e. be `root` or a trusted user,
+and targets using `sudo` aren't supported.
+
+
+### `flake-input`
+
+The flake-input source type delivers an input of a flake, as recorded in the
+flake's lock file, to the target. This makes the lock file of a flake the
+single place that pins, e.g., nixpkgs, for flakes as well as for `NIX_PATH`.
+It works like the `flake` source type, but delivers the selected input
+instead of the flake, so the target path becomes a symlink to the input's
+store path. The inputs that the input's own lock file pins, which evaluating
+the input needs, get delivered as well, with garbage collector roots in
+`.populate/flake-inputs/<name>`. An input without a lock file comes without
+its inputs, because locking it would change it.
+
+Because the target path points at the store path named by the lock file, it
+can be used in `NIX_PATH` as well as by flakes evaluated on the target,
+without network access.
+
+Supported attributes:
+
+* `url` -
+ flake reference of the flake whose input gets delivered, e.g. the absolute
+ path of a directory containing a `flake.nix`. Its lock file is used as is;
+ populating fails if the lock file needs to be updated.
+
+* `name` -
+ name of the input to deliver. Inputs of inputs are separated by `/`,
+ e.g. `stockholm/nixpkgs`, and resolved as the flake's lock file resolves
+ them, so an input that follows another one names the other one.
+
+* `requireInputs` (optional) -
+ boolean that controls whether populating fails if the inputs that the
+ input's own lock file pins can't be delivered, e.g. because the lock file
+ pins something unreachable, or lacks an input its `flake.nix` has. With
+ `false`, populate prints a warning and delivers the input with the inputs
+ Nix locks when it adds what the lock file lacks, or else without its
+ inputs. Evaluating the input as a flake then needs network access.
+ Defaults to `true`.
+
+* `fetchOnTarget` (optional) -
+ boolean that controls whether the target fetches the input itself instead
+ of receiving it from the deploying machine. The target then uses the
+ arguments recorded in the lock file, so inputs that refer to local paths
+ can only be fetched if the target has these paths as well; populating fails
+ otherwise, naming the input. This requires Nix with flake support (Nix 2.4
+ or later) on the target, and works for targets using `sudo`. Defaults to
+ `false`.
+
+* `inputs` (optional) -
+ inputs of the flake in this source to draw from other sources, see
+ [Flake inputs from sources](#flake-inputs-from-sources).
+
+* `materialize` (optional) -
+ boolean that controls whether the target path becomes a writable copy
+ instead of a link into the store. The copy gets made again only if what
+ it derives from changes: the delivered store path, the patches, or which
+ inputs get drawn from which sources. Until then, changes made to it on the
+ target stay. Nix copies it into the store whenever evaluating a flake
+ changed it, which is noticeable for big flakes like nixpkgs. For a flake
+ in a subdirectory, only that subdirectory gets copied. Defaults to
+ `false`.
+
+* `patches` (optional) -
+ list of paths of patches on the deploying machine, which get applied in
+ order with `patch -p1` on the target, in the directory the target path
+ shows. Populating fails if a patch doesn't apply. Defaults to `[]`.
+
+* `rewriteLock` (optional) -
+ boolean that controls whether inputs get drawn from other sources by
+ rewriting the lock file of the flake in this source, or else through a
+ generated flake, see [Flake inputs from sources](#flake-inputs-from-sources).
+ Defaults to `true`.
+
+Without `fetchOnTarget`, the same requirements as for the `flake` source type
+apply.
+
+Example:
+
+```nix
+{
+ nixpkgs.flake-input = {
+ url = toString ./.;
+ name = "nixpkgs";
+ };
+}
+```
+
+
+### `git`
+
+Git sources that will be fetched on the target machine.
+
+Supported attributes:
+
+* `url` -
+ URL of the Git repository that should be fetched.
+
+* `ref` -
+ Branch / tag / commit that should be fetched. A branch or tag gets fetched
+ on every run, so the work tree follows it. A commit, given by its hash or a
+ prefix thereof of at least four hex digits, only gets fetched if the work
+ tree lacks it, unless `fetchAlways` is set. So a branch or tag whose name
+ consists of hex digits only counts as a commit.
+
+* `clean.exclude` -
+ List of patterns that should be excluded from Git cleaning.
+
+* `shallow` (optional)
+ boolean that controls whether only the requested commit ref. should be fetched
+ instead of the whole history, to save disk space and bandwith. Defaults to `false`.
+
+* `reclone` (optional) -
+ boolean that controls whether a work tree populate can't use, see
+ [The target directory](#the-target-directory), gets replaced by a fresh
+ clone, instead of failing. Defaults to `false`.
+
+* `inputs` (optional) -
+ inputs of the flake in this source to draw from other sources, see
+ [Flake inputs from sources](#flake-inputs-from-sources).
+
+* `rewriteLock` (optional) -
+ boolean that controls whether inputs get drawn from other sources by
+ rewriting the lock file of the flake in this source, or else through a
+ generated flake, see [Flake inputs from sources](#flake-inputs-from-sources).
+ Defaults to `true`.
+
+
+### `pass`
+
+The pass source type transfers contents from a local
+[password store](https://www.passwordstore.org/) to the target machine.
+
+Supported attributes:
+
+* `dir` -
+ absolute path to the password store.
+
+* `name` -
+ sub-directory in the password store.
+
+
+### `passage`
+
+The passage source type decrypts files from a local
+[passage store](https://github.com/FiloSottile/passage)
+and transfers them to the target using
+[`rsync`](https://rsync.samba.org/).
+
+Supported attributes:
+
+* `dir` -
+ Path to the passage store.
+ For a partial transfer, this may point to a subdirectory.
+ Example: `~/.passage/store/hosts/MYHOSTNAME`
+
+* `identities_file` (optional) -
+ Path to the identities file.
+ Defaults to `~/.passage/identities`.
+
+* `age` (optional) -
+ Path of the age binary.
+ Defaults to `age` (absolute path gets resolved using `passage`'s search path.)
+
+
+### `pipe`
+
+Executes a local command, capture its stdout, and send that as a file to the
+target machine.
+
+Supported attributes:
+
+* `command` -
+ The (shell) command to run.
+
+### `symlink`
+
+Symlink to create at the target, relative to the target directory.
+This can be used to reference files in other sources.
+
+Supported attributes:
+
+* `target` -
+ Content of the symlink. This is typically a relative path.
+
+* `inputs` (optional) -
+ inputs of the flake in this source to draw from other sources, see
+ [Flake inputs from sources](#flake-inputs-from-sources).
+
+
+## Flake inputs from sources
+
+Sources of type `file`, `flake`, `flake-input`, `git`, and `symlink` can
+declare which inputs of the flake they contain get drawn from other sources,
+in the style of `flake.nix`, where `follows` names a source instead of an
+input. This makes flakes on the target use what populate delivers there,
+much like `NIX_PATH` does for Nix expressions.
+
+```nix
+{
+ nixpkgs.flake-input = {
+ url = toString ./.;
+ name = "nixpkgs";
+ };
+ crate2nix.flake = {
+ url = "github:nix-community/crate2nix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ myconf.file = {
+ path = toString ./.;
+ inputs = {
+ crate2nix.follows = "crate2nix";
+ stockholm.inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+}
+```
+
+Populate rewrites the lock file of the flake in each source that draws
+inputs from other sources, using `nix flake lock --override-input`, so the
+flake in the source itself uses the other sources. `file` and `git` sources
+get their lock file rewritten in place. `flake` and `flake-input` sources
+get delivered as a copy with the rewritten lock file, which gets added to the
+store, unless materialized, so the target path links to that instead of the
+delivered store path, as it does for patched ones. A source drawing an input
+from a source that draws inputs itself uses that source as rewritten, so in
+the example above, myconf's crate2nix uses nixpkgs from the `nixpkgs` source as
+well.
+
+With `rewriteLock = false`, and for `symlink` sources, which point at other
+sources, the source stays as it is, and populate generates a flake in
+`.populate/flakes/<name>` instead, which re-exports the outputs of the flake
+in the source, with those inputs drawn from the other sources. A source
+drawing an input from a source with a generated flake uses that flake.
+krops' `writeDeploy` uses a source's generated flake automatically.
+
+Sources in a Git work tree are referred to as `git+file:` (with submodules if
+there are any), all other sources as `path:`, with symlinks resolved. Nix
+runs Git in a `git+file:` flake, with its configuration and hooks, so
+that's only for work trees whose `.git` directory a `git` source would use,
+see [The target directory](#the-target-directory), and has no hooks. Lock
+files get rewritten or generated on every run, so they match the sources.
+Copies of `flake` and `flake-input` sources get made again only if the
+delivered store path, the patches, or the sources they draw inputs from
+change. Inputs that get drawn from other sources don't get delivered with
+a `flake` or `flake-input` source, and neither does what they pin.
+Generated flakes and copies get removed once their sources no longer need
+them. This requires Nix with flake support (Nix 2.4 or later) on the
+target. Populating fails if a source that draws inputs from other sources
+contains no `flake.nix`, or if its flake has no such input, before anything
+changes. Sources that inputs get drawn from don't need to be flakes, as long
+as the inputs drawing from them are declared with `flake = false`.
+
+
+## Output
+
+What populate prints on standard error is part of its interface: people
+read it, and tests check it. For example:
+
+```
+populating 'host:/var/src/nixpkgs'...
+pruning 'host:/var/src/old'...
+replacing symlink 'host:/var/src/x', which points to '/elsewhere'...
+replacing the Git work tree 'host:/var/src/x', because ...
+```
+
+A target path in a message starts with `user@host:` for remote targets,
+without `user@` if the user is root or not set, and is a plain path for the
+local target. Errors start with `error:` or `krops:`, warnings with
+`warning:`. rsync's itemized changes appear as `%i %n%L %'''b/%'''l`, with
+sizes in units of 1024.
+
+
+## Tests
+
+`tests/` contains NixOS VM tests: one test per source type, one for
+features spanning sources, such as flake inputs from sources, one for edge
+cases of flakes, such as unusual lock files, and one for staying inside the
+target directory, whatever links it contains. After every run, all tests
+check that nothing outside the target directory changed. The tests deliver
+combinations of a source type's settings to the machine populate runs on,
+to a remote target as `root`, and to a remote target using `sudo`, and check
+the result after delivering and after delivering again. They cover every
+combination that a code path tells apart; the exhaustive tests add the
+remaining ones, e.g. drawing inputs combined with each of rsync's options.
+The scenarios whose results depend on the version of rsync also run with
+rsync 3.4.1 and 3.5.0, which `tests/rsync.nix` builds, whatever version
+nixpkgs has. The tests require KVM and nixpkgs with
+`pkgs.testers.runNixOSTest` (23.05 or later), and use `<nixpkgs>` by
+default.
+
+```sh
+nix-build tests # all tests, except the exhaustive ones
+nix-build tests -A flake # the test for one source type
+nix-build tests -A exhaustive # all exhaustive tests
+nix-build tests -A exhaustive.git # the remaining combinations of one test
+nix-build tests -A rsync-3-5-0 # the scenarios depending on rsync's version
+nix-build tests -A smoke # a few scenarios of each kind, in one VM
+nix-build tests -A only --argstr only 'lock-hooks-.*' # scenarios by name
+cargo test # the unit tests, without VMs
+```
+
+[`docs/design.md`](docs/design.md) describes how populate works.
diff --git a/pkgs/populate/docs/design.md b/pkgs/populate/docs/design.md
index 940ab5f..10e6f4b 100644
--- a/pkgs/populate/docs/design.md
+++ b/pkgs/populate/docs/design.md
@@ -36,74 +36,13 @@ 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
+## Command line and sources
-```
-populate [OPTION]... SOURCES [TARGET]
-```
-
-`SOURCES` is the path of a file containing the sources, or `-` for
-standard input. `TARGET` is `[USER@][HOST][:PORT][PATH]`, parsed like
-krops' lib's `mkTarget` parses it: the user up to the first `@`, the host
-up to a `:` or a `/`, the port up to a `/`, and the path from there. What
-it leaves out gets filled in when the program runs, as `mkTarget` would:
-the user from `LOGNAME`, the host with the machine's name, and the path
-with `/var/src`. An empty user means ssh's default.
-
-| Option | Meaning |
-|-----------------|---------------------------------------------------------|
-| `--backup` | Copy the target directory to a backup first. |
-| `--force` | Create the target directory's sentinel if it's missing. |
-| `--prune` | Remove what isn't a source from the target directory. |
-| `--sudo` | Deliver as root, via sudo on the target. |
-| `--ssh-arg ARG` | Pass `ARG` to ssh; repeatable. |
-| `--user USER` | The target's user, instead of `TARGET`'s. |
-| `--host HOST` | The target's host, e.g. one `TARGET` can't express. |
-| `--port PORT` | The port ssh connects to. |
-| `--path PATH` | The target directory. |
-| `--local` | The target is the machine populate runs on. |
-| `--remote` | The target isn't, even if it looks so. |
-| `-h`, `--help` | Print the usage and exit. |
-| `-V`, `--version` | Print populate's version and exit. |
-
-Options take their argument as the next argument or after `=`, and may
-come before, between, or after `SOURCES` and `TARGET`, up to `--`. Exit
-status:
-
-- 0: populate delivered all sources.
-- 1: a check of the sources or the target failed, or delivering did, with
- a message of its own.
-- The exit status of the command that failed, if delivering failed with
- that, like the shell implementation does: e.g. 255 if ssh can't reach
- the target, or 128 if git fetch fails.
-- 2: the command line is wrong, or the sources can't be read, aren't JSON,
- or aren't sources.
-- 129, 130, 143: populate got SIGHUP, SIGINT, or SIGTERM, and stopped.
-
-## Sources
-
-The sources are a JSON object, of sources by name. Each source is an
-object with the field `type`, the source's type, and a field named after
-the type, holding the source's settings:
-
-```json
-{ "nixpkgs": { "type": "git", "git": { "url": "https://...", "ref": "main" } } }
-```
-
-The types and their settings are those of `lib/types/populate.nix`, as
-`evalSource` returns them: with shorthands expanded, e.g. `file =
-"/etc/x"` as `{ "path": "/etc/x" }`, and without settings that are null.
-Settings with defaults may be left out. The source types are
-`derivation`, `file`, `flake`, `flake-input`, `git`, `pass`, `passage`,
-`pipe`, and `symlink`.
-
-Fields and settings populate doesn't know are errors, so a misspelt option
-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 krops' `pkgs.populate`
-writes depends on them. Something producing sources at run time has to build them first.
+[`../README.md`](../README.md) describes populate's command line, the
+sources, and what populate prints, which are its interface. The program
+reads the sources into `spec::Sources`, and parses the target with
+`spec::TargetParts`, completing it with `run::Origin`, the user running
+populate and the machine's name, when it runs.
## Checks
@@ -129,24 +68,6 @@ 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
-
-What populate prints on standard error is part of its interface: people
-read it, and the tests check it. For example:
-
-```
-populating 'host:/var/src/nixpkgs'...
-pruning 'host:/var/src/old'...
-replacing symlink 'host:/var/src/x', which points to '/elsewhere'...
-replacing the Git work tree 'host:/var/src/x', because ...
-```
-
-A target path in a message starts with `user@host:` for remote targets,
-without `user@` if the user is root or not set, and is a plain path for
-the local target. Errors start with `error:` or `krops:`, warnings with
-`warning:`. rsync's itemized changes appear as `%i %n%L %'''b/%'''l`, with sizes in
-units of 1024.
-
## Safety
Populate never changes or removes anything outside the target directory,
@@ -190,6 +111,8 @@ Populate runs rsync, Git, ssh, and Nix as programs, found in `PATH`.
- `nix-build tests -A smoke` runs the smoke test, a few scenarios covering
each source type and each kind of target.
- `nix-build tests` runs all scenarios but the exhaustive ones.
+- krops' tests run populate's scenarios that expect refusals, and those of
+ the smoke test, through krops' `pkgs.populate`, with `tests/builder.nix`.
## Status
diff --git a/pkgs/populate/src/check.rs b/pkgs/populate/src/check.rs
index ebe5dbf..5b73deb 100644
--- a/pkgs/populate/src/check.rs
+++ b/pkgs/populate/src/check.rs
@@ -1,14 +1,14 @@
-//! Checks of the sources and the target, the same as checks.nix makes during
-//! evaluation, so sources from anywhere else get checked as well. Messages
-//! are the same.
+//! Checks of the sources and the target, the same as krops' checks.nix
+//! makes during evaluation, so sources from anywhere else get checked as
+//! well. Messages are the same.
use crate::spec::{Source, Spec};
use serde_json::{Map, Value};
use std::collections::BTreeMap;
-/// The checks in the order checks.nix makes them, the first failing one's
-/// message. The target's host and user, which a command line gives, come
-/// first.
+/// The checks in the order krops' checks.nix makes them, the first failing
+/// one's message. The target's host and user, which a command line gives,
+/// come first.
pub fn spec(spec: &Spec) -> Result<(), String> {
target_host(&spec.target.host, spec.target.user.as_deref())?;
target_path(&spec.target.path)?;
@@ -267,7 +267,7 @@ pub fn inputs(sources: &BTreeMap<String, Source>) -> Result<(), String> {
}
// Depth-first search, with the path of sources on the way, which repeats
// a source if drawing goes round in circles. The message names the
- // whole path, the way into the circle included, like checks.nix.
+ // whole path, the way into the circle included, like krops' checks.nix.
fn visit<'a>(
name: &'a str,
drawn_from: &'a BTreeMap<&str, Vec<String>>,