commit e8a377b93f599ae55bce87f5a20cf248328ade7d
parent b1ae195bc988f1ed3fe62226f1356a2bb73f40d2
Author: mtmn <miro@haravara.org>
Date: Tue, 7 Jul 2026 21:43:00 +0200
nix: add etherpad module
Diffstat:
5 files changed, 174 insertions(+), 0 deletions(-)
diff --git a/hosts/nixaran/configuration.nix b/hosts/nixaran/configuration.nix
Binary files differ.
diff --git a/modules/services/etherpad.nix b/modules/services/etherpad.nix
@@ -0,0 +1,172 @@
+{
+ config,
+ lib,
+ pkgs,
+ haravara,
+ ...
+}: let
+ cfg = config.services.haravara.etherpad;
+ stateDir = "/var/lib/etherpad";
+in {
+ options.services.haravara.etherpad = {
+ enable = lib.mkEnableOption "Etherpad collaborative editor";
+
+ domain = lib.mkOption {
+ type = lib.types.str;
+ description = "Public domain for the Etherpad instance";
+ };
+
+ port = lib.mkOption {
+ type = lib.types.port;
+ default = 9090;
+ description = "Local TCP port for Etherpad to listen on";
+ };
+
+ image = lib.mkOption {
+ type = lib.types.str;
+ default = "etherpad/etherpad@sha256:044e5b58686f22e2c8b792ed0a7c35a83986458e1ee91f12119276c1e2572bce";
+ description = "OCI image reference (pinned by digest) for Etherpad";
+ };
+
+ title = lib.mkOption {
+ type = lib.types.str;
+ default = "Etherpad";
+ description = "Title shown in the Etherpad UI";
+ };
+
+ adminPasswordFile = lib.mkOption {
+ type = lib.types.nullOr lib.types.path;
+ default = null;
+ description = "Path to a file containing the admin user password. If null, no admin user is created.";
+ };
+
+ userPasswordFile = lib.mkOption {
+ type = lib.types.nullOr lib.types.path;
+ default = null;
+ description = "Path to a file containing the shared non-admin user password. If null, no user account is created.";
+ };
+
+ dbType = lib.mkOption {
+ type = lib.types.str;
+ default = "sqlite";
+ description = "UeberDB database driver (e.g. dirty, sqlite, postgres, mysql)";
+ };
+
+ dbFilename = lib.mkOption {
+ type = lib.types.str;
+ default = "etherpad.sqlite3";
+ description = "Database filename when dbType is sqlite or dirty, relative to the state directory";
+ };
+
+ extraEnvironment = lib.mkOption {
+ type = lib.types.attrsOf lib.types.str;
+ default = {};
+ description = "Additional environment variables passed to the Etherpad container";
+ };
+
+ authLabel = haravara.authLabelOption;
+ };
+
+ config = lib.mkIf cfg.enable {
+ # The Etherpad container image runs as UID 5001 (user "etherpad", group 0).
+ # Match that ownership on the host bind-mount so the process can read/write.
+ systemd = {
+ tmpfiles.rules = [
+ "d ${stateDir} 0750 5001 0 -"
+ ];
+
+ services.etherpad-init-state = {
+ description = "Ensure Etherpad state directory ownership";
+ before = ["podman-etherpad.service"];
+ requiredBy = ["podman-etherpad.service"];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ User = "root";
+ Group = "root";
+ ExecStart = pkgs.writeShellScript "etherpad-init-state" ''
+ set -euo pipefail
+ install -d -m 0750 -o 5001 -g 0 ${stateDir}
+ '';
+ };
+ };
+ };
+
+ virtualisation.oci-containers = {
+ backend = "podman";
+
+ containers.etherpad = {
+ inherit (cfg) image;
+ autoStart = true;
+ ports = ["127.0.0.1:${toString cfg.port}:9001"];
+ volumes = [
+ "${stateDir}:/opt/etherpad-lite/var:Z"
+ ];
+ environment =
+ {
+ TITLE = cfg.title;
+ PORT = "9001";
+ IP = "0.0.0.0";
+ TRUST_PROXY = "true";
+ DB_TYPE = cfg.dbType;
+ DB_FILENAME = "/opt/etherpad-lite/var/${cfg.dbFilename}";
+ LOGLEVEL = "INFO";
+ DISABLE_IP_LOGGING = "true";
+ SHOW_SETTINGS_IN_ADMIN_PAGE = "false";
+ SKIN_NAME = "colibris";
+ SKIN_VARIANTS = "super-light-toolbar super-light-editor light-background";
+ DEFAULT_PAD_TEXT = "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!";
+ SHOW_RECENT_PADS = "false";
+ REQUIRE_AUTHENTICATION = "true";
+ REQUIRE_AUTHORIZATION = "false";
+ EDIT_ONLY = "true";
+ AUTHENTICATION_METHOD = "apikey";
+ }
+ // cfg.extraEnvironment;
+ environmentFiles = lib.optionals (cfg.adminPasswordFile != null || cfg.userPasswordFile != null) [
+ "/run/etherpad-admin-password.env"
+ ];
+ };
+ };
+
+ systemd.services = {
+ etherpad-init-password = lib.mkIf (cfg.adminPasswordFile != null || cfg.userPasswordFile != null) {
+ description = "Prepare Etherpad password environment file";
+ before = ["podman-etherpad.service"];
+ requiredBy = ["podman-etherpad.service"];
+ unitConfig = {};
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ User = "root";
+ Group = "root";
+ PrivateTmp = true;
+ UMask = "0077";
+ ExecStart = pkgs.writeShellScript "etherpad-init-password" ''
+ set -euo pipefail
+ : > /run/etherpad-admin-password.env
+ ${lib.optionalString (cfg.adminPasswordFile != null) ''
+ pass="$(cat ${lib.escapeShellArg cfg.adminPasswordFile})"
+ printf 'ADMIN_PASSWORD=%s\n' "$pass" >> /run/etherpad-admin-password.env
+ ''}
+ ${lib.optionalString (cfg.userPasswordFile != null) ''
+ pass="$(cat ${lib.escapeShellArg cfg.userPasswordFile})"
+ printf 'USER_PASSWORD=%s\n' "$pass" >> /run/etherpad-admin-password.env
+ ''}
+ chmod 600 /run/etherpad-admin-password.env
+ '';
+ };
+ };
+
+ podman-etherpad = {
+ after = ["etherpad-init-state.service"];
+ requires = ["etherpad-init-state.service"];
+ };
+ };
+
+ services.caddy.virtualHosts."${cfg.domain}".extraConfig = ''
+ ${haravara.mkAuthImport cfg.authLabel}
+ reverse_proxy 127.0.0.1:${toString cfg.port}
+ '';
+ };
+}
diff --git a/secrets.nix b/secrets.nix
@@ -16,4 +16,6 @@ in {
"secrets/lastfm-password.age".publicKeys = allKeys;
"secrets/corpus.age".publicKeys = allKeys;
"secrets/smtp-alerts-password.age".publicKeys = allKeys;
+ "secrets/etherpad-admin-password.age".publicKeys = allKeys;
+ "secrets/etherpad-user-password.age".publicKeys = allKeys;
}
diff --git a/secrets/etherpad-admin-password.age b/secrets/etherpad-admin-password.age
Binary files differ.
diff --git a/secrets/etherpad-user-password.age b/secrets/etherpad-user-password.age
Binary files differ.