2023-06-20 00:54:46 +03:00
|
|
|
# Based on https://github.com/cachix/devenv/blob/main/src/modules/services/postgres.nix
|
2023-07-17 23:45:33 +03:00
|
|
|
{ name, config, pkgs, lib, ... }:
|
|
|
|
with lib.types; let
|
2023-06-20 00:54:46 +03:00
|
|
|
inherit (lib) types;
|
|
|
|
in
|
|
|
|
{
|
2023-07-17 23:45:33 +03:00
|
|
|
options = {
|
|
|
|
enable = lib.mkEnableOption name;
|
2023-06-20 00:54:46 +03:00
|
|
|
|
2023-12-24 22:05:03 +03:00
|
|
|
package = lib.mkOption {
|
|
|
|
type = types.package;
|
|
|
|
description = "Which package of postgresql to use";
|
|
|
|
default = pkgs.postgresql;
|
|
|
|
defaultText = lib.literalExpression "pkgs.postgresql";
|
|
|
|
apply = postgresPkg:
|
|
|
|
if config.extensions != null then
|
|
|
|
if builtins.hasAttr "withPackages" postgresPkg
|
|
|
|
then postgresPkg.withPackages config.extensions
|
|
|
|
else
|
|
|
|
builtins.throw ''
|
|
|
|
Cannot add extensions to the PostgreSQL package.
|
|
|
|
`services.postgres.package` is missing the `withPackages` attribute. Did you already add extensions to the package?
|
|
|
|
''
|
|
|
|
else postgresPkg;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
extensions = lib.mkOption {
|
|
|
|
type = with types; nullOr (functionTo (listOf package));
|
|
|
|
default = null;
|
|
|
|
example = lib.literalExpression ''
|
|
|
|
extensions: [
|
|
|
|
extensions.pg_cron
|
|
|
|
extensions.postgis
|
|
|
|
extensions.timescaledb
|
|
|
|
];
|
|
|
|
'';
|
|
|
|
description = ''
|
|
|
|
Additional PostgreSQL extensions to install.
|
2023-06-20 00:54:46 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
The available extensions are:
|
2023-06-20 00:54:46 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
${lib.concatLines (builtins.map (x: "- " + x) (builtins.attrNames pkgs.postgresql.pkgs))}
|
|
|
|
'';
|
|
|
|
};
|
2023-06-20 00:54:46 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
dataDir = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
default = "./data/${name}";
|
|
|
|
description = "The DB data directory";
|
|
|
|
};
|
|
|
|
|
2024-01-18 20:40:16 +03:00
|
|
|
socketDir = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
default = config.dataDir;
|
|
|
|
description = "The DB socket directory";
|
|
|
|
};
|
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
hbaConf =
|
|
|
|
let
|
|
|
|
hbaConfSubmodule = lib.types.submodule {
|
|
|
|
options = {
|
|
|
|
type = lib.mkOption { type = lib.types.str; };
|
|
|
|
database = lib.mkOption { type = lib.types.str; };
|
|
|
|
user = lib.mkOption { type = lib.types.str; };
|
|
|
|
address = lib.mkOption { type = lib.types.str; };
|
|
|
|
method = lib.mkOption { type = lib.types.str; };
|
|
|
|
};
|
2023-06-20 00:54:46 +03:00
|
|
|
};
|
2023-07-17 23:45:33 +03:00
|
|
|
in
|
|
|
|
lib.mkOption {
|
|
|
|
type = lib.types.listOf hbaConfSubmodule;
|
|
|
|
default = [ ];
|
|
|
|
description = ''
|
|
|
|
A list of objects that represent the entries in the pg_hba.conf file.
|
2023-07-05 08:53:56 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
Each object has sub-options for type, database, user, address, and method.
|
2023-07-05 08:53:56 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
See the official PostgreSQL documentation for more information:
|
|
|
|
https://www.postgresql.org/docs/current/auth-pg-hba-conf.html
|
|
|
|
'';
|
|
|
|
example = [
|
|
|
|
{ type = "local"; database = "all"; user = "postgres"; address = ""; method = "md5"; }
|
|
|
|
{ type = "host"; database = "all"; user = "all"; address = "0.0.0.0/0"; method = "md5"; }
|
|
|
|
];
|
|
|
|
};
|
|
|
|
hbaConfFile =
|
|
|
|
let
|
|
|
|
# Default pg_hba.conf entries
|
|
|
|
defaultHbaConf = [
|
|
|
|
{ type = "local"; database = "all"; user = "all"; address = ""; method = "trust"; }
|
|
|
|
{ type = "host"; database = "all"; user = "all"; address = "127.0.0.1/32"; method = "trust"; }
|
|
|
|
{ type = "host"; database = "all"; user = "all"; address = "::1/128"; method = "trust"; }
|
|
|
|
{ type = "local"; database = "replication"; user = "all"; address = ""; method = "trust"; }
|
|
|
|
{ type = "host"; database = "replication"; user = "all"; address = "127.0.0.1/32"; method = "trust"; }
|
|
|
|
{ type = "host"; database = "replication"; user = "all"; address = "::1/128"; method = "trust"; }
|
|
|
|
];
|
2023-07-05 08:53:56 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
# Merge the default pg_hba.conf entries with the user-defined entries
|
|
|
|
hbaConf = defaultHbaConf ++ config.hbaConf;
|
2023-07-05 08:53:56 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
# Convert the pgHbaConf array to a string
|
|
|
|
hbaConfString = ''
|
|
|
|
# Generated by Nix
|
|
|
|
${"# TYPE\tDATABASE\tUSER\tADDRESS\tMETHOD\n"}
|
|
|
|
${lib.concatMapStrings (cnf: " ${cnf.type}\t${cnf.database}\t${cnf.user}\t${cnf.address}\t${cnf.method}\n") hbaConf}
|
|
|
|
'';
|
|
|
|
in
|
|
|
|
lib.mkOption {
|
|
|
|
type = lib.types.package;
|
|
|
|
internal = true;
|
|
|
|
readOnly = true;
|
|
|
|
description = "The `pg_hba.conf` file.";
|
|
|
|
default = pkgs.writeText "pg_hba.conf" hbaConfString;
|
|
|
|
};
|
2023-07-05 08:53:56 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
listen_addresses = lib.mkOption {
|
|
|
|
type = lib.types.str;
|
|
|
|
description = "Listen address";
|
|
|
|
default = "";
|
|
|
|
example = "127.0.0.1";
|
|
|
|
};
|
2023-07-05 08:53:56 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
port = lib.mkOption {
|
|
|
|
type = lib.types.port;
|
|
|
|
default = 5432;
|
|
|
|
description = ''
|
|
|
|
The TCP port to accept connections.
|
|
|
|
'';
|
|
|
|
};
|
2023-06-20 00:54:46 +03:00
|
|
|
|
2023-07-22 01:12:04 +03:00
|
|
|
superuser = lib.mkOption {
|
|
|
|
type = lib.types.nullOr lib.types.str;
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Name of superuser.
|
|
|
|
null defaults to $USER
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
createDatabase = lib.mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
|
|
|
description = ''
|
|
|
|
Create a database named like current user on startup. Only applies when initialDatabases is an empty list.
|
|
|
|
'';
|
|
|
|
};
|
2023-06-20 00:54:46 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
initdbArgs = lib.mkOption {
|
|
|
|
type = types.listOf types.lines;
|
|
|
|
default = [ "--locale=C" "--encoding=UTF8" ];
|
|
|
|
example = [ "--data-checksums" "--allow-group-access" ];
|
|
|
|
description = ''
|
|
|
|
Additional arguments passed to `initdb` during data dir
|
|
|
|
initialisation.
|
|
|
|
'';
|
|
|
|
};
|
2023-06-20 00:54:46 +03:00
|
|
|
|
2023-07-22 01:08:57 +03:00
|
|
|
defaultSettings =
|
|
|
|
lib.mkOption {
|
|
|
|
type = with lib.types; attrsOf (oneOf [ bool float int str ]);
|
|
|
|
internal = true;
|
|
|
|
readOnly = true;
|
|
|
|
description = ''
|
|
|
|
Default configuration for `postgresql.conf`. `settings` can override these values.
|
|
|
|
'';
|
|
|
|
default = {
|
|
|
|
listen_addresses = config.listen_addresses;
|
|
|
|
port = config.port;
|
2024-01-18 20:40:16 +03:00
|
|
|
unix_socket_directories = config.socketDir;
|
2023-07-22 01:08:57 +03:00
|
|
|
hba_file = "${config.hbaConfFile}";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
settings =
|
|
|
|
lib.mkOption {
|
|
|
|
type = with lib.types; attrsOf (oneOf [ bool float int str ]);
|
|
|
|
default = { };
|
|
|
|
description = ''
|
|
|
|
PostgreSQL configuration. Refer to
|
|
|
|
<https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
|
|
|
|
for an overview of `postgresql.conf`.
|
|
|
|
|
|
|
|
String values will automatically be enclosed in single quotes. Single quotes will be
|
|
|
|
escaped with two single quotes as described by the upstream documentation linked above.
|
|
|
|
'';
|
|
|
|
default = {
|
|
|
|
listen_addresses = config.listen_addresses;
|
|
|
|
port = config.port;
|
2024-01-18 20:40:16 +03:00
|
|
|
unix_socket_directories = lib.mkDefault config.socketDir;
|
2023-07-17 23:45:33 +03:00
|
|
|
hba_file = "${config.hbaConfFile}";
|
2023-06-20 00:54:46 +03:00
|
|
|
};
|
2023-07-17 23:45:33 +03:00
|
|
|
example = lib.literalExpression ''
|
|
|
|
{
|
|
|
|
log_connections = true;
|
|
|
|
log_statement = "all";
|
|
|
|
logging_collector = true
|
|
|
|
log_disconnections = true
|
|
|
|
log_destination = lib.mkForce "syslog";
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
};
|
2023-06-20 00:54:46 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
initialDatabases = lib.mkOption {
|
|
|
|
type = types.listOf (types.submodule {
|
|
|
|
options = {
|
|
|
|
name = lib.mkOption {
|
|
|
|
type = types.str;
|
2023-07-05 08:53:56 +03:00
|
|
|
description = ''
|
2023-07-17 23:45:33 +03:00
|
|
|
The name of the database to create.
|
2023-07-05 08:53:56 +03:00
|
|
|
'';
|
2023-07-17 23:45:33 +03:00
|
|
|
};
|
2024-01-13 20:18:46 +03:00
|
|
|
schemas = lib.mkOption {
|
|
|
|
type = types.nullOr (types.listOf types.path);
|
2023-07-17 23:45:33 +03:00
|
|
|
default = null;
|
|
|
|
description = ''
|
2024-01-13 20:18:46 +03:00
|
|
|
The initial list of schemas for the database; if null (the default),
|
2023-07-17 23:45:33 +03:00
|
|
|
an empty database is created.
|
2023-07-05 08:53:56 +03:00
|
|
|
'';
|
2023-06-20 00:54:46 +03:00
|
|
|
};
|
|
|
|
};
|
2023-07-17 23:45:33 +03:00
|
|
|
});
|
|
|
|
default = [ ];
|
|
|
|
description = ''
|
|
|
|
List of database names and their initial schemas that should be used to create databases on the first startup
|
|
|
|
of Postgres. The schema attribute is optional: If not specified, an empty database is created.
|
|
|
|
'';
|
|
|
|
example = lib.literalExpression ''
|
|
|
|
[
|
|
|
|
{
|
|
|
|
name = "foodatabase";
|
2024-01-13 20:18:46 +03:00
|
|
|
schemas = [ ./fooschemas ./bar.sql ];
|
2023-07-17 23:45:33 +03:00
|
|
|
}
|
|
|
|
{ name = "bardatabase"; }
|
|
|
|
]
|
|
|
|
'';
|
|
|
|
};
|
2023-06-20 00:54:46 +03:00
|
|
|
|
2023-07-17 23:45:33 +03:00
|
|
|
initialScript = lib.mkOption {
|
|
|
|
type = types.submodule ({ config, ... }: {
|
|
|
|
options = {
|
|
|
|
before = lib.mkOption {
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
SQL commands to run before the database initialization.
|
|
|
|
'';
|
|
|
|
example = lib.literalExpression ''
|
|
|
|
CREATE USER postgres SUPERUSER;
|
|
|
|
CREATE USER bar;
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
after = lib.mkOption {
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
SQL commands to run after the database initialization.
|
|
|
|
'';
|
|
|
|
example = lib.literalExpression ''
|
|
|
|
CREATE TABLE users (
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
name VARCHAR(50) NOT NULL,
|
|
|
|
email VARCHAR(50) NOT NULL UNIQUE
|
|
|
|
);
|
|
|
|
'';
|
|
|
|
};
|
2023-06-20 00:54:46 +03:00
|
|
|
};
|
2023-07-17 23:45:33 +03:00
|
|
|
});
|
|
|
|
default = { before = null; after = null; };
|
|
|
|
description = ''
|
|
|
|
Initial SQL commands to run during database initialization. This can be multiple
|
|
|
|
SQL expressions separated by a semi-colon.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
outputs.settings = lib.mkOption {
|
|
|
|
type = types.deferredModule;
|
|
|
|
internal = true;
|
|
|
|
readOnly = true;
|
|
|
|
default =
|
|
|
|
{
|
|
|
|
processes = {
|
|
|
|
# DB initialization
|
2023-10-04 20:20:59 +03:00
|
|
|
"${name}-init" =
|
2023-07-10 18:40:52 +03:00
|
|
|
let
|
2023-12-24 22:05:03 +03:00
|
|
|
setupScript = import ./setup-script.nix { inherit config pkgs lib; };
|
2023-07-17 23:45:33 +03:00
|
|
|
in
|
2023-10-04 20:20:59 +03:00
|
|
|
{
|
2023-12-24 22:05:03 +03:00
|
|
|
command = setupScript;
|
2023-10-04 20:20:59 +03:00
|
|
|
namespace = name;
|
|
|
|
};
|
2023-07-17 23:45:33 +03:00
|
|
|
|
|
|
|
# DB process
|
|
|
|
${name} =
|
|
|
|
let
|
|
|
|
startScript = pkgs.writeShellApplication {
|
|
|
|
name = "start-postgres";
|
2023-12-24 22:05:03 +03:00
|
|
|
runtimeInputs = [ config.package pkgs.coreutils ];
|
2023-07-17 23:45:33 +03:00
|
|
|
text = ''
|
2023-12-24 22:05:03 +03:00
|
|
|
set -euo pipefail
|
2023-07-17 23:45:33 +03:00
|
|
|
PGDATA=$(readlink -f "${config.dataDir}")
|
2024-01-18 20:40:16 +03:00
|
|
|
PGSOCKETDIR=$(readlink -f "${config.socketDir}")
|
2023-07-17 23:45:33 +03:00
|
|
|
export PGDATA
|
2024-01-18 20:40:16 +03:00
|
|
|
postgres -k "$PGSOCKETDIR"
|
2023-07-17 23:45:33 +03:00
|
|
|
'';
|
|
|
|
};
|
2023-07-22 01:12:04 +03:00
|
|
|
pg_isreadyArgs = [
|
2024-01-18 20:40:16 +03:00
|
|
|
"-h $(readlink -f \"${config.socketDir}\")"
|
2023-07-22 01:12:04 +03:00
|
|
|
"-p ${toString config.port}"
|
|
|
|
"-d template1"
|
|
|
|
] ++ (lib.optional (config.superuser != null) "-U ${config.superuser}");
|
2023-07-17 23:45:33 +03:00
|
|
|
in
|
|
|
|
{
|
|
|
|
command = startScript;
|
|
|
|
# SIGINT (= 2) for faster shutdown: https://www.postgresql.org/docs/current/server-shutdown.html
|
|
|
|
shutdown.signal = 2;
|
|
|
|
readiness_probe = {
|
2023-12-24 22:05:03 +03:00
|
|
|
exec.command = "${config.package}/bin/pg_isready ${lib.concatStringsSep " " pg_isreadyArgs}";
|
2023-07-17 23:45:33 +03:00
|
|
|
initial_delay_seconds = 2;
|
|
|
|
period_seconds = 10;
|
|
|
|
timeout_seconds = 4;
|
|
|
|
success_threshold = 1;
|
|
|
|
failure_threshold = 5;
|
|
|
|
};
|
2023-10-04 20:20:59 +03:00
|
|
|
namespace = name;
|
2023-12-24 22:05:03 +03:00
|
|
|
depends_on."${name}-init".condition = "process_completed_successfully";
|
2023-07-17 23:45:33 +03:00
|
|
|
# https://github.com/F1bonacc1/process-compose#-auto-restart-if-not-healthy
|
|
|
|
availability.restart = "on_failure";
|
|
|
|
};
|
2023-06-21 17:00:45 +03:00
|
|
|
};
|
2023-07-17 23:45:33 +03:00
|
|
|
};
|
|
|
|
};
|
2023-06-20 00:54:46 +03:00
|
|
|
};
|
|
|
|
}
|