at 24.11-pre 4.5 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7with lib; let 8 cfg = config.services.anki-sync-server; 9 name = "anki-sync-server"; 10 specEscape = replaceStrings ["%"] ["%%"]; 11 usersWithIndexes = 12 lists.imap1 (i: user: { 13 i = i; 14 user = user; 15 }) 16 cfg.users; 17 usersWithIndexesFile = filter (x: x.user.passwordFile != null) usersWithIndexes; 18 usersWithIndexesNoFile = filter (x: x.user.passwordFile == null && x.user.password != null) usersWithIndexes; 19 anki-sync-server-run = pkgs.writeShellScriptBin "anki-sync-server-run" '' 20 # When services.anki-sync-server.users.passwordFile is set, 21 # each password file is passed as a systemd credential, which is mounted in 22 # a file system exposed to the service. Here we read the passwords from 23 # the credential files to pass them as environment variables to the Anki 24 # sync server. 25 ${ 26 concatMapStringsSep 27 "\n" 28 (x: ''export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:"''$(cat "''${CREDENTIALS_DIRECTORY}/"${escapeShellArg x.user.username})"'') 29 usersWithIndexesFile 30 } 31 # For users where services.anki-sync-server.users.password isn't set, 32 # export passwords in environment variables in plaintext. 33 ${ 34 concatMapStringsSep 35 "\n" 36 (x: ''export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:${escapeShellArg x.user.password}'') 37 usersWithIndexesNoFile 38 } 39 exec ${cfg.package}/bin/anki-sync-server 40 ''; 41in { 42 options.services.anki-sync-server = { 43 enable = mkEnableOption "anki-sync-server"; 44 45 package = mkPackageOption pkgs "anki-sync-server" { }; 46 47 address = mkOption { 48 type = types.str; 49 default = "::1"; 50 description = '' 51 IP address anki-sync-server listens to. 52 Note host names are not resolved. 53 ''; 54 }; 55 56 port = mkOption { 57 type = types.port; 58 default = 27701; 59 description = "Port number anki-sync-server listens to."; 60 }; 61 62 openFirewall = mkOption { 63 default = false; 64 type = types.bool; 65 description = "Whether to open the firewall for the specified port."; 66 }; 67 68 users = mkOption { 69 type = with types; 70 listOf (submodule { 71 options = { 72 username = mkOption { 73 type = str; 74 description = "User name accepted by anki-sync-server."; 75 }; 76 password = mkOption { 77 type = nullOr str; 78 default = null; 79 description = '' 80 Password accepted by anki-sync-server for the associated username. 81 **WARNING**: This option is **not secure**. This password will 82 be stored in *plaintext* and will be visible to *all users*. 83 See {option}`services.anki-sync-server.users.passwordFile` for 84 a more secure option. 85 ''; 86 }; 87 passwordFile = mkOption { 88 type = nullOr path; 89 default = null; 90 description = '' 91 File containing the password accepted by anki-sync-server for 92 the associated username. Make sure to make readable only by 93 root. 94 ''; 95 }; 96 }; 97 }); 98 description = "List of user-password pairs to provide to the sync server."; 99 }; 100 }; 101 102 config = mkIf cfg.enable { 103 assertions = [ 104 { 105 assertion = (builtins.length usersWithIndexesFile) + (builtins.length usersWithIndexesNoFile) > 0; 106 message = "At least one username-password pair must be set."; 107 } 108 ]; 109 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.port]; 110 111 systemd.services.anki-sync-server = { 112 description = "anki-sync-server: Anki sync server built into Anki"; 113 after = ["network.target"]; 114 wantedBy = ["multi-user.target"]; 115 path = [cfg.package]; 116 environment = { 117 SYNC_BASE = "%S/%N"; 118 SYNC_HOST = specEscape cfg.address; 119 SYNC_PORT = toString cfg.port; 120 }; 121 122 serviceConfig = { 123 Type = "simple"; 124 DynamicUser = true; 125 StateDirectory = name; 126 ExecStart = "${anki-sync-server-run}/bin/anki-sync-server-run"; 127 Restart = "always"; 128 LoadCredential = 129 map 130 (x: "${specEscape x.user.username}:${specEscape (toString x.user.passwordFile)}") 131 usersWithIndexesFile; 132 }; 133 }; 134 }; 135 136 meta = { 137 maintainers = with maintainers; [telotortium]; 138 doc = ./anki-sync-server.md; 139 }; 140}