at 23.11-pre 9.8 kB view raw
1{ lib, pkgs, config, ... }: 2 3with lib; 4 5let 6 cfg = config.services.plausible; 7 8in { 9 options.services.plausible = { 10 enable = mkEnableOption (lib.mdDoc "plausible"); 11 12 package = mkPackageOptionMD pkgs "plausible" { }; 13 14 releaseCookiePath = mkOption { 15 type = with types; either str path; 16 description = lib.mdDoc '' 17 The path to the file with release cookie. (used for remote connection to the running node). 18 ''; 19 }; 20 21 adminUser = { 22 name = mkOption { 23 default = "admin"; 24 type = types.str; 25 description = lib.mdDoc '' 26 Name of the admin user that plausible will created on initial startup. 27 ''; 28 }; 29 30 email = mkOption { 31 type = types.str; 32 example = "admin@localhost"; 33 description = lib.mdDoc '' 34 Email-address of the admin-user. 35 ''; 36 }; 37 38 passwordFile = mkOption { 39 type = types.either types.str types.path; 40 description = lib.mdDoc '' 41 Path to the file which contains the password of the admin user. 42 ''; 43 }; 44 45 activate = mkEnableOption (lib.mdDoc "activating the freshly created admin-user"); 46 }; 47 48 database = { 49 clickhouse = { 50 setup = mkEnableOption (lib.mdDoc "creating a clickhouse instance") // { default = true; }; 51 url = mkOption { 52 default = "http://localhost:8123/default"; 53 type = types.str; 54 description = lib.mdDoc '' 55 The URL to be used to connect to `clickhouse`. 56 ''; 57 }; 58 }; 59 postgres = { 60 setup = mkEnableOption (lib.mdDoc "creating a postgresql instance") // { default = true; }; 61 dbname = mkOption { 62 default = "plausible"; 63 type = types.str; 64 description = lib.mdDoc '' 65 Name of the database to use. 66 ''; 67 }; 68 socket = mkOption { 69 default = "/run/postgresql"; 70 type = types.str; 71 description = lib.mdDoc '' 72 Path to the UNIX domain-socket to communicate with `postgres`. 73 ''; 74 }; 75 }; 76 }; 77 78 server = { 79 disableRegistration = mkOption { 80 default = true; 81 type = types.bool; 82 description = lib.mdDoc '' 83 Whether to prohibit creating an account in plausible's UI. 84 ''; 85 }; 86 secretKeybaseFile = mkOption { 87 type = types.either types.path types.str; 88 description = lib.mdDoc '' 89 Path to the secret used by the `phoenix`-framework. Instructions 90 how to generate one are documented in the 91 [ 92 framework docs](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content). 93 ''; 94 }; 95 port = mkOption { 96 default = 8000; 97 type = types.port; 98 description = lib.mdDoc '' 99 Port where the service should be available. 100 ''; 101 }; 102 baseUrl = mkOption { 103 type = types.str; 104 description = lib.mdDoc '' 105 Public URL where plausible is available. 106 107 Note that `/path` components are currently ignored: 108 [ 109 https://github.com/plausible/analytics/issues/1182 110 ](https://github.com/plausible/analytics/issues/1182). 111 ''; 112 }; 113 }; 114 115 mail = { 116 email = mkOption { 117 default = "hello@plausible.local"; 118 type = types.str; 119 description = lib.mdDoc '' 120 The email id to use for as *from* address of all communications 121 from Plausible. 122 ''; 123 }; 124 smtp = { 125 hostAddr = mkOption { 126 default = "localhost"; 127 type = types.str; 128 description = lib.mdDoc '' 129 The host address of your smtp server. 130 ''; 131 }; 132 hostPort = mkOption { 133 default = 25; 134 type = types.port; 135 description = lib.mdDoc '' 136 The port of your smtp server. 137 ''; 138 }; 139 user = mkOption { 140 default = null; 141 type = types.nullOr types.str; 142 description = lib.mdDoc '' 143 The username/email in case SMTP auth is enabled. 144 ''; 145 }; 146 passwordFile = mkOption { 147 default = null; 148 type = with types; nullOr (either str path); 149 description = lib.mdDoc '' 150 The path to the file with the password in case SMTP auth is enabled. 151 ''; 152 }; 153 enableSSL = mkEnableOption (lib.mdDoc "SSL when connecting to the SMTP server"); 154 retries = mkOption { 155 type = types.ints.unsigned; 156 default = 2; 157 description = lib.mdDoc '' 158 Number of retries to make until mailer gives up. 159 ''; 160 }; 161 }; 162 }; 163 }; 164 165 config = mkIf cfg.enable { 166 assertions = [ 167 { assertion = cfg.adminUser.activate -> cfg.database.postgres.setup; 168 message = '' 169 Unable to automatically activate the admin-user if no locally managed DB for 170 postgres (`services.plausible.database.postgres.setup') is enabled! 171 ''; 172 } 173 ]; 174 175 services.postgresql = mkIf cfg.database.postgres.setup { 176 enable = true; 177 }; 178 179 services.clickhouse = mkIf cfg.database.clickhouse.setup { 180 enable = true; 181 }; 182 183 services.epmd.enable = true; 184 185 environment.systemPackages = [ cfg.package ]; 186 187 systemd.services = mkMerge [ 188 { 189 plausible = { 190 inherit (cfg.package.meta) description; 191 documentation = [ "https://plausible.io/docs/self-hosting" ]; 192 wantedBy = [ "multi-user.target" ]; 193 after = optional cfg.database.clickhouse.setup "clickhouse.service" 194 ++ optionals cfg.database.postgres.setup [ 195 "postgresql.service" 196 "plausible-postgres.service" 197 ]; 198 requires = optional cfg.database.clickhouse.setup "clickhouse.service" 199 ++ optionals cfg.database.postgres.setup [ 200 "postgresql.service" 201 "plausible-postgres.service" 202 ]; 203 204 environment = { 205 # NixOS specific option to avoid that it's trying to write into its store-path. 206 # See also https://github.com/lau/tzdata#data-directory-and-releases 207 STORAGE_DIR = "/var/lib/plausible/elixir_tzdata"; 208 209 # Configuration options from 210 # https://plausible.io/docs/self-hosting-configuration 211 PORT = toString cfg.server.port; 212 DISABLE_REGISTRATION = boolToString cfg.server.disableRegistration; 213 214 RELEASE_TMP = "/var/lib/plausible/tmp"; 215 # Home is needed to connect to the node with iex 216 HOME = "/var/lib/plausible"; 217 218 ADMIN_USER_NAME = cfg.adminUser.name; 219 ADMIN_USER_EMAIL = cfg.adminUser.email; 220 221 DATABASE_SOCKET_DIR = cfg.database.postgres.socket; 222 DATABASE_NAME = cfg.database.postgres.dbname; 223 CLICKHOUSE_DATABASE_URL = cfg.database.clickhouse.url; 224 225 BASE_URL = cfg.server.baseUrl; 226 227 MAILER_EMAIL = cfg.mail.email; 228 SMTP_HOST_ADDR = cfg.mail.smtp.hostAddr; 229 SMTP_HOST_PORT = toString cfg.mail.smtp.hostPort; 230 SMTP_RETRIES = toString cfg.mail.smtp.retries; 231 SMTP_HOST_SSL_ENABLED = boolToString cfg.mail.smtp.enableSSL; 232 233 SELFHOST = "true"; 234 } // (optionalAttrs (cfg.mail.smtp.user != null) { 235 SMTP_USER_NAME = cfg.mail.smtp.user; 236 }); 237 238 path = [ cfg.package ] 239 ++ optional cfg.database.postgres.setup config.services.postgresql.package; 240 script = '' 241 export CONFIG_DIR=$CREDENTIALS_DIRECTORY 242 243 export RELEASE_COOKIE="$(< $CREDENTIALS_DIRECTORY/RELEASE_COOKIE )" 244 245 # setup 246 ${cfg.package}/createdb.sh 247 ${cfg.package}/migrate.sh 248 ${optionalString cfg.adminUser.activate '' 249 if ! ${cfg.package}/init-admin.sh | grep 'already exists'; then 250 psql -d plausible <<< "UPDATE users SET email_verified=true;" 251 fi 252 ''} 253 254 exec plausible start 255 ''; 256 257 serviceConfig = { 258 DynamicUser = true; 259 PrivateTmp = true; 260 WorkingDirectory = "/var/lib/plausible"; 261 StateDirectory = "plausible"; 262 LoadCredential = [ 263 "ADMIN_USER_PWD:${cfg.adminUser.passwordFile}" 264 "SECRET_KEY_BASE:${cfg.server.secretKeybaseFile}" 265 "RELEASE_COOKIE:${cfg.releaseCookiePath}" 266 ] ++ lib.optionals (cfg.mail.smtp.passwordFile != null) [ "SMTP_USER_PWD:${cfg.mail.smtp.passwordFile}"]; 267 }; 268 }; 269 } 270 (mkIf cfg.database.postgres.setup { 271 # `plausible' requires the `citext'-extension. 272 plausible-postgres = { 273 after = [ "postgresql.service" ]; 274 partOf = [ "plausible.service" ]; 275 serviceConfig = { 276 Type = "oneshot"; 277 User = config.services.postgresql.superUser; 278 RemainAfterExit = true; 279 }; 280 script = with cfg.database.postgres; '' 281 PSQL() { 282 ${config.services.postgresql.package}/bin/psql --port=5432 "$@" 283 } 284 # check if the database already exists 285 if ! PSQL -lqt | ${pkgs.coreutils}/bin/cut -d \| -f 1 | ${pkgs.gnugrep}/bin/grep -qw ${dbname} ; then 286 PSQL -tAc "CREATE ROLE plausible WITH LOGIN;" 287 PSQL -tAc "CREATE DATABASE ${dbname} WITH OWNER plausible;" 288 PSQL -d ${dbname} -tAc "CREATE EXTENSION IF NOT EXISTS citext;" 289 fi 290 ''; 291 }; 292 }) 293 ]; 294 }; 295 296 meta.maintainers = with maintainers; [ ma27 ]; 297 meta.doc = ./plausible.md; 298}