syncthing setup
This commit is contained in:
parent
24df8a251b
commit
52c074f973
19 changed files with 244 additions and 16 deletions
44
bin/gen-syncthing-cert
Executable file
44
bin/gen-syncthing-cert
Executable file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf -- "$tmp"' EXIT
|
||||||
|
|
||||||
|
FILEPATH="${MANAGED_CLIENTS:-./var/syncthing-managed-clients.json}"
|
||||||
|
PKI_PATH="${PKI_PATH:-./pki/syncthing}"
|
||||||
|
SECRETS_PATH="${SECRETS_PATH:-secrets/syncthing}"
|
||||||
|
|
||||||
|
first_missing=$(
|
||||||
|
jq -r '
|
||||||
|
. as $root
|
||||||
|
| $root.managed_clients[]
|
||||||
|
| select($root.hashes[.] | not)
|
||||||
|
' $FILEPATH \
|
||||||
|
| head -n 1 \
|
||||||
|
)
|
||||||
|
[ -z "$first_missing" ] && echo "Done" >&2 && exit 0
|
||||||
|
|
||||||
|
echo "Generating cerificate for $first_missing"
|
||||||
|
mkdir $tmp/$first_missing
|
||||||
|
hash=$(
|
||||||
|
syncthing generate \
|
||||||
|
--config $tmp/$first_missing \
|
||||||
|
--data $tmp/$first_missing/data \
|
||||||
|
| grep -oP '(?<=device=)[A-Z0-9-]+' \
|
||||||
|
)
|
||||||
|
|
||||||
|
mkdir -p $PKI_PATH
|
||||||
|
mv $tmp/$first_missing/cert.pem $PKI_PATH/$first_missing.cert
|
||||||
|
|
||||||
|
# Remove the file so agenix does not try to decrypt
|
||||||
|
[ -f "$SECRETS_PATH/$first_missing.age" ] && rm "$SECRETS_PATH/$first_missing.age"
|
||||||
|
agenix -e $SECRETS_PATH/$first_missing.age < $tmp/$first_missing/key.pem
|
||||||
|
|
||||||
|
jq --arg client "$first_missing" \
|
||||||
|
--arg hash "$hash" \
|
||||||
|
'.hashes[$client] = $hash' "$FILEPATH" \
|
||||||
|
> "$tmp/new-syncthing-managed-clients.json" \
|
||||||
|
&& mv "$tmp/new-syncthing-managed-clients.json" "$FILEPATH"
|
||||||
|
|
||||||
|
# Revoke self to handle next client
|
||||||
|
"$0"
|
||||||
|
|
@ -137,6 +137,8 @@
|
||||||
colmena.packages.${system}.colmena
|
colmena.packages.${system}.colmena
|
||||||
agenix.packages.${system}.default
|
agenix.packages.${system}.default
|
||||||
pkgs.openssl
|
pkgs.openssl
|
||||||
|
pkgs.jq
|
||||||
|
pkgs.syncthing
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,12 @@
|
||||||
];
|
];
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
./hardware-configuration.nix
|
|
||||||
inputs.disko.nixosModules.disko
|
inputs.disko.nixosModules.disko
|
||||||
./disko.nix
|
|
||||||
inputs.nixos-hardware.nixosModules.framework-amd-ai-300-series
|
inputs.nixos-hardware.nixosModules.framework-amd-ai-300-series
|
||||||
inputs.lanzaboote.nixosModules.lanzaboote
|
inputs.lanzaboote.nixosModules.lanzaboote
|
||||||
|
./disko.nix
|
||||||
|
./hardware-configuration.nix
|
||||||
|
./syncthing.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
# https://github.com/NixOS/nixos-hardware/issues/1603
|
# https://github.com/NixOS/nixos-hardware/issues/1603
|
||||||
|
|
|
||||||
8
host/fw/syncthing.nix
Normal file
8
host/fw/syncthing.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
services.syncthing = {
|
||||||
|
enable = true;
|
||||||
|
user = "hd";
|
||||||
|
configDir = "/home/hd/.config/syncthing";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,19 @@
|
||||||
{ ... }:
|
{ ... }:
|
||||||
|
let
|
||||||
|
guiAddress = "127.0.0.1:8384";
|
||||||
|
in
|
||||||
{
|
{
|
||||||
services.syncthing = {
|
services.syncthing = {
|
||||||
enable = false; # TODO
|
enable = true;
|
||||||
|
inherit guiAddress;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
privateVirtualHosts."syncthing.roam.lan" = {
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://${guiAddress}/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ with lib;
|
||||||
extraGroups = [ "wheel" ];
|
extraGroups = [ "wheel" ];
|
||||||
shell = pkgs.fish;
|
shell = pkgs.fish;
|
||||||
packages = [ ];
|
packages = [ ];
|
||||||
openssh.authorizedKeys.keys = var.ssh-keys.trusted;
|
openssh.authorizedKeys.keys = var.ssh-keys.trusted-hd;
|
||||||
hashedPasswordFile = config.age.secrets.hd-password.path;
|
hashedPasswordFile = config.age.secrets.hd-password.path;
|
||||||
};
|
};
|
||||||
users.root = {
|
users.root = {
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,6 @@
|
||||||
./common
|
./common
|
||||||
./desktop
|
./desktop
|
||||||
./nginx.nix
|
./nginx.nix
|
||||||
|
./syncthing.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
65
mod/syncthing.nix
Normal file
65
mod/syncthing.nix
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
var,
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
secrets,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.services.syncthing;
|
||||||
|
this = config.networking.hostName;
|
||||||
|
|
||||||
|
is-managed = builtins.elem this var.syncthing-managed-clients.managed_clients;
|
||||||
|
is-server = this == "roam";
|
||||||
|
devices = lib.attrNames var.syncthing;
|
||||||
|
devices-without-this = lib.remove this devices;
|
||||||
|
type-encrypt = if is-server then "receiveencrypted" else "sendreceive";
|
||||||
|
devices-encrypt =
|
||||||
|
if is-server then
|
||||||
|
devices-without-this
|
||||||
|
else
|
||||||
|
lib.remove "roam" devices-without-this
|
||||||
|
++ [
|
||||||
|
{
|
||||||
|
name = "roam";
|
||||||
|
encryptionPasswordFile = config.age.secrets.syncthing-password.path;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
folders = {
|
||||||
|
documents = {
|
||||||
|
id = "documents-hd";
|
||||||
|
path = if is-server then "/data/sync/documents-hd" else "/home/hd/Documents";
|
||||||
|
type = type-encrypt;
|
||||||
|
devices = devices-encrypt;
|
||||||
|
versioning = {
|
||||||
|
type = "simple";
|
||||||
|
params.keep = "10";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
age.secrets.syncthing-password = lib.mkIf (cfg.enable && !is-server) {
|
||||||
|
file = secrets."syncthing-password.age";
|
||||||
|
mode = "440";
|
||||||
|
owner = config.services.syncthing.user;
|
||||||
|
group = config.services.syncthing.group;
|
||||||
|
};
|
||||||
|
|
||||||
|
age.secrets.syncthing-key = lib.mkIf (cfg.enable && is-managed) {
|
||||||
|
file = secrets.syncthing."${this}.age";
|
||||||
|
mode = "440";
|
||||||
|
owner = config.services.syncthing.user;
|
||||||
|
group = config.services.syncthing.group;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.syncthing = lib.mkIf cfg.enable {
|
||||||
|
inherit folders;
|
||||||
|
settings = {
|
||||||
|
devices = var.syncthing;
|
||||||
|
};
|
||||||
|
key = lib.optionalAttrs is-managed config.age.secrets.syncthing-key.path;
|
||||||
|
cert = lib.optionalAttrs is-managed "${../pki/syncthing + "/${this}.cert"}";
|
||||||
|
};
|
||||||
|
}
|
||||||
11
pki/syncthing/fw.cert
Normal file
11
pki/syncthing/fw.cert
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBnzCCAVGgAwIBAgIIDgM/cFGbbhowBQYDK2VwMEoxEjAQBgNVBAoTCVN5bmN0
|
||||||
|
aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0ZWQxEjAQBgNVBAMT
|
||||||
|
CXN5bmN0aGluZzAeFw0yNTEyMzAwMDAwMDBaFw00NTEyMjUwMDAwMDBaMEoxEjAQ
|
||||||
|
BgNVBAoTCVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0
|
||||||
|
ZWQxEjAQBgNVBAMTCXN5bmN0aGluZzAqMAUGAytlcAMhAGUklM+K4ns56bvytz1O
|
||||||
|
dTs+XvCn2QQ+MbWh7z1ejy0Jo1UwUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw
|
||||||
|
FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJ
|
||||||
|
c3luY3RoaW5nMAUGAytlcANBAPmfwk4OUbL+S6TgCF2XqND4KeMO1LxXZ57IuqxC
|
||||||
|
NK0w/nLcfebOoRlxCXLnW/VIKnhysr92nvW3SOuRqJOdDAg=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
11
pki/syncthing/roam.cert
Normal file
11
pki/syncthing/roam.cert
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBnzCCAVGgAwIBAgIIP1QH/ukEmpEwBQYDK2VwMEoxEjAQBgNVBAoTCVN5bmN0
|
||||||
|
aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0ZWQxEjAQBgNVBAMT
|
||||||
|
CXN5bmN0aGluZzAeFw0yNTEyMzAwMDAwMDBaFw00NTEyMjUwMDAwMDBaMEoxEjAQ
|
||||||
|
BgNVBAoTCVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0
|
||||||
|
ZWQxEjAQBgNVBAMTCXN5bmN0aGluZzAqMAUGAytlcAMhAI8QrJk1duZ5hrAhWh/l
|
||||||
|
iaR/0kmK0H+TM+mtpt4YcOMMo1UwUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw
|
||||||
|
FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJ
|
||||||
|
c3luY3RoaW5nMAUGAytlcANBAP0aSI/TMmhS7RX5sRHlgCHRae4+4H5EDXckecGg
|
||||||
|
uUA87D5Hq2sVULwhPWDbMChA3dbhLVN09mJO8IU8loQ+0Qs=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
21
secrets.nix
21
secrets.nix
|
|
@ -1,12 +1,29 @@
|
||||||
let
|
let
|
||||||
pkgs = import <nixpkgs> { };
|
pkgs = import <nixpkgs> { };
|
||||||
inherit (pkgs) lib;
|
inherit (pkgs) lib;
|
||||||
keys = (import ./var { inherit lib; }).ssh-keys.root;
|
ssh-keys = (import ./var { inherit lib; }).ssh-keys;
|
||||||
|
keys = ssh-keys.root;
|
||||||
|
trusted-keys = ssh-keys.trusted-root;
|
||||||
secrets = [
|
secrets = [
|
||||||
"roam/rclone-conf"
|
"roam/rclone-conf"
|
||||||
"roam/firefox-sync-secret"
|
"roam/firefox-sync-secret"
|
||||||
"hd-password"
|
"hd-password"
|
||||||
"tlskey"
|
"tlskey"
|
||||||
];
|
];
|
||||||
|
trusted-secrets = [
|
||||||
|
# Can only be decrypted by clients
|
||||||
|
"syncthing-password"
|
||||||
|
];
|
||||||
|
mkSecrets =
|
||||||
|
keys: secrets: lib.mergeAttrsList (map (x: { "secrets/${x}.age".publicKeys = keys; }) secrets);
|
||||||
|
syncthingManagedClients = (lib.importJSON ./var/syncthing-managed-clients.json).managed_clients;
|
||||||
|
mkSyncthingSecret = client: {
|
||||||
|
"secrets/syncthing/${client}.age".publicKeys = [ ssh-keys.by-host.root.${client} ];
|
||||||
|
};
|
||||||
|
syncthingSercrets = lib.mergeAttrsList (map mkSyncthingSecret syncthingManagedClients);
|
||||||
in
|
in
|
||||||
builtins.foldl' (acc: x: acc // { "secrets/${x}.age".publicKeys = keys; }) { } secrets
|
lib.mergeAttrsList ([
|
||||||
|
(mkSecrets keys secrets)
|
||||||
|
(mkSecrets trusted-keys trusted-secrets)
|
||||||
|
(syncthingSercrets)
|
||||||
|
])
|
||||||
|
|
|
||||||
10
secrets/syncthing-password.age
Normal file
10
secrets/syncthing-password.age
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 FTMbvw XgjPSuj97t3rQ4LUyKQUFrcFF6cwQMSJFjTJDAG/Nj0
|
||||||
|
ygJ4dT50p4F92ZOEBOA9gZbQ6DoSFLF7ES1nNA+5EkI
|
||||||
|
-> ssh-ed25519 ydxpSQ 32hIAyI93zJvZ9W/RwzLAMM+HuICDXiu6nkwZYryl0Y
|
||||||
|
O2gITm4v/e40dVjE5KAeea+j4VwdYVnxXSTbKVmc1ag
|
||||||
|
-> ssh-ed25519 IbE9zA Uvjx9kbxOt6zIVZsIiLValtgKeL7hufv8O45OrU7Nww
|
||||||
|
yAzSC6ggAInYOfbh9DmcnoAbrun8LLTWpqF9YK9dzCw
|
||||||
|
--- oVhNPf8TcFS/eZpcR0Yae3jddZziNWXPweId4HledhM
|
||||||
|
„кÖ—¦
{
ät •q[ö<>;qDª€ðr£LA}ªÚú‚ܬ~}œÙKD_zgýv(¡d¢
|
||||||
|
âUÝì …<ð_òôåÄ
¹°¥Õ°§ðÞ<14>‘Õ¯Ú婞zœIúiä,²
|
||||||
BIN
secrets/syncthing/fw.age
Normal file
BIN
secrets/syncthing/fw.age
Normal file
Binary file not shown.
6
secrets/syncthing/roam.age
Normal file
6
secrets/syncthing/roam.age
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 gbs8eg hQNgGRxHRPhSq5iWURBpPd3nZDh4ofjOtGlC9V3jnWc
|
||||||
|
AJ8CDyayy0kUoPiUp0LMyHRBeKjRZUTD9BbQYPKuw90
|
||||||
|
--- nxoh+y+XGoihcSXsc9CW5/KzuDCQkbpjiiXMnfJVs3A
|
||||||
|
á\vU¯:I~ù¯§t/u§ßדÍ<E2809C>YU<>w$7š8[”xƒ]
<0A>¸;nÜRëG9Ð<á â ·úûþ×o,>áS–Ù¥Ë7Í¥aàB9láð{‚ˆ;;Úkä<6B>e7gjÃ<6A>¡$€¦
|
||||||
|
e<EFBFBD>@8Èü³TÕÒñ;Ç€Ûã…ÌFužrLq¯váòËDîš8z"òªÐìãŽPT6nˆ<]<5D>
|
||||||
|
|
@ -1,13 +1,20 @@
|
||||||
{ ... }@inputs:
|
{
|
||||||
|
lib ? null,
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
inputs' = inputs // {
|
lib' = if builtins.isNull lib then (import <nixpkgs> { }).lib else lib;
|
||||||
|
inputs' = {
|
||||||
|
lib = lib';
|
||||||
var = outputs;
|
var = outputs;
|
||||||
};
|
};
|
||||||
|
load-var = x: import x inputs';
|
||||||
# watch out for cycles
|
# watch out for cycles
|
||||||
outputs = {
|
outputs = {
|
||||||
"lan-dns" = import ./lan-dns.nix inputs';
|
"lan-dns" = load-var ./lan-dns.nix;
|
||||||
"ssh-keys" = import ./ssh-keys.nix inputs';
|
"ssh-keys" = load-var ./ssh-keys.nix;
|
||||||
"wg" = import ./wg.nix inputs';
|
"wg" = load-var ./wg.nix;
|
||||||
|
"syncthing" = load-var ./syncthing.nix;
|
||||||
|
"syncthing-managed-clients" = lib'.importJSON ./syncthing-managed-clients.json;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
outputs
|
outputs
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ let
|
||||||
}) var.wg.ips;
|
}) var.wg.ips;
|
||||||
custom-hosts = with var.wg.ips; {
|
custom-hosts = with var.wg.ips; {
|
||||||
"git.lan" = roam;
|
"git.lan" = roam;
|
||||||
|
"syncthing.roam.lan" = roam;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
rec {
|
rec {
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,15 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
keys' = mkKeys keys;
|
keys' = mkKeys keys;
|
||||||
in
|
mkTrusted =
|
||||||
keys'
|
user: with keys'.by-host.${user}; [
|
||||||
// {
|
|
||||||
trusted = with keys'.by-host.hd; [
|
|
||||||
solo
|
solo
|
||||||
c2
|
c2
|
||||||
fw
|
fw
|
||||||
];
|
];
|
||||||
|
in
|
||||||
|
keys'
|
||||||
|
// {
|
||||||
|
trusted-hd = mkTrusted "hd";
|
||||||
|
trusted-root = mkTrusted "root";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
var/syncthing-managed-clients.json
Normal file
10
var/syncthing-managed-clients.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"managed_clients": [
|
||||||
|
"fw",
|
||||||
|
"roam"
|
||||||
|
],
|
||||||
|
"hashes": {
|
||||||
|
"fw": "YZGGXOT-MPFD7O4-ACLGOGT-LIMZVD3-7JBSZZR-LFCFWQL-BLO435I-LLH6GAL",
|
||||||
|
"roam": "HMB7ZRF-OODFHHW-2QCIFFJ-M7COVK5-YUB3GKT-SI56D2U-CPTTJEP-R3ZKOQ7"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
var/syncthing.nix
Normal file
18
var/syncthing.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{ var, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (var.syncthing-managed-clients) managed_clients hashes;
|
||||||
|
unmanaged = {
|
||||||
|
# "roam".id = "OIKOKOT-LY4JWPX-T7OXE4D-I4ZC3IR-ZLMKFCO-IXSVEYZ-Y3FZOUB-LIG2XAO";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
assert (
|
||||||
|
lib.assertMsg (
|
||||||
|
builtins.attrNames hashes == managed_clients
|
||||||
|
) "Not all declaratively configured syncthing clients have keys. Rerun ./bin/gen-syncthing-cert"
|
||||||
|
);
|
||||||
|
assert (
|
||||||
|
lib.assertMsg (
|
||||||
|
[ ] == (lib.intersectLists managed_clients (builtins.attrNames unmanaged))
|
||||||
|
) "Syncthing clients must either be unmanaged or declaratively configured."
|
||||||
|
);
|
||||||
|
unmanaged // builtins.mapAttrs (_: v: { id = v; }) hashes
|
||||||
Loading…
Add table
Add a link
Reference in a new issue