at master 5.3 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7 8let 9 cfg = config.services.pinchflat; 10 inherit (lib) 11 mkEnableOption 12 mkPackageOption 13 mkOption 14 types 15 mkIf 16 getExe 17 literalExpression 18 optional 19 attrValues 20 mapAttrs 21 ; 22 23 stateDir = "/var/lib/pinchflat"; 24in 25{ 26 options = { 27 services.pinchflat = { 28 enable = mkEnableOption "pinchflat"; 29 30 mediaDir = mkOption { 31 type = types.path; 32 default = "${stateDir}/media"; 33 description = "The directory into which Pinchflat downloads videos."; 34 }; 35 36 port = mkOption { 37 type = types.port; 38 default = 8945; 39 description = "Port on which the Pinchflat web interface is available."; 40 }; 41 42 openFirewall = mkOption { 43 type = types.bool; 44 default = false; 45 description = "Open ports in the firewall for the Pinchflat web interface"; 46 }; 47 48 selfhosted = mkOption { 49 type = types.bool; 50 default = false; 51 description = "Use a weak secret. If true, you are not required to provide a {env}`SECRET_KEY_BASE` through the `secretsFile` option. Do not use this option in production!"; 52 }; 53 54 logLevel = mkOption { 55 type = types.enum [ 56 "debug" 57 "info" 58 "warning" 59 "error" 60 ]; 61 default = "info"; 62 description = "Log level for Pinchflat."; 63 }; 64 65 user = lib.mkOption { 66 type = lib.types.str; 67 default = "pinchflat"; 68 description = '' 69 User account under which Pinchflat runs. 70 ''; 71 }; 72 73 group = lib.mkOption { 74 type = lib.types.str; 75 default = "pinchflat"; 76 description = '' 77 Group under which Pinchflat runs. 78 ''; 79 }; 80 81 extraConfig = mkOption { 82 type = 83 with types; 84 attrsOf ( 85 nullOr (oneOf [ 86 bool 87 int 88 str 89 ]) 90 ); 91 default = { }; 92 example = literalExpression '' 93 { 94 YT_DLP_WORKER_CONCURRENCY = 1; 95 } 96 ''; 97 description = '' 98 The configuration of Pinchflat is handled through environment variables. 99 The available configuration options can be found in [the Pinchflat README](https://github.com/kieraneglin/pinchflat/README.md#environment-variables). 100 ''; 101 }; 102 103 secretsFile = mkOption { 104 type = with types; nullOr path; 105 default = null; 106 example = "/run/secrets/pinchflat"; 107 description = '' 108 Secrets like {env}`SECRET_KEY_BASE` and {env}`BASIC_AUTH_PASSWORD` 109 should be passed to the service without adding them to the world-readable Nix store. 110 111 Note that either this file needs to be available on the host on which `pinchflat` is running, 112 or the option `selfhosted` must be `true`. 113 Further, {env}`SECRET_KEY_BASE` has a minimum length requirement of 64 bytes. 114 One way to generate such a secret is to use `openssl rand -hex 64`. 115 116 As an example, the contents of the file might look like this: 117 ``` 118 SECRET_KEY_BASE=...copy-paste a secret token here... 119 BASIC_AUTH_USERNAME=...basic auth username... 120 BASIC_AUTH_PASSWORD=...basic auth password... 121 ``` 122 ''; 123 }; 124 125 package = mkPackageOption pkgs "pinchflat" { }; 126 }; 127 }; 128 129 config = mkIf cfg.enable { 130 assertions = [ 131 { 132 assertion = cfg.selfhosted || !builtins.isNull cfg.secretsFile; 133 message = "Either `selfhosted` must be true, or a `secretsFile` must be configured."; 134 } 135 ]; 136 137 systemd.services.pinchflat = { 138 description = "pinchflat"; 139 after = [ "network.target" ]; 140 wantedBy = [ "multi-user.target" ]; 141 142 serviceConfig = { 143 Type = "simple"; 144 User = cfg.user; 145 Group = cfg.group; 146 147 StateDirectory = baseNameOf stateDir; 148 Environment = [ 149 "PORT=${builtins.toString cfg.port}" 150 "TZ=${config.time.timeZone}" 151 "MEDIA_PATH=${cfg.mediaDir}" 152 "CONFIG_PATH=${stateDir}" 153 "DATABASE_PATH=${stateDir}/db/pinchflat.db" 154 "LOG_PATH=${stateDir}/logs/pinchflat.log" 155 "METADATA_PATH=${stateDir}/metadata" 156 "EXTRAS_PATH=${stateDir}/extras" 157 "TMPFILE_PATH=${stateDir}/tmp" 158 "TZ_DATA_PATH=${stateDir}/extras/elixir_tz_data" 159 "LOG_LEVEL=${cfg.logLevel}" 160 "PHX_SERVER=true" 161 ] 162 ++ optional cfg.selfhosted [ "RUN_CONTEXT=selfhosted" ] 163 ++ attrValues (mapAttrs (name: value: name + "=" + builtins.toString value) cfg.extraConfig); 164 EnvironmentFile = optional (cfg.secretsFile != null) cfg.secretsFile; 165 ExecStartPre = "${lib.getExe' cfg.package "migrate"}"; 166 ExecStart = "${getExe cfg.package} start"; 167 Restart = "on-failure"; 168 }; 169 }; 170 171 users.users = lib.mkIf (cfg.user == "pinchflat") { 172 pinchflat = { 173 group = cfg.group; 174 isSystemUser = true; 175 }; 176 }; 177 178 users.groups = lib.mkIf (cfg.group == "pinchflat") { 179 pinchflat = { }; 180 }; 181 182 networking.firewall = mkIf cfg.openFirewall { 183 allowedTCPPorts = [ cfg.port ]; 184 }; 185 }; 186}