nixpkgs/nixos/modules/services/misc/pinchflat.nix
2025-03-18 12:24:01 +01:00

158 lines
4.6 KiB
Nix

{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.pinchflat;
inherit (lib)
mkEnableOption
mkPackageOption
mkOption
types
mkIf
getExe
literalExpression
optional
attrValues
mapAttrs
;
stateDir = "/var/lib/pinchflat";
in
{
options = {
services.pinchflat = {
enable = mkEnableOption "pinchflat";
mediaDir = mkOption {
type = types.path;
default = "${stateDir}/media";
description = "The directory into which Pinchflat downloads videos.";
};
port = mkOption {
type = types.port;
default = 8945;
description = "Port on which the Pinchflat web interface is available.";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Open ports in the firewall for the Pinchflat web interface";
};
selfhosted = mkOption {
type = types.bool;
default = false;
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!";
};
logLevel = mkOption {
type = types.enum [
"debug"
"info"
"warning"
"error"
];
default = "info";
description = "Log level for Pinchflat.";
};
extraConfig = mkOption {
type =
with types;
attrsOf (
nullOr (oneOf [
bool
int
str
])
);
default = { };
example = literalExpression ''
{
YT_DLP_WORKER_CONCURRENCY = 1;
}
'';
description = ''
The configuration of Pinchflat is handled through environment variables.
The available configuration options can be found in [the Pinchflat README](https://github.com/kieraneglin/pinchflat/README.md#environment-variables).
'';
};
secretsFile = mkOption {
type = with types; nullOr path;
default = null;
example = "/run/secrets/pinchflat";
description = ''
Secrets like {env}`SECRET_KEY_BASE` and {env}`BASIC_AUTH_PASSWORD`
should be passed to the service without adding them to the world-readable Nix store.
Note that either this file needs to be available on the host on which `pinchflat` is running,
or the option `selfhosted` must be `true`.
Further, {env}`SECRET_KEY_BASE` has a minimum length requirement of 64 bytes.
One way to generate such a secret is to use `openssl rand -hex 64`.
As an example, the contents of the file might look like this:
```
SECRET_KEY_BASE=...copy-paste a secret token here...
BASIC_AUTH_USERNAME=...basic auth username...
BASIC_AUTH_PASSWORD=...basic auth password...
```
'';
};
package = mkPackageOption pkgs "pinchflat" { };
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.selfhosted || !builtins.isNull cfg.secretsFile;
message = "Either `selfhosted` must be true, or a `secretsFile` must be configured.";
}
];
systemd.services.pinchflat = {
description = "pinchflat";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
DynamicUser = true;
StateDirectory = baseNameOf stateDir;
Environment =
[
"PORT=${builtins.toString cfg.port}"
"TZ=${config.time.timeZone}"
"MEDIA_PATH=${cfg.mediaDir}"
"CONFIG_PATH=${stateDir}"
"DATABASE_PATH=${stateDir}/db/pinchflat.db"
"LOG_PATH=${stateDir}/logs/pinchflat.log"
"METADATA_PATH=${stateDir}/metadata"
"EXTRAS_PATH=${stateDir}/extras"
"TMPFILE_PATH=${stateDir}/tmp"
"TZ_DATA_PATH=${stateDir}/extras/elixir_tz_data"
"LOG_LEVEL=${cfg.logLevel}"
"PHX_SERVER=true"
]
++ optional cfg.selfhosted [ "RUN_CONTEXT=selfhosted" ]
++ attrValues (mapAttrs (name: value: name + "=" + builtins.toString value) cfg.extraConfig);
EnvironmentFile = optional (cfg.secretsFile != null) cfg.secretsFile;
ExecStartPre = "${lib.getExe' cfg.package "migrate"}";
ExecStart = "${getExe cfg.package} start";
Restart = "on-failure";
};
};
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
};
};
}