1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
# Populate's design
Populate delivers sources to a target directory. It consists of:
- 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
Haskell, takes the same command line, prints the same messages, and passes
the same tests.
## Nix side
`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.
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.
## Command line and sources
[`../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
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
this, since krops' targets come from the configuration.
- Source names are portable file names: letters, digits, and `._-`, not
starting with `-`, and neither `.`, `..`, nor `.populate`.
- The target path is absolute, not the root directory, and contains
neither `..` nor control characters.
- Inputs get drawn from sources that exist, not from the source itself,
and not in circles.
- The settings of a source are those `lib/types/populate.nix` defines for
its type, of the kinds it defines, e.g. absolute paths for the paths of
`file`, `pass`, and `pipe` sources. `checks.nix` doesn't repeat this,
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. krops' `checks.nix`
does that during evaluation too, where `pkgs.populate` decides whether the
target is local.
## Safety
Populate never changes or removes anything outside the target directory,
whatever the sources and whatever it finds on the target:
- The checks above keep source names and the target path inside it.
- A symlink at the target path of a source gets replaced, never written
through, and populate's state directory, `.populate`, must not be one.
- Removing a tree makes only its directories writable, and removes what's
below them, not what symlinks point to.
- Hard links to files of a source get broken before rsync writes to them.
- A Git work tree gets reused only if its `.git` is a directory of its
own, with no other configuration than `git clone` and populate set,
and Git runs without hooks and file system monitors.
- Temporary files get created with `mktemp`, and moved into place.
The tests' canaries, files around the target directories and in other
places populate must not touch, check this after every phase.
## Execution
Whether a target is local is what `--local` or `--remote` says, or else
gets decided when the program runs, like krops' lib's `isLocalTarget`
does: it is, if its user is `LOGNAME`, or null if that's empty or unset,
and its host is `localhost` or the machine's name, from `HOSTNAME`, or, if
that's empty or unset, the only line of `/etc/hostname` that is a label.
For a local target, populate runs commands directly.
For a remote target, populate runs commands over ssh, sharing one master
connection per target. The commands are fixed POSIX shell snippets, with
values passed as positional arguments, so nothing gets quoted into them.
The target needs a POSIX shell, coreutils, findutils, rsync, `patch` for
patches, Git for `git` sources, and Nix; NixOS lacks Git. With `sudo`,
the snippets run via `sudo`.
Populate runs rsync, Git, ssh, and Nix as programs, found in `PATH`.
## Development
- `cargo test` in this directory runs the program's unit tests.
- `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
The program delivers all source types, draws inputs from sources, makes
backups, and prunes. It decides what to do itself: the snippets it runs
on the target send back what it needs to know, e.g. what Nix prints as
JSON, where paths resolve to, or store paths. Delivering flake and
flake-input sources is in `src/flake_input.rs`, drawing inputs and
deriving copies in `src/flake.rs`.
|