{ description = "Prototype tooling for deploying PostgreSQL"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; flake-utils.url = "github:numtide/flake-utils"; nix2container.url = "github:nlewo/nix2container"; nix-editor.url = "github:snowfallorg/nix-editor"; rust-overlay.url = "github:oxalica/rust-overlay"; }; outputs = { self, nixpkgs, flake-utils, nix2container, nix-editor, rust-overlay, ...}: let gitRev = "vcs=${self.shortRev or "dirty"}+${builtins.substring 0 8 (self.lastModifiedDate or self.lastModified or "19700101")}"; ourSystems = with flake-utils.lib; [ system.x86_64-linux system.aarch64-linux system.aarch64-darwin ]; in flake-utils.lib.eachSystem ourSystems (system: let pgsqlDefaultPort = "5435"; pgsqlSuperuser = "supabase_admin"; nix2img = nix2container.packages.${system}.nix2container; pkgs = import nixpkgs { config = { allowUnfree = true; permittedInsecurePackages = [ "v8-9.7.106.18" ]; }; inherit system; overlays = [ # NOTE: add any needed overlays here. in theory we could # pull them from the overlays/ directory automatically, but we don't # want to have an arbitrary order, since it might matter. being # explicit is better. (import rust-overlay) (final: prev: { cargo-pgrx = final.callPackage ./nix/cargo-pgrx/default.nix { inherit (final) lib; inherit (final) darwin; inherit (final) fetchCrate; inherit (final) openssl; inherit (final) pkg-config; inherit (final) makeRustPlatform; inherit (final) stdenv; inherit (final) rust-bin; }; buildPgrxExtension = final.callPackage ./nix/cargo-pgrx/buildPgrxExtension.nix { inherit (final) cargo-pgrx; inherit (final) lib; inherit (final) Security; inherit (final) pkg-config; inherit (final) makeRustPlatform; inherit (final) stdenv; inherit (final) writeShellScriptBin; }; buildPgrxExtension_0_11_3 = prev.buildPgrxExtension.override { cargo-pgrx = final.cargo-pgrx.cargo-pgrx_0_11_3; }; buildPgrxExtension_0_12_6 = prev.buildPgrxExtension.override { cargo-pgrx = final.cargo-pgrx.cargo-pgrx_0_12_6; }; buildPgrxExtension_0_12_9 = prev.buildPgrxExtension.override { cargo-pgrx = final.cargo-pgrx.cargo-pgrx_0_12_9; }; }) (final: prev: { postgresql = final.callPackage ./nix/postgresql/default.nix { inherit (final) lib stdenv fetchurl makeWrapper callPackage buildEnv newScope; }; }) ]; }; sfcgal = pkgs.callPackage ./nix/ext/sfcgal/sfcgal.nix { }; supabase-groonga = pkgs.callPackage ./nix/supabase-groonga.nix { }; mecab-naist-jdic = pkgs.callPackage ./nix/ext/mecab-naist-jdic/default.nix { }; # Our list of PostgreSQL extensions which come from upstream Nixpkgs. # These are maintained upstream and can easily be used here just by # listing their name. Anytime the version of nixpkgs is upgraded, these # may also bring in new versions of the extensions. psqlExtensions = [ /* pljava */ /*"postgis"*/ ]; #FIXME for now, timescaledb is not included in the orioledb version of supabase extensions, as there is an issue # with building timescaledb with the orioledb patched version of postgresql orioledbPsqlExtensions = [ /* pljava */ /*"timescaledb"*/ ]; # Custom extensions that exist in our repository. These aren't upstream # either because nobody has done the work, maintaining them here is # easier and more expedient, or because they may not be suitable, or are # too niche/one-off. # # Ideally, most of these should have copies upstream for third party # use, but even if they did, keeping our own copies means that we can # rollout new versions of these critical things easier without having to # go through the upstream release engineering process. ourExtensions = [ ./nix/ext/rum.nix ./nix/ext/timescaledb.nix ./nix/ext/timescaledb-2.9.1.nix ./nix/ext/pgroonga.nix ./nix/ext/index_advisor.nix ./nix/ext/wal2json.nix ./nix/ext/pgmq.nix ./nix/ext/pg_repack.nix ./nix/ext/pg-safeupdate.nix ./nix/ext/plpgsql-check.nix ./nix/ext/pgjwt.nix ./nix/ext/pgaudit.nix ./nix/ext/postgis.nix ./nix/ext/pgrouting.nix ./nix/ext/pgtap.nix ./nix/ext/pg_backtrace.nix ./nix/ext/pg_cron.nix ./nix/ext/pgsql-http.nix ./nix/ext/pg_plan_filter.nix ./nix/ext/pg_net.nix ./nix/ext/pg_hashids.nix ./nix/ext/pgsodium.nix ./nix/ext/pg_graphql.nix ./nix/ext/pg_stat_monitor.nix ./nix/ext/pg_jsonschema.nix ./nix/ext/pgvector.nix ./nix/ext/vault.nix ./nix/ext/hypopg.nix ./nix/ext/pg_tle.nix ./nix/ext/wrappers/default.nix ./nix/ext/supautils.nix ./nix/ext/plv8.nix ]; #Where we import and build the orioledb extension, we add on our custom extensions # plus the orioledb option #we're not using timescaledb in the orioledb version of supabase extensions orioleFilteredExtensions = builtins.filter ( x: x != ./nix/ext/timescaledb.nix && x != ./nix/ext/timescaledb-2.9.1.nix && x != ./nix/ext/plv8.nix ) ourExtensions; orioledbExtensions = orioleFilteredExtensions ++ [ ./nix/ext/orioledb.nix ]; getPostgresqlPackage = version: pkgs.postgresql."postgresql_${version}"; # Create a 'receipt' file for a given postgresql package. This is a way # of adding a bit of metadata to the package, which can be used by other # tools to inspect what the contents of the install are: the PSQL # version, the installed extensions, et cetera. # # This takes three arguments: # - pgbin: the postgresql package we are building on top of # - upstreamExts: the list of extensions from upstream nixpkgs. This is # not a list of packages, but an attrset containing extension names # mapped to versions. # - ourExts: the list of extensions from upstream nixpkgs. This is not # a list of packages, but an attrset containing extension names # mapped to versions. # # The output is a package containing the receipt.json file, which can be # merged with the PostgreSQL installation using 'symlinkJoin'. makeReceipt = pgbin: upstreamExts: ourExts: pkgs.writeTextFile { name = "receipt"; destination = "/receipt.json"; text = builtins.toJSON { revision = gitRev; psql-version = pgbin.version; nixpkgs = { revision = nixpkgs.rev; extensions = upstreamExts; }; extensions = ourExts; # NOTE this field can be used to do cache busting (e.g. # force a rebuild of the psql packages) but also to helpfully inform # tools what version of the schema is being used, for forwards and # backwards compatibility receipt-version = "1"; }; }; makeOurPostgresPkgs = version: let postgresql = getPostgresqlPackage version; extensionsToUse = if (builtins.elem version ["orioledb-17"]) then orioledbExtensions else ourExtensions; in map (path: pkgs.callPackage path { inherit postgresql; }) extensionsToUse; # Create an attrset that contains all the extensions included in a server. makeOurPostgresPkgsSet = version: (builtins.listToAttrs (map (drv: { name = drv.pname; value = drv; } ) (makeOurPostgresPkgs version))) // { recurseForDerivations = true; }; # Create a binary distribution of PostgreSQL, given a version. # # NOTE: The version here does NOT refer to the exact PostgreSQL version; # it refers to the *major number only*, which is used to select the # correct version of the package from nixpkgs. This is because we want # to be able to do so in an open ended way. As an example, the version # "15" passed in will use the nixpkgs package "postgresql_15" as the # basis for building extensions, etc. makePostgresBin = version: let postgresql = getPostgresqlPackage version; upstreamExts = map (ext: { name = postgresql.pkgs."${ext}".pname; version = postgresql.pkgs."${ext}".version; }) psqlExtensions; ourExts = map (ext: { name = ext.pname; version = ext.version; }) (makeOurPostgresPkgs version); pgbin = postgresql.withPackages (ps: (map (ext: ps."${ext}") psqlExtensions) ++ (makeOurPostgresPkgs version) ); in pkgs.symlinkJoin { inherit (pgbin) name version; paths = [ pgbin (makeReceipt pgbin upstreamExts ourExts) ]; }; # Create an attribute set, containing all the relevant packages for a # PostgreSQL install, wrapped up with a bow on top. There are three # packages: # # - bin: the postgresql package itself, with all the extensions # installed, and a receipt.json file containing metadata about the # install. # - exts: an attrset containing all the extensions, mapped to their # package names. makePostgres = version: rec { bin = makePostgresBin version; exts = makeOurPostgresPkgsSet version; recurseForDerivations = true; }; makePostgresDevSetup = { pkgs, name, extraSubstitutions ? {} }: let paths = { migrationsDir = builtins.path { name = "migrations"; path = ./migrations/db; }; postgresqlSchemaSql = builtins.path { name = "postgresql-schema"; path = ./nix/tools/postgresql_schema.sql; }; pgbouncerAuthSchemaSql = builtins.path { name = "pgbouncer-auth-schema"; path = ./ansible/files/pgbouncer_config/pgbouncer_auth_schema.sql; }; statExtensionSql = builtins.path { name = "stat-extension"; path = ./ansible/files/stat_extension.sql; }; pgconfigFile = builtins.path { name = "postgresql.conf"; path = ./ansible/files/postgresql_config/postgresql.conf.j2; }; supautilsConfigFile = builtins.path { name = "supautils.conf"; path = ./ansible/files/postgresql_config/supautils.conf.j2; }; loggingConfigFile = builtins.path { name = "logging.conf"; path = ./ansible/files/postgresql_config/postgresql-csvlog.conf; }; readReplicaConfigFile = builtins.path { name = "readreplica.conf"; path = ./ansible/files/postgresql_config/custom_read_replica.conf.j2; }; pgHbaConfigFile = builtins.path { name = "pg_hba.conf"; path = ./ansible/files/postgresql_config/pg_hba.conf.j2; }; pgIdentConfigFile = builtins.path { name = "pg_ident.conf"; path = ./ansible/files/postgresql_config/pg_ident.conf.j2; }; postgresqlExtensionCustomScriptsPath = builtins.path { name = "extension-custom-scripts"; path = ./ansible/files/postgresql_extension_custom_scripts; }; getkeyScript = builtins.path { name = "pgsodium_getkey.sh"; path = ./nix/tests/util/pgsodium_getkey.sh; }; }; localeArchive = if pkgs.stdenv.isDarwin then "${pkgs.darwin.locale}/share/locale" else "${pkgs.glibcLocales}/lib/locale/locale-archive"; substitutions = { SHELL_PATH = "${pkgs.bash}/bin/bash"; PGSQL_DEFAULT_PORT = "${pgsqlDefaultPort}"; PGSQL_SUPERUSER = "${pgsqlSuperuser}"; PSQL15_BINDIR = "${basePackages.psql_15.bin}"; PSQL_CONF_FILE = "${paths.pgconfigFile}"; PSQLORIOLEDB17_BINDIR = "${basePackages.psql_orioledb-17.bin}"; PGSODIUM_GETKEY = "${paths.getkeyScript}"; READREPL_CONF_FILE = "${paths.readReplicaConfigFile}"; LOGGING_CONF_FILE = "${paths.loggingConfigFile}"; SUPAUTILS_CONF_FILE = "${paths.supautilsConfigFile}"; PG_HBA = "${paths.pgHbaConfigFile}"; PG_IDENT = "${paths.pgIdentConfigFile}"; LOCALES = "${localeArchive}"; EXTENSION_CUSTOM_SCRIPTS_DIR = "${paths.postgresqlExtensionCustomScriptsPath}"; MECAB_LIB = "${basePackages.psql_15.exts.pgroonga}/lib/groonga/plugins/tokenizers/tokenizer_mecab.so"; GROONGA_DIR = "${supabase-groonga}"; MIGRATIONS_DIR = "${paths.migrationsDir}"; POSTGRESQL_SCHEMA_SQL = "${paths.postgresqlSchemaSql}"; PGBOUNCER_AUTH_SCHEMA_SQL = "${paths.pgbouncerAuthSchemaSql}"; STAT_EXTENSION_SQL = "${paths.statExtensionSql}"; CURRENT_SYSTEM = "${system}"; } // extraSubstitutions; # Merge in any extra substitutions in pkgs.runCommand name { inherit (paths) migrationsDir postgresqlSchemaSql pgbouncerAuthSchemaSql statExtensionSql; } '' set -x mkdir -p $out/bin $out/etc/postgresql-custom $out/etc/postgresql $out/extension-custom-scripts # Copy config files with error handling cp ${paths.supautilsConfigFile} $out/etc/postgresql-custom/supautils.conf || { echo "Failed to copy supautils.conf"; exit 1; } cp ${paths.pgconfigFile} $out/etc/postgresql/postgresql.conf || { echo "Failed to copy postgresql.conf"; exit 1; } cp ${paths.loggingConfigFile} $out/etc/postgresql-custom/logging.conf || { echo "Failed to copy logging.conf"; exit 1; } cp ${paths.readReplicaConfigFile} $out/etc/postgresql-custom/read-replica.conf || { echo "Failed to copy read-replica.conf"; exit 1; } cp ${paths.pgHbaConfigFile} $out/etc/postgresql/pg_hba.conf || { echo "Failed to copy pg_hba.conf"; exit 1; } cp ${paths.pgIdentConfigFile} $out/etc/postgresql/pg_ident.conf || { echo "Failed to copy pg_ident.conf"; exit 1; } cp -r ${paths.postgresqlExtensionCustomScriptsPath}/* $out/extension-custom-scripts/ || { echo "Failed to copy custom scripts"; exit 1; } echo "Copy operation completed" chmod 644 $out/etc/postgresql-custom/supautils.conf chmod 644 $out/etc/postgresql/postgresql.conf chmod 644 $out/etc/postgresql-custom/logging.conf chmod 644 $out/etc/postgresql/pg_hba.conf substitute ${./nix/tools/run-server.sh.in} $out/bin/start-postgres-server \ ${builtins.concatStringsSep " " (builtins.attrValues (builtins.mapAttrs (name: value: "--subst-var-by '${name}' '${value}'") substitutions ))} chmod +x $out/bin/start-postgres-server ''; # The base set of packages that we export from this Nix Flake, that can # be used with 'nix build'. Don't use the names listed below; check the # name in 'nix flake show' in order to make sure exactly what name you # want. basePackages = let # Function to get the PostgreSQL version from the attribute name getVersion = name: let match = builtins.match "psql_([0-9]+)" name; in if match == null then null else builtins.head match; # Define the available PostgreSQL versions postgresVersions = { psql_15 = makePostgres "15"; psql_orioledb-17 = makePostgres "orioledb-17" ; }; # Find the active PostgreSQL version activeVersion = getVersion (builtins.head (builtins.attrNames postgresVersions)); # Function to create the pg_regress package makePgRegress = version: let postgresqlPackage = pkgs."postgresql_${version}"; in pkgs.callPackage ./nix/ext/pg_regress.nix { postgresql = postgresqlPackage; }; postgresql_15 = getPostgresqlPackage "15"; postgresql_orioledb-17 = getPostgresqlPackage "orioledb-17"; in postgresVersions // { supabase-groonga = supabase-groonga; cargo-pgrx_0_11_3 = pkgs.cargo-pgrx.cargo-pgrx_0_11_3; cargo-pgrx_0_12_6 = pkgs.cargo-pgrx.cargo-pgrx_0_12_6; cargo-pgrx_0_12_9 = pkgs.cargo-pgrx.cargo-pgrx_0_12_9; # PostgreSQL versions. psql_15 = postgresVersions.psql_15; psql_orioledb-17 = postgresVersions.psql_orioledb-17; sfcgal = sfcgal; pg_prove = pkgs.perlPackages.TAPParserSourceHandlerpgTAP; inherit postgresql_15 postgresql_orioledb-17; postgresql_15_debug = if pkgs.stdenv.isLinux then postgresql_15.debug else null; postgresql_orioledb-17_debug = if pkgs.stdenv.isLinux then postgresql_orioledb-17.debug else null; postgresql_15_src = pkgs.stdenv.mkDerivation { pname = "postgresql-15-src"; version = postgresql_15.version; src = postgresql_15.src; nativeBuildInputs = [ pkgs.bzip2 ]; phases = [ "unpackPhase" "installPhase" ]; installPhase = '' mkdir -p $out cp -r . $out ''; meta = with pkgs.lib; { description = "PostgreSQL 15 source files"; homepage = "https://www.postgresql.org/"; license = licenses.postgresql; platforms = platforms.all; }; }; postgresql_orioledb-17_src = pkgs.stdenv.mkDerivation { pname = "postgresql-17-src"; version = postgresql_orioledb-17.version; src = postgresql_orioledb-17.src; nativeBuildInputs = [ pkgs.bzip2 ]; phases = [ "unpackPhase" "installPhase" ]; installPhase = '' mkdir -p $out cp -r . $out ''; meta = with pkgs.lib; { description = "PostgreSQL 15 source files"; homepage = "https://www.postgresql.org/"; license = licenses.postgresql; platforms = platforms.all; }; }; mecab_naist_jdic = mecab-naist-jdic; supabase_groonga = supabase-groonga; pg_regress = makePgRegress activeVersion; # Start a version of the server. start-server = makePostgresDevSetup { inherit pkgs; name = "start-postgres-server"; }; # Start a version of the client and runs migrations script on server. start-client = let migrationsDir = ./migrations/db; postgresqlSchemaSql = ./nix/tools/postgresql_schema.sql; pgbouncerAuthSchemaSql = ./ansible/files/pgbouncer_config/pgbouncer_auth_schema.sql; statExtensionSql = ./ansible/files/stat_extension.sql; in pkgs.runCommand "start-postgres-client" { } '' mkdir -p $out/bin substitute ${./nix/tools/run-client.sh.in} $out/bin/start-postgres-client \ --subst-var-by 'PGSQL_DEFAULT_PORT' '${pgsqlDefaultPort}' \ --subst-var-by 'PGSQL_SUPERUSER' '${pgsqlSuperuser}' \ --subst-var-by 'PSQL15_BINDIR' '${basePackages.psql_15.bin}' \ --subst-var-by 'PSQLORIOLEDB17_BINDIR' '${basePackages.psql_orioledb-17.bin}' \ --subst-var-by 'MIGRATIONS_DIR' '${migrationsDir}' \ --subst-var-by 'POSTGRESQL_SCHEMA_SQL' '${postgresqlSchemaSql}' \ --subst-var-by 'PGBOUNCER_AUTH_SCHEMA_SQL' '${pgbouncerAuthSchemaSql}' \ --subst-var-by 'STAT_EXTENSION_SQL' '${statExtensionSql}' chmod +x $out/bin/start-postgres-client ''; # Migrate between two data directories. migrate-tool = let configFile = ./nix/tests/postgresql.conf.in; getkeyScript = ./nix/tests/util/pgsodium_getkey.sh; primingScript = ./nix/tests/prime.sql; migrationData = ./nix/tests/migrations/data.sql; in pkgs.runCommand "migrate-postgres" { } '' mkdir -p $out/bin substitute ${./nix/tools/migrate-tool.sh.in} $out/bin/migrate-postgres \ --subst-var-by 'PSQL15_BINDIR' '${basePackages.psql_15.bin}' \ --subst-var-by 'PSQL_CONF_FILE' '${configFile}' \ --subst-var-by 'PGSODIUM_GETKEY' '${getkeyScript}' \ --subst-var-by 'PRIMING_SCRIPT' '${primingScript}' \ --subst-var-by 'MIGRATION_DATA' '${migrationData}' chmod +x $out/bin/migrate-postgres ''; start-replica = pkgs.runCommand "start-postgres-replica" { } '' mkdir -p $out/bin substitute ${./nix/tools/run-replica.sh.in} $out/bin/start-postgres-replica \ --subst-var-by 'PGSQL_SUPERUSER' '${pgsqlSuperuser}' \ --subst-var-by 'PSQL15_BINDIR' '${basePackages.psql_15.bin}' chmod +x $out/bin/start-postgres-replica ''; pg-restore = pkgs.runCommand "run-pg-restore" { } '' mkdir -p $out/bin substitute ${./nix/tools/run-restore.sh.in} $out/bin/pg-restore \ --subst-var-by PSQL15_BINDIR '${basePackages.psql_15.bin}' chmod +x $out/bin/pg-restore ''; sync-exts-versions = pkgs.runCommand "sync-exts-versions" { } '' mkdir -p $out/bin substitute ${./nix/tools/sync-exts-versions.sh.in} $out/bin/sync-exts-versions \ --subst-var-by 'YQ' '${pkgs.yq}/bin/yq' \ --subst-var-by 'JQ' '${pkgs.jq}/bin/jq' \ --subst-var-by 'NIX_EDITOR' '${nix-editor.packages.${system}.nix-editor}/bin/nix-editor' \ --subst-var-by 'NIXPREFETCHURL' '${pkgs.nixVersions.nix_2_20}/bin/nix-prefetch-url' \ --subst-var-by 'NIX' '${pkgs.nixVersions.nix_2_20}/bin/nix' chmod +x $out/bin/sync-exts-versions ''; local-infra-bootstrap = pkgs.runCommand "local-infra-bootstrap" { } '' mkdir -p $out/bin substitute ${./nix/tools/local-infra-bootstrap.sh.in} $out/bin/local-infra-bootstrap chmod +x $out/bin/local-infra-bootstrap ''; dbmate-tool = let migrationsDir = ./migrations/db; ansibleVars = ./ansible/vars.yml; pgbouncerAuthSchemaSql = ./ansible/files/pgbouncer_config/pgbouncer_auth_schema.sql; statExtensionSql = ./ansible/files/stat_extension.sql; in pkgs.runCommand "dbmate-tool" { buildInputs = with pkgs; [ overmind dbmate nix jq yq ]; nativeBuildInputs = with pkgs; [ makeWrapper ]; } '' mkdir -p $out/bin $out/migrations cp -r ${migrationsDir}/* $out substitute ${./nix/tools/dbmate-tool.sh.in} $out/bin/dbmate-tool \ --subst-var-by 'PGSQL_DEFAULT_PORT' '${pgsqlDefaultPort}' \ --subst-var-by 'MIGRATIONS_DIR' $out \ --subst-var-by 'PGSQL_SUPERUSER' '${pgsqlSuperuser}' \ --subst-var-by 'ANSIBLE_VARS' ${ansibleVars} \ --subst-var-by 'CURRENT_SYSTEM' '${system}' \ --subst-var-by 'PGBOUNCER_AUTH_SCHEMA_SQL' '${pgbouncerAuthSchemaSql}' \ --subst-var-by 'STAT_EXTENSION_SQL' '${statExtensionSql}' chmod +x $out/bin/dbmate-tool wrapProgram $out/bin/dbmate-tool \ --prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.overmind pkgs.dbmate pkgs.nix pkgs.jq pkgs.yq ]} ''; update-readme = pkgs.runCommand "update-readme" { nativeBuildInputs = [ pkgs.makeWrapper ]; buildInputs = [ pkgs.nushell ]; } '' mkdir -p $out/bin cp ${./nix/tools/update_readme.nu} $out/bin/update-readme chmod +x $out/bin/update-readme wrapProgram $out/bin/update-readme \ --prefix PATH : ${pkgs.nushell}/bin ''; }; # Create a testing harness for a PostgreSQL package. This is used for # 'nix flake check', and works with any PostgreSQL package you hand it. makeCheckHarness = pgpkg: let sqlTests = ./nix/tests/smoke; pg_prove = pkgs.perlPackages.TAPParserSourceHandlerpgTAP; pg_regress = basePackages.pg_regress; getkey-script = pkgs.writeScriptBin "pgsodium-getkey" '' #!${pkgs.bash}/bin/bash set -euo pipefail TMPDIR_BASE=$(mktemp -d) if [[ "$(uname)" == "Darwin" ]]; then KEY_DIR="/private/tmp/pgsodium" else KEY_DIR="''${PGSODIUM_KEY_DIR:-$TMPDIR_BASE/pgsodium}" fi KEY_FILE="$KEY_DIR/pgsodium.key" if ! mkdir -p "$KEY_DIR" 2>/dev/null; then echo "Error: Could not create key directory $KEY_DIR" >&2 exit 1 fi chmod 1777 "$KEY_DIR" if [[ ! -f "$KEY_FILE" ]]; then if ! (dd if=/dev/urandom bs=32 count=1 2>/dev/null | od -A n -t x1 | tr -d ' \n' > "$KEY_FILE"); then if ! (openssl rand -hex 32 > "$KEY_FILE"); then echo "00000000000000000000000000000000" > "$KEY_FILE" echo "Warning: Using fallback key" >&2 fi fi chmod 644 "$KEY_FILE" fi if [[ -f "$KEY_FILE" && -r "$KEY_FILE" ]]; then cat "$KEY_FILE" else echo "Error: Cannot read key file $KEY_FILE" >&2 exit 1 fi ''; # Use the shared setup but with a test-specific name start-postgres-server-bin = makePostgresDevSetup { inherit pkgs; name = "start-postgres-server-test"; extraSubstitutions = { PGSODIUM_GETKEY = "${getkey-script}/bin/pgsodium-getkey"; }; }; getVersionArg = pkg: let name = pkg.version; in if builtins.match "15.*" name != null then "15" else if builtins.match "17.*" name != null then "orioledb-17" else throw "Unsupported PostgreSQL version: ${name}"; # Helper function to filter SQL files based on version filterTestFiles = version: dir: let files = builtins.readDir dir; isValidFile = name: let isVersionSpecific = builtins.match "z_([0-9]+)_.*" name != null; matchesVersion = if isVersionSpecific then builtins.match ("z_" + version + "_.*") name != null else true; in pkgs.lib.hasSuffix ".sql" name && matchesVersion; in pkgs.lib.filterAttrs (name: _: isValidFile name) files; # Get the major version for filtering majorVersion = if builtins.match ".*17.*" pgpkg.version != null then "17" else "15"; # Filter SQL test files filteredSqlTests = filterTestFiles majorVersion ./nix/tests/sql; # Convert filtered tests to a sorted list of basenames (without extension) testList = pkgs.lib.mapAttrsToList (name: _: builtins.substring 0 (pkgs.lib.stringLength name - 4) name ) filteredSqlTests; sortedTestList = builtins.sort (a: b: a < b) testList; in pkgs.runCommand "postgres-${pgpkg.version}-check-harness" { nativeBuildInputs = with pkgs; [ coreutils bash perl pgpkg pg_prove pg_regress procps start-postgres-server-bin which getkey-script supabase-groonga ]; } '' set -e #First we need to create a generic pg cluster for pgtap tests and run those export GRN_PLUGINS_DIR=${supabase-groonga}/lib/groonga/plugins PGTAP_CLUSTER=$(mktemp -d) initdb --locale=C --username=supabase_admin -D "$PGTAP_CLUSTER" substitute ${./nix/tests/postgresql.conf.in} "$PGTAP_CLUSTER"/postgresql.conf \ --subst-var-by PGSODIUM_GETKEY_SCRIPT "${getkey-script}/bin/pgsodium-getkey" echo "listen_addresses = '*'" >> "$PGTAP_CLUSTER"/postgresql.conf echo "port = 5435" >> "$PGTAP_CLUSTER"/postgresql.conf echo "host all all 127.0.0.1/32 trust" >> $PGTAP_CLUSTER/pg_hba.conf # Remove timescaledb if running orioledb-17 check echo "I AM ${pgpkg.version}====================================================" if [[ "${pgpkg.version}" == *"17"* ]]; then perl -pi -e 's/ timescaledb,//g' "$PGTAP_CLUSTER/postgresql.conf" fi #NOTE in the future we may also need to add the orioledb extension to the cluster when cluster is oriole echo "PGTAP_CLUSTER directory contents:" ls -la "$PGTAP_CLUSTER" # Check if postgresql.conf exists if [ ! -f "$PGTAP_CLUSTER/postgresql.conf" ]; then echo "postgresql.conf is missing!" exit 1 fi # PostgreSQL startup if [[ "$(uname)" == "Darwin" ]]; then pg_ctl -D "$PGTAP_CLUSTER" -l "$PGTAP_CLUSTER"/postgresql.log -o "-k "$PGTAP_CLUSTER" -p 5435 -d 5" start 2>&1 else mkdir -p "$PGTAP_CLUSTER/sockets" pg_ctl -D "$PGTAP_CLUSTER" -l "$PGTAP_CLUSTER"/postgresql.log -o "-k $PGTAP_CLUSTER/sockets -p 5435 -d 5" start 2>&1 fi || { echo "pg_ctl failed to start PostgreSQL" echo "Contents of postgresql.log:" cat "$PGTAP_CLUSTER"/postgresql.log exit 1 } for i in {1..60}; do if pg_isready -h localhost -p 5435; then echo "PostgreSQL is ready" break fi sleep 1 if [ $i -eq 60 ]; then echo "PostgreSQL is not ready after 60 seconds" echo "PostgreSQL status:" pg_ctl -D "$PGTAP_CLUSTER" status echo "PostgreSQL log content:" cat "$PGTAP_CLUSTER"/postgresql.log exit 1 fi done createdb -p 5435 -h localhost --username=supabase_admin testing if ! psql -p 5435 -h localhost --username=supabase_admin -d testing -v ON_ERROR_STOP=1 -Xaf ${./nix/tests/prime.sql}; then echo "Error executing SQL file. PostgreSQL log content:" cat "$PGTAP_CLUSTER"/postgresql.log pg_ctl -D "$PGTAP_CLUSTER" stop exit 1 fi SORTED_DIR=$(mktemp -d) for t in $(printf "%s\n" ${builtins.concatStringsSep " " sortedTestList}); do psql -p 5435 -h localhost --username=supabase_admin -d testing -f "${./nix/tests/sql}/$t.sql" || true done rm -rf "$SORTED_DIR" pg_ctl -D "$PGTAP_CLUSTER" stop rm -rf $PGTAP_CLUSTER # End of pgtap tests # from here on out we are running pg_regress tests, we use a different cluster for this # which is start by the start-postgres-server-bin script # start-postgres-server-bin script closely matches our AMI setup, configurations and migrations # Ensure pgsodium key directory exists with proper permissions if [[ "$(uname)" == "Darwin" ]]; then mkdir -p /private/tmp/pgsodium chmod 1777 /private/tmp/pgsodium fi unset GRN_PLUGINS_DIR ${start-postgres-server-bin}/bin/start-postgres-server ${getVersionArg pgpkg} --daemonize for i in {1..60}; do if pg_isready -h localhost -p 5435 -U supabase_admin -q; then echo "PostgreSQL is ready" break fi sleep 1 if [ $i -eq 60 ]; then echo "PostgreSQL failed to start" exit 1 fi done if ! psql -p 5435 -h localhost --no-password --username=supabase_admin -d postgres -v ON_ERROR_STOP=1 -Xaf ${./nix/tests/prime.sql}; then echo "Error executing SQL file" exit 1 fi mkdir -p $out/regression_output if ! pg_regress \ --use-existing \ --dbname=postgres \ --inputdir=${./nix/tests} \ --outputdir=$out/regression_output \ --host=localhost \ --port=5435 \ --user=supabase_admin \ ${builtins.concatStringsSep " " sortedTestList}; then echo "pg_regress tests failed" exit 1 fi # Copy logs to output for logfile in $(find /tmp -name postgresql.log -type f); do cp "$logfile" $out/postgresql.log done exit 0 ''; in rec { # The list of all packages that can be built with 'nix build'. The list # of names that can be used can be shown with 'nix flake show' packages = flake-utils.lib.flattenTree basePackages // { # Any extra packages we might want to include in our package # set can go here. inherit (pkgs); }; # The list of exported 'checks' that are run with every run of 'nix # flake check'. This is run in the CI system, as well. checks = { psql_15 = makeCheckHarness basePackages.psql_15.bin; psql_orioledb-17 = makeCheckHarness basePackages.psql_orioledb-17.bin; }; # Apps is a list of names of things that can be executed with 'nix run'; # these are distinct from the things that can be built with 'nix build', # so they need to be listed here too. apps = let mkApp = attrName: binName: { type = "app"; program = "${basePackages."${attrName}"}/bin/${binName}"; }; in { start-server = mkApp "start-server" "start-postgres-server"; start-client = mkApp "start-client" "start-postgres-client"; start-replica = mkApp "start-replica" "start-postgres-replica"; migrate-postgres = mkApp "migrate-tool" "migrate-postgres"; sync-exts-versions = mkApp "sync-exts-versions" "sync-exts-versions"; pg-restore = mkApp "pg-restore" "pg-restore"; local-infra-bootstrap = mkApp "local-infra-bootstrap" "local-infra-bootstrap"; dbmate-tool = mkApp "dbmate-tool" "dbmate-tool"; update-readme = mkApp "update-readme" "update-readme"; }; # 'devShells.default' lists the set of packages that are included in the # ambient $PATH environment when you run 'nix develop'. This is useful # for development and puts many convenient devtools instantly within # reach. devShells = let mkCargoPgrxDevShell = { pgrxVersion, rustVersion }: pkgs.mkShell { packages = with pkgs; [ basePackages."cargo-pgrx_${pgrxVersion}" (rust-bin.stable.${rustVersion}.default.override { extensions = [ "rust-src" ]; }) ]; shellHook = '' export HISTFILE=.history ''; }; in { default = pkgs.mkShell { packages = with pkgs; [ coreutils just nix-update #pg_prove shellcheck ansible ansible-lint (packer.overrideAttrs (oldAttrs: { version = "1.7.8"; })) basePackages.start-server basePackages.start-client basePackages.start-replica basePackages.migrate-tool basePackages.sync-exts-versions dbmate nushell ]; shellHook = '' export HISTFILE=.history ''; }; cargo-pgrx_0_11_3 = mkCargoPgrxDevShell { pgrxVersion = "0_11_3"; rustVersion = "1.80.0"; }; cargo-pgrx_0_12_6 = mkCargoPgrxDevShell { pgrxVersion = "0_12_6"; rustVersion = "1.80.0"; }; }; } ); }