at 25.11-pre 6.6 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 inherit (lib) mkOption types; 9 cfg = config.services.send; 10in 11{ 12 options = { 13 services.send = { 14 enable = lib.mkEnableOption "Send, a file sharing web sevice for ffsend."; 15 16 package = lib.mkPackageOption pkgs "send" { }; 17 18 environment = mkOption { 19 type = 20 with types; 21 attrsOf ( 22 nullOr (oneOf [ 23 bool 24 int 25 str 26 (listOf int) 27 ]) 28 ); 29 description = '' 30 All the available config options and their defaults can be found here: https://github.com/timvisee/send/blob/master/server/config.js, 31 some descriptions can found here: https://github.com/timvisee/send/blob/master/docs/docker.md#environment-variables 32 33 Values under {option}`services.send.environment` will override the predefined values in the Send service. 34 - Time/duration should be in seconds 35 - Filesize values should be in bytes 36 ''; 37 example = { 38 DEFAULT_DOWNLOADS = 1; 39 DETECT_BASE_URL = true; 40 EXPIRE_TIMES_SECONDS = [ 41 300 42 3600 43 86400 44 604800 45 ]; 46 }; 47 }; 48 49 dataDir = lib.mkOption { 50 type = types.path; 51 readOnly = true; 52 default = "/var/lib/send"; 53 description = '' 54 Directory for uploaded files. 55 Due to limitations in {option}`systemd.services.send.serviceConfig.DynamicUser`, this item is read only. 56 ''; 57 }; 58 59 baseUrl = mkOption { 60 type = types.nullOr types.str; 61 default = null; 62 description = '' 63 Base URL for the Send service. 64 Leave it blank to automatically detect the base url. 65 ''; 66 }; 67 68 host = lib.mkOption { 69 type = types.str; 70 default = "127.0.0.1"; 71 description = "The hostname or IP address for Send to bind to."; 72 }; 73 74 port = lib.mkOption { 75 type = types.port; 76 default = 1443; 77 description = "Port the Send service listens on."; 78 }; 79 80 openFirewall = lib.mkOption { 81 type = types.bool; 82 default = false; 83 description = "Whether to open firewall ports for send"; 84 }; 85 86 redis = { 87 createLocally = lib.mkOption { 88 type = types.bool; 89 default = true; 90 description = "Whether to create a local redis automatically."; 91 }; 92 93 name = lib.mkOption { 94 type = types.str; 95 default = "send"; 96 description = '' 97 Name of the redis server. 98 Only used if {option}`services.send.redis.createLocally` is set to true. 99 ''; 100 }; 101 102 host = lib.mkOption { 103 type = types.str; 104 default = "localhost"; 105 description = "Redis server address."; 106 }; 107 108 port = lib.mkOption { 109 type = types.port; 110 default = 6379; 111 description = "Port of the redis server."; 112 }; 113 114 passwordFile = mkOption { 115 type = types.nullOr types.path; 116 default = null; 117 example = "/run/agenix/send-redis-password"; 118 description = '' 119 The path to the file containing the Redis password. 120 121 If {option}`services.send.redis.createLocally` is set to true, 122 the content of this file will be used as the password for the locally created Redis instance. 123 124 Leave it blank if no password is required. 125 ''; 126 }; 127 }; 128 }; 129 }; 130 131 config = lib.mkIf cfg.enable { 132 133 services.send.environment.DETECT_BASE_URL = cfg.baseUrl == null; 134 135 assertions = [ 136 { 137 assertion = cfg.redis.createLocally -> cfg.redis.host == "localhost"; 138 message = "the redis host must be localhost if services.send.redis.createLocally is set to true"; 139 } 140 ]; 141 142 networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.port; 143 144 services.redis = lib.optionalAttrs cfg.redis.createLocally { 145 servers."${cfg.redis.name}" = { 146 enable = true; 147 bind = "localhost"; 148 port = cfg.redis.port; 149 }; 150 }; 151 152 systemd.services.send = { 153 serviceConfig = { 154 Type = "simple"; 155 Restart = "always"; 156 StateDirectory = "send"; 157 WorkingDirectory = cfg.dataDir; 158 ReadWritePaths = cfg.dataDir; 159 LoadCredential = lib.optionalString ( 160 cfg.redis.passwordFile != null 161 ) "redis-password:${cfg.redis.passwordFile}"; 162 163 # Hardening 164 RestrictAddressFamilies = [ 165 "AF_UNIX" 166 "AF_INET" 167 "AF_INET6" 168 ]; 169 AmbientCapabilities = lib.optionalString (cfg.port < 1024) "cap_net_bind_service"; 170 DynamicUser = true; 171 CapabilityBoundingSet = ""; 172 NoNewPrivileges = true; 173 RemoveIPC = true; 174 PrivateTmp = true; 175 ProcSubset = "pid"; 176 ProtectClock = true; 177 ProtectControlGroups = true; 178 ProtectHome = true; 179 ProtectHostname = true; 180 ProtectKernelLogs = true; 181 ProtectKernelModules = true; 182 ProtectKernelTunables = true; 183 ProtectProc = "invisible"; 184 ProtectSystem = "full"; 185 RestrictNamespaces = true; 186 RestrictRealtime = true; 187 RestrictSUIDSGID = true; 188 SystemCallArchitectures = "native"; 189 UMask = "0077"; 190 }; 191 environment = 192 { 193 IP_ADDRESS = cfg.host; 194 PORT = toString cfg.port; 195 BASE_URL = if (cfg.baseUrl == null) then "http://${cfg.host}:${toString cfg.port}" else cfg.baseUrl; 196 FILE_DIR = cfg.dataDir + "/uploads"; 197 REDIS_HOST = cfg.redis.host; 198 REDIS_PORT = toString cfg.redis.port; 199 } 200 // (lib.mapAttrs ( 201 name: value: 202 if lib.isList value then 203 "[" + lib.concatStringsSep ", " (map (x: toString x) value) + "]" 204 else if lib.isBool value then 205 lib.boolToString value 206 else 207 toString value 208 ) cfg.environment); 209 after = 210 [ 211 "network.target" 212 ] 213 ++ lib.optionals cfg.redis.createLocally [ 214 "redis-${cfg.redis.name}.service" 215 ]; 216 description = "Send web service"; 217 wantedBy = [ "multi-user.target" ]; 218 script = '' 219 ${lib.optionalString (cfg.redis.passwordFile != null) '' 220 export REDIS_PASSWORD="$(cat $CREDENTIALS_DIRECTORY/redis-password)" 221 ''} 222 ${lib.getExe cfg.package} 223 ''; 224 }; 225 }; 226 227 meta.maintainers = with lib.maintainers; [ moraxyc ]; 228}