Skip to content

Commit 0da41a7

Browse files
committed
nixos/postgresql: add upgrade.* options
1 parent d2253cb commit 0da41a7

File tree

1 file changed

+160
-4
lines changed

1 file changed

+160
-4
lines changed

nixos/modules/services/databases/postgresql.nix

Lines changed: 160 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,32 @@ let
3636

3737
groupAccessAvailable = versionAtLeast postgresql.version "11.0";
3838

39+
upgradeScript = let
40+
args = {
41+
old-bindir = "${cfg.databaseDir}/current/nix-postgresql-bin";
42+
old-datadir = "${cfg.databaseDir}/current";
43+
new-datadir = cfg.dataDir;
44+
};
45+
in pkgs.writeShellApplication {
46+
name = "upgrade.sh";
47+
text = ''
48+
if ! [[ -e "${cfg.databaseDir}/current" ]]; then
49+
echo "No old data found, assuming fresh deployment"
50+
exit 0
51+
fi
52+
53+
if [[ "$(tr --delete '[:space:]' <"${cfg.databaseDir}/current/PG_VERSION")" == "${postgresql.psqlSchema}" ]]; then
54+
echo "Previous major version matches the current one. No upgrade necessary"
55+
exit 0
56+
fi
57+
58+
pushd "${cfg.dataDir}"
59+
${postgresql}/bin/pg_upgrade ${lib.cli.toGNUCommandLineShell {} args} ${lib.escapeShellArgs cfg.upgrade.extraArgs}
60+
popd
61+
touch "${cfg.dataDir}/.post_upgrade"
62+
'';
63+
};
64+
3965
in
4066

4167
{
@@ -75,9 +101,18 @@ in
75101
description = lib.mdDoc "Check the syntax of the configuration file at compile time";
76102
};
77103

104+
databaseDir = mkOption {
105+
type = types.path;
106+
description = ''
107+
Version-independent location of the database data directories.
108+
Unlike `dataDir`, this value must not contain the postgresql version.
109+
'';
110+
default = "/var/lib/postgresql";
111+
};
112+
78113
dataDir = mkOption {
79114
type = types.path;
80-
defaultText = literalExpression ''"/var/lib/postgresql/''${config.services.postgresql.package.psqlSchema}"'';
115+
defaultText = literalExpression ''"''${config.services.postgresql.databaseDir}/''${config.services.postgresql.package.psqlSchema}"'';
81116
example = "/var/lib/postgresql/11";
82117
description = lib.mdDoc ''
83118
The data directory for PostgreSQL. If left as the default value
@@ -438,7 +473,48 @@ in
438473
PostgreSQL superuser account to use for various operations. Internal since changing
439474
this value would lead to breakage while setting up databases.
440475
'';
476+
};
477+
478+
upgrade = mkOption {
479+
type = types.submodule {
480+
options = {
481+
enable = mkEnableOption (lib.mdDoc "Major version automatic upgrades");
482+
483+
enablePreviousInstallationAutodetection = lib.mkOption {
484+
type = types.bool;
485+
default = true; # Can be disabled by default in newer stateVersion
486+
description = lib.mdDoc ''
487+
Adds an activation script that tries to detect dataDir and binDir from previous installation using systemctl Environment.
488+
The activation script has no effect if "''${cfg.databaseDir}/current" symlink already exists.
489+
490+
If this option is disabled, you must ensure that the "''${cfg.databaseDir}/current" symlink already exists (e.g. by starting the database at least once without upgrading),
491+
otherwise setting newer major release of postgresql will start with empty dataDir.
492+
Not setting the option is ok for fresh deployments.
493+
'';
494+
};
495+
496+
runAnalyze = mkOption {
497+
type = types.bool;
498+
default = true;
499+
description = lib.mdDoc ''
500+
Whether to run `vacuumdb --all --analyze-in-stages` after an successful upgrade as pg_upgrade suggests.
501+
'';
502+
};
503+
504+
extraArgs = mkOption {
505+
type = types.listOf types.str;
506+
description = lib.mdDoc ''
507+
Additional arguments passed to pg_upgrade.
508+
See [pg_upgrade docs]<https://www.postgresql.org/docs/current/pgupgrade.html> for available options.
509+
'';
510+
default = [];
511+
example = [ "--link" "--jobs=16" ];
512+
};
513+
};
441514
};
515+
default = {};
516+
description = lib.mdDoc "Settings related to automatically upgrading major versions";
517+
};
442518
};
443519

444520
};
@@ -447,6 +523,28 @@ in
447523
###### implementation
448524

449525
config = mkIf cfg.enable {
526+
assertions = [
527+
{
528+
# Note: We don't really need to be versioned _under databaseDir_ can probably we could make this less strict.
529+
# However, the requirement for versioned dataDir is necessary.
530+
assertion = cfg.upgrade.enable -> cfg.dataDir == "${cfg.databaseDir}/${cfg.package.psqlSchema}";
531+
message = ''
532+
Automatic postgresql upgrades require dataDir to be versioned under databaseDir.
533+
534+
Example:
535+
```
536+
databaseDir = "/var/lib/postgresql"
537+
dataDir = "/var/lib/postgresql/15"
538+
```
539+
540+
Current:
541+
```
542+
databaseDir = "${cfg.databaseDir}"
543+
dataDir = "${cfg.dataDir}"
544+
```
545+
'';
546+
}
547+
];
450548

451549
services.postgresql.settings =
452550
{
@@ -473,7 +571,7 @@ in
473571
# systems!
474572
mkDefault (if cfg.enableJIT then base.withJIT else base);
475573

476-
services.postgresql.dataDir = mkDefault "/var/lib/postgresql/${cfg.package.psqlSchema}";
574+
services.postgresql.dataDir = mkDefault "${cfg.databaseDir}/${cfg.package.psqlSchema}";
477575

478576
services.postgresql.authentication = mkAfter
479577
''
@@ -522,15 +620,24 @@ in
522620
# Initialise the database.
523621
initdb -U ${cfg.superUser} ${concatStringsSep " " cfg.initdbArgs}
524622
525-
# See postStart!
526-
touch "${cfg.dataDir}/.first_startup"
623+
${optionalString cfg.upgrade.enable (getExe upgradeScript)}
624+
625+
if ! test -e "${cfg.dataDir}/.post_upgrade"; then
626+
# See postStart!
627+
touch "${cfg.dataDir}/.first_startup"
628+
fi
527629
fi
528630
529631
ln -sfn "${configFile}/postgresql.conf" "${cfg.dataDir}/postgresql.conf"
530632
${optionalString (cfg.recoveryConfig != null) ''
531633
ln -sfn "${pkgs.writeText "recovery.conf" cfg.recoveryConfig}" \
532634
"${cfg.dataDir}/recovery.conf"
533635
''}
636+
637+
ln -sfn "${postgresql}/bin" "${cfg.dataDir}/nix-postgresql-bin"
638+
if [[ -d "${cfg.databaseDir}" ]]; then
639+
ln -sfn "${cfg.dataDir}" "${cfg.databaseDir}/current"
640+
fi
534641
'';
535642

536643
# Wait for PostgreSQL to be ready to accept connections.
@@ -549,6 +656,13 @@ in
549656
''}
550657
rm -f "${cfg.dataDir}/.first_startup"
551658
fi
659+
'' + optionalString cfg.upgrade.enable ''
660+
if test -e "${cfg.dataDir}/.post_upgrade"; then
661+
${optionalString cfg.upgrade.runAnalyze ''
662+
vacuumdb --port=${toString cfg.port} --all --analyze-in-stages
663+
''}
664+
rm -f "${cfg.dataDir}/.post_upgrade"
665+
fi
552666
'' + optionalString (cfg.ensureDatabases != []) ''
553667
${concatMapStrings (database: ''
554668
$PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${database}"'
@@ -608,6 +722,48 @@ in
608722
unitConfig.RequiresMountsFor = "${cfg.dataDir}";
609723
};
610724

725+
system.activationScripts.detect-previous-postgresql-installation = lib.mkIf (cfg.upgrade.enable && cfg.upgrade.enablePreviousInstallationAutodetection) {
726+
deps = [ "etc" ];
727+
text = ''
728+
previousPgDetect() {
729+
echo "Trying to detect prevoius PostgreSQL installation, because 'current' symlink does not exist in 'config.services.postgresql.databaseDir'."
730+
731+
if [[ ! -x /run/current-system/sw/bin/systemctl ]]; then
732+
echo "systemctl binary is missing or is not executable. This is ok on fresh deployments. Not running previous PostgreSQL installation detection."
733+
return
734+
fi
735+
736+
prev_postgresql_env="$(/run/current-system/sw/bin/systemctl show postgresql.service --property=Environment --value || true)"
737+
if [[ -z "$prev_postgresql_env" ]]; then
738+
echo "Cannot load old PostgreSQL Environment from systemctl."
739+
return
740+
fi
741+
old_data_dir="$(export $prev_postgresql_env; echo "''${PGDATA:-}")"
742+
if [[ -z "$old_data_dir" ]]; then
743+
echo "Cannot detect old PostgreSQL data dir!"
744+
return
745+
fi
746+
old_bin=$(export $prev_postgresql_env; command -v postgres)
747+
if [[ -z "$old_bin" ]]; then
748+
echo "Cannot detect old PostgreSQL binary!"
749+
return
750+
fi
751+
old_bin_dir=$(dirname "$old_bin")
752+
753+
echo "Detected old PostgreSQL installation!"
754+
echo "Setting old dataDir to '$old_data_dir'"
755+
echo "Setting old binDir to '$old_bin_dir'"
756+
757+
ln -sn "$old_data_dir" "${cfg.databaseDir}/current"
758+
ln -sn "$old_bin_dir" "${cfg.databaseDir}/current/nix-postgresql-bin"
759+
}
760+
761+
if [[ ! -e "${cfg.databaseDir}/current" ]]; then
762+
previousPgDetect
763+
fi
764+
'';
765+
};
766+
611767
};
612768

613769
meta.doc = ./postgresql.md;

0 commit comments

Comments
 (0)