mirror of
https://cgit.krebsco.de/krops
synced 2024-11-01 00:39:48 +01:00
Compare commits
97 commits
Author | SHA1 | Date | |
---|---|---|---|
|
a6c7ecd8ba | ||
|
cbc475bdf4 | ||
|
1c524b6727 | ||
|
9c0d53cf44 | ||
|
59aa5d0e41 | ||
|
3ebbfc6261 | ||
|
e5c13343a6 | ||
|
6ee1d00b92 | ||
|
625bd446dd | ||
|
3aa04be96f | ||
|
117b0b32cd | ||
|
9c49e9aa24 | ||
|
89e5e67659 | ||
|
824aa36b2a | ||
|
13ae434b14 | ||
|
9fc8cbf8e8 | ||
|
05f0d3b5c1 | ||
|
6ef8900af4 | ||
|
53eda9cafe | ||
|
bdce88820b | ||
|
0fc8f1b2a6 | ||
|
d80cb74c74 | ||
|
c1b24328c4 | ||
|
a3bda5c49b | ||
|
b78e4d5a92 | ||
|
cccebf3ff7 | ||
|
2ea0cdb99d | ||
|
438d3f8738 | ||
|
9eb2c2d0d6 | ||
|
c2fa48550f | ||
|
efe400d87c | ||
|
804c79a14d | ||
|
73f0cdcb0b | ||
|
790e31fead | ||
|
c207e1f71b | ||
b83fd5c682 | |||
|
5ea125514e | ||
|
54eb1c89cf | ||
|
3e731035ed | ||
|
bdf56191e2 | ||
|
56a066d470 | ||
|
44e8dd5cea | ||
|
1eb67a9b78 | ||
|
486300fb35 | ||
|
81c4885124 | ||
|
67132ed53f | ||
|
be3fa4608b | ||
|
476fb97dc9 | ||
|
55aa2c77ce | ||
|
8a8b2cf861 | ||
|
2cafddd78d | ||
|
9c16ab1ce1 | ||
|
ed9fc66582 | ||
|
8161ec7367 | ||
|
d51f353cb3 | ||
|
56d4dc28b3 | ||
|
f1b7112ac3 | ||
|
ea7e0c3a35 | ||
|
402c9cac25 | ||
|
fce5826802 | ||
|
fff9b24ec9 | ||
|
f2f8cbf1af | ||
|
01b82ecaf3 | ||
|
53dfb30af3 | ||
|
70fa39607f | ||
|
8de797dae0 | ||
|
cd21575333 | ||
|
2e94e6eb24 | ||
|
2dc1725309 | ||
|
3d59510ac2 | ||
|
6d1c35ea2c | ||
|
8f44460003 | ||
|
14a54637ce | ||
|
79aa2c6a88 | ||
|
42e8085b95 | ||
|
1921a71a75 | ||
|
f2348bfbc2 | ||
|
10fd67a6a2 | ||
|
ee41207df1 | ||
|
219e5aa008 | ||
|
ad4c3cad0a | ||
|
5dd80e884a | ||
|
ba211472ef | ||
|
5b8fb8dc0e | ||
|
1c0601c56a | ||
|
61b5ef3b8e | ||
|
943c6567fc | ||
|
78830f5ea7 | ||
|
21a894dc6f | ||
|
140bdfdf6c | ||
|
eb68146cc4 | ||
|
4017c60485 | ||
|
6f49342b2d | ||
|
d92cc88a3c | ||
|
4ce5dae7bc | ||
|
ce37b2a9c2 | ||
|
170c46d53c |
10 changed files with 828 additions and 94 deletions
13
LICENSE
Normal file
13
LICENSE
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
Version 2, December 2004
|
||||||
|
|
||||||
|
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim or modified
|
||||||
|
copies of this license document, and changing it is allowed as long
|
||||||
|
as the name is changed.
|
||||||
|
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. You just DO WHAT THE FUCK YOU WANT TO.
|
311
README.md
311
README.md
|
@ -1,24 +1,27 @@
|
||||||
# krops (krebs ops)
|
# krops (krebs operations)
|
||||||
|
|
||||||
|
krops is a lightweight toolkit to deploy NixOS systems, remotely or locally.
|
||||||
|
|
||||||
krops is a lightweigt toolkit to deploy NixOS systems, remotely or locally.
|
|
||||||
|
|
||||||
## Some Features
|
## Some Features
|
||||||
|
|
||||||
- store your secrets in [password store](https://www.passwordstore.org/)
|
- store your secrets in [password store](https://www.passwordstore.org/)
|
||||||
- build your system remotely
|
or [passage](https://github.com/FiloSottile/passage)
|
||||||
|
- build your systems remotely
|
||||||
- minimal overhead (it's basically just `nixos-rebuild switch`!)
|
- minimal overhead (it's basically just `nixos-rebuild switch`!)
|
||||||
- run from custom nixpkgs branch/checkout/fork
|
- run from custom nixpkgs branch/checkout/fork
|
||||||
|
|
||||||
|
|
||||||
## Minimal Example
|
## Minimal Example
|
||||||
|
|
||||||
Create a file named `krops.nix` (name doesn't matter) with following content:
|
Create a file named `krops.nix` (name doesn't matter) with following content:
|
||||||
|
|
||||||
```
|
```nix
|
||||||
let
|
let
|
||||||
krops = (import <nixpkgs> {}).fetchgit {
|
krops = (import <nixpkgs> {}).fetchgit {
|
||||||
url = https://cgit.krebsco.de/krops/;
|
url = https://cgit.krebsco.de/krops/;
|
||||||
rev = "3022582ade8049e6ccf18f358cedb996d6716945";
|
rev = "v1.25.0";
|
||||||
sha256 = "0k3zhv2830z4bljcdvf6ciwjihk2zzcn9y23p49c6sba5hbsd6jb";
|
sha256 = "07mg3iaqjf1w49vmwfchi7b1w55bh7rvsbgicp2m47gnj9alwdb6";
|
||||||
};
|
};
|
||||||
|
|
||||||
lib = import "${krops}/lib";
|
lib = import "${krops}/lib";
|
||||||
|
@ -26,9 +29,9 @@ let
|
||||||
|
|
||||||
source = lib.evalSource [{
|
source = lib.evalSource [{
|
||||||
nixpkgs.git = {
|
nixpkgs.git = {
|
||||||
|
clean.exclude = ["/.version-suffix"];
|
||||||
ref = "4b4bbce199d3b3a8001ee93495604289b01aaad3";
|
ref = "4b4bbce199d3b3a8001ee93495604289b01aaad3";
|
||||||
url = https://github.com/NixOS/nixpkgs;
|
url = https://github.com/NixOS/nixpkgs;
|
||||||
|
|
||||||
};
|
};
|
||||||
nixos-config.file = toString (pkgs.writeText "nixos-config" ''
|
nixos-config.file = toString (pkgs.writeText "nixos-config" ''
|
||||||
{ pkgs, ... }: {
|
{ pkgs, ... }: {
|
||||||
|
@ -50,18 +53,306 @@ in
|
||||||
```
|
```
|
||||||
|
|
||||||
and run `$(nix-build --no-out-link krops.nix)` to deploy the target machine.
|
and run `$(nix-build --no-out-link krops.nix)` to deploy the target machine.
|
||||||
|
krops exports some funtions under `krops.` namely:
|
||||||
|
|
||||||
Under the hood, this will make the sources available on the target machine
|
## writeDeploy
|
||||||
|
|
||||||
|
This will make the sources available on the target machine
|
||||||
below `/var/src`, and execute `nixos-rebuild switch -I /var/src`.
|
below `/var/src`, and execute `nixos-rebuild switch -I /var/src`.
|
||||||
|
|
||||||
|
|
||||||
|
### `target`
|
||||||
|
|
||||||
|
The `target` attribute to `writeDeploy` can either be a string or an attribute
|
||||||
|
set, specifying where to make the sources available, as well as where to run
|
||||||
|
the deployment.
|
||||||
|
|
||||||
|
If specified as string, the format could be described as:
|
||||||
|
```
|
||||||
|
[[USER]@]HOST[:PORT][/SOME/PATH]
|
||||||
|
```
|
||||||
|
|
||||||
|
Portions in square brackets are optional.
|
||||||
|
|
||||||
|
If the `USER` is the empty string, as in e.g. `@somehost`, then the username
|
||||||
|
will be obtained by ssh from its configuration files.
|
||||||
|
|
||||||
|
If the `target` attribute is an attribute set, then it can specify the
|
||||||
|
attributes `extraOptions`, `host`, `path`, `port`, `sudo`, and `user`.
|
||||||
|
The `extraOptions` is a list of strings that get passed to ssh as additional
|
||||||
|
arguments. The `sudo` attribute is a boolean and if set to true, then it's
|
||||||
|
possible to to deploy to targets that disallow sshing in as root, but allow
|
||||||
|
(preferably passwordless) sudo.
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
pkgs.krops.writeDeploy "deploy" {
|
||||||
|
source = /* ... */;
|
||||||
|
target = lib.mkTarget "user@host/path" // {
|
||||||
|
extraOptions = [
|
||||||
|
"-o" "LogLevel=DEBUG"
|
||||||
|
];
|
||||||
|
sudo = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
For more details about the `target` attribute, please check the `mkTarget`
|
||||||
|
function in [lib/default.nix](lib/default.nix).
|
||||||
|
|
||||||
|
### `backup` (optional, defaults to false)
|
||||||
|
|
||||||
|
Backup all paths specified in source before syncing new sources.
|
||||||
|
|
||||||
|
### `buildTarget` (optional)
|
||||||
|
|
||||||
|
If set the evaluation and build of the system will be executed on this host.
|
||||||
|
`buildTarget` takes the same arguments as target.
|
||||||
|
Sources will be synced to both `buildTarget` and `target`.
|
||||||
|
Built packages will be uploaded from the `buildTarget` to `target` directly
|
||||||
|
This requires the building machine to have ssh access to the target.
|
||||||
|
To build the system on the same machine, that runs the krops command,
|
||||||
|
set up a local ssh service and set the build host to localhost.
|
||||||
|
|
||||||
|
### `crossDeploy` (optional, defaults to false)
|
||||||
|
|
||||||
|
Use this option if target host architecture is not the same as the build host
|
||||||
|
architecture as set by `buildHost` i.e. deploying to aarch64 from a x86_64
|
||||||
|
machine. Setting this option will disable building & running nix in the wrong
|
||||||
|
architecture when running `nixos-rebuild` on the deploying machine. It is
|
||||||
|
required to set `nixpkgs.localSystem.system` in the NixOS configuration to the
|
||||||
|
architecture of the target host. This option is only useful if the build host
|
||||||
|
also has remote builders that are capable of producing artifacts for the deploy
|
||||||
|
architecture.
|
||||||
|
|
||||||
|
### `fast` (optional, defaults to false)
|
||||||
|
|
||||||
|
Run `nixos-rebuild` immediately without building the system in a dedicated `nix
|
||||||
|
build` step.
|
||||||
|
|
||||||
|
### `force` (optional, defaults to false)
|
||||||
|
|
||||||
|
Create the sentinel file (`/var/src/.populate`) before syncing the new source.
|
||||||
|
|
||||||
|
### `operation` (optional, defaults to "switch")
|
||||||
|
|
||||||
|
Specifies which `nixos-rebuild` operation to perform.
|
||||||
|
|
||||||
|
### `useNixOutputMonitor` (optional, defaults to `"opportunistic"`)
|
||||||
|
|
||||||
|
Specifies when to pipe `nixos-rebuild`'s output to
|
||||||
|
[nom](https://github.com/maralorn/nix-output-monitor).
|
||||||
|
|
||||||
|
Supported values:
|
||||||
|
|
||||||
|
* `"opportunistic"` (default) -
|
||||||
|
Use `nom` only if it is present on the target machine.
|
||||||
|
|
||||||
|
* `"optimistic"` -
|
||||||
|
Use `nom`, assuming it is present on the target machine.
|
||||||
|
|
||||||
|
* `"pessimistic"` -
|
||||||
|
Use `nom` via `nix-shell` on the target machine.
|
||||||
|
|
||||||
|
* `true` -
|
||||||
|
Use `nom`.
|
||||||
|
If it is not present on the target machine, then use it via `nix-shell`.
|
||||||
|
|
||||||
|
* `false` -
|
||||||
|
Don't use `nom`
|
||||||
|
|
||||||
|
## writeTest
|
||||||
|
|
||||||
|
Very similiar to writeDeploy, but just builds the system on the target without
|
||||||
|
activating it.
|
||||||
|
|
||||||
|
This basically makes the sources available on the target machine
|
||||||
|
below `/var/src`, and executes `NIX_PATH=/var/src nix-build -A system '<nixpkgs/nixos>'`.
|
||||||
|
|
||||||
|
### `target`
|
||||||
|
|
||||||
|
[see `writeDeploy`](#writeDeploy)
|
||||||
|
|
||||||
|
### `backup` (optional, defaults to false)
|
||||||
|
|
||||||
|
[see `writeDeploy`](#writeDeploy)
|
||||||
|
|
||||||
|
### `force` (optional, defaults to false)
|
||||||
|
|
||||||
|
[see `writeDeploy`](#writeDeploy)
|
||||||
|
|
||||||
|
### `trace` (optional, defaults to false)
|
||||||
|
|
||||||
|
run nix-build with `--show-trace`
|
||||||
|
|
||||||
|
## writeCommand
|
||||||
|
|
||||||
|
This can be used to run other commands than `nixos-rebuild` or pre/post build hooks.
|
||||||
|
|
||||||
|
### `command`
|
||||||
|
|
||||||
|
A function which takes the targetPath as an attribute.
|
||||||
|
Example to activate/deactivate a swapfile before/after build:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
pkgs.krops.writeCommand "deploy-with-swap" {
|
||||||
|
source = source;
|
||||||
|
target = "root@YOUR_IP_ADDRESS_OR_HOST_NAME_HERE";
|
||||||
|
command = targetPath: ''
|
||||||
|
swapon /var/swapfile
|
||||||
|
nixos-rebuild -I ${targetPath} switch
|
||||||
|
swapoff /var/swapfile
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target`
|
||||||
|
|
||||||
|
[see `writeDeploy`](#writeDeploy)
|
||||||
|
|
||||||
|
### `backup` (optional, defaults to false)
|
||||||
|
|
||||||
|
[see `writeDeploy`](#writeDeploy)
|
||||||
|
|
||||||
|
### `force` (optional, defaults to false)
|
||||||
|
|
||||||
|
[see `writeDeploy`](#writeDeploy)
|
||||||
|
|
||||||
|
### `allocateTTY` (optional, defaults to false)
|
||||||
|
|
||||||
|
whether the ssh session should do a pseudo-terminal allocation.
|
||||||
|
sets `-t` on the ssh command.
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
|
|
||||||
|
### `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.
|
||||||
|
|
||||||
|
* `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`.
|
||||||
|
|
||||||
|
|
||||||
|
### `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.
|
||||||
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [In-depth example](http://tech.ingolf-wagner.de/nixos/krops/) by [Ingolf Wagner](https://ingolf-wagner.de/)
|
- [In-depth example](http://tech.ingolf-wagner.de/nixos/krops/) by [Ingolf Wagner](https://ingolf-wagner.de/)
|
||||||
|
|
||||||
|
|
||||||
## Communication
|
## Communication
|
||||||
|
|
||||||
Comments, questions, pull-requests, etc. are very welcome, and can be directed
|
Comments, questions, pull-requests and patches, etc. are very welcome, and can be directed
|
||||||
at:
|
at:
|
||||||
|
|
||||||
- IRC: #krebs at freenode
|
- IRC: #krebs at hackint
|
||||||
- Mail: [spam@krebsco.de](mailto:spam@krebsco.de)
|
- Mail: [spam@krebsco.de](mailto:spam@krebsco.de)
|
||||||
|
- Github: https://github.com/krebs/krops/
|
||||||
|
|
5
ci.nix
5
ci.nix
|
@ -5,7 +5,7 @@ let
|
||||||
pkgs = import "${krops}/pkgs" {};
|
pkgs = import "${krops}/pkgs" {};
|
||||||
|
|
||||||
source = lib.evalSource [{
|
source = lib.evalSource [{
|
||||||
nixos-config.file = toString (pkgs.writeText "nixos-config" ''
|
nixos-config.file = pkgs.writeText "nixos-config" ''
|
||||||
{ pkgs, ... }: {
|
{ pkgs, ... }: {
|
||||||
|
|
||||||
fileSystems."/" = { device = "/dev/sda1"; };
|
fileSystems."/" = { device = "/dev/sda1"; };
|
||||||
|
@ -13,7 +13,8 @@ let
|
||||||
services.openssh.enable = true;
|
services.openssh.enable = true;
|
||||||
environment.systemPackages = [ pkgs.git ];
|
environment.systemPackages = [ pkgs.git ];
|
||||||
}
|
}
|
||||||
'');
|
'';
|
||||||
|
nixpkgs.symlink = toString <nixpkgs>;
|
||||||
}];
|
}];
|
||||||
in {
|
in {
|
||||||
test = pkgs.krops.writeTest "test" {
|
test = pkgs.krops.writeTest "test" {
|
||||||
|
|
27
flake.lock
Normal file
27
flake.lock
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1689940971,
|
||||||
|
"narHash": "sha256-397xShPnFqPC59Bmpo3lS+/Aw0yoDRMACGo1+h2VJMo=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9ca785644d067445a4aa749902b29ccef61f7476",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
29
flake.nix
Normal file
29
flake.nix
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
description = "krops - krebs operations";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, ... }:
|
||||||
|
let
|
||||||
|
supportedSystems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"i686-linux"
|
||||||
|
"aarch64-linux"
|
||||||
|
"riscv64-linux"
|
||||||
|
];
|
||||||
|
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
lib = forAllSystems (system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
krops = pkgs.callPackage ./pkgs/krops {};
|
||||||
|
populate = pkgs.callPackage ./pkgs/populate {};
|
||||||
|
in {
|
||||||
|
inherit populate;
|
||||||
|
inherit (krops) rebuild runShell withNixOutputMonitor writeCommand writeDeploy writeTest;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
|
@ -16,6 +16,8 @@ let {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
sanitize = x: lib.getAttr (lib.typeOf x) {
|
sanitize = x: lib.getAttr (lib.typeOf x) {
|
||||||
|
bool = x;
|
||||||
|
list = map sanitize x;
|
||||||
set = lib.mapAttrs
|
set = lib.mapAttrs
|
||||||
(lib.const sanitize)
|
(lib.const sanitize)
|
||||||
(lib.filterAttrs
|
(lib.filterAttrs
|
||||||
|
@ -26,13 +28,23 @@ let {
|
||||||
# This function's return value can be used as pkgs.populate input.
|
# This function's return value can be used as pkgs.populate input.
|
||||||
source: sanitize (eval source).config.source;
|
source: sanitize (eval source).config.source;
|
||||||
|
|
||||||
getHostName = let
|
maybeHostName = default: let
|
||||||
# We're parsing /etc/hostname here because reading
|
# We're parsing /etc/hostname here because reading
|
||||||
# /proc/sys/kernel/hostname yields ""
|
# /proc/sys/kernel/hostname yields ""
|
||||||
y = lib.filter lib.types.label.check (lib.splitString "\n" (lib.readFile /etc/hostname));
|
path = "/etc/hostname";
|
||||||
|
lines = lib.splitString "\n" (lib.readFile path);
|
||||||
|
hostNames = lib.filter lib.types.label.check lines;
|
||||||
in
|
in
|
||||||
if lib.length y != 1 then throw "malformed /etc/hostname" else
|
if lib.pathExists path then
|
||||||
lib.elemAt y 0;
|
if lib.length hostNames == 1 then
|
||||||
|
lib.head hostNames
|
||||||
|
else
|
||||||
|
lib.trace "malformed ${path}" default
|
||||||
|
else
|
||||||
|
default;
|
||||||
|
|
||||||
|
firstWord = s:
|
||||||
|
lib.head (lib.match "^([^[:space:]]*).*" s);
|
||||||
|
|
||||||
isLocalTarget = let
|
isLocalTarget = let
|
||||||
origin = lib.mkTarget "";
|
origin = lib.mkTarget "";
|
||||||
|
@ -41,15 +53,26 @@ let {
|
||||||
lib.elem target.host [origin.host "localhost"];
|
lib.elem target.host [origin.host "localhost"];
|
||||||
|
|
||||||
mkTarget = s: let
|
mkTarget = s: let
|
||||||
default = defVal: val: if val != null then val else defVal;
|
parse = lib.match "(([^@]*)@)?(([^:/]+))?(:([^/]+))?(/.*)?" s;
|
||||||
parse = lib.match "(([^@]+)@)?(([^:/]+))?(:([^/]+))?(/.*)?" s;
|
|
||||||
elemAt' = xs: i: if lib.length xs > i then lib.elemAt xs i else null;
|
elemAt' = xs: i: if lib.length xs > i then lib.elemAt xs i else null;
|
||||||
|
filterNull = lib.filterAttrs (n: v: v != null);
|
||||||
in {
|
in {
|
||||||
user = default (lib.getEnv "LOGNAME") (elemAt' parse 1);
|
user = lib.maybeEnv "LOGNAME" null;
|
||||||
host = default (lib.maybeEnv "HOSTNAME" lib.getHostName) (elemAt' parse 3);
|
host = lib.maybeEnv "HOSTNAME" (lib.maybeHostName "localhost");
|
||||||
port = default "22" /* "ssh"? */ (elemAt' parse 5);
|
port = null;
|
||||||
path = default "/var/src" /* no default? */ (elemAt' parse 6);
|
path = "/var/src";
|
||||||
};
|
sudo = false;
|
||||||
|
extraOptions = [];
|
||||||
|
} // (if lib.isString s then filterNull {
|
||||||
|
user = elemAt' parse 1;
|
||||||
|
host = elemAt' parse 3;
|
||||||
|
port = elemAt' parse 5;
|
||||||
|
path = elemAt' parse 6;
|
||||||
|
} else s);
|
||||||
|
|
||||||
|
mkUserPortSSHOpts = target:
|
||||||
|
(lib.optionals (target.user != null) ["-l" target.user]) ++
|
||||||
|
(lib.optionals (target.port != null) ["-p" target.port]);
|
||||||
|
|
||||||
shell = let
|
shell = let
|
||||||
isSafeChar = lib.testString "[-+./0-9:=A-Z_a-z]";
|
isSafeChar = lib.testString "[-+./0-9:=A-Z_a-z]";
|
||||||
|
|
|
@ -11,13 +11,25 @@
|
||||||
else throw "cannot determine type";
|
else throw "cannot determine type";
|
||||||
type = lib.types.enum known-types;
|
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 {
|
file = lib.mkOption {
|
||||||
apply = x:
|
apply = x:
|
||||||
if lib.types.absolute-pathname.check x
|
if lib.types.absolute-pathname.check x || lib.types.package.check x
|
||||||
then { path = x; }
|
then { path = x; }
|
||||||
else x;
|
else x;
|
||||||
default = null;
|
default = null;
|
||||||
type = lib.types.nullOr (lib.types.either lib.types.absolute-pathname source-types.file);
|
type = lib.types.nullOr (lib.types.oneOf [
|
||||||
|
lib.types.absolute-pathname
|
||||||
|
lib.types.package
|
||||||
|
source-types.file
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
git = lib.mkOption {
|
git = lib.mkOption {
|
||||||
default = null;
|
default = null;
|
||||||
|
@ -27,6 +39,17 @@
|
||||||
default = null;
|
default = null;
|
||||||
type = lib.types.nullOr source-types.pass;
|
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 {
|
pipe = lib.mkOption {
|
||||||
apply = x:
|
apply = x:
|
||||||
if lib.types.absolute-pathname.check x
|
if lib.types.absolute-pathname.check x
|
||||||
|
@ -46,22 +69,96 @@
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 = {
|
source-types = {
|
||||||
|
derivation = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
text = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
file = lib.types.submodule {
|
file = lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
path = lib.mkOption {
|
path = lib.mkOption {
|
||||||
type = lib.types.absolute-pathname;
|
type = lib.types.absolute-pathname;
|
||||||
};
|
};
|
||||||
|
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;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
git = lib.types.submodule {
|
git = lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
|
clean = {
|
||||||
|
exclude = lib.mkOption {
|
||||||
|
default = [];
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
fetchAlways = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
ref = lib.mkOption {
|
ref = lib.mkOption {
|
||||||
type = lib.types.str; # TODO lib.types.git.ref
|
type = lib.types.str; # TODO lib.types.git.ref
|
||||||
};
|
};
|
||||||
url = lib.mkOption {
|
url = lib.mkOption {
|
||||||
type = lib.types.str; # TODO lib.types.git.url
|
type = lib.types.str; # TODO lib.types.git.url
|
||||||
};
|
};
|
||||||
|
shallow = lib.mkOption {
|
||||||
|
default = false;
|
||||||
|
type = lib.types.bool;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
pass = lib.types.submodule {
|
pass = lib.types.submodule {
|
||||||
|
@ -74,6 +171,21 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
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 {
|
pipe = lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
command = lib.mkOption {
|
command = lib.mkOption {
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
{ overlays ? [], ... }@args:
|
{ overlays ? [], ... }@args:
|
||||||
|
|
||||||
let
|
|
||||||
nix-writers = builtins.fetchGit {
|
|
||||||
url = https://cgit.krebsco.de/nix-writers/;
|
|
||||||
rev = "c27a9416e8ee04d708b11b48f8cf1a055c0cc079";
|
|
||||||
};
|
|
||||||
in
|
|
||||||
|
|
||||||
import <nixpkgs> (args // {
|
import <nixpkgs> (args // {
|
||||||
overlays = overlays ++ [
|
overlays = [
|
||||||
(import ./overlay.nix)
|
(import ./overlay.nix)
|
||||||
(import "${nix-writers}/pkgs")
|
] ++ overlays;
|
||||||
];
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,43 +2,145 @@ let
|
||||||
lib = import ../../lib;
|
lib = import ../../lib;
|
||||||
in
|
in
|
||||||
|
|
||||||
{ exec, nix, openssh, populate, writeDash }: rec {
|
{ nix, openssh, populate, writers }: rec {
|
||||||
|
|
||||||
rebuild = target:
|
rebuild = {
|
||||||
exec "rebuild.${target.host}" rec {
|
useNixOutputMonitor
|
||||||
filename = "${openssh}/bin/ssh";
|
}:
|
||||||
argv = [
|
args: target:
|
||||||
filename
|
runShell target {}
|
||||||
"-l" target.user
|
(withNixOutputMonitor target useNixOutputMonitor /* sh */ ''
|
||||||
"-p" target.port
|
NIX_PATH=${lib.escapeShellArg target.path} \
|
||||||
target.host
|
nixos-rebuild ${lib.escapeShellArgs args}
|
||||||
"nixos-rebuild switch -I ${lib.escapeShellArg target.path}"
|
'');
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
writeDeploy = name: { force ? false, source, target }: let
|
runShell = target: {
|
||||||
|
allocateTTY ? false
|
||||||
|
}: command:
|
||||||
|
let
|
||||||
|
command' = /* sh */ ''
|
||||||
|
${lib.optionalString target.sudo "sudo"} \
|
||||||
|
/bin/sh -c ${lib.escapeShellArg command}
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
if lib.isLocalTarget target
|
||||||
|
then command'
|
||||||
|
else
|
||||||
|
writers.writeDash "krops.${target.host}.${lib.firstWord command}" ''
|
||||||
|
exec ${openssh}/bin/ssh ${lib.escapeShellArgs (lib.flatten [
|
||||||
|
(lib.mkUserPortSSHOpts target)
|
||||||
|
(if allocateTTY then "-t" else "-T")
|
||||||
|
target.extraOptions
|
||||||
|
target.host
|
||||||
|
command'
|
||||||
|
])}
|
||||||
|
'';
|
||||||
|
|
||||||
|
withNixOutputMonitor = target: mode_: command: let
|
||||||
|
mode =
|
||||||
|
lib.getAttr (lib.typeOf mode_) {
|
||||||
|
bool = lib.toJSON mode_;
|
||||||
|
string = mode_;
|
||||||
|
};
|
||||||
|
in /* sh */ ''
|
||||||
|
printf '# use nix-output-monitor: %s\n' ${lib.escapeShellArg mode} >&2
|
||||||
|
${lib.getAttr mode rec {
|
||||||
|
opportunistic = /* sh */ ''
|
||||||
|
if command -v nom >/dev/null; then
|
||||||
|
${optimistic}
|
||||||
|
else
|
||||||
|
${false}
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
optimistic = /* sh */ ''
|
||||||
|
(${command}) 2>&1 | nom
|
||||||
|
'';
|
||||||
|
pessimistic = /* sh */ ''
|
||||||
|
NIX_PATH=${lib.escapeShellArg target.path} \
|
||||||
|
nix-shell -p nix-output-monitor --run ${lib.escapeShellArg optimistic}
|
||||||
|
'';
|
||||||
|
true = /* sh */ ''
|
||||||
|
if command -v nom >/dev/null; then
|
||||||
|
${optimistic}
|
||||||
|
else
|
||||||
|
${pessimistic}
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
false = command;
|
||||||
|
}}
|
||||||
|
'';
|
||||||
|
|
||||||
|
writeCommand = name: {
|
||||||
|
command ? (targetPath: "echo ${targetPath}"),
|
||||||
|
backup ? false,
|
||||||
|
force ? false,
|
||||||
|
allocateTTY ? false,
|
||||||
|
source,
|
||||||
|
target
|
||||||
|
}: let
|
||||||
target' = lib.mkTarget target;
|
target' = lib.mkTarget target;
|
||||||
in
|
in
|
||||||
writeDash name ''
|
writers.writeDash name ''
|
||||||
set -efu
|
set -efu
|
||||||
${populate { inherit force source; target = target'; }}
|
${populate { inherit backup force source; target = target'; }}
|
||||||
${rebuild target'}
|
${runShell target' { inherit allocateTTY; } (command target'.path)}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
writeTest = name: { force ? false, source, target }: let
|
writeDeploy = name: {
|
||||||
|
backup ? false,
|
||||||
|
buildTarget ? null,
|
||||||
|
crossDeploy ? false,
|
||||||
|
fast ? null,
|
||||||
|
force ? false,
|
||||||
|
operation ? "switch",
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
useNixOutputMonitor ? "opportunistic"
|
||||||
|
}: let
|
||||||
|
buildTarget' =
|
||||||
|
if buildTarget == null
|
||||||
|
then target'
|
||||||
|
else lib.mkTarget buildTarget;
|
||||||
|
target' = lib.mkTarget target;
|
||||||
|
in
|
||||||
|
lib.traceIf (fast != null) "writeDeploy: it's now always fast, setting the `fast` attribute is deprecated and will be removed in future" (
|
||||||
|
writers.writeDash name ''
|
||||||
|
set -efu
|
||||||
|
${lib.optionalString (buildTarget' != target')
|
||||||
|
(populate { inherit backup force source; target = buildTarget'; })}
|
||||||
|
${populate { inherit backup force source; target = target'; }}
|
||||||
|
${rebuild { inherit useNixOutputMonitor; } ([
|
||||||
|
operation
|
||||||
|
] ++ lib.optionals crossDeploy [
|
||||||
|
"--no-build-nix"
|
||||||
|
] ++ lib.optionals (buildTarget' != target') [
|
||||||
|
"--build-host" "${buildTarget'.user}@${buildTarget'.host}"
|
||||||
|
"--target-host" "${target'.user}@${target'.host}"
|
||||||
|
] ++ lib.optionals target'.sudo [
|
||||||
|
"--use-remote-sudo"
|
||||||
|
]) buildTarget'}
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
writeTest = name: {
|
||||||
|
backup ? false,
|
||||||
|
force ? false,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
trace ? false
|
||||||
|
}: let
|
||||||
target' = lib.mkTarget target;
|
target' = lib.mkTarget target;
|
||||||
in
|
in
|
||||||
assert lib.isLocalTarget target';
|
assert lib.isLocalTarget target';
|
||||||
writeDash name ''
|
writers.writeDash name ''
|
||||||
set -efu
|
set -efu
|
||||||
${populate { inherit force source; target = target'; }}
|
${populate { inherit backup force source; target = target'; }} >&2
|
||||||
|
NIX_PATH=${lib.escapeShellArg target'.path} \
|
||||||
${nix}/bin/nix-build \
|
${nix}/bin/nix-build \
|
||||||
-A system \
|
-A system \
|
||||||
-I ${target'.path} \
|
|
||||||
--keep-going \
|
--keep-going \
|
||||||
--no-out-link \
|
--no-out-link \
|
||||||
--show-trace \
|
${lib.optionalString trace "--show-trace"} \
|
||||||
'<nixpkgs/nixos>'
|
'<nixpkgs/nixos>'
|
||||||
'';
|
'';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
with import ../../lib;
|
with import ../../lib;
|
||||||
with shell;
|
with shell;
|
||||||
|
|
||||||
{ coreutils, dash, findutils, git, jq, openssh, pass, rsync, writeDash }:
|
{ coreutils, dash, findutils, git, jq, openssh, pass, passage, rsync, writers }:
|
||||||
|
|
||||||
let
|
let
|
||||||
check = { force, target }: let
|
check = { force, target }: let
|
||||||
sentinelFile = "${target.path}/.populate";
|
sentinelFile = "${target.path}/.populate";
|
||||||
in shell' target /* sh */ ''
|
in runShell target /* sh */ ''
|
||||||
${optionalString force /* sh */ ''
|
${optionalString force /* sh */ ''
|
||||||
mkdir -vp ${quote (dirOf sentinelFile)}
|
mkdir -vp ${quote (dirOf sentinelFile)} >&2
|
||||||
touch ${quote sentinelFile}
|
touch ${quote sentinelFile}
|
||||||
''}
|
''}
|
||||||
if ! test -f ${quote sentinelFile}; then
|
if ! test -e ${quote sentinelFile}; then
|
||||||
>&2 printf 'error: missing sentinel file: %s\n' ${quote (
|
>&2 printf 'error: missing sentinel file: %s\n' ${quote (
|
||||||
optionalString (!isLocalTarget target) "${target.host}:" +
|
optionalString (!isLocalTarget target) "${target.host}:" +
|
||||||
sentinelFile
|
sentinelFile
|
||||||
|
@ -20,11 +20,60 @@ let
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
pop.file = target: source: rsync' target (quote source.path);
|
do-backup = { target }: let
|
||||||
|
sentinelFile = "${target.path}/.populate";
|
||||||
|
in
|
||||||
|
runShell target /* sh */ ''
|
||||||
|
if ! test -d ${quote sentinelFile}; then
|
||||||
|
>&2 printf 'error" sentinel file is not a directory: %s\n' ${quote (
|
||||||
|
optionalString (!isLocalTarget target) "${target.host}:" +
|
||||||
|
sentinelFile
|
||||||
|
)}
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rsync >&2 \
|
||||||
|
-aAXF \
|
||||||
|
--delete \
|
||||||
|
--exclude /.populate \
|
||||||
|
--link-dest=${quote target.path} \
|
||||||
|
${target.path}/ \
|
||||||
|
${target.path}/.populate/backup/
|
||||||
|
'';
|
||||||
|
|
||||||
pop.git = target: source: shell' target /* sh */ ''
|
pop.derivation = target: source: runShell target /* sh */ ''
|
||||||
|
nix-build -E ${quote source.text} -o ${quote target.path} >&2
|
||||||
|
'';
|
||||||
|
|
||||||
|
pop.file = target: source: let
|
||||||
|
config = rsyncDefaultConfig // derivedConfig // sourceConfig;
|
||||||
|
derivedConfig = {
|
||||||
|
useChecksum =
|
||||||
|
if isStorePath source.path
|
||||||
|
then true
|
||||||
|
else rsyncDefaultConfig.useChecksum;
|
||||||
|
};
|
||||||
|
sourceConfig =
|
||||||
|
filterAttrs (name: _: elem name (attrNames rsyncDefaultConfig)) source;
|
||||||
|
sourcePath =
|
||||||
|
if isStorePath source.path
|
||||||
|
then quote (toString source.path)
|
||||||
|
else quote source.path;
|
||||||
|
in
|
||||||
|
rsync' target config sourcePath;
|
||||||
|
|
||||||
|
pop.git = target: source: runShell target /* sh */ ''
|
||||||
|
set -efu
|
||||||
|
# Remove target path if it doesn't look like a git worktree.
|
||||||
|
# This can happen e.g. when it had a different type earlier.
|
||||||
|
if ! test -e ${quote target.path}/.git; then
|
||||||
|
rm -fR ${quote target.path}
|
||||||
|
fi
|
||||||
if ! test -e ${quote target.path}; then
|
if ! test -e ${quote target.path}; then
|
||||||
git clone --recurse-submodules ${quote source.url} ${quote target.path}
|
${if source.shallow then /* sh */ ''
|
||||||
|
git init ${quote target.path}
|
||||||
|
'' else /* sh */ ''
|
||||||
|
git clone --recurse-submodules ${quote source.url} ${quote target.path}
|
||||||
|
''}
|
||||||
fi
|
fi
|
||||||
cd ${quote target.path}
|
cd ${quote target.path}
|
||||||
if ! url=$(git config remote.origin.url); then
|
if ! url=$(git config remote.origin.url); then
|
||||||
|
@ -37,105 +86,200 @@ let
|
||||||
hash=${quote source.ref}
|
hash=${quote source.ref}
|
||||||
|
|
||||||
if ! test "$(git log --format=%H -1)" = "$hash"; then
|
if ! test "$(git log --format=%H -1)" = "$hash"; then
|
||||||
if ! git log -1 "$hash" >/dev/null 2>&1; then
|
${if source.fetchAlways then /* sh */ ''
|
||||||
git fetch origin
|
${if source.shallow then /* sh */ ''
|
||||||
fi
|
git fetch --depth=1 origin "$hash"
|
||||||
git checkout "$hash" -- ${quote target.path}
|
'' else /* sh */ ''
|
||||||
git -c advice.detachedHead=false checkout -f "$hash"
|
git fetch origin
|
||||||
|
''}
|
||||||
|
'' else /* sh */ ''
|
||||||
|
if ! git log -1 "$hash" >/dev/null 2>&1; then
|
||||||
|
${if source.shallow then /* sh */ ''
|
||||||
|
git fetch --depth=1 origin "$hash"
|
||||||
|
'' else /* sh */ ''
|
||||||
|
git fetch origin
|
||||||
|
''}
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
git reset --hard "$hash" >&2
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git clean -dfx
|
git clean -dfx \
|
||||||
|
${concatMapStringsSep " "
|
||||||
|
(pattern: /* sh */ "-e ${quote pattern}")
|
||||||
|
source.clean.exclude }
|
||||||
'';
|
'';
|
||||||
|
|
||||||
pop.pass = target: source: let
|
pop.pass = target: source: let
|
||||||
passPrefix = "${source.dir}/${source.name}";
|
passPrefix = "${source.dir}/${source.name}";
|
||||||
in /* sh */ ''
|
in /* sh */ ''
|
||||||
|
set -efu
|
||||||
|
|
||||||
umask 0077
|
umask 0077
|
||||||
|
|
||||||
|
if test -e ${quote source.dir}/.git; then
|
||||||
|
local_pass_info=${quote source.name}\ $(
|
||||||
|
${git}/bin/git -C ${quote source.dir} log -1 --format=%H ${quote source.name}
|
||||||
|
# we append a hash for every symlink, otherwise we would miss updates on
|
||||||
|
# files where the symlink points to
|
||||||
|
${findutils}/bin/find ${quote source.dir}/${quote source.name} -type l \
|
||||||
|
-exec ${coreutils}/bin/realpath {} + |
|
||||||
|
${coreutils}/bin/sort |
|
||||||
|
${findutils}/bin/xargs -r -n 1 ${git}/bin/git -C ${quote source.dir} log -1 --format=%H
|
||||||
|
)
|
||||||
|
remote_pass_info=$(${runShell target /* sh */ ''
|
||||||
|
cat ${quote target.path}/.pass_info || :
|
||||||
|
''})
|
||||||
|
|
||||||
|
if test "$local_pass_info" = "$remote_pass_info"; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
tmp_dir=$(${coreutils}/bin/mktemp -dt populate-pass.XXXXXXXX)
|
tmp_dir=$(${coreutils}/bin/mktemp -dt populate-pass.XXXXXXXX)
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
cleanup() {
|
cleanup() {
|
||||||
rm -fR "$tmp_dir"
|
rm -fR "$tmp_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
${findutils}/bin/find ${quote passPrefix} -type f |
|
${findutils}/bin/find ${quote passPrefix} -type f -follow ! -name .gpg-id |
|
||||||
while read -r gpg_path; do
|
while read -r gpg_path; do
|
||||||
|
|
||||||
rel_name=''${gpg_path#${quote passPrefix}}
|
rel_name=''${gpg_path#${quote passPrefix}}
|
||||||
rel_name=''${rel_name%.gpg}
|
rel_name=''${rel_name%.gpg}
|
||||||
|
|
||||||
pass_date=$(
|
pass_date=$(
|
||||||
${git}/bin/git -C ${quote source.dir} log -1 --format=%aI "$gpg_path"
|
if test -e ${quote source.dir}/.git; then
|
||||||
|
${git}/bin/git -C ${quote source.dir} log -1 --format=%aI "$gpg_path"
|
||||||
|
fi
|
||||||
)
|
)
|
||||||
pass_name=${quote source.name}/$rel_name
|
pass_name=${quote source.name}/$rel_name
|
||||||
tmp_path=$tmp_dir/$rel_name
|
tmp_path=$tmp_dir/$rel_name
|
||||||
|
|
||||||
${coreutils}/bin/mkdir -p "$(${coreutils}/bin/dirname "$tmp_path")"
|
${coreutils}/bin/mkdir -p "$(${coreutils}/bin/dirname "$tmp_path")"
|
||||||
PASSWORD_STORE_DIR=${quote source.dir} ${pass}/bin/pass show "$pass_name" > "$tmp_path"
|
PASSWORD_STORE_DIR=${quote source.dir} ${pass}/bin/pass show "$pass_name" > "$tmp_path"
|
||||||
${coreutils}/bin/touch -d "$pass_date" "$tmp_path"
|
if [ -n "$pass_date" ]; then
|
||||||
|
${coreutils}/bin/touch -d "$pass_date" "$tmp_path"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
${rsync' target /* sh */ "$tmp_dir"}
|
if test -n "''${local_pass_info-}"; then
|
||||||
|
echo "$local_pass_info" > "$tmp_dir"/.pass_info
|
||||||
|
fi
|
||||||
|
|
||||||
|
${rsync' target rsyncDefaultConfig /* sh */ "$tmp_dir"}
|
||||||
|
'';
|
||||||
|
|
||||||
|
pop.passage = target: source: /* sh */ ''
|
||||||
|
set -efu
|
||||||
|
|
||||||
|
export PASSAGE_AGE=${quote source.age}
|
||||||
|
export PASSAGE_DIR=${quote source.dir}
|
||||||
|
export PASSAGE_IDENTITIES_FILE=${quote source.identities_file}
|
||||||
|
|
||||||
|
umask 0077
|
||||||
|
|
||||||
|
tmp_dir=$(${coreutils}/bin/mktemp -dt populate-passage.XXXXXXXX)
|
||||||
|
trap cleanup EXIT
|
||||||
|
cleanup() {
|
||||||
|
rm -fR "$tmp_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
${findutils}/bin/find "$PASSAGE_DIR" -type f -name \*.age -follow |
|
||||||
|
while read -r age_path; do
|
||||||
|
|
||||||
|
rel_name=''${age_path#$PASSAGE_DIR}
|
||||||
|
rel_name=''${rel_name%.age}
|
||||||
|
|
||||||
|
tmp_path=$tmp_dir/$rel_name
|
||||||
|
|
||||||
|
${coreutils}/bin/mkdir -p "$(${coreutils}/bin/dirname "$tmp_path")"
|
||||||
|
${passage}/bin/passage show "$rel_name" > "$tmp_path"
|
||||||
|
${coreutils}/bin/touch -r "$age_path" "$tmp_path"
|
||||||
|
done
|
||||||
|
|
||||||
|
${rsync' target rsyncDefaultConfig /* sh */ "$tmp_dir"}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
pop.pipe = target: source: /* sh */ ''
|
pop.pipe = target: source: /* sh */ ''
|
||||||
${quote source.command} | {
|
${quote source.command} | {
|
||||||
${shell' target /* sh */ "cat > ${quote target.path}"}
|
${runShell target /* sh */ "cat > ${quote target.path}"}
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# TODO rm -fR instead of ln -f?
|
# TODO rm -fR instead of ln -f?
|
||||||
pop.symlink = target: source: shell' target /* sh */ ''
|
pop.symlink = target: source: runShell target /* sh */ ''
|
||||||
ln -fns ${quote source.target} ${quote target.path}
|
ln -fnsT ${quote source.target} ${quote target.path}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
populate = target: name: source: let
|
populate = target: name: source: let
|
||||||
source' = source.${source.type};
|
source' = source.${source.type};
|
||||||
target' = target // { path = "${target.path}/${name}"; };
|
target' = target // { path = "${target.path}/${name}"; };
|
||||||
in writeDash "populate.${target'.host}.${name}" ''
|
in writers.writeDash "populate.${target'.host}.${name}" ''
|
||||||
set -efu
|
set -efu
|
||||||
${pop.${source.type} target' source'}
|
${pop.${source.type} target' source'}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
rsync' = target: sourcePath: /* sh */ ''
|
rsync' = target: config: sourcePath: /* sh */ ''
|
||||||
source_path=${sourcePath}
|
source_path=${sourcePath}
|
||||||
if test -d "$source_path"; then
|
if test -d "$source_path"; then
|
||||||
source_path=$source_path/
|
source_path=$source_path/
|
||||||
fi
|
fi
|
||||||
${rsync}/bin/rsync \
|
${rsync}/bin/rsync \
|
||||||
|
${optionalString config.useChecksum /* sh */ "--checksum"} \
|
||||||
|
${optionalString target.sudo /* sh */ "--rsync-path=\"sudo rsync\""} \
|
||||||
|
${concatMapStringsSep " "
|
||||||
|
(pattern: /* sh */ "--exclude ${quote pattern}")
|
||||||
|
config.exclude} \
|
||||||
|
${concatMapStringsSep " "
|
||||||
|
(filter: /* sh */ "--${filter.type} ${quote filter.pattern}")
|
||||||
|
config.filters} \
|
||||||
-e ${quote (ssh' target)} \
|
-e ${quote (ssh' target)} \
|
||||||
-vFrlptD \
|
-vFrlptD \
|
||||||
--delete-excluded \
|
${optionalString config.deleteExcluded /* sh */ "--delete-excluded"} \
|
||||||
"$source_path" \
|
"$source_path" \
|
||||||
${quote (
|
${quote (
|
||||||
optionalString (!isLocalTarget target)
|
optionalString (!isLocalTarget target) (
|
||||||
"${target.user}@${target.host}:" +
|
(optionalString (target.user != "") "${target.user}@") +
|
||||||
|
"${target.host}:"
|
||||||
|
) +
|
||||||
target.path
|
target.path
|
||||||
)} \
|
)} \
|
||||||
>&2
|
>&2
|
||||||
'';
|
'';
|
||||||
|
|
||||||
shell' = target: script:
|
rsyncDefaultConfig = {
|
||||||
if isLocalTarget target
|
useChecksum = false;
|
||||||
then script
|
exclude = [];
|
||||||
else /* sh */ ''
|
filters = [];
|
||||||
${ssh' target} ${quote target.host} ${quote script}
|
deleteExcluded = true;
|
||||||
'';
|
};
|
||||||
|
|
||||||
ssh' = target: concatMapStringsSep " " quote [
|
runShell = target: command:
|
||||||
|
if isLocalTarget target
|
||||||
|
then command
|
||||||
|
else
|
||||||
|
if target.sudo then /* sh */ ''
|
||||||
|
${ssh' target} ${quote target.host} ${quote "sudo bash -c ${quote command}"}
|
||||||
|
'' else ''
|
||||||
|
${ssh' target} ${quote target.host} ${quote command}
|
||||||
|
'';
|
||||||
|
|
||||||
|
ssh' = target: concatMapStringsSep " " quote (flatten [
|
||||||
"${openssh}/bin/ssh"
|
"${openssh}/bin/ssh"
|
||||||
"-l" target.user
|
(mkUserPortSSHOpts target)
|
||||||
"-o" "ControlPersist=no"
|
|
||||||
"-p" target.port
|
|
||||||
"-T"
|
"-T"
|
||||||
];
|
target.extraOptions
|
||||||
|
]);
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
{ force ? false, source, target }: writeDash "populate.${target.host}" ''
|
{ backup ? false, force ? false, source, target }:
|
||||||
|
writers.writeDash "populate.${target.host}" ''
|
||||||
set -efu
|
set -efu
|
||||||
${check { inherit force target; }}
|
${check { inherit force target; }}
|
||||||
set -x
|
set -x
|
||||||
|
${optionalString backup (do-backup { inherit target; })}
|
||||||
${concatStringsSep "\n" (mapAttrsToList (populate target) source)}
|
${concatStringsSep "\n" (mapAttrsToList (populate target) source)}
|
||||||
''
|
''
|
||||||
|
|
Loading…
Reference in a new issue