From bae1d0276124a50b83f6e4179721f91d5810adae Mon Sep 17 00:00:00 2001
From: sinavir <sinavir@sinavir.fr>
Date: Fri, 27 Oct 2023 14:16:48 +0200
Subject: [PATCH] feat(web01): Add castopod

---
 machines/web01/_configuration.nix             |   1 +
 machines/web01/castopod.nix                   |  36 ++
 .../web01/secrets/castopod-environment_file   | Bin 0 -> 1580 bytes
 machines/web01/secrets/secrets.nix            |   1 +
 patches/castopod.patch                        | 556 ++++++++++++++++++
 patches/default.nix                           |   7 +
 6 files changed, 601 insertions(+)
 create mode 100644 machines/web01/castopod.nix
 create mode 100644 machines/web01/secrets/castopod-environment_file
 create mode 100644 patches/castopod.patch

diff --git a/machines/web01/_configuration.nix b/machines/web01/_configuration.nix
index 66fc366..f284ef2 100644
--- a/machines/web01/_configuration.nix
+++ b/machines/web01/_configuration.nix
@@ -8,6 +8,7 @@ lib.extra.mkConfig {
 
   enabledServices = [
     # List of services to enable
+    "castopod"
     "metis"
     "linkal"
     "ntfy-sh"
diff --git a/machines/web01/castopod.nix b/machines/web01/castopod.nix
new file mode 100644
index 0000000..9f69e14
--- /dev/null
+++ b/machines/web01/castopod.nix
@@ -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;
+  };
+}
diff --git a/machines/web01/secrets/castopod-environment_file b/machines/web01/secrets/castopod-environment_file
new file mode 100644
index 0000000000000000000000000000000000000000..1ecf60ef336343a66665d21dd1c6ebcdb3347e0c
GIT binary patch
literal 1580
zcmZA0{qGwE0mpG%NEQqF!+gk`eo2FxZ%qAq&v!i}kn3IVq1RsTq1S6mMs`oV=exGo
zYkQrj1Ezx*1f2?^@)Qz&P;f#NVh}Rsm&Fi8oyLgYWD=t|{c3c|&=})lvR}S`!7HCn
z@-{tp%T4WBIhhAp`mt>00Sl<IbyGFZ{VmslF$^NBpwL$>swxYz0xT4(1`b#xDV9SQ
z?YezLi+!1oaSK|`ji?C-g~K;1>%5<LI_PlO3F8K^p4$B$M=#U_^mK}x6fQIEdbQRh
zAE?S+EffZ0nW`=p?cO-cJ0@;7W)9g)VTqA-p+3+O%M=G;psa>&L6am+RSHe-qYDpS
zPYj8g6(XjU%QhjK5hDepZic1m(^;96gdFYFlvr($N>z}ij3+GC_=wF?g2ajv2AWhI
zXDl9SA!Vt{sty=~Xui%XdL7J2bVzy$F!NFpLya-1VZC6E@h}*0Qo=bgNXCfZW_z_+
zEfoQ!8UVaho1*QmH4;$(6QH5%*QPD3F($KQNlKGuk!qkRE|+%2rx05hYc+Y)3Pn+f
zIU#{&?P7BI0EKaUdL`#8C{0szrC-rzxmk^72HjRFew6ph6zE~}U;&O$k*)I>U%DXK
zN?o0ri(2di(nwodLqcmLE6R?`9#3)cV(j-&MiDDW)Jz??=1gGTwmelfgLOg5Lktp>
znpXGweura4SBE4(ndFlOL1cwqI;`n>z^WDyWF;1D2pKj*LmiF>TDnF^4);cqEQ7h%
zZ83Gg!a4wpLqd$@Qh?i3Gg?NLHx_u-UqvibNrVtG0H=-#bH*l>HJ{=F501=&kk$zW
zq!N)J5Rm&y8>Ulbnx!q)0|387nV~fB)m9~9@Q^KsaG|9fYj(u~^1+N~fTh{<k`RRR
zoY5O{rjqcY$jhc1@c;%h16Y9hrA2^trIju4AmG-B6C;tuQ;9Vn1JsqbuT%momWr)Z
zJGNw#sX)^L87Ans!{T7%Ec|9>V5!{_l!Bvc^;pJq1+CyP*hAuRu9S^Y3sN|tANC<Y
z4K-1O^+de9P^nF84Fsiyy&9;J#N|qs-L=6@I17?GHJoKLYYI1pM!k?yOXCenPMcXF
zrxMkTvx+%q>a$oKlXfGRT6vHo{iJJS$#gQDQ&Sn}w9S?sEI=<_?$uh{ijcf&VS#o~
zce|rp2KuFv5cJ9s>XMEbjZkA<wA1b4_1TCM474%~5_yHTZORyF)`~~qwPOT?iPMBr
zr%OyKL%G96gbL9AuS9D0k8F7}*Ua4tg*aXD^1oYKRXkf$Y!+ZyDXwZv7|Fv$xpr3b
z8KJhYfoevGopjMUNCr`5-$&j~u(w|O!riBoOU$vepL+v&`S{|m+#~uf^rI8*r|*2}
zp{w7!+IQ~_pS|Jxe*@X)ZfkR0_7W_r8@rEQ+&N~PAKmu&_OYM*aDV#DE8Zj4(ZkV+
z!#ART`Tf7XzH#jz4*b#o#O~+Mzxep+OWiY%oz=_-H^2R!3GUs*Zr*oc|IX9Zn-Bl{
zx<kKAKlR!(@~^8WU*CVnTZg`R)sbzPfBvn58xMBwy8VrhKgGPd@!c<nhqC0gv&XL6
zJpGwl=oif29D3gq=J$^8Z2Z{T{U`j5=WjU`ed}unFZ}5^JG}MkFP=Pb_qk635A59c
zg?}(ly*?=_VRX$s58rY2;;&A!Up@Ds!$;nI@clo3W%Weu%;vK@7k|28E)Q%LPe1w2
zQ!jUJPR>1hs(v*79k<IJf6_<JT>p1rwf(LW`j;M_Zr<~=7k^P2+dImc4^A#D&%aH(
vzWBlE?eSG_zVy+1k6!#h`QhLzckTQ0o%cWYomUUM8NcJd|M~5Wx7+^%5#u$)

literal 0
HcmV?d00001

diff --git a/machines/web01/secrets/secrets.nix b/machines/web01/secrets/secrets.nix
index 75cdc98..87eaede 100644
--- a/machines/web01/secrets/secrets.nix
+++ b/machines/web01/secrets/secrets.nix
@@ -9,4 +9,5 @@ in lib.setDefault { inherit publicKeys; } [
   "plausible_secret-key-base-file"
   "plausible_release-cookie-file"
   "_smtp-password-file"
+  "castopod-environment_file"
 ]
diff --git a/patches/castopod.patch b/patches/castopod.patch
new file mode 100644
index 0000000..73df444
--- /dev/null
+++ b/patches/castopod.patch
@@ -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;
++    };
++  };
++}
diff --git a/patches/default.nix b/patches/default.nix
index a818d00..5930666 100644
--- a/patches/default.nix
+++ b/patches/default.nix
@@ -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;
+    }
   ];
 }