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