···
1
+
{ config, lib, pkgs, ... }:
3
+
cfg = config.services.castopod;
4
+
fpm = config.services.phpfpm.pools.castopod;
7
+
stateDirectory = "/var/lib/castopod";
9
+
# https://docs.castopod.org/getting-started/install.html#requirements
10
+
phpPackage = pkgs.php.withExtensions ({ enabled, all }: with all; [
20
+
meta.doc = ./castopod.md;
21
+
meta.maintainers = with lib.maintainers; [ alexoundos misuzu ];
23
+
options.services = {
25
+
enable = lib.mkEnableOption (lib.mdDoc "Castopod");
26
+
package = lib.mkOption {
27
+
type = lib.types.package;
28
+
default = pkgs.castopod;
29
+
defaultText = lib.literalMD "pkgs.castopod";
30
+
description = lib.mdDoc "Which Castopod package to use.";
33
+
createLocally = lib.mkOption {
34
+
type = lib.types.bool;
36
+
description = lib.mdDoc ''
37
+
Create the database and database user locally.
40
+
hostname = lib.mkOption {
41
+
type = lib.types.str;
42
+
default = "localhost";
43
+
description = lib.mdDoc "Database hostname.";
45
+
name = lib.mkOption {
46
+
type = lib.types.str;
47
+
default = "castopod";
48
+
description = lib.mdDoc "Database name.";
50
+
user = lib.mkOption {
51
+
type = lib.types.str;
53
+
description = lib.mdDoc "Database user.";
55
+
passwordFile = lib.mkOption {
56
+
type = lib.types.nullOr lib.types.path;
58
+
example = "/run/keys/castopod-dbpassword";
59
+
description = lib.mdDoc ''
60
+
A file containing the password corresponding to
61
+
[](#opt-services.castopod.database.user).
65
+
settings = lib.mkOption {
66
+
type = with lib.types; attrsOf (oneOf [ str int bool ]);
69
+
"email.protocol" = "smtp";
70
+
"email.SMTPHost" = "localhost";
71
+
"email.SMTPUser" = "myuser";
72
+
"email.fromEmail" = "castopod@example.com";
74
+
description = lib.mdDoc ''
75
+
Environment variables used for Castopod.
76
+
See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
77
+
for available environment variables.
80
+
environmentFile = lib.mkOption {
81
+
type = lib.types.nullOr lib.types.path;
83
+
example = "/run/keys/castopod-env";
84
+
description = lib.mdDoc ''
85
+
Environment file to inject e.g. secrets into the configuration.
86
+
See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
87
+
for available environment variables.
90
+
configureNginx = lib.mkOption {
91
+
type = lib.types.bool;
93
+
description = lib.mdDoc "Configure nginx as a reverse proxy for CastoPod.";
95
+
localDomain = lib.mkOption {
96
+
type = lib.types.str;
97
+
example = "castopod.example.org";
98
+
description = lib.mdDoc "The domain serving your CastoPod instance.";
100
+
poolSettings = lib.mkOption {
101
+
type = with lib.types; attrsOf (oneOf [ str int bool ]);
104
+
"pm.max_children" = "32";
105
+
"pm.start_servers" = "2";
106
+
"pm.min_spare_servers" = "2";
107
+
"pm.max_spare_servers" = "4";
108
+
"pm.max_requests" = "500";
110
+
description = lib.mdDoc ''
111
+
Options for Castopod's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
117
+
config = lib.mkIf cfg.enable {
118
+
services.castopod.settings =
120
+
sslEnabled = with config.services.nginx.virtualHosts.${cfg.localDomain}; addSSL || forceSSL || onlySSL || enableACME || useACMEHost != null;
121
+
baseURL = "http${lib.optionalString sslEnabled "s"}://${cfg.localDomain}";
123
+
lib.mapAttrs (name: lib.mkDefault) {
124
+
"app.forceGlobalSecureRequests" = sslEnabled;
125
+
"app.baseURL" = baseURL;
127
+
"media.baseURL" = "/";
128
+
"media.root" = "media";
129
+
"media.storage" = stateDirectory;
131
+
"admin.gateway" = "admin";
132
+
"auth.gateway" = "auth";
134
+
"database.default.hostname" = cfg.database.hostname;
135
+
"database.default.database" = cfg.database.name;
136
+
"database.default.username" = cfg.database.user;
137
+
"database.default.DBPrefix" = "cp_";
139
+
"cache.handler" = "file";
142
+
services.phpfpm.pools.castopod = {
144
+
group = config.services.nginx.group;
145
+
phpPackage = phpPackage;
147
+
# https://code.castopod.org/adaures/castopod/-/blob/main/docker/production/app/uploads.ini
149
+
memory_limit = 512M
150
+
upload_max_filesize = 500M
151
+
post_max_size = 512M
152
+
max_execution_time = 300
153
+
max_input_time = 300
156
+
"listen.owner" = config.services.nginx.user;
157
+
"listen.group" = config.services.nginx.group;
158
+
} // cfg.poolSettings;
161
+
systemd.services.castopod-setup = {
162
+
after = lib.optional config.services.mysql.enable "mysql.service";
163
+
requires = lib.optional config.services.mysql.enable "mysql.service";
164
+
wantedBy = [ "multi-user.target" ];
165
+
path = [ pkgs.openssl phpPackage ];
168
+
envFile = "${stateDirectory}/.env";
169
+
media = "${cfg.settings."media.storage"}/${cfg.settings."media.root"}";
172
+
mkdir -p ${stateDirectory}/writable/{cache,logs,session,temp,uploads}
174
+
if [ ! -d ${lib.escapeShellArg media} ]; then
175
+
cp --no-preserve=mode,ownership -r ${cfg.package}/share/castopod/public/media ${lib.escapeShellArg media}
178
+
if [ ! -f ${stateDirectory}/salt ]; then
179
+
openssl rand -base64 33 > ${stateDirectory}/salt
182
+
cat <<'EOF' > ${envFile}
183
+
${lib.generators.toKeyValue { } cfg.settings}
186
+
echo "analytics.salt=$(cat ${stateDirectory}/salt)" >> ${envFile}
188
+
${if (cfg.database.passwordFile != null) then ''
189
+
echo "database.default.password=$(cat ${lib.escapeShellArg cfg.database.passwordFile})" >> ${envFile}
191
+
echo "database.default.password=" >> ${envFile}
194
+
${lib.optionalString (cfg.environmentFile != null) ''
195
+
cat ${lib.escapeShellArg cfg.environmentFile}) >> ${envFile}
198
+
php spark castopod:database-update
201
+
StateDirectory = "castopod";
202
+
WorkingDirectory = "${cfg.package}/share/castopod";
204
+
RemainAfterExit = true;
206
+
Group = config.services.nginx.group;
210
+
systemd.services.castopod-scheduled = {
211
+
after = [ "castopod-setup.service" ];
212
+
wantedBy = [ "multi-user.target" ];
213
+
path = [ phpPackage ];
215
+
php public/index.php scheduled-activities
216
+
php public/index.php scheduled-websub-publish
217
+
php public/index.php scheduled-video-clips
220
+
StateDirectory = "castopod";
221
+
WorkingDirectory = "${cfg.package}/share/castopod";
224
+
Group = config.services.nginx.group;
228
+
systemd.timers.castopod-scheduled = {
229
+
wantedBy = [ "timers.target" ];
231
+
OnCalendar = "*-*-* *:*:00";
232
+
Unit = "castopod-scheduled.service";
236
+
services.mysql = lib.mkIf cfg.database.createLocally {
238
+
package = lib.mkDefault pkgs.mariadb;
239
+
ensureDatabases = [ cfg.database.name ];
241
+
name = cfg.database.user;
242
+
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
246
+
services.nginx = lib.mkIf cfg.configureNginx {
248
+
virtualHosts."${cfg.localDomain}" = {
249
+
root = lib.mkForce "${cfg.package}/share/castopod/public";
252
+
try_files $uri $uri/ /index.php?$args;
253
+
index index.php index.html;
256
+
locations."^~ /${cfg.settings."media.root"}/" = {
257
+
root = cfg.settings."media.storage";
259
+
add_header Access-Control-Allow-Origin "*";
265
+
locations."~ \.php$" = {
267
+
SERVER_NAME = "$host";
270
+
fastcgi_intercept_errors on;
271
+
fastcgi_index index.php;
272
+
fastcgi_pass unix:${fpm.socket};
273
+
try_files $uri =404;
274
+
fastcgi_read_timeout 3600;
275
+
fastcgi_send_timeout 3600;
281
+
users.users.${user} = lib.mapAttrs (name: lib.mkDefault) {
282
+
description = "Castopod user";
283
+
isSystemUser = true;
284
+
group = config.services.nginx.group;