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