Skip to content

Commit 91bbbb1

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 91bbbb1

File tree

3 files changed

+99
-54
lines changed

3 files changed

+99
-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: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,70 @@
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+
58+
systemctl daemon-reload
59+
systemctl start "$PACKAGE_MOUNT_PATH"
60+
'';
61+
};
1362
switchPgNetVersion = writeShellApplication {
1463
name = "switch_pg_net_version";
1564
runtimeInputs = [ pkgs.coreutils ];
1665
text = ''
1766
# Create version switcher script
18-
set -e
67+
set -euo pipefail
68+
69+
# Check if the required environment variables are set
70+
if [ -z "''${EXT_WRAPPER:-}" ]; then
71+
echo "Error: EXT_WRAPPER environment variable is not set."
72+
exit 1
73+
fi
1974
2075
if [ $# -ne 1 ]; then
2176
echo "Usage: $0 <version>"
@@ -28,68 +83,32 @@ let
2883
exit 1
2984
fi
3085
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
86+
VERSION="$1"
87+
echo "$VERSION"
6588
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"
71-
72-
echo "Looking for file: $LIB_DIR/pg_net-$VERSION${postgresql.dlSuffix}"
73-
ls -la "$LIB_DIR" || true
89+
# Enable overlay on the wrapper package to be able to switch version
90+
${lib.getExe enableOverlayOnPackage} "$EXT_WRAPPER"
7491
7592
# 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"
93+
EXT_WRAPPER_LIB="$EXT_WRAPPER/lib"
94+
PG_NET_LIB_TO_USE="$EXT_WRAPPER_LIB/pg_net-$VERSION${postgresql.dlSuffix}"
95+
if [ ! -f "$PG_NET_LIB_TO_USE" ]; then
96+
echo "Error: Version $VERSION not found in $EXT_WRAPPER_LIB"
7897
echo "Available versions:"
7998
#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"
99+
ls "$EXT_WRAPPER_LIB"/pg_net-*${postgresql.dlSuffix} 2>/dev/null | sed 's/.*pg_net-/ /' | sed 's/${postgresql.dlSuffix}$//' || echo " No versions found"
81100
exit 1
82101
fi
83102
84103
# Update library symlink
85-
ln -sfnv "pg_net-$VERSION${postgresql.dlSuffix}" "$LIB_DIR/pg_net${postgresql.dlSuffix}"
104+
ln -sfnv "$PG_NET_LIB_TO_USE" "$EXT_WRAPPER_LIB/pg_net${postgresql.dlSuffix}"
86105
87106
# 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"
107+
EXT_WRAPPER_SHARE="$EXT_WRAPPER/share/postgresql/extension"
108+
echo "default_version = '$VERSION'" > "$EXT_WRAPPER_SHARE/pg_net.control"
109+
cat "$EXT_WRAPPER_SHARE/pg_net--$VERSION.control" >> "$EXT_WRAPPER_SHARE/pg_net.control"
90110
91111
echo "Successfully switched pg_net to version $VERSION"
92-
EOF
93112
'';
94113
};
95114
pname = "pg_net";
@@ -175,7 +194,8 @@ let
175194
in
176195
pkgs.buildEnv {
177196
name = pname;
178-
paths = packages ++ [ switchPgNetVersion ];
197+
paths = packages;
198+
nativeBuildInputs = [ makeWrapper ];
179199
postBuild = ''
180200
{
181201
echo "default_version = '${latestVersion}'"
@@ -190,6 +210,9 @@ pkgs.buildEnv {
190210
toString (numberOfVersions + 1)
191211
}"
192212
)
213+
214+
makeWrapper ${lib.getExe switchPgNetVersion} $out/bin/switch_pg_net_version \
215+
--prefix EXT_WRAPPER : "$out"
193216
'';
194217

195218
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)