feat(web01): Add castopod

This commit is contained in:
sinavir 2023-10-27 14:16:48 +02:00
parent 5e6330d3ea
commit bae1d02761
6 changed files with 601 additions and 0 deletions

View file

@ -8,6 +8,7 @@ lib.extra.mkConfig {
enabledServices = [
# List of services to enable
"castopod"
"metis"
"linkal"
"ntfy-sh"

View file

@ -0,0 +1,36 @@
{ config, pkgs, ...}:
let
host = "podcasts.dgnum.eu";
in
{
# Notes:
# le paramètre analytics.salt est créé par le service
services.castopod = {
enable = true;
localDomain = host;
environmentFile = config.age.secrets.castopod-environment_file.path;
settings = {
"email.fromEmail"="podcasts@infra.dgnum.eu";
"email.SMTPHost"="kurisu.lahfa.xyz";
"email.SMTPUser"="web-services@infra.dgnum.eu";
"email.SMTPPort"="465";
"media.fileManager"="s3";
"media.s3.endpoint"="https://s3.dgnum.eu/";
"media.s3.region"="garage";
"media.s3.bucket"="castopod";
"media.s3.pathStyleEndpoint"=true;
"restapi.enabled"=true;
"restapi.basicAuthUsername"="castopod";
"restapi.basicAuth"=true;
};
database.createLocally = true;
configureNginx = true;
};
services.mysql.package = pkgs.mariadb;
services.nginx.virtualHosts.${host} = {
forceSSL = true;
enableACME = true;
};
}

Binary file not shown.

View file

@ -9,4 +9,5 @@ in lib.setDefault { inherit publicKeys; } [
"plausible_secret-key-base-file"
"plausible_release-cookie-file"
"_smtp-password-file"
"castopod-environment_file"
]

556
patches/castopod.patch Normal file
View file

@ -0,0 +1,556 @@
From 9d7860c7e7830d9bf82733cecd443ff167dc2174 Mon Sep 17 00:00:00 2001
From: Alexander Tomokhov <alexoundos@gmail.com>
Date: Thu, 14 Sep 2023 02:19:40 +0400
Subject: [PATCH 1/4] maintainers: add alexoundos
---
maintainers/maintainer-list.nix | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix
index e54123ac9e4907..bc28b303afaad5 100644
--- a/maintainers/maintainer-list.nix
+++ b/maintainers/maintainer-list.nix
@@ -759,6 +759,12 @@
github = "Alexnortung";
githubId = 1552267;
};
+ alexoundos = {
+ email = "alexoundos@gmail.com";
+ github = "AleXoundOS";
+ githubId = 464913;
+ name = "Alexander Tomokhov";
+ };
alexshpilkin = {
email = "ashpilkin@gmail.com";
github = "alexshpilkin";
From 17f036f4fbdd1024ebc91ff58686b9b8ce62990b Mon Sep 17 00:00:00 2001
From: misuzu <bakalolka@gmail.com>
Date: Mon, 13 Mar 2023 20:53:59 +0200
Subject: [PATCH 2/4] castopod: init at 1.6.4
---
pkgs/applications/audio/castopod/default.nix | 51 +++++++++++
pkgs/applications/audio/castopod/update.sh | 89 ++++++++++++++++++++
pkgs/top-level/all-packages.nix | 2 +
3 files changed, 142 insertions(+)
create mode 100644 pkgs/applications/audio/castopod/default.nix
create mode 100755 pkgs/applications/audio/castopod/update.sh
diff --git a/pkgs/applications/audio/castopod/default.nix b/pkgs/applications/audio/castopod/default.nix
new file mode 100644
index 00000000000000..e4cdb6025f313f
--- /dev/null
+++ b/pkgs/applications/audio/castopod/default.nix
@@ -0,0 +1,55 @@
+{ stdenv
+, fetchurl
+, ffmpeg-headless
+, lib
+, stateDirectory ? "/var/lib/castopod"
+}:
+stdenv.mkDerivation {
+ pname = "castopod";
+ version = "1.6.5";
+
+ src = fetchurl {
+ url = "https://code.castopod.org/adaures/castopod/uploads/5aaaa6cf2edaed25bd7253449e5f8584/castopod-1.6.5.tar.gz";
+ sha256 = "04gcq2vmfy5aa2fmsm1qqv1k8g024nikmysdrhy33wj460d529b5";
+ };
+
+ dontBuild = true;
+ dontFixup = true;
+
+ postPatch = ''
+ # not configurable at runtime unfortunately:
+ substituteInPlace app/Config/Paths.php \
+ --replace "__DIR__ . '/../../writable'" "'${stateDirectory}/writable'"
+
+ substituteInPlace modules/Admin/Controllers/DashboardController.php \
+ --replace "disk_total_space('./')" "disk_total_space('${stateDirectory}')"
+
+ # configuration file must be writable, place it to ${stateDirectory}
+ substituteInPlace modules/Install/Controllers/InstallController.php \
+ --replace "ROOTPATH" "'${stateDirectory}/'"
+ substituteInPlace public/index.php spark \
+ --replace "DotEnv(ROOTPATH)" "DotEnv('${stateDirectory}')"
+
+ # ffmpeg is required for Video Clips feature
+ substituteInPlace modules/MediaClipper/VideoClipper.php \
+ --replace "ffmpeg" "${ffmpeg-headless}/bin/ffmpeg"
+ substituteInPlace modules/Admin/Controllers/VideoClipsController.php \
+ --replace "which ffmpeg" "echo ${ffmpeg-headless}/bin/ffmpeg"
+ '';
+
+ installPhase = ''
+ mkdir -p $out/share/castopod
+ cp -r . $out/share/castopod
+ '';
+
+ passthru.stateDirectory = stateDirectory;
+ passthru.updateScript = ./update.sh;
+
+ meta = with lib; {
+ description = "An open-source hosting platform made for podcasters who want to engage and interact with their audience";
+ homepage = "https://castopod.org";
+ license = licenses.agpl3Only;
+ maintainers = with maintainers; [ alexoundos misuzu ];
+ platforms = platforms.all;
+ };
+}
diff --git a/pkgs/applications/audio/castopod/update.sh b/pkgs/applications/audio/castopod/update.sh
new file mode 100755
index 00000000000000..742788dc8ddfdd
--- /dev/null
+++ b/pkgs/applications/audio/castopod/update.sh
@@ -0,0 +1,89 @@
+#! /usr/bin/env nix-shell
+#! nix-shell -i bash -p curl jq
+set -euo pipefail
+
+nixpkgs="$(git rev-parse --show-toplevel)"
+castopod_nix="$nixpkgs/pkgs/applications/audio/castopod/default.nix"
+
+# https://www.meetup.com/api/guide/#p02-querying-section
+query='
+query allReleases($fullPath: ID!, $first: Int, $last: Int, $before: String, $after: String, $sort: ReleaseSort) {
+ project(fullPath: $fullPath) {
+ id
+ releases(
+ first: $first
+ last: $last
+ before: $before
+ after: $after
+ sort: $sort
+ ) {
+ nodes {
+ ...Release
+ __typename
+ }
+ __typename
+ }
+ __typename
+ }
+}
+
+fragment Release on Release {
+ id
+ name
+ tagName
+ releasedAt
+ createdAt
+ upcomingRelease
+ historicalRelease
+ assets {
+ links {
+ nodes {
+ id
+ name
+ url
+ directAssetUrl
+ linkType
+ __typename
+ }
+ __typename
+ }
+ __typename
+ }
+ __typename
+}
+'
+variables='{
+ "fullPath": "adaures/castopod",
+ "first": 1,
+ "sort": "RELEASED_AT_DESC"
+}'
+
+post=$(cat <<EOF
+{"query": "$(echo $query)", "variables": $(echo $variables)}
+EOF
+)
+
+json="$(curl -s -X POST https://code.castopod.org/api/graphql \
+ -H 'Content-Type: application/json' \
+ -d "$post")"
+
+echo "$json"
+TAG=$(echo $json | jq -r '.data.project.releases.nodes[].tagName')
+ASSET_URL=$(echo $json | jq -r '.data.project.releases.nodes[].assets.links.nodes[].url' | grep .tar.gz$)
+
+CURRENT_VERSION=$(nix eval -f "$nixpkgs" --raw castopod.version)
+VERSION=${TAG:1}
+
+if [[ "$CURRENT_VERSION" == "$VERSION" ]]; then
+ echo "castopod is up-to-date: ${CURRENT_VERSION}"
+ exit 0
+fi
+
+SHA256=$(nix-prefetch-url "$ASSET_URL")
+
+URL=$(echo $ASSET_URL | sed -e 's/[\/&]/\\&/g')
+
+sed -e "s/version =.*;/version = \"$VERSION\";/g" \
+ -e "s/url =.*;/url = \"$URL\";/g" \
+ -e "s/sha256 =.*;/sha256 = \"$SHA256\";/g" \
+ -i "$castopod_nix"
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 57e94596b541d1..fbdcbbfe318cea 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -3583,6 +3583,8 @@ with pkgs;
caroline = callPackage ../development/libraries/caroline { };
+ castopod = callPackage ../applications/audio/castopod { };
+
castget = callPackage ../applications/networking/feedreaders/castget { };
castxml = callPackage ../development/tools/castxml { };
From cf6e43a3dd32d1fce0a315afd365aa90ee19130d Mon Sep 17 00:00:00 2001
From: misuzu <bakalolka@gmail.com>
Date: Fri, 7 Apr 2023 15:59:08 +0300
Subject: [PATCH 3/4] nixos/castopod: init
---
nixos/modules/module-list.nix | 1 +
nixos/modules/services/audio/castopod.md | 22 ++
nixos/modules/services/audio/castopod.nix | 287 ++++++++++++++++++
3 files changed, 312 insertions(+)
create mode 100644 nixos/modules/services/audio/castopod.md
create mode 100644 nixos/modules/services/audio/castopod.nix
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 206d5eaf75dedc..54fd5c7b040314 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -324,6 +324,7 @@
./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/hqplayerd.nix
diff --git a/nixos/modules/services/audio/castopod.md b/nixos/modules/services/audio/castopod.md
new file mode 100644
index 00000000000000..ee8590737a7c73
--- /dev/null
+++ b/nixos/modules/services/audio/castopod.md
@@ -0,0 +1,22 @@
+# 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.
diff --git a/nixos/modules/services/audio/castopod.nix b/nixos/modules/services/audio/castopod.nix
new file mode 100644
index 00000000000000..b782b548914795
--- /dev/null
+++ b/nixos/modules/services/audio/castopod.nix
@@ -0,0 +1,287 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.services.castopod;
+ fpm = config.services.phpfpm.pools.castopod;
+
+ user = "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" = baseURL;
+ "media.root" = "media";
+ "media.storage" = cfg.package.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 = "${cfg.package.stateDirectory}/.env";
+ media = "${cfg.settings."media.storage"}/${cfg.settings."media.root"}";
+ in
+ ''
+ mkdir -p ${cfg.package.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 ${cfg.package.stateDirectory}/salt ]; then
+ openssl rand -base64 33 > ${cfg.package.stateDirectory}/salt
+ fi
+
+ cat <<'EOF' > ${envFile}
+ ${lib.generators.toKeyValue { } cfg.settings}
+ EOF
+
+ echo "analytics.salt=$(cat ${cfg.package.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 "$CREDENTIALS_DIRECTORY/envfile" >> ${envFile}
+ ''}
+
+ php spark castopod:database-update
+ '';
+ serviceConfig = {
+ StateDirectory = "castopod";
+ LoadCredential = lib.mkIf (cfg.environmentFile != null) [
+ "envfile:${cfg.environmentFile}"
+ ];
+ 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 ${cfg.package}/share/castopod/spark tasks:run
+ '';
+ 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;
+ };
+ };
+}

View file

@ -109,5 +109,12 @@
id = 234811;
hash = "sha256-Yz007dCmGl5OxRDMSHv63Ww+LzoQISm9Ttiw0p/6spY=";
}
# castopod: init
# Ne pas mettre à jour sans savoir ce qu'on fait (patch un peu customisé par rapport à upstream)
{
_type = "static";
path = ./castopod.patch;
}
];
}