blob: bbdafc7d9fc15670d1057fa8e371613402c10928 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
{
config,
pkgs,
lib,
...
}:
let
cfg = config.services.pinchflat;
inherit (lib)
mkEnableOption
mkPackageOption
mkOption
types
mkIf
getExe
literalExpression
optional
optionals
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.";
};
user = lib.mkOption {
type = lib.types.str;
default = "pinchflat";
description = ''
User account under which Pinchflat runs.
'';
};
group = lib.mkOption {
type = lib.types.str;
default = "pinchflat";
description = ''
Group under which Pinchflat runs.
'';
};
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 || !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";
User = cfg.user;
Group = cfg.group;
StateDirectory = baseNameOf stateDir;
Environment = [
"PORT=${toString cfg.port}"
"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"
]
++ optionals cfg.selfhosted [ "RUN_CONTEXT=selfhosted" ]
++ optional (!isNull config.time.timeZone) "TZ=${config.time.timeZone}"
++ attrValues (mapAttrs (name: value: name + "=" + 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";
};
};
users.users = lib.mkIf (cfg.user == "pinchflat") {
pinchflat = {
group = cfg.group;
isSystemUser = true;
};
};
users.groups = lib.mkIf (cfg.group == "pinchflat") {
pinchflat = { };
};
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
};
};
}
|