at 23.11-beta 8.2 kB view raw
1{ lib, pkgs, config, ... }: 2with lib; 3let 4 cfg = config.services.rustus; 5in 6{ 7 meta.maintainers = with maintainers; [ happysalada ]; 8 9 options.services.rustus = { 10 11 enable = mkEnableOption (lib.mdDoc "TUS protocol implementation in Rust"); 12 13 host = mkOption { 14 type = types.str; 15 description = lib.mdDoc '' 16 The host that rustus will connect to. 17 ''; 18 default = "127.0.0.1"; 19 example = "127.0.0.1"; 20 }; 21 22 port = mkOption { 23 type = types.port; 24 description = lib.mdDoc '' 25 The port that rustus will connect to. 26 ''; 27 default = 1081; 28 example = 1081; 29 }; 30 31 log_level = mkOption { 32 type = types.enum [ "DEBUG" "INFO" "ERROR" ]; 33 description = lib.mdDoc '' 34 Desired log level 35 ''; 36 default = "INFO"; 37 example = "ERROR"; 38 }; 39 40 max_body_size = mkOption { 41 type = types.str; 42 description = lib.mdDoc '' 43 Maximum body size in bytes 44 ''; 45 default = "10000000"; # 10 mb 46 example = "100000000"; 47 }; 48 49 url = mkOption { 50 type = types.str; 51 description = lib.mdDoc '' 52 url path for uploads 53 ''; 54 default = "/files"; 55 }; 56 57 disable_health_access_logs = mkOption { 58 type = types.bool; 59 description = lib.mdDoc '' 60 disable access log for /health endpoint 61 ''; 62 default = false; 63 }; 64 65 cors = mkOption { 66 type = types.listOf types.str; 67 description = lib.mdDoc '' 68 list of origins allowed to upload 69 ''; 70 default = ["*"]; 71 example = ["*.staging.domain" "*.prod.domain"]; 72 }; 73 74 tus_extensions = mkOption { 75 type = types.listOf (types.enum [ 76 "getting" 77 "creation" 78 "termination" 79 "creation-with-upload" 80 "creation-defer-length" 81 "concatenation" 82 "checksum" 83 ]); 84 description = lib.mdDoc '' 85 Since TUS protocol offers extensibility you can turn off some protocol extensions. 86 ''; 87 default = [ 88 "getting" 89 "creation" 90 "termination" 91 "creation-with-upload" 92 "creation-defer-length" 93 "concatenation" 94 "checksum" 95 ]; 96 }; 97 98 remove_parts = mkOption { 99 type = types.bool; 100 description = lib.mdDoc '' 101 remove parts files after successful concatenation 102 ''; 103 default = true; 104 example = false; 105 }; 106 107 storage = lib.mkOption { 108 description = lib.mdDoc '' 109 Storages are used to actually store your files. You can configure where you want to store files. 110 ''; 111 default = {}; 112 example = lib.literalExpression '' 113 { 114 type = "hybrid-s3" 115 s3_access_key_file = konfig.age.secrets.R2_ACCESS_KEY.path; 116 s3_secret_key_file = konfig.age.secrets.R2_SECRET_KEY.path; 117 s3_bucket = "my_bucket"; 118 s3_url = "https://s3.example.com"; 119 } 120 ''; 121 type = lib.types.submodule { 122 options = { 123 type = lib.mkOption { 124 type = lib.types.enum ["file-storage" "hybrid-s3"]; 125 description = lib.mdDoc "Type of storage to use"; 126 }; 127 s3_access_key_file = lib.mkOption { 128 type = lib.types.str; 129 description = lib.mdDoc "File path that contains the S3 access key."; 130 }; 131 s3_secret_key_file = lib.mkOption { 132 type = lib.types.path; 133 description = lib.mdDoc "File path that contains the S3 secret key."; 134 }; 135 s3_region = lib.mkOption { 136 type = lib.types.str; 137 default = "us-east-1"; 138 description = lib.mdDoc "S3 region name."; 139 }; 140 s3_bucket = lib.mkOption { 141 type = lib.types.str; 142 description = lib.mdDoc "S3 bucket."; 143 }; 144 s3_url = lib.mkOption { 145 type = lib.types.str; 146 description = lib.mdDoc "S3 url."; 147 }; 148 149 force_sync = lib.mkOption { 150 type = lib.types.bool; 151 description = lib.mdDoc "calls fsync system call after every write to disk in local storage"; 152 default = true; 153 }; 154 data_dir = lib.mkOption { 155 type = lib.types.str; 156 description = lib.mdDoc "path to the local directory where all files are stored"; 157 default = "/var/lib/rustus"; 158 }; 159 dir_structure = lib.mkOption { 160 type = lib.types.str; 161 description = lib.mdDoc "pattern of a directory structure locally and on s3"; 162 default = "{year}/{month}/{day}"; 163 }; 164 }; 165 }; 166 }; 167 168 info_storage = lib.mkOption { 169 description = lib.mdDoc '' 170 Info storages are used to store information about file uploads. These storages must be persistent, because every time chunk is uploaded rustus updates information about upload. And when someone wants to download file, information about it requested from storage to get actual path of an upload. 171 ''; 172 default = {}; 173 type = lib.types.submodule { 174 options = { 175 type = lib.mkOption { 176 type = lib.types.enum ["file-info-storage"]; 177 description = lib.mdDoc "Type of info storage to use"; 178 default = "file-info-storage"; 179 }; 180 dir = lib.mkOption { 181 type = lib.types.str; 182 description = lib.mdDoc "directory to store info about uploads"; 183 default = "/var/lib/rustus"; 184 }; 185 }; 186 }; 187 }; 188 }; 189 190 config = lib.mkIf cfg.enable { 191 192 systemd.services.rustus = 193 let 194 isHybridS3 = cfg.storage.type == "hybrid-s3"; 195 in 196 { 197 description = "Rustus server"; 198 documentation = [ "https://s3rius.github.io/rustus/" ]; 199 200 wantedBy = [ "multi-user.target" ]; 201 after = [ "network.target" ]; 202 203 environment = { 204 RUSTUS_SERVER_HOST = cfg.host; 205 RUSTUS_SERVER_PORT = toString cfg.port; 206 RUSTUS_LOG_LEVEL = cfg.log_level; 207 RUSTUS_MAX_BODY_SIZE = cfg.max_body_size; 208 RUSTUS_URL = cfg.url; 209 RUSTUS_DISABLE_HEALTH_ACCESS_LOG = lib.mkIf cfg.disable_health_access_logs "true"; 210 RUSTUS_CORS = lib.concatStringsSep "," cfg.cors; 211 RUSTUS_TUS_EXTENSIONS = lib.concatStringsSep "," cfg.tus_extensions; 212 RUSTUS_REMOVE_PARTS= if cfg.remove_parts then "true" else "false"; 213 RUSTUS_STORAGE = cfg.storage.type; 214 RUSTUS_DATA_DIR = cfg.storage.data_dir; 215 RUSTUS_DIR_STRUCTURE = cfg.storage.dir_structure; 216 RUSTUS_FORCE_FSYNC = if cfg.storage.force_sync then "true" else "false"; 217 RUSTUS_S3_URL = mkIf isHybridS3 cfg.storage.s3_url; 218 RUSTUS_S3_BUCKET = mkIf isHybridS3 cfg.storage.s3_bucket; 219 RUSTUS_S3_REGION = mkIf isHybridS3 cfg.storage.s3_region; 220 RUSTUS_S3_ACCESS_KEY_PATH = mkIf isHybridS3 "%d/S3_ACCESS_KEY_PATH"; 221 RUSTUS_S3_SECRET_KEY_PATH = mkIf isHybridS3 "%d/S3_SECRET_KEY_PATH"; 222 RUSTUS_INFO_STORAGE = cfg.info_storage.type; 223 RUSTUS_INFO_DIR = cfg.info_storage.dir; 224 }; 225 226 serviceConfig = { 227 ExecStart = "${pkgs.rustus}/bin/rustus"; 228 StateDirectory = "rustus"; 229 # User name is defined here to enable restoring a backup for example 230 # You will run the backup restore command as sudo -u rustus in order 231 # to have write permissions to /var/lib 232 User = "rustus"; 233 DynamicUser = true; 234 LoadCredential = lib.optionals isHybridS3 [ 235 "S3_ACCESS_KEY_PATH:${cfg.storage.s3_access_key_file}" 236 "S3_SECRET_KEY_PATH:${cfg.storage.s3_secret_key_file}" 237 ]; 238 # hardening 239 RestrictRealtime=true; 240 RestrictNamespaces=true; 241 LockPersonality=true; 242 ProtectKernelModules=true; 243 ProtectKernelTunables=true; 244 ProtectKernelLogs=true; 245 ProtectControlGroups=true; 246 ProtectHostUserNamespaces=true; 247 ProtectClock=true; 248 RestrictSUIDSGID=true; 249 SystemCallArchitectures="native"; 250 CapabilityBoundingSet=""; 251 ProtectProc = "invisible"; 252 # TODO consider SystemCallFilter LimitAS ProcSubset 253 }; 254 }; 255 }; 256}