Skip to content

Commit b83b127

Browse files
committed
feat(pg_net): switch version on file system using overlayfs
For pg_net background worker, we need to switch the version of the extension on the file system (both pg_net.so and pg_net.control need to point to the desired version). As the extension is in the Nix store, we cannot simply symlink to the desired version, as the Nix store is read-only. To work around this, we use overlayfs to create a writable layer on top of the pg_net store path.
1 parent 268cadb commit b83b127

File tree

3 files changed

+98
-54
lines changed

3 files changed

+98
-54
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ result*
1919
.history
2020
.envrc
2121
.direnv
22-
22+
.nixos-test-history
2323

2424
#IDE
2525
.idea/

nix/ext/pg_net.nix

Lines changed: 73 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,69 @@
77
postgresql,
88
libuv,
99
writeShellApplication,
10+
makeWrapper,
1011
}:
1112

1213
let
14+
enableOverlayOnPackage = writeShellApplication {
15+
name = "enable_overlay_on_package";
16+
runtimeInputs = [ pkgs.coreutils ];
17+
text = ''
18+
# This script enable overlayfs on a specific nix store path
19+
set -euo pipefail
20+
21+
if [ $# -ne 1 ]; then
22+
echo "Usage: $0 <path>"
23+
exit 1
24+
fi
25+
26+
PACKAGE_PATH="$1"
27+
PACKAGE_NAME=$(basename "$1"|cut -c 34-)
28+
29+
# Nixos compatibility: use systemd mount unit
30+
#shellcheck disable=SC1091
31+
source /etc/os-release || true
32+
if [[ "$ID" == "nixos" ]]; then
33+
# This script is used in NixOS test only for the moment
34+
SYSTEMD_DIR="/run/systemd/system"
35+
else
36+
SYSTEMD_DIR="/etc/systemd/system"
37+
fi
38+
39+
# Create required directories for overlay
40+
echo "$PACKAGE_NAME"
41+
mkdir -p "/var/lib/overlay/$PACKAGE_NAME/"{upper,work}
42+
43+
PACKAGE_MOUNT_PATH=$(systemd-escape -p --suffix=mount "$PACKAGE_PATH")
44+
45+
cat > "$SYSTEMD_DIR/$PACKAGE_MOUNT_PATH" <<EOF
46+
[Unit]
47+
Description=Overlay mount for PostgreSQL extension $PACKAGE_NAME
48+
49+
[Mount]
50+
What=overlay
51+
Type=overlay
52+
Options=lowerdir=$PACKAGE_PATH,upperdir=/var/lib/overlay/$PACKAGE_NAME/upper,workdir=/var/lib/overlay/$PACKAGE_NAME/work
53+
54+
[Install]
55+
WantedBy=multi-user.target
56+
EOF
57+
systemctl daemon-reload
58+
systemctl start "$PACKAGE_MOUNT_PATH"
59+
'';
60+
};
1361
switchPgNetVersion = writeShellApplication {
1462
name = "switch_pg_net_version";
1563
runtimeInputs = [ pkgs.coreutils ];
1664
text = ''
1765
# Create version switcher script
18-
set -e
66+
set -euo pipefail
67+
68+
# Check if the required environment variables are set
69+
if [ -z "''${EXT_WRAPPER:-}" ]; then
70+
echo "Error: EXT_WRAPPER environment variable is not set."
71+
exit 1
72+
fi
1973
2074
if [ $# -ne 1 ]; then
2175
echo "Usage: $0 <version>"
@@ -28,68 +82,32 @@ let
2882
exit 1
2983
fi
3084
31-
VERSION=$1
32-
33-
# Set defaults, allow environment variable overrides
34-
: "''${NIX_PROFILE:="/var/lib/postgresql/.nix-profile"}"
35-
: "''${LIB_DIR:=""}"
36-
: "''${EXTENSION_DIR:=""}"
37-
38-
# If LIB_DIR not explicitly set, auto-detect it
39-
if [ -z "$LIB_DIR" ]; then
40-
# Follow the complete chain of symlinks to find the multi-version directory
41-
CURRENT_LINK="$NIX_PROFILE/lib/pg_net-$VERSION${postgresql.dlSuffix}"
42-
echo "Starting with link: $CURRENT_LINK"
43-
44-
# Follow first two symlinks to get to the multi-version directory
45-
for _ in 1 2; do
46-
if [ -L "$CURRENT_LINK" ]; then
47-
NEXT_LINK=$(readlink "$CURRENT_LINK")
48-
echo "Following link: $NEXT_LINK"
49-
if echo "$NEXT_LINK" | grep -q '^/'; then
50-
CURRENT_LINK="$NEXT_LINK"
51-
else
52-
CURRENT_LINK="$(dirname "$CURRENT_LINK")/$NEXT_LINK"
53-
fi
54-
echo "Current link is now: $CURRENT_LINK"
55-
fi
56-
done
57-
58-
# The multi-version directory should be the parent of the current link
59-
MULTI_VERSION_DIR=$(dirname "$CURRENT_LINK")
60-
echo "Found multi-version directory: $MULTI_VERSION_DIR"
61-
LIB_DIR="$MULTI_VERSION_DIR"
62-
else
63-
echo "Using provided LIB_DIR: $LIB_DIR"
64-
fi
65-
66-
# If EXTENSION_DIR not explicitly set, use default
67-
if [ -z "$EXTENSION_DIR" ]; then
68-
EXTENSION_DIR="$NIX_PROFILE/share/postgresql/extension"
69-
fi
70-
echo "Using EXTENSION_DIR: $EXTENSION_DIR"
85+
VERSION="$1"
86+
echo "$VERSION"
7187
72-
echo "Looking for file: $LIB_DIR/pg_net-$VERSION${postgresql.dlSuffix}"
73-
ls -la "$LIB_DIR" || true
88+
# Enable overlay on the wrapper package to be able to switch version
89+
${lib.getExe enableOverlayOnPackage} "$EXT_WRAPPER"
7490
7591
# Check if version exists
76-
if [ ! -f "$LIB_DIR/pg_net-$VERSION${postgresql.dlSuffix}" ]; then
77-
echo "Error: Version $VERSION not found in $LIB_DIR"
92+
EXT_WRAPPER_LIB="$EXT_WRAPPER/lib"
93+
PG_NET_LIB_TO_USE="$EXT_WRAPPER_LIB/pg_net-$VERSION${postgresql.dlSuffix}"
94+
if [ ! -f "$PG_NET_LIB_TO_USE" ]; then
95+
echo "Error: Version $VERSION not found in $EXT_WRAPPER_LIB"
7896
echo "Available versions:"
7997
#shellcheck disable=SC2012
80-
ls "$LIB_DIR"/pg_net-*${postgresql.dlSuffix} 2>/dev/null | sed 's/.*pg_net-/ /' | sed 's/${postgresql.dlSuffix}$//' || echo " No versions found"
98+
ls "$EXT_WRAPPER_LIB"/pg_net-*${postgresql.dlSuffix} 2>/dev/null | sed 's/.*pg_net-/ /' | sed 's/${postgresql.dlSuffix}$//' || echo " No versions found"
8199
exit 1
82100
fi
83101
84102
# Update library symlink
85-
ln -sfnv "pg_net-$VERSION${postgresql.dlSuffix}" "$LIB_DIR/pg_net${postgresql.dlSuffix}"
103+
ln -sfnv "$PG_NET_LIB_TO_USE" "$EXT_WRAPPER_LIB/pg_net${postgresql.dlSuffix}"
86104
87105
# Update control file
88-
echo "default_version = '$VERSION'" > "$EXTENSION_DIR/pg_net.control"
89-
cat "$EXTENSION_DIR/pg_net--$VERSION.control" >> "$EXTENSION_DIR/pg_net.control"
106+
EXT_WRAPPER_SHARE="$EXT_WRAPPER/share/postgresql/extension"
107+
echo "default_version = '$VERSION'" > "$EXT_WRAPPER_SHARE/pg_net.control"
108+
cat "$EXT_WRAPPER_SHARE/pg_net--$VERSION.control" >> "$EXT_WRAPPER_SHARE/pg_net.control"
90109
91110
echo "Successfully switched pg_net to version $VERSION"
92-
EOF
93111
'';
94112
};
95113
pname = "pg_net";
@@ -175,7 +193,8 @@ let
175193
in
176194
pkgs.buildEnv {
177195
name = pname;
178-
paths = packages ++ [ switchPgNetVersion ];
196+
paths = packages;
197+
nativeBuildInputs = [ makeWrapper ];
179198
postBuild = ''
180199
{
181200
echo "default_version = '${latestVersion}'"
@@ -190,6 +209,9 @@ pkgs.buildEnv {
190209
toString (numberOfVersions + 1)
191210
}"
192211
)
212+
213+
makeWrapper ${lib.getExe switchPgNetVersion} $out/bin/switch_pg_net_version \
214+
--prefix EXT_WRAPPER : "$out"
193215
'';
194216

195217
passthru = {

nix/ext/tests/pg_net.nix

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ let
3636
};
3737
in
3838
pkg;
39+
psql_15 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
40+
psql_17 = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17;
3941
in
4042
self.inputs.nixpkgs.lib.nixos.runTest {
4143
name = "pg_net";
@@ -61,17 +63,19 @@ self.inputs.nixpkgs.lib.nixos.runTest {
6163

6264
services.postgresql = {
6365
enable = true;
64-
package = postgresqlWithExtension self.packages.${pkgs.system}.postgresql_15;
66+
package = psql_15;
6567
settings = {
6668
shared_preload_libraries = "pg_net";
6769
};
6870
};
6971

7072
specialisation.postgresql17.configuration = {
7173
services.postgresql = {
72-
package = lib.mkForce (postgresqlWithExtension self.packages.${pkgs.system}.postgresql_17);
74+
package = lib.mkForce psql_17;
7375
};
7476

77+
environment.systemPackages = [ psql_17 ];
78+
7579
systemd.services.postgresql-migrate = {
7680
serviceConfig = {
7781
Type = "oneshot";
@@ -133,6 +137,23 @@ self.inputs.nixpkgs.lib.nixos.runTest {
133137
134138
check_upgrade_path()
135139
140+
with subtest("Test switch_pg_net_version"):
141+
# Check that we are using the last version first
142+
pg_net_version = server.succeed("readlink -f ${psql_15}/lib/pg_net.so").strip()
143+
print(f"Current pg_net version: {pg_net_version}")
144+
assert pg_net_version.endswith("pg_net-${latestVersion}.so"), f"Expected pg_net version ${latestVersion}, but found {pg_net_version}"
145+
146+
server.succeed(
147+
"switch_pg_net_version ${firstVersion}"
148+
)
149+
150+
pg_net_version = server.succeed("readlink -f ${psql_15}/lib/pg_net.so").strip()
151+
assert pg_net_version.endswith("pg_net-${firstVersion}.so"), f"Expected pg_net version ${firstVersion}, but found {pg_net_version}"
152+
153+
server.succeed(
154+
"switch_pg_net_version ${latestVersion}"
155+
)
156+
136157
with subtest("Check pg_net latest extension version"):
137158
server.succeed("sudo -u postgres psql -c 'DROP EXTENSION pg_net;'")
138159
server.succeed("sudo -u postgres psql -c 'CREATE EXTENSION pg_net;'")
@@ -149,5 +170,6 @@ self.inputs.nixpkgs.lib.nixos.runTest {
149170
assert "pg_net,${latestVersion}" in installed_extensions
150171
151172
check_upgrade_path()
173+
152174
'';
153175
}

0 commit comments

Comments
 (0)