From 9d7860c7e7830d9bf82733cecd443ff167dc2174 Mon Sep 17 00:00:00 2001 From: Alexander Tomokhov 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 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 < 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.nix | 299 ++++++++++++++++++ 2 files changed, 299 insertions(+) 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.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,298 @@ +{ 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.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; + resolver.addresses = [ "127.0.0.53" ]; + virtualHosts."${cfg.localDomain}" = { + root = lib.mkForce "${cfg.package}/share/castopod/public"; + + extraConfig = '' + client_max_body_size 512m; + try_files $uri $uri/ /index.php?$args; + index index.php index.html; + ''; + + locations."@force_get" = { + extraConfig = '' + recursive_error_pages on; + proxy_method GET; + proxy_pass https://podcasts.dgnum.eu/$request_uri; + ''; + }; + + 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 = '' + error_page 550 = @force_get; + if ($request_method = HEAD) { return 550; } + 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; + }; + }; +}