diff --git a/ansible/files/postgres_prestart.sh.j2 b/ansible/files/postgres_prestart.sh.j2 index d092062206..48c64bbfa8 100644 --- a/ansible/files/postgres_prestart.sh.j2 +++ b/ansible/files/postgres_prestart.sh.j2 @@ -6,85 +6,26 @@ log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } -check_extensions_file() { +handle_extension_versions() { local extensions_file="/etc/adminapi/pg-extensions.json" - if [ ! -f "$extensions_file" ]; then - log "extensions: No extensions file found, skipping extensions versions check" - return 0 #if file not found, skip - fi if [ ! -r "$extensions_file" ]; then - log "extensions: Cannot read extensions file" - return 1 #a true error, we should be able to read file - fi - return 0 -} - -switch_extension_version() { - local extension_name="$1" - local version="$2" - - # Use BIN_PATH environment variable or default to /var/lib/postgresql/.nix-profile - : ${BIN_PATH:="/var/lib/postgresql/.nix-profile"} - - local switch_script="$BIN_PATH/bin/switch_${extension_name}_version" - - if [ ! -x "$switch_script" ]; then - log "$extension_name: No version switch script available at $switch_script, skipping" + log "extensions: no manifest at $extensions_file, skipping" return 0 fi - log "$extension_name: Switching to version $version" - # Run directly as root since we're already running as root - "$switch_script" "$version" - local exit_code=$? - if [ $exit_code -eq 0 ]; then - log "$extension_name: Version switch completed successfully" + if update-site-extensions "$extensions_file"; then + log "extensions: profile updated" else - log "$extension_name: Version switch failed with exit code $exit_code" - fi - return $exit_code -} - -handle_extension_versions() { - if ! check_extensions_file; then - return - fi - - local extensions_file="/etc/adminapi/pg-extensions.json" - - # Get all extension names from the JSON file - local extensions - extensions=$(jq -r 'keys[]' "$extensions_file" 2>/dev/null) - - if [ -z "$extensions" ]; then - log "extensions: No extensions found in configuration" - return + log "extensions: update failed" + return 1 fi - - # Iterate through each extension - while IFS= read -r extension_name; do - # Get the version for this extension - local version - version=$(jq -r --arg ext "$extension_name" '.[$ext] // empty' "$extensions_file") - - if [ -z "$version" ]; then - log "$extension_name: No version specified, skipping" - continue - fi - - log "$extension_name: Found version $version in extensions file" - - # Don't fail if version switch fails - just log and continue - switch_extension_version "$extension_name" "$version" || log "$extension_name: Version switch failed but continuing" - - done <<< "$extensions" } main() { log "Starting prestart script" # 1. Handle all extension versions from config file - handle_extension_versions + handle_extension_versions || return 1 log "Prestart script completed" } diff --git a/ansible/files/postgresql_config/postgresql.service b/ansible/files/postgresql_config/postgresql.service index 07aac3bcae..0fc95e3264 100644 --- a/ansible/files/postgresql_config/postgresql.service +++ b/ansible/files/postgresql_config/postgresql.service @@ -19,7 +19,7 @@ RestartSec=5 OOMScoreAdjust=-1000 EnvironmentFile=-/etc/environment.d/postgresql.env LimitNOFILE=16384 -ReadOnlyPaths=/etc +ReadOnlyPaths=/etc /usr/local InaccessiblePaths=/root -/var/lib/supabase -/var/lib/supabase-admin-agent -/var/cache/supabase-admin-agent -/opt/saltstack -/etc/salt AppArmorProfile=-sbpostgres [Install] diff --git a/nix/ext/tests/site-extensions-upgrade.nix b/nix/ext/tests/site-extensions-upgrade.nix new file mode 100644 index 0000000000..1fcaddbb24 --- /dev/null +++ b/nix/ext/tests/site-extensions-upgrade.nix @@ -0,0 +1,85 @@ +{ self, pkgs }: +let + testLib = import ./lib.nix { inherit self pkgs; }; + inherit (pkgs) lib; + system = pkgs.pkgsLinux.stdenv.hostPlatform.system; + + pinnedExtension = "pgaudit"; + pinnedVersion = "99.0"; + + placeholderPackage = pkgs.pkgsLinux.runCommand "pgaudit-${pinnedVersion}-generic" { } '' + mkdir -p "$out/lib" "$out/share/postgresql/extension" + echo "default_version = '${pinnedVersion}'" > "$out/share/postgresql/extension/pgaudit.control" + ''; + fakeCatalog = pkgs.pkgsLinux.writeText "pg-extensions-catalog.json" ( + builtins.toJSON { + ${pinnedExtension} = { + ${pinnedVersion} = "${placeholderPackage}"; + }; + } + ); + + prestartScript = pkgs.pkgsLinux.writeShellScript "postgres-prestart-site-extensions" ( + builtins.readFile ../../../ansible/files/postgres_prestart.sh.j2 + ); +in +pkgs.testers.runNixOSTest { + name = "site-extensions-upgrade"; + nodes.server = { + imports = [ (testLib.makeSupabaseTestConfig { majorVersion = "17"; }) ]; + + environment.systemPackages = [ self.packages.${system}.update-site-extensions ]; + systemd.services.postgresql.path = [ + self.packages.${system}.update-site-extensions + pkgs.pkgsLinux.nix + ]; + systemd.services.postgresql.environment.PG_EXTENSIONS_CATALOG = "${fakeCatalog}"; + + # No network in the VM, so pre-realize it. + virtualisation.additionalPaths = [ placeholderPackage ]; + + environment.etc."adminapi/pg-extensions.json".text = builtins.toJSON { + ${pinnedExtension} = pinnedVersion; + }; + + systemd.services.postgresql.serviceConfig.ExecStartPre = lib.mkAfter [ + ("+" + prestartScript) + ]; + }; + + testScript = '' + pinned_extension = "${pinnedExtension}" + pinned_version = "${pinnedVersion}" + + start_all() + + server.wait_for_unit("multi-user.target") + server.wait_for_unit("postgresql.service") + server.wait_for_unit("supabase-db-init.service") + + with subtest("extensions manifest present"): + server.succeed("test -r /etc/adminapi/pg-extensions.json") + + with subtest("restart postgresql to trigger the real prestart path"): + server.execute("systemctl restart postgresql.service") + server.wait_for_unit("postgresql.service") + + with subtest("prestart ran update-site-extensions"): + unit_log = server.succeed("journalctl -u postgresql.service -o cat --no-pager") + assert "Starting prestart script" in unit_log, unit_log + assert "extensions: profile updated" in unit_log, unit_log + + with subtest("site-extensions profile has the pinned version"): + profile_contents = server.succeed( + "nix-env --profile /nix/var/nix/profiles/site-extensions -q" + ).strip() + assert pinned_extension in profile_contents, profile_contents + + with subtest("EXPECTED TO FAIL: profile isn't on dynamic_library_path/extension_control_path"): + default_version = server.succeed( + "psql -U supabase_admin -d postgres -tAc " + f"\"select default_version from pg_available_extensions where name = '{pinned_extension}'\"" + ).strip() + assert default_version == pinned_version, f"expected {pinned_version}, got {default_version}" + ''; +} diff --git a/nix/packages/extension-catalog.nix b/nix/packages/extension-catalog.nix index 9dc0708c91..74bd746b11 100644 --- a/nix/packages/extension-catalog.nix +++ b/nix/packages/extension-catalog.nix @@ -110,8 +110,8 @@ makeWrapper ${self'.packages.site-extensions-resolve}/bin/site-extensions-resolve \ "$out/bin/site-extensions-resolve" \ --set PG_EXTENSIONS_CATALOG "$out/share/pg-extensions-catalog.json" - makeWrapper ${self'.packages.site-extensions-update}/bin/site-extensions-update \ - "$out/bin/site-extensions-update" \ + makeWrapper ${self'.packages.update-site-extensions}/bin/update-site-extensions \ + "$out/bin/update-site-extensions" \ --set PG_EXTENSIONS_CATALOG "$out/share/pg-extensions-catalog.json" '' ) @@ -151,19 +151,16 @@ ''; }; - # Takes manifest json as argument. - # Downloads paths and installs them as an env into the profile, replacing all existing ones. - site-extensions-update = pkgs.writeShellApplication { - name = "site-extensions-update"; - runtimeInputs = [ - self'.packages.site-extensions-resolve - pkgs.nix - ]; + # Downloads the manifest's paths and installs them as the profile's env, replacing all existing ones. + # nix comes from the environment. + update-site-extensions = pkgs.writeShellApplication { + name = "update-site-extensions"; + runtimeInputs = [ self'.packages.site-extensions-resolve ]; text = '' manifest="''${1:?Usage: $0 path-to/pg-extensions.json}" - profile="''${NIX_PROFILE:-/nix/var/nix/profiles/site-extensions}" + profile="/nix/var/nix/profiles/site-extensions" readarray -t paths < <(site-extensions-resolve "$manifest") - nix-store -r --option stalled-download-timeout 120 "''${paths[@]}" >/dev/null + nix-store --realise --option stalled-download-timeout 120 "''${paths[@]}" >/dev/null nix-env --profile "$profile" --install "''${paths[@]}" --remove-all ''; };