nixpkgs/nixos/modules/services/misc/anki-sync-server.nix

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

145 lines
4.6 KiB
Nix
Raw Normal View History

{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.anki-sync-server;
name = "anki-sync-server";
specEscape = replaceStrings [ "%" ] [ "%%" ];
usersWithIndexes = lists.imap1 (i: user: {
i = i;
user = user;
}) cfg.users;
usersWithIndexesFile = filter (x: x.user.passwordFile != null) usersWithIndexes;
usersWithIndexesNoFile = filter (
x: x.user.passwordFile == null && x.user.password != null
) usersWithIndexes;
anki-sync-server-run = pkgs.writeShellScript "anki-sync-server-run" ''
# When services.anki-sync-server.users.passwordFile is set,
# each password file is passed as a systemd credential, which is mounted in
# a file system exposed to the service. Here we read the passwords from
# the credential files to pass them as environment variables to the Anki
# sync server.
${concatMapStringsSep "\n" (x: ''
read -r pass < "''${CREDENTIALS_DIRECTORY}/"${escapeShellArg x.user.username}
export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:"$pass"
'') usersWithIndexesFile}
# For users where services.anki-sync-server.users.password isn't set,
# export passwords in environment variables in plaintext.
${concatMapStringsSep "\n" (
x:
''export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:${escapeShellArg x.user.password}''
) usersWithIndexesNoFile}
exec ${lib.getExe cfg.package}
'';
in
{
options.services.anki-sync-server = {
2023-11-30 17:32:03 +01:00
enable = mkEnableOption "anki-sync-server";
2023-11-30 17:32:03 +01:00
package = mkPackageOption pkgs "anki-sync-server" { };
address = mkOption {
type = types.str;
default = "::1";
2023-11-30 17:32:03 +01:00
description = ''
IP address anki-sync-server listens to.
Note host names are not resolved.
'';
};
port = mkOption {
type = types.port;
default = 27701;
2023-11-30 17:32:03 +01:00
description = "Port number anki-sync-server listens to.";
};
baseDirectory = mkOption {
type = types.str;
default = "%S/%N";
description = "Base directory where user(s) synchronized data will be stored.";
};
openFirewall = mkOption {
default = false;
type = types.bool;
2023-11-30 17:32:03 +01:00
description = "Whether to open the firewall for the specified port.";
};
users = mkOption {
type =
with types;
listOf (submodule {
options = {
username = mkOption {
type = str;
2023-11-30 17:32:03 +01:00
description = "User name accepted by anki-sync-server.";
};
password = mkOption {
type = nullOr str;
default = null;
2023-11-30 17:32:03 +01:00
description = ''
Password accepted by anki-sync-server for the associated username.
**WARNING**: This option is **not secure**. This password will
be stored in *plaintext* and will be visible to *all users*.
See {option}`services.anki-sync-server.users.passwordFile` for
a more secure option.
'';
};
passwordFile = mkOption {
type = nullOr path;
default = null;
2023-11-30 17:32:03 +01:00
description = ''
File containing the password accepted by anki-sync-server for
the associated username. Make sure to make readable only by
root.
'';
};
};
});
2023-11-30 17:32:03 +01:00
description = "List of user-password pairs to provide to the sync server.";
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = (builtins.length usersWithIndexesFile) + (builtins.length usersWithIndexesNoFile) > 0;
message = "At least one username-password pair must be set.";
}
];
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
systemd.services.anki-sync-server = {
description = "anki-sync-server: Anki sync server built into Anki";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ cfg.package ];
environment = {
SYNC_BASE = cfg.baseDirectory;
SYNC_HOST = specEscape cfg.address;
SYNC_PORT = toString cfg.port;
};
serviceConfig = {
Type = "simple";
DynamicUser = true;
StateDirectory = name;
ExecStart = anki-sync-server-run;
Restart = "always";
LoadCredential = map (
x: "${specEscape x.user.username}:${specEscape (toString x.user.passwordFile)}"
) usersWithIndexesFile;
};
};
};
meta = {
maintainers = with maintainers; [ telotortium ];
doc = ./anki-sync-server.md;
};
}