syncthing: Declarative

I could split this commit in more but I won't.
The first commit in this repository said it would be the last legible
one, and I haven't followed that, so YOLO.
This commit is contained in:
Geoffrey Frogeye 2024-06-23 23:57:03 +02:00
parent b7d56a3118
commit 8edb670486
Signed by: geoffrey
GPG key ID: C72403E7F82E6AD8
20 changed files with 222 additions and 45 deletions

10
abavorana/standin.nix Normal file
View file

@ -0,0 +1,10 @@
{ ... }:
{
config = {
frogeye = {
name = "abavorana";
storageSize = "big";
syncthing.name = "Abavorana";
};
};
}

View file

@ -1,11 +1,11 @@
{ pkgs, lib, config, ... }: { pkgs, lib, config, ... }:
let let
passwordFile = "/tmp/dotfiles_${config.networking.hostName}_password"; passwordFile = "/tmp/dotfiles_${config.frogeye.name}_password";
in in
{ {
disko.devices = { disko.devices = {
disk = { disk = {
"${config.networking.hostName}" = { "${config.frogeye.name}" = {
type = "disk"; type = "disk";
content = { content = {
type = "gpt"; type = "gpt";
@ -27,7 +27,7 @@ in
size = "100%"; size = "100%";
content = { content = {
type = "luks"; type = "luks";
name = "${config.networking.hostName}"; name = "${config.frogeye.name}";
passwordFile = passwordFile; passwordFile = passwordFile;
settings = { settings = {
# Not having SSDs die fast is more important than crypto # Not having SSDs die fast is more important than crypto

View file

@ -1,8 +1,8 @@
{ pkgs, lib, config, ... }: { pkgs, lib, config, ... }:
{ {
config = { config = {
disko.devices.disk."${config.networking.hostName}".device = "/dev/disk/by-id/nvme-UMIS_RPJTJ128MEE1MWX_SS0L25188X3RC12121TP"; frogeye.name = "cranberry";
networking.hostName = "cranberry"; disko.devices.disk."${config.frogeye.name}".device = "/dev/disk/by-id/nvme-UMIS_RPJTJ128MEE1MWX_SS0L25188X3RC12121TP";
}; };
imports = [ imports = [
../common/disko/single_uefi_btrfs.nix ../common/disko/single_uefi_btrfs.nix

View file

@ -1,7 +1,7 @@
{ pkgs, lib, config, ... }: { pkgs, lib, config, ... }:
{ {
config = { config = {
networking.hostName = "curacao"; frogeye.name = "curacao";
}; };
imports = [ imports = [
./backup ./backup

View file

@ -10,7 +10,7 @@ let
"space_cache" "space_cache"
"ssd" "ssd"
]; ];
passwordFile = "/tmp/dotfiles_${config.networking.hostName}_password"; passwordFile = "/tmp/dotfiles_${config.frogeye.name}_password";
in in
{ {
disko.devices = { disko.devices = {

View file

@ -9,5 +9,6 @@
}; };
extra = true; extra = true;
gaming = true; gaming = true;
storageSize = "big";
}; };
} }

View file

@ -2,8 +2,8 @@
{ {
config = { config = {
boot.loader.efi.canTouchEfiVariables = false; boot.loader.efi.canTouchEfiVariables = false;
disko.devices.disk."${config.networking.hostName}".device = "/dev/disk/by-id/usb-Kingston_DataTraveler_3.0_E0D55EA57414F510489F0F1A-0:0"; disko.devices.disk."${config.frogeye.name}".device = "/dev/disk/by-id/usb-Kingston_DataTraveler_3.0_E0D55EA57414F510489F0F1A-0:0";
networking.hostName = "curacao-usb"; frogeye.name = "curacao-usb";
}; };
imports = [ imports = [
../common/disko/single_uefi_btrfs.nix ../common/disko/single_uefi_btrfs.nix

View file

@ -59,6 +59,7 @@
{ {
nixpkgs = nixpkgsConfig; nixpkgs = nixpkgsConfig;
home-manager = homeManagerConfig; home-manager = homeManagerConfig;
frogeye.toplevel = { _type = "override"; content = self; priority = 1000; };
} }
]; ];
}; };
@ -145,5 +146,15 @@
modules = [ ./cranberry ]; modules = [ ./cranberry ];
}; };
nixOnDroidConfigurations.sprinkles = lib.nixOnDroidConfiguration { }; nixOnDroidConfigurations.sprinkles = lib.nixOnDroidConfiguration { };
# Fake systems
nixosConfigurations.abavorana = lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./abavorana/standin.nix ];
};
nixosConfigurations.sprinkles = lib.nixosSystem {
system = "aarch64-linux";
modules = [ ./sprinkles/standin.nix ];
};
# TODO devices/ or configs/ folders
} // (lib.flakeTools { inherit self; }); } // (lib.flakeTools { inherit self; });
} }

View file

@ -64,17 +64,23 @@
}; };
xdg = { xdg = {
userDirs = { userDirs =
enable = true; # TODO Which ones do we want? let
createDirectories = true; wellKnownUserDirs = [ "desktop" "documents" "download" "music" "pictures" "publicShare" "templates" "videos" ];
# French, because then it there's a different initial for each, making navigation easier wellKnownUserDirsNulled = builtins.listToAttrs (builtins.map (name: { inherit name; value = null; }) wellKnownUserDirs);
desktop = null; allFolders = builtins.attrValues config.frogeye.folders;
download = "${config.home.homeDirectory}/Téléchargements"; folders = builtins.filter (folder: folder.xdgUserDirVariable != null && folder.user == config.home.username) allFolders;
pictures = "${config.home.homeDirectory}/Images"; in
publicShare = null; {
templates = null; enable = true;
videos = "${config.home.homeDirectory}/Vidéos"; createDirectories = true;
}; extraConfig = builtins.listToAttrs (builtins.map
(folder: {
name = folder.xdgUserDirVariable;
value = "${config.home.homeDirectory}/${folder.path}";
})
folders);
} // wellKnownUserDirsNulled; # Don't use defaults dirs
}; };
services = { services = {
blueman-applet.enable = true; blueman-applet.enable = true;

View file

@ -131,11 +131,6 @@ class PeriodicUpdater(Updater):
class InotifyUpdaterEventHandler(pyinotify.ProcessEvent): class InotifyUpdaterEventHandler(pyinotify.ProcessEvent):
def process_default(self, event: pyinotify.Event) -> None: def process_default(self, event: pyinotify.Event) -> None:
# DEBUG
# from pprint import pprint
# pprint(event.__dict__)
# return
assert event.path in InotifyUpdater.paths assert event.path in InotifyUpdater.paths
if 0 in InotifyUpdater.paths[event.path]: if 0 in InotifyUpdater.paths[event.path]:

View file

@ -20,6 +20,7 @@
extraConfig = '' extraConfig = ''
restore_paused "yes" restore_paused "yes"
''; '';
musicDirectory = "${config.home.homeDirectory}/Musiques";
}; };
xdg = { xdg = {
configFile = { configFile = {
@ -36,7 +37,6 @@
''; '';
}; };
}; };
userDirs.music = "${config.home.homeDirectory}/Musiques";
}; };
xsession.windowManager.i3.config.keybindings = xsession.windowManager.i3.config.keybindings =
{ {

View file

@ -27,6 +27,7 @@
] ++ lib.optionals config.frogeye.desktop.xorg [ ] ++ lib.optionals config.frogeye.desktop.xorg [
# Common # Common
zeal-qt6 # Offline documentation zeal-qt6 # Offline documentation
sqlitebrowser
# Network # Network
wireshark-qt wireshark-qt

View file

@ -61,8 +61,5 @@
# https://hydra.nixos.org/job/nixos/release-23.11/nixpkgs.blender.aarch64-linux # https://hydra.nixos.org/job/nixos/release-23.11/nixpkgs.blender.aarch64-linux
blender blender
]); ]);
services = {
syncthing.enable = true;
};
}; };
} }

View file

@ -1,6 +1,17 @@
{ lib, config, ... }: { pkgs, lib, config, ... }:
let
capitalizeFirstLetter = str: (lib.strings.toUpper (builtins.substring 0 1 str)) + (builtins.substring 1 (builtins.stringLength str) str);
in
{ {
options.frogeye = { options.frogeye = {
name = lib.mkOption {
description = "Name of the system";
type = lib.types.str;
};
toplevel = lib.mkOption {
description = "top-level flake";
type = lib.types.attrs;
};
extra = lib.mkEnableOption "Big software"; extra = lib.mkEnableOption "Big software";
gaming = lib.mkEnableOption "Games"; gaming = lib.mkEnableOption "Games";
polarity = lib.mkOption { polarity = lib.mkOption {
@ -40,11 +51,98 @@
prose = lib.mkEnableOption "Writing stuff"; prose = lib.mkEnableOption "Writing stuff";
python = lib.mkEnableOption "Python dev stuff"; python = lib.mkEnableOption "Python dev stuff";
}; };
storageSize = lib.mkOption {
default = "small";
type = lib.types.enum [ "small" "big" "phone" ];
description = "Type of storage for the device. Used to determine which folder to include.";
};
folders = lib.mkOption {
default = { };
description = "Folders used by users";
# Top-level so Syncthing can work for all users. Also there's no real home-manager syncthing module.
type = lib.types.attrsOf (lib.types.submodule ({ config, name, ... }: {
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
readOnly = true;
internal = true;
description = "Name accessing this entry";
};
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to use this folder.";
};
path = lib.mkOption {
type = lib.types.str;
description = "Path relative to HOME. Prefer French names which has more varied initials, easing navigation.";
};
label = lib.mkOption {
type = lib.types.str;
description = "Friendly name";
default = capitalizeFirstLetter config.path;
};
user = lib.mkOption {
type = lib.types.str;
default = "geoffrey";
description = "User using this directory.";
};
fullPath = lib.mkOption {
type = lib.types.str;
default = "/home/${config.user}/${config.path}";
# Hardcoded due to outside of any user context
description = "Absolute path.";
};
xdgUserDirVariable = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "XDG_${lib.strings.toUpper name}_DIR";
description = "Name for the XDG user dir variable. If set, will automatically create the directory.";
};
syncthing = lib.mkOption {
default = { };
description = "Syncthing configuration for the folder";
type = lib.types.submodule ({ ... }: {
freeformType = (pkgs.formats.json { }).type;
options = {
enable = lib.mkOption {
type = lib.types.bool;
default = config.enable && config.syncthing.id != null;
description = "Whether to sync.";
};
id = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Syncthing folder id";
};
};
});
};
versionsMaxDays = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = 365;
description = "For big systems, how many days of staggered versions should be kept";
};
};
}));
};
hooks.lock = lib.mkOption { hooks.lock = lib.mkOption {
type = lib.types.lines; type = lib.types.lines;
default = ""; default = "";
description = "Bash commands to execute on locking the session."; description = "Bash commands to execute on locking the session.";
}; };
syncthing = {
id = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Full syncthing id";
};
name = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "Geoffrey ${capitalizeFirstLetter config.frogeye.name}";
description = "Syncthing name";
};
};
}; };
config = { config = {
@ -53,6 +151,7 @@
prose = lib.mkDefault true; prose = lib.mkDefault true;
python = lib.mkDefault true; python = lib.mkDefault true;
}; };
folders.download.path = "Téléchargements";
}; };
}; };
} }

View file

@ -1,6 +1,9 @@
{ pkgs, lib, ... }: { pkgs, lib, config, ... }:
{ {
networking.domain = "geoffrey.frogeye.fr"; networking = {
hostName = config.frogeye.name;
domain = "geoffrey.frogeye.fr";
};
time.timeZone = "Europe/Amsterdam"; time.timeZone = "Europe/Amsterdam";

View file

@ -32,6 +32,7 @@ do
done <<< "$(ls /nix/var/nix/profiles/system/specialisation)" done <<< "$(ls /nix/var/nix/profiles/system/specialisation)"
# Apply # Apply
confirm="n"
if [ "$verb" = "confirm" ] if [ "$verb" = "confirm" ]
then then
echo "Apply configuration? [y/N]" echo "Apply configuration? [y/N]"

View file

@ -4,16 +4,28 @@ let
service = "syncthing"; service = "syncthing";
secretsDir = "/etc/secrets/${service}"; secretsDir = "/etc/secrets/${service}";
password = { password = {
path = "syncthing/${config.networking.hostName}"; path = "syncthing/${config.frogeye.name}";
selector = "@"; selector = "@";
generator = ''(t="$(mktemp -d)" && ${lib.getExe pkgs.syncthing} generate --home="$t" &> /dev/null && cat "$t"/{cert,key}.pem && rm -rf "$t")''; generator = ''(t="$(mktemp -d)" && ${lib.getExe pkgs.syncthing} generate --home="$t" &> /dev/null && cat "$t"/{cert,key}.pem && rm -rf "$t")'';
}; };
capitalizeFirstLetter = str: (lib.strings.toUpper (builtins.substring 0 1 str)) + (builtins.substring 1 (builtins.stringLength str) str);
nixosDevices = builtins.map (system: system.config.frogeye) (builtins.attrValues config.frogeye.toplevel.nixosConfigurations);
allDevices = nixosDevices;
syncingDevices = builtins.filter (device: device.syncthing.id != null) allDevices;
peerDevices = builtins.filter (device: device.name != config.frogeye.name) syncingDevices;
# Can't use the module's folders enable option, as it still requests things somehow
allFolders = builtins.attrValues config.frogeye.folders;
syncedFolders = builtins.filter (folder: folder.syncthing.enable) allFolders;
folderShouldSyncWith = folder: device: (lib.hasAttrByPath [ folder.name ] device.folders) && device.folders.${folder.name}.syncthing.enable;
folderDeviceEntry = folder: device: { deviceID = device.syncthing.id; };
in in
{ {
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
services.${service} = { services.${service} = {
guiAddress = "127.0.0.1:8385"; # DEBUG
openDefaultPorts = true; openDefaultPorts = true;
configDir = "/var/lib/${service}"; configDir = "/var/lib/${service}";
databaseDir = "/var/cache/${service}"; databaseDir = "/var/cache/${service}";
@ -21,14 +33,46 @@ in
key = "${secretsDir}/key.pem"; key = "${secretsDir}/key.pem";
cert = "${secretsDir}/cert.pem"; cert = "${secretsDir}/cert.pem";
settings = {
devices = builtins.listToAttrs (builtins.map (device: { inherit (device) name; value = device.syncthing; }) syncingDevices);
folders = builtins.listToAttrs (builtins.map
(folder: {
inherit (folder) name;
value =
{
label = "${capitalizeFirstLetter folder.user} ${folder.label}";
path = "${config.users.users.${folder.user}.home}/${folder.path}";
# Despite further in the code indicating this is possible, it is, actually not
# devices = builtins.map (folderDeviceEntry folder) (builtins.filter (folderShouldSyncWith folder) peerDevices);
devices = builtins.map (device: device.name) (builtins.filter (folderShouldSyncWith folder) peerDevices);
versioning =
if (config.frogeye.storageSize == "big" && folder.versionsMaxDays != null) then {
type = "staggered";
params.maxAge = builtins.toString (folder.versionsMaxDays * 24 * 3600);
# TODO Increase cleanupIntervalS to 1 day or so
} else null;
rescanIntervalS = 10 * 3600; # Using watcher, should be good enough
copyRangeMethod = "all"; # Prevents duplication
copyOwnershipFromParent = true;
} // folder.syncthing;
})
syncedFolders);
options = rec {
urAccepted = 3;
urSeen = urAccepted;
};
};
}; };
systemd.services.${service} = { systemd.services.${service}.serviceConfig = {
serviceConfig.ExecStartPre = [ ExecStartPre = [
"+${pkgs.writeShellScript "syncthing-create-folders" '' "+${pkgs.writeShellScript "syncthing-create-folders" ''
install -Dm700 -o ${cfg.user} -g ${cfg.group} -d ${cfg.configDir} install -Dm700 -o ${cfg.user} -g ${cfg.group} -d ${cfg.configDir}
install -Dm700 -o ${cfg.user} -g ${cfg.group} -d ${cfg.databaseDir} install -Dm700 -o ${cfg.user} -g ${cfg.group} -d ${cfg.databaseDir}
''}" ''}"
]; ];
PrivateUsers = lib.mkForce false;
AmbientCapabilities = ["CAP_CHOWN" "CAP_DAC_OVERRIDE" "CAP_FOWNER"];
}; };
vivarium.passwordFiles = { vivarium.passwordFiles = {
${cfg.key}.password = password // { ${cfg.key}.password = password // {

View file

@ -1,8 +1,8 @@
{ pkgs, lib, config, ... }: { pkgs, lib, config, ... }:
{ {
config = { config = {
disko.devices.disk."${config.networking.hostName}".device = "/dev/disk/by-id/mmc-DA4064_0x931f080f"; disko.devices.disk."${config.frogeye.name}".device = "/dev/disk/by-id/mmc-DA4064_0x931f080f";
networking.hostName = "pindakaas"; frogeye.name = "pindakaas";
}; };
imports = [ imports = [
../common/disko/single_uefi_btrfs.nix ../common/disko/single_uefi_btrfs.nix

View file

@ -2,8 +2,8 @@
{ {
config = { config = {
boot.loader.efi.canTouchEfiVariables = false; boot.loader.efi.canTouchEfiVariables = false;
disko.devices.disk."${config.networking.hostName}".device = "/dev/disk/by-id/mmc-SN32G_0xfb19ae99"; disko.devices.disk."${config.frogeye.name}".device = "/dev/disk/by-id/mmc-SN32G_0xfb19ae99";
networking.hostName = "pindakaas-sd"; frogeye.name = "pindakaas-sd";
}; };
imports = [ imports = [
../common/disko/single_uefi_btrfs.nix ../common/disko/single_uefi_btrfs.nix

9
sprinkles/standin.nix Normal file
View file

@ -0,0 +1,9 @@
{ ... }:
{
config = {
frogeye = {
name = "sprinkles";
storageSize = "phone";
};
};
}