grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / admin / pgadmin.nix
blobc9da556b763df3bfa1c80b1f336d0fef6dd1d2ac
1 { config, lib, pkgs, ... }:
2 let
3   cfg = config.services.pgadmin;
5   _base = with lib.types; [ int bool str ];
6   base = with lib.types; oneOf ([ (listOf (oneOf _base)) (attrsOf (oneOf _base)) ] ++ _base);
8   formatAttrset = attr:
9     "{${lib.concatStringsSep "\n" (lib.mapAttrsToList (key: value: "${builtins.toJSON key}: ${formatPyValue value},") attr)}}";
11   formatPyValue = value:
12     if builtins.isString value then builtins.toJSON value
13     else if value ? _expr then value._expr
14     else if builtins.isInt value then toString value
15     else if builtins.isBool value then (if value then "True" else "False")
16     else if builtins.isAttrs value then (formatAttrset value)
17     else if builtins.isList value then "[${lib.concatStringsSep "\n" (map (v: "${formatPyValue v},") value)}]"
18     else throw "Unrecognized type";
20   formatPy = attrs:
21     lib.concatStringsSep "\n" (lib.mapAttrsToList (key: value: "${key} = ${formatPyValue value}") attrs);
23   pyType = with lib.types; attrsOf (oneOf [ (attrsOf base) (listOf base) base ]);
26   options.services.pgadmin = {
27     enable = lib.mkEnableOption "PostgreSQL Admin 4";
29     port = lib.mkOption {
30       description = "Port for pgadmin4 to run on";
31       type = lib.types.port;
32       default = 5050;
33     };
35     package = lib.mkPackageOption pkgs "pgadmin4" { };
37     initialEmail = lib.mkOption {
38       description = "Initial email for the pgAdmin account";
39       type = lib.types.str;
40     };
42     initialPasswordFile = lib.mkOption {
43       description = ''
44         Initial password file for the pgAdmin account. Minimum length by default is 6.
45         Please see `services.pgadmin.minimumPasswordLength`.
46         NOTE: Should be string not a store path, to prevent the password from being world readable
47       '';
48       type = lib.types.path;
49     };
51     minimumPasswordLength = lib.mkOption {
52       description = "Minimum length of the password";
53       type = lib.types.int;
54       default = 6;
55     };
57     emailServer = {
58       enable = lib.mkOption {
59         description = ''
60           Enable SMTP email server. This is necessary, if you want to use password recovery or change your own password
61         '';
62         type = lib.types.bool;
63         default = false;
64       };
65       address = lib.mkOption {
66         description = "SMTP server for email delivery";
67         type = lib.types.str;
68         default = "localhost";
69       };
70       port = lib.mkOption {
71         description = "SMTP server port for email delivery";
72         type = lib.types.port;
73         default = 25;
74       };
75       useSSL = lib.mkOption {
76         description = "SMTP server should use SSL";
77         type = lib.types.bool;
78         default = false;
79       };
80       useTLS = lib.mkOption {
81         description = "SMTP server should use TLS";
82         type = lib.types.bool;
83         default = false;
84       };
85       username = lib.mkOption {
86         description = "SMTP server username for email delivery";
87         type = lib.types.nullOr lib.types.str;
88         default = null;
89       };
90       sender = lib.mkOption {
91         description = ''
92           SMTP server sender email for email delivery. Some servers require this to be a valid email address from that server
93         '';
94         type = lib.types.str;
95         example = "noreply@example.com";
96       };
97       passwordFile = lib.mkOption {
98         description = ''
99           Password for SMTP email account.
100           NOTE: Should be string not a store path, to prevent the password from being world readable
101         '';
102         type = lib.types.path;
103       };
104     };
106     openFirewall = lib.mkEnableOption "firewall passthrough for pgadmin4";
108     settings = lib.mkOption {
109       description = ''
110         Settings for pgadmin4.
111         [Documentation](https://www.pgadmin.org/docs/pgadmin4/development/config_py.html)
112       '';
113       type = pyType;
114       default = { };
115     };
116   };
118   config = lib.mkIf (cfg.enable) {
119     networking.firewall.allowedTCPPorts = lib.mkIf (cfg.openFirewall) [ cfg.port ];
121     services.pgadmin.settings = {
122       DEFAULT_SERVER_PORT = cfg.port;
123       PASSWORD_LENGTH_MIN = cfg.minimumPasswordLength;
124       SERVER_MODE = true;
125       UPGRADE_CHECK_ENABLED = false;
126     } // (lib.optionalAttrs cfg.openFirewall {
127       DEFAULT_SERVER = lib.mkDefault "::";
128     }) // (lib.optionalAttrs cfg.emailServer.enable {
129       MAIL_SERVER = cfg.emailServer.address;
130       MAIL_PORT = cfg.emailServer.port;
131       MAIL_USE_SSL = cfg.emailServer.useSSL;
132       MAIL_USE_TLS = cfg.emailServer.useTLS;
133       MAIL_USERNAME = cfg.emailServer.username;
134       SECURITY_EMAIL_SENDER = cfg.emailServer.sender;
135     });
137     systemd.services.pgadmin = {
138       wantedBy = [ "multi-user.target" ];
139       after = [ "network.target" ];
140       requires = [ "network.target" ];
141       # we're adding this optionally so just in case there's any race it'll be caught
142       # in case postgres doesn't start, pgadmin will just start normally
143       wants = [ "postgresql.service" ];
145       path = [ config.services.postgresql.package pkgs.coreutils pkgs.bash ];
147       preStart = ''
148         # NOTE: this is idempotent (aka running it twice has no effect)
149         # Check here for password length to prevent pgadmin from starting
150         # and presenting a hard to find error message
151         # see https://github.com/NixOS/nixpkgs/issues/270624
152         PW_FILE="$CREDENTIALS_DIRECTORY/initial_password"
153         PW_LENGTH=$(wc -m < "$PW_FILE")
154         if [ $PW_LENGTH -lt ${toString cfg.minimumPasswordLength} ]; then
155             echo "Password must be at least ${toString cfg.minimumPasswordLength} characters long"
156             exit 1
157         fi
158         (
159           # Email address:
160           echo ${lib.escapeShellArg cfg.initialEmail}
162           # file might not contain newline. echo hack fixes that.
163           PW=$(cat "$PW_FILE")
165           # Password:
166           echo "$PW"
167           # Retype password:
168           echo "$PW"
169         ) | ${cfg.package}/bin/pgadmin4-cli setup-db
170       '';
172       restartTriggers = [
173         "/etc/pgadmin/config_system.py"
174       ];
176       serviceConfig = {
177         User = "pgadmin";
178         DynamicUser = true;
179         LogsDirectory = "pgadmin";
180         StateDirectory = "pgadmin";
181         ExecStart = "${cfg.package}/bin/pgadmin4";
182         LoadCredential = [ "initial_password:${cfg.initialPasswordFile}" ]
183           ++ lib.optional cfg.emailServer.enable "email_password:${cfg.emailServer.passwordFile}";
184       };
185     };
187     users.users.pgadmin = {
188       isSystemUser = true;
189       group = "pgadmin";
190     };
192     users.groups.pgadmin = { };
194     environment.etc."pgadmin/config_system.py" = {
195       text = lib.optionalString cfg.emailServer.enable ''
196         import os
197         with open(os.path.join(os.environ['CREDENTIALS_DIRECTORY'], 'email_password')) as f:
198           pw = f.read()
199         MAIL_PASSWORD = pw
200       '' + formatPy cfg.settings;
201       mode = "0600";
202       user = "pgadmin";
203       group = "pgadmin";
204     };
205   };