From 3d5e3fdb368d845521a0a5e5069f1dbf08100f24 Mon Sep 17 00:00:00 2001 From: yukkop Date: Thu, 30 Apr 2026 21:59:53 +0000 Subject: [PATCH] feat: postgres hooks --- lib/default.nix | 4 +- lib/hook/apply-hectic-bundle.sh | 58 +++++++++++++++ package/db-tool/database.sh | 12 ++- package/db-tool/default.nix | 28 +++++-- package/db-tool/postgres-init.sh | 10 --- package/default.nix | 4 +- package/migrator/default.nix | 19 ++++- package/migrator/migrator.sh | 124 ++++--------------------------- 8 files changed, 127 insertions(+), 132 deletions(-) create mode 100644 lib/hook/apply-hectic-bundle.sh diff --git a/lib/default.nix b/lib/default.nix index 4ff2f6b..ab7a5a0 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -102,7 +102,8 @@ in { # Consolidated SQL bundles for the `hectic` schema. Single source of truth # for everything that creates objects in the `hectic` namespace, used by - # migrator (init-time), db-tool (postgres-init), and pkgs.hectic.postgres-secrets. + # migrator (init-time) and db-tool (postgres-init + hydrate). Consumers apply + # the full bundle via lib/hook/apply-hectic-bundle.sh. # # The whole hectic system shares one `versionString`; `hectic-version.sql` # registers (`'hectic'`, versionString) into `hectic.version` and raises an @@ -126,6 +127,7 @@ in { secret = static ./hook/sql/hectic-secret.sql; migration = static ./hook/sql/hectic-migration.sql; inheritance = static ./hook/sql/hectic-inheritance.sql; + applyBundleScript = ./hook/apply-hectic-bundle.sh; }; # Back-compat alias. Prefer `self.lib.hectic.inheritance`. diff --git a/lib/hook/apply-hectic-bundle.sh b/lib/hook/apply-hectic-bundle.sh new file mode 100644 index 0000000..0d3c1e1 --- /dev/null +++ b/lib/hook/apply-hectic-bundle.sh @@ -0,0 +1,58 @@ +#!/bin/dash +# Applies the full hectic SQL bundle to a PostgreSQL database, in order: +# 1. version (hard-fails on version mismatch) +# 2. secret (hectic.secret table + load_secrets_from_env + get_secret) +# 3. migration (hectic.migration table + domains + sha256_lower trigger) +# 4. inheritance (created_at/updated_at/immutable enforcement triggers) +# +# Idempotent: each SQL file uses IF NOT EXISTS / CREATE OR REPLACE. +# +# Required env (caller injects from Nix): +# HECTIC_VERSION_SQL - path to hectic-version.sql (substituted) +# HECTIC_SECRET_SQL - path to hectic-secret.sql +# HECTIC_MIGRATION_SQL - path to hectic-migration.sql +# HECTIC_INHERITANCE_SQL - path to hectic-inheritance.sql +# +# Usage: +# apply_hectic_bundle [] +# +# If DOTENV_CONTENT is non-empty, it is loaded into hectic.secret via +# hectic.load_secrets_from_env() after the bundle is applied. + +apply_hectic_bundle() { + pgurl="${1:-}" + env_content="${2:-}" + + if [ -z "$pgurl" ]; then + printf '%s\n' 'apply-hectic-bundle: PGURL is required (arg 1)' >&2 + return 3 + fi + + for var in HECTIC_VERSION_SQL HECTIC_SECRET_SQL HECTIC_MIGRATION_SQL HECTIC_INHERITANCE_SQL; do + eval "val=\${$var:-}" + if [ -z "$val" ]; then + printf '%s\n' "apply-hectic-bundle: $var not set" >&2 + return 3 + fi + if [ ! -r "$val" ]; then + printf '%s\n' "apply-hectic-bundle: $var not readable: $val" >&2 + return 1 + fi + done + + psql "$pgurl" -v ON_ERROR_STOP=1 -f "$HECTIC_VERSION_SQL" || return 1 + psql "$pgurl" -v ON_ERROR_STOP=1 -f "$HECTIC_SECRET_SQL" || return 1 + psql "$pgurl" -v ON_ERROR_STOP=1 -f "$HECTIC_MIGRATION_SQL" || return 1 + psql "$pgurl" -v ON_ERROR_STOP=1 -f "$HECTIC_INHERITANCE_SQL" || return 1 + + if [ -n "$env_content" ]; then + # Dollar-quote with $ps_env$ tag to preserve all content verbatim. + psql "$pgurl" -v ON_ERROR_STOP=1 <&2 - return 3 - fi - [ -r "$sql_file" ] || { printf '%s\n' "postgres-init: hectic-inheritance SQL not readable: $sql_file" >&2; return 1; } - psql -h "$sockdir" -p "$PG_PORT" -U "$user" -d "$db" -v ON_ERROR_STOP=1 -f "$sql_file" || return 1 - fi - export POSTGRESQL_HOST="$sockdir" POSTGRESQL_PORT="$PG_PORT" POSTGRESQL_USER="$user" POSTGRESQL_DATABASE="$db" _pg_url="postgresql://${POSTGRESQL_USER}@/${POSTGRESQL_DATABASE}?host=${POSTGRESQL_HOST}&port=${POSTGRESQL_PORT}" case $PG_URL_VAR in ''|*[!ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_]* ) printf '%s\n' 'postgres-init: invalid PG_URL_VAR' >&2; return 1 ;; esac diff --git a/package/default.nix b/package/default.nix index dbbdaad..7652f1e 100644 --- a/package/default.nix +++ b/package/default.nix @@ -107,7 +107,7 @@ }; nativeBuildInputs = with pkgs; [pkg-config curl]; }; - dbToolPkgs = pkgs.callPackage ./db-tool {}; + dbToolPkgs = pkgs.callPackage ./db-tool { inherit self; }; in { py3-datetime = pkgs.callPackage ./py3-datetime.nix {}; py3-marzban = pkgs.callPackage ./py3-marzban.nix { inherit self; }; @@ -140,7 +140,7 @@ in { deploy = pkgs.callPackage ./deploy { inherit inputs; }; shellplot = pkgs.callPackage ./shellplot {}; onlinepubs2man = pkgs.callPackage ./onlinepubs2man {}; - migrator = pkgs.callPackage ./migrator {}; + migrator = pkgs.callPackage ./migrator { inherit self; }; "parse-uri" = pkgs.callPackage ./parse-uri {}; "db-tool" = dbToolPkgs."db-tool"; "postgres-init" = dbToolPkgs."postgres-init"; diff --git a/package/migrator/default.nix b/package/migrator/default.nix index 51b7283..9bc1565 100644 --- a/package/migrator/default.nix +++ b/package/migrator/default.nix @@ -1,4 +1,4 @@ -{ dash, hectic, sqlite, postgresql_17, gawk }: +{ dash, hectic, sqlite, postgresql_17, gawk, runCommand, self }: let shell = "${dash}/bin/dash"; bashOptions = [ @@ -6,6 +6,21 @@ let "nounset" ]; + hecticVersionSqlFile = runCommand "hectic-version.sql" { + text = self.lib.hectic.version.sql; + passAsFile = [ "text" ]; + } ''cp "$textPath" "$out"''; + + hecticEnv = '' + HECTIC_VERSION_SQL=${hecticVersionSqlFile} + HECTIC_SECRET_SQL=${self.lib.hectic.secret.path} + HECTIC_MIGRATION_SQL=${self.lib.hectic.migration.path} + HECTIC_INHERITANCE_SQL=${self.lib.hectic.inheritance.path} + export HECTIC_VERSION_SQL HECTIC_SECRET_SQL HECTIC_MIGRATION_SQL HECTIC_INHERITANCE_SQL + ''; + + applyBundle = builtins.readFile self.lib.hectic.applyBundleScript; + migrator = hectic.writeShellApplication { inherit shell bashOptions; name = "migrator"; @@ -13,6 +28,8 @@ let text = '' ${builtins.readFile hectic.helpers.posix-shell.log} + ${hecticEnv} + ${applyBundle} ${builtins.readFile ./migrator.sh} ''; }; diff --git a/package/migrator/migrator.sh b/package/migrator/migrator.sh index 3c47259..f1ed585 100644 --- a/package/migrator/migrator.sh +++ b/package/migrator/migrator.sh @@ -170,35 +170,19 @@ init() { db_type=$(detect_db_type) - # INHERITS is PostgreSQL-only feature - [ ${INHERITS_LIST+x} ] && { - if [ "$db_type" != "postgresql" ]; then - log error "INHERITS is only supported for PostgreSQL" - exit 1 + if [ "$db_type" = "postgresql" ]; then + if ! apply_hectic_bundle "$DB_URL"; then + log error "init failed: hectic bundle apply" + exit 13 fi + fi - oldIFS="$IFS" - IFS=',' - check_inherits= - for table in $INHERITS_LIST; do - check_inherits="$(printf '%s\nSELECT 1 FROM %s LIMIT 1;' "$check_inherits" "$table")" - done - IFS="$oldIFS" - - check_inherits=$(printf '%s\n' \ - 'BEGIN;' \ - "$check_inherits" \ - 'COMMIT;') - - if ! db_exec "$check_inherits"; then - log error "init failed: ${WHITE}one of inherits table does not exists: ${CYAN}$INHERITS_LIST" - exit 5 + init_sql_extra="$(init_sql)" + if [ -n "$init_sql_extra" ]; then + if ! db_exec "$init_sql_extra"; then + log error "init failed" + exit 13 fi - } - - if ! db_exec "$(init_sql)"; then - log error "init failed" - exit 13 fi } @@ -209,86 +193,11 @@ error_handler_no_db_url() { } init_sql_postgresql() { - local sql inherits - - inherits= - [ ${INHERITS_LIST+x} ] && inherits="$(printf 'INHERITS(%s)' "$INHERITS_LIST")" - - sql="$(cat < pass through @@ -1110,7 +1019,6 @@ if ! [ "${AS_LIBRARY+x}" ]; then esac done - [ "${INHERITS_LIST+x}" ] && INHERITS_LIST="$(printf '%s' "$INHERITS_LIST" | sed -E 's/"/,/g; s/([^,]+)/"\1"/g')" [ "${SUBCOMMAND+x}" ] || { log error "no subcommand specified. Use 'migrator help' for usage information."; exit 1; }