at 23.11-beta 9.3 kB view raw
1{ config, lib, pkgs, ... }: 2let 3 cfg = config.services.castopod; 4 fpm = config.services.phpfpm.pools.castopod; 5 6 user = "castopod"; 7 stateDirectory = "/var/lib/castopod"; 8 9 # https://docs.castopod.org/getting-started/install.html#requirements 10 phpPackage = pkgs.php.withExtensions ({ enabled, all }: with all; [ 11 intl 12 curl 13 mbstring 14 gd 15 exif 16 mysqlnd 17 ] ++ enabled); 18in 19{ 20 meta.doc = ./castopod.md; 21 meta.maintainers = with lib.maintainers; [ alexoundos misuzu ]; 22 23 options.services = { 24 castopod = { 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."; 31 }; 32 database = { 33 createLocally = lib.mkOption { 34 type = lib.types.bool; 35 default = true; 36 description = lib.mdDoc '' 37 Create the database and database user locally. 38 ''; 39 }; 40 hostname = lib.mkOption { 41 type = lib.types.str; 42 default = "localhost"; 43 description = lib.mdDoc "Database hostname."; 44 }; 45 name = lib.mkOption { 46 type = lib.types.str; 47 default = "castopod"; 48 description = lib.mdDoc "Database name."; 49 }; 50 user = lib.mkOption { 51 type = lib.types.str; 52 default = user; 53 description = lib.mdDoc "Database user."; 54 }; 55 passwordFile = lib.mkOption { 56 type = lib.types.nullOr lib.types.path; 57 default = null; 58 example = "/run/keys/castopod-dbpassword"; 59 description = lib.mdDoc '' 60 A file containing the password corresponding to 61 [](#opt-services.castopod.database.user). 62 ''; 63 }; 64 }; 65 settings = lib.mkOption { 66 type = with lib.types; attrsOf (oneOf [ str int bool ]); 67 default = { }; 68 example = { 69 "email.protocol" = "smtp"; 70 "email.SMTPHost" = "localhost"; 71 "email.SMTPUser" = "myuser"; 72 "email.fromEmail" = "castopod@example.com"; 73 }; 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. 78 ''; 79 }; 80 environmentFile = lib.mkOption { 81 type = lib.types.nullOr lib.types.path; 82 default = null; 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. 88 ''; 89 }; 90 configureNginx = lib.mkOption { 91 type = lib.types.bool; 92 default = true; 93 description = lib.mdDoc "Configure nginx as a reverse proxy for CastoPod."; 94 }; 95 localDomain = lib.mkOption { 96 type = lib.types.str; 97 example = "castopod.example.org"; 98 description = lib.mdDoc "The domain serving your CastoPod instance."; 99 }; 100 poolSettings = lib.mkOption { 101 type = with lib.types; attrsOf (oneOf [ str int bool ]); 102 default = { 103 "pm" = "dynamic"; 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"; 109 }; 110 description = lib.mdDoc '' 111 Options for Castopod's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives. 112 ''; 113 }; 114 }; 115 }; 116 117 config = lib.mkIf cfg.enable { 118 services.castopod.settings = 119 let 120 sslEnabled = with config.services.nginx.virtualHosts.${cfg.localDomain}; addSSL || forceSSL || onlySSL || enableACME || useACMEHost != null; 121 baseURL = "http${lib.optionalString sslEnabled "s"}://${cfg.localDomain}"; 122 in 123 lib.mapAttrs (name: lib.mkDefault) { 124 "app.forceGlobalSecureRequests" = sslEnabled; 125 "app.baseURL" = baseURL; 126 127 "media.baseURL" = "/"; 128 "media.root" = "media"; 129 "media.storage" = stateDirectory; 130 131 "admin.gateway" = "admin"; 132 "auth.gateway" = "auth"; 133 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_"; 138 139 "cache.handler" = "file"; 140 }; 141 142 services.phpfpm.pools.castopod = { 143 inherit user; 144 group = config.services.nginx.group; 145 phpPackage = phpPackage; 146 phpOptions = '' 147 # https://code.castopod.org/adaures/castopod/-/blob/main/docker/production/app/uploads.ini 148 file_uploads = On 149 memory_limit = 512M 150 upload_max_filesize = 500M 151 post_max_size = 512M 152 max_execution_time = 300 153 max_input_time = 300 154 ''; 155 settings = { 156 "listen.owner" = config.services.nginx.user; 157 "listen.group" = config.services.nginx.group; 158 } // cfg.poolSettings; 159 }; 160 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 ]; 166 script = 167 let 168 envFile = "${stateDirectory}/.env"; 169 media = "${cfg.settings."media.storage"}/${cfg.settings."media.root"}"; 170 in 171 '' 172 mkdir -p ${stateDirectory}/writable/{cache,logs,session,temp,uploads} 173 174 if [ ! -d ${lib.escapeShellArg media} ]; then 175 cp --no-preserve=mode,ownership -r ${cfg.package}/share/castopod/public/media ${lib.escapeShellArg media} 176 fi 177 178 if [ ! -f ${stateDirectory}/salt ]; then 179 openssl rand -base64 33 > ${stateDirectory}/salt 180 fi 181 182 cat <<'EOF' > ${envFile} 183 ${lib.generators.toKeyValue { } cfg.settings} 184 EOF 185 186 echo "analytics.salt=$(cat ${stateDirectory}/salt)" >> ${envFile} 187 188 ${if (cfg.database.passwordFile != null) then '' 189 echo "database.default.password=$(cat ${lib.escapeShellArg cfg.database.passwordFile})" >> ${envFile} 190 '' else '' 191 echo "database.default.password=" >> ${envFile} 192 ''} 193 194 ${lib.optionalString (cfg.environmentFile != null) '' 195 cat ${lib.escapeShellArg cfg.environmentFile}) >> ${envFile} 196 ''} 197 198 php spark castopod:database-update 199 ''; 200 serviceConfig = { 201 StateDirectory = "castopod"; 202 WorkingDirectory = "${cfg.package}/share/castopod"; 203 Type = "oneshot"; 204 RemainAfterExit = true; 205 User = user; 206 Group = config.services.nginx.group; 207 }; 208 }; 209 210 systemd.services.castopod-scheduled = { 211 after = [ "castopod-setup.service" ]; 212 wantedBy = [ "multi-user.target" ]; 213 path = [ phpPackage ]; 214 script = '' 215 php public/index.php scheduled-activities 216 php public/index.php scheduled-websub-publish 217 php public/index.php scheduled-video-clips 218 ''; 219 serviceConfig = { 220 StateDirectory = "castopod"; 221 WorkingDirectory = "${cfg.package}/share/castopod"; 222 Type = "oneshot"; 223 User = user; 224 Group = config.services.nginx.group; 225 }; 226 }; 227 228 systemd.timers.castopod-scheduled = { 229 wantedBy = [ "timers.target" ]; 230 timerConfig = { 231 OnCalendar = "*-*-* *:*:00"; 232 Unit = "castopod-scheduled.service"; 233 }; 234 }; 235 236 services.mysql = lib.mkIf cfg.database.createLocally { 237 enable = true; 238 package = lib.mkDefault pkgs.mariadb; 239 ensureDatabases = [ cfg.database.name ]; 240 ensureUsers = [{ 241 name = cfg.database.user; 242 ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; 243 }]; 244 }; 245 246 services.nginx = lib.mkIf cfg.configureNginx { 247 enable = true; 248 virtualHosts."${cfg.localDomain}" = { 249 root = lib.mkForce "${cfg.package}/share/castopod/public"; 250 251 extraConfig = '' 252 try_files $uri $uri/ /index.php?$args; 253 index index.php index.html; 254 ''; 255 256 locations."^~ /${cfg.settings."media.root"}/" = { 257 root = cfg.settings."media.storage"; 258 extraConfig = '' 259 add_header Access-Control-Allow-Origin "*"; 260 expires max; 261 access_log off; 262 ''; 263 }; 264 265 locations."~ \.php$" = { 266 fastcgiParams = { 267 SERVER_NAME = "$host"; 268 }; 269 extraConfig = '' 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; 276 ''; 277 }; 278 }; 279 }; 280 281 users.users.${user} = lib.mapAttrs (name: lib.mkDefault) { 282 description = "Castopod user"; 283 isSystemUser = true; 284 group = config.services.nginx.group; 285 }; 286 }; 287}