nixos/castopod: init

Changed files
+312
nixos
doc
manual
release-notes
modules
+2
nixos/doc/manual/release-notes/rl-2311.section.md
···
- [GoToSocial](https://gotosocial.org/), an ActivityPub social network server, written in Golang. Available as [services.gotosocial](#opt-services.gotosocial.enable).
+
- [Castopod](https://castopod.org/), an open-source hosting platform made for podcasters who want to engage and interact with their audience. Available as [services.castopod](#opt-services.castopod.enable).
+
- [Typesense](https://github.com/typesense/typesense), a fast, typo-tolerant search engine for building delightful search experiences. Available as [services.typesense](#opt-services.typesense.enable).
* [NS-USBLoader](https://github.com/developersu/ns-usbloader/), an all-in-one tool for managing Nintendo Switch homebrew. Available as [programs.ns-usbloader](#opt-programs.ns-usbloader.enable).
+1
nixos/modules/module-list.nix
···
./services/amqp/rabbitmq.nix
./services/audio/alsa.nix
./services/audio/botamusique.nix
+
./services/audio/castopod.nix
./services/audio/gmediarender.nix
./services/audio/gonic.nix
./services/audio/goxlr-utility.nix
+22
nixos/modules/services/audio/castopod.md
···
+
# Castopod {#module-services-castopod}
+
+
Castopod is an open-source hosting platform made for podcasters who want to engage and interact with their audience.
+
+
## Quickstart {#module-services-castopod-quickstart}
+
+
Use the following configuration to start a public instance of Castopod on `castopod.example.com` domain:
+
+
```nix
+
networking.firewall.allowedTCPPorts = [ 80 443 ];
+
services.castopod = {
+
enable = true;
+
database.createLocally = true;
+
nginx.virtualHost = {
+
serverName = "castopod.example.com";
+
enableACME = true;
+
forceSSL = true;
+
};
+
};
+
```
+
+
Go to `https://castopod.example.com/cp-install` to create superadmin account after applying the above configuration.
+287
nixos/modules/services/audio/castopod.nix
···
+
{ config, lib, pkgs, ... }:
+
let
+
cfg = config.services.castopod;
+
fpm = config.services.phpfpm.pools.castopod;
+
+
user = "castopod";
+
stateDirectory = "/var/lib/castopod";
+
+
# https://docs.castopod.org/getting-started/install.html#requirements
+
phpPackage = pkgs.php.withExtensions ({ enabled, all }: with all; [
+
intl
+
curl
+
mbstring
+
gd
+
exif
+
mysqlnd
+
] ++ enabled);
+
in
+
{
+
meta.doc = ./castopod.md;
+
meta.maintainers = with lib.maintainers; [ alexoundos misuzu ];
+
+
options.services = {
+
castopod = {
+
enable = lib.mkEnableOption (lib.mdDoc "Castopod");
+
package = lib.mkOption {
+
type = lib.types.package;
+
default = pkgs.castopod;
+
defaultText = lib.literalMD "pkgs.castopod";
+
description = lib.mdDoc "Which Castopod package to use.";
+
};
+
database = {
+
createLocally = lib.mkOption {
+
type = lib.types.bool;
+
default = true;
+
description = lib.mdDoc ''
+
Create the database and database user locally.
+
'';
+
};
+
hostname = lib.mkOption {
+
type = lib.types.str;
+
default = "localhost";
+
description = lib.mdDoc "Database hostname.";
+
};
+
name = lib.mkOption {
+
type = lib.types.str;
+
default = "castopod";
+
description = lib.mdDoc "Database name.";
+
};
+
user = lib.mkOption {
+
type = lib.types.str;
+
default = user;
+
description = lib.mdDoc "Database user.";
+
};
+
passwordFile = lib.mkOption {
+
type = lib.types.nullOr lib.types.path;
+
default = null;
+
example = "/run/keys/castopod-dbpassword";
+
description = lib.mdDoc ''
+
A file containing the password corresponding to
+
[](#opt-services.castopod.database.user).
+
'';
+
};
+
};
+
settings = lib.mkOption {
+
type = with lib.types; attrsOf (oneOf [ str int bool ]);
+
default = { };
+
example = {
+
"email.protocol" = "smtp";
+
"email.SMTPHost" = "localhost";
+
"email.SMTPUser" = "myuser";
+
"email.fromEmail" = "castopod@example.com";
+
};
+
description = lib.mdDoc ''
+
Environment variables used for Castopod.
+
See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
+
for available environment variables.
+
'';
+
};
+
environmentFile = lib.mkOption {
+
type = lib.types.nullOr lib.types.path;
+
default = null;
+
example = "/run/keys/castopod-env";
+
description = lib.mdDoc ''
+
Environment file to inject e.g. secrets into the configuration.
+
See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
+
for available environment variables.
+
'';
+
};
+
configureNginx = lib.mkOption {
+
type = lib.types.bool;
+
default = true;
+
description = lib.mdDoc "Configure nginx as a reverse proxy for CastoPod.";
+
};
+
localDomain = lib.mkOption {
+
type = lib.types.str;
+
example = "castopod.example.org";
+
description = lib.mdDoc "The domain serving your CastoPod instance.";
+
};
+
poolSettings = lib.mkOption {
+
type = with lib.types; attrsOf (oneOf [ str int bool ]);
+
default = {
+
"pm" = "dynamic";
+
"pm.max_children" = "32";
+
"pm.start_servers" = "2";
+
"pm.min_spare_servers" = "2";
+
"pm.max_spare_servers" = "4";
+
"pm.max_requests" = "500";
+
};
+
description = lib.mdDoc ''
+
Options for Castopod's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
+
'';
+
};
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
services.castopod.settings =
+
let
+
sslEnabled = with config.services.nginx.virtualHosts.${cfg.localDomain}; addSSL || forceSSL || onlySSL || enableACME || useACMEHost != null;
+
baseURL = "http${lib.optionalString sslEnabled "s"}://${cfg.localDomain}";
+
in
+
lib.mapAttrs (name: lib.mkDefault) {
+
"app.forceGlobalSecureRequests" = sslEnabled;
+
"app.baseURL" = baseURL;
+
+
"media.baseURL" = "/";
+
"media.root" = "media";
+
"media.storage" = stateDirectory;
+
+
"admin.gateway" = "admin";
+
"auth.gateway" = "auth";
+
+
"database.default.hostname" = cfg.database.hostname;
+
"database.default.database" = cfg.database.name;
+
"database.default.username" = cfg.database.user;
+
"database.default.DBPrefix" = "cp_";
+
+
"cache.handler" = "file";
+
};
+
+
services.phpfpm.pools.castopod = {
+
inherit user;
+
group = config.services.nginx.group;
+
phpPackage = phpPackage;
+
phpOptions = ''
+
# https://code.castopod.org/adaures/castopod/-/blob/main/docker/production/app/uploads.ini
+
file_uploads = On
+
memory_limit = 512M
+
upload_max_filesize = 500M
+
post_max_size = 512M
+
max_execution_time = 300
+
max_input_time = 300
+
'';
+
settings = {
+
"listen.owner" = config.services.nginx.user;
+
"listen.group" = config.services.nginx.group;
+
} // cfg.poolSettings;
+
};
+
+
systemd.services.castopod-setup = {
+
after = lib.optional config.services.mysql.enable "mysql.service";
+
requires = lib.optional config.services.mysql.enable "mysql.service";
+
wantedBy = [ "multi-user.target" ];
+
path = [ pkgs.openssl phpPackage ];
+
script =
+
let
+
envFile = "${stateDirectory}/.env";
+
media = "${cfg.settings."media.storage"}/${cfg.settings."media.root"}";
+
in
+
''
+
mkdir -p ${stateDirectory}/writable/{cache,logs,session,temp,uploads}
+
+
if [ ! -d ${lib.escapeShellArg media} ]; then
+
cp --no-preserve=mode,ownership -r ${cfg.package}/share/castopod/public/media ${lib.escapeShellArg media}
+
fi
+
+
if [ ! -f ${stateDirectory}/salt ]; then
+
openssl rand -base64 33 > ${stateDirectory}/salt
+
fi
+
+
cat <<'EOF' > ${envFile}
+
${lib.generators.toKeyValue { } cfg.settings}
+
EOF
+
+
echo "analytics.salt=$(cat ${stateDirectory}/salt)" >> ${envFile}
+
+
${if (cfg.database.passwordFile != null) then ''
+
echo "database.default.password=$(cat ${lib.escapeShellArg cfg.database.passwordFile})" >> ${envFile}
+
'' else ''
+
echo "database.default.password=" >> ${envFile}
+
''}
+
+
${lib.optionalString (cfg.environmentFile != null) ''
+
cat ${lib.escapeShellArg cfg.environmentFile}) >> ${envFile}
+
''}
+
+
php spark castopod:database-update
+
'';
+
serviceConfig = {
+
StateDirectory = "castopod";
+
WorkingDirectory = "${cfg.package}/share/castopod";
+
Type = "oneshot";
+
RemainAfterExit = true;
+
User = user;
+
Group = config.services.nginx.group;
+
};
+
};
+
+
systemd.services.castopod-scheduled = {
+
after = [ "castopod-setup.service" ];
+
wantedBy = [ "multi-user.target" ];
+
path = [ phpPackage ];
+
script = ''
+
php public/index.php scheduled-activities
+
php public/index.php scheduled-websub-publish
+
php public/index.php scheduled-video-clips
+
'';
+
serviceConfig = {
+
StateDirectory = "castopod";
+
WorkingDirectory = "${cfg.package}/share/castopod";
+
Type = "oneshot";
+
User = user;
+
Group = config.services.nginx.group;
+
};
+
};
+
+
systemd.timers.castopod-scheduled = {
+
wantedBy = [ "timers.target" ];
+
timerConfig = {
+
OnCalendar = "*-*-* *:*:00";
+
Unit = "castopod-scheduled.service";
+
};
+
};
+
+
services.mysql = lib.mkIf cfg.database.createLocally {
+
enable = true;
+
package = lib.mkDefault pkgs.mariadb;
+
ensureDatabases = [ cfg.database.name ];
+
ensureUsers = [{
+
name = cfg.database.user;
+
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+
}];
+
};
+
+
services.nginx = lib.mkIf cfg.configureNginx {
+
enable = true;
+
virtualHosts."${cfg.localDomain}" = {
+
root = lib.mkForce "${cfg.package}/share/castopod/public";
+
+
extraConfig = ''
+
try_files $uri $uri/ /index.php?$args;
+
index index.php index.html;
+
'';
+
+
locations."^~ /${cfg.settings."media.root"}/" = {
+
root = cfg.settings."media.storage";
+
extraConfig = ''
+
add_header Access-Control-Allow-Origin "*";
+
expires max;
+
access_log off;
+
'';
+
};
+
+
locations."~ \.php$" = {
+
fastcgiParams = {
+
SERVER_NAME = "$host";
+
};
+
extraConfig = ''
+
fastcgi_intercept_errors on;
+
fastcgi_index index.php;
+
fastcgi_pass unix:${fpm.socket};
+
try_files $uri =404;
+
fastcgi_read_timeout 3600;
+
fastcgi_send_timeout 3600;
+
'';
+
};
+
};
+
};
+
+
users.users.${user} = lib.mapAttrs (name: lib.mkDefault) {
+
description = "Castopod user";
+
isSystemUser = true;
+
group = config.services.nginx.group;
+
};
+
};
+
}