etherpad.nix (5819B)
1 { 2 config, 3 lib, 4 pkgs, 5 haravara, 6 ... 7 }: let 8 cfg = config.services.haravara.etherpad; 9 stateDir = "/var/lib/etherpad"; 10 in { 11 options.services.haravara.etherpad = { 12 enable = lib.mkEnableOption "Etherpad collaborative editor"; 13 14 domain = lib.mkOption { 15 type = lib.types.str; 16 description = "Public domain for the Etherpad instance"; 17 }; 18 19 port = lib.mkOption { 20 type = lib.types.port; 21 default = 9090; 22 description = "Local TCP port for Etherpad to listen on"; 23 }; 24 25 image = lib.mkOption { 26 type = lib.types.str; 27 default = "etherpad/etherpad@sha256:044e5b58686f22e2c8b792ed0a7c35a83986458e1ee91f12119276c1e2572bce"; 28 description = "OCI image reference (pinned by digest) for Etherpad"; 29 }; 30 31 title = lib.mkOption { 32 type = lib.types.str; 33 default = "Etherpad"; 34 description = "Title shown in the Etherpad UI"; 35 }; 36 37 adminPasswordFile = lib.mkOption { 38 type = lib.types.nullOr lib.types.path; 39 default = null; 40 description = "Path to a file containing the admin user password. If null, no admin user is created."; 41 }; 42 43 userPasswordFile = lib.mkOption { 44 type = lib.types.nullOr lib.types.path; 45 default = null; 46 description = "Path to a file containing the shared non-admin user password. If null, no user account is created."; 47 }; 48 49 dbType = lib.mkOption { 50 type = lib.types.str; 51 default = "sqlite"; 52 description = "UeberDB database driver (e.g. dirty, sqlite, postgres, mysql)"; 53 }; 54 55 dbFilename = lib.mkOption { 56 type = lib.types.str; 57 default = "etherpad.sqlite3"; 58 description = "Database filename when dbType is sqlite or dirty, relative to the state directory"; 59 }; 60 61 extraEnvironment = lib.mkOption { 62 type = lib.types.attrsOf lib.types.str; 63 default = {}; 64 description = "Additional environment variables passed to the Etherpad container"; 65 }; 66 67 authLabel = haravara.authLabelOption; 68 }; 69 70 config = lib.mkIf cfg.enable { 71 # The Etherpad container image runs as UID 5001 (user "etherpad", group 0). 72 # Match that ownership on the host bind-mount so the process can read/write. 73 systemd = { 74 tmpfiles.rules = [ 75 "d ${stateDir} 0750 5001 0 -" 76 ]; 77 78 services.etherpad-init-state = { 79 description = "Ensure Etherpad state directory ownership"; 80 before = ["podman-etherpad.service"]; 81 requiredBy = ["podman-etherpad.service"]; 82 serviceConfig = { 83 Type = "oneshot"; 84 RemainAfterExit = true; 85 User = "root"; 86 Group = "root"; 87 ExecStart = pkgs.writeShellScript "etherpad-init-state" '' 88 set -euo pipefail 89 install -d -m 0750 -o 5001 -g 0 ${stateDir} 90 ''; 91 }; 92 }; 93 }; 94 95 virtualisation.oci-containers = { 96 backend = "podman"; 97 98 containers.etherpad = { 99 inherit (cfg) image; 100 autoStart = true; 101 ports = ["127.0.0.1:${toString cfg.port}:9001"]; 102 volumes = [ 103 "${stateDir}:/opt/etherpad-lite/var:Z" 104 ]; 105 environment = 106 { 107 TITLE = cfg.title; 108 PORT = "9001"; 109 IP = "0.0.0.0"; 110 TRUST_PROXY = "true"; 111 DB_TYPE = cfg.dbType; 112 DB_FILENAME = "/opt/etherpad-lite/var/${cfg.dbFilename}"; 113 LOGLEVEL = "INFO"; 114 DISABLE_IP_LOGGING = "true"; 115 SHOW_SETTINGS_IN_ADMIN_PAGE = "false"; 116 SKIN_NAME = "colibris"; 117 SKIN_VARIANTS = "super-light-toolbar super-light-editor light-background"; 118 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!"; 119 SHOW_RECENT_PADS = "false"; 120 REQUIRE_AUTHENTICATION = "true"; 121 REQUIRE_AUTHORIZATION = "false"; 122 EDIT_ONLY = "true"; 123 AUTHENTICATION_METHOD = "apikey"; 124 } 125 // cfg.extraEnvironment; 126 environmentFiles = lib.optionals (cfg.adminPasswordFile != null || cfg.userPasswordFile != null) [ 127 "/run/etherpad-admin-password.env" 128 ]; 129 }; 130 }; 131 132 systemd.services = { 133 etherpad-init-password = lib.mkIf (cfg.adminPasswordFile != null || cfg.userPasswordFile != null) { 134 description = "Prepare Etherpad password environment file"; 135 before = ["podman-etherpad.service"]; 136 requiredBy = ["podman-etherpad.service"]; 137 unitConfig = {}; 138 serviceConfig = { 139 Type = "oneshot"; 140 RemainAfterExit = true; 141 User = "root"; 142 Group = "root"; 143 PrivateTmp = true; 144 UMask = "0077"; 145 ExecStart = pkgs.writeShellScript "etherpad-init-password" '' 146 set -euo pipefail 147 : > /run/etherpad-admin-password.env 148 ${lib.optionalString (cfg.adminPasswordFile != null) '' 149 pass="$(cat ${lib.escapeShellArg cfg.adminPasswordFile})" 150 printf 'ADMIN_PASSWORD=%s\n' "$pass" >> /run/etherpad-admin-password.env 151 ''} 152 ${lib.optionalString (cfg.userPasswordFile != null) '' 153 pass="$(cat ${lib.escapeShellArg cfg.userPasswordFile})" 154 printf 'USER_PASSWORD=%s\n' "$pass" >> /run/etherpad-admin-password.env 155 ''} 156 chmod 600 /run/etherpad-admin-password.env 157 ''; 158 }; 159 }; 160 161 podman-etherpad = { 162 after = ["etherpad-init-state.service"]; 163 requires = ["etherpad-init-state.service"]; 164 }; 165 }; 166 167 services.caddy.virtualHosts."${cfg.domain}".extraConfig = '' 168 ${haravara.mkAuthImport cfg.authLabel} 169 reverse_proxy 127.0.0.1:${toString cfg.port} 170 ''; 171 }; 172 }