import { Client } from "pg"; export declare const SUPABASE_ROOT_CA = "-----BEGIN CERTIFICATE-----\nMIIDxDCCAqygAwIBAgIUbLxMod62P2ktCiAkxnKJwtE9VPYwDQYJKoZIhvcNAQEL\nBQAwazELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0RlbHdhcmUxEzARBgNVBAcMCk5l\ndyBDYXN0bGUxFTATBgNVBAoMDFN1cGFiYXNlIEluYzEeMBwGA1UEAwwVU3VwYWJh\nc2UgUm9vdCAyMDIxIENBMB4XDTIxMDQyODEwNTY1M1oXDTMxMDQyNjEwNTY1M1ow\nazELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0RlbHdhcmUxEzARBgNVBAcMCk5ldyBD\nYXN0bGUxFTATBgNVBAoMDFN1cGFiYXNlIEluYzEeMBwGA1UEAwwVU3VwYWJhc2Ug\nUm9vdCAyMDIxIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqQXW\nQyHOB+qR2GJobCq/CBmQ40G0oDmCC3mzVnn8sv4XNeWtE5XcEL0uVih7Jo4Dkx1Q\nDmGHBH1zDfgs2qXiLb6xpw/CKQPypZW1JssOTMIfQppNQ87K75Ya0p25Y3ePS2t2\nGtvHxNjUV6kjOZjEn2yWEcBdpOVCUYBVFBNMB4YBHkNRDa/+S4uywAoaTWnCJLUi\ncvTlHmMw6xSQQn1UfRQHk50DMCEJ7Cy1RxrZJrkXXRP3LqQL2ijJ6F4yMfh+Gyb4\nO4XajoVj/+R4GwywKYrrS8PrSNtwxr5StlQO8zIQUSMiq26wM8mgELFlS/32Uclt\nNaQ1xBRizkzpZct9DwIDAQABo2AwXjALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFKjX\nuXY32CztkhImng4yJNUtaUYsMB8GA1UdIwQYMBaAFKjXuXY32CztkhImng4yJNUt\naUYsMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAB8spzNn+4VU\ntVxbdMaX+39Z50sc7uATmus16jmmHjhIHz+l/9GlJ5KqAMOx26mPZgfzG7oneL2b\nVW+WgYUkTT3XEPFWnTp2RJwQao8/tYPXWEJDc0WVQHrpmnWOFKU/d3MqBgBm5y+6\njB81TU/RG2rVerPDWP+1MMcNNy0491CTL5XQZ7JfDJJ9CCmXSdtTl4uUQnSuv/Qx\nCea13BX2ZgJc7Au30vihLhub52De4P/4gonKsNHYdbWjg7OWKwNv/zitGDVDB9Y2\nCMTyZKG3XEu5Ghl1LEnI3QmEKsqaCLv12BnVjbkSeZsMnevJPs1Ye6TjjJwdik5P\no/bKiIz+Fq8=\n-----END CERTIFICATE-----\n"; /** node-pg 的 ssl 配置:验签(用打包的 Supabase 根 CA)+ 验主机名(rejectUnauthorized)。 */ export declare const SUPABASE_SSL: { readonly ca: "-----BEGIN CERTIFICATE-----\nMIIDxDCCAqygAwIBAgIUbLxMod62P2ktCiAkxnKJwtE9VPYwDQYJKoZIhvcNAQEL\nBQAwazELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0RlbHdhcmUxEzARBgNVBAcMCk5l\ndyBDYXN0bGUxFTATBgNVBAoMDFN1cGFiYXNlIEluYzEeMBwGA1UEAwwVU3VwYWJh\nc2UgUm9vdCAyMDIxIENBMB4XDTIxMDQyODEwNTY1M1oXDTMxMDQyNjEwNTY1M1ow\nazELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0RlbHdhcmUxEzARBgNVBAcMCk5ldyBD\nYXN0bGUxFTATBgNVBAoMDFN1cGFiYXNlIEluYzEeMBwGA1UEAwwVU3VwYWJhc2Ug\nUm9vdCAyMDIxIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqQXW\nQyHOB+qR2GJobCq/CBmQ40G0oDmCC3mzVnn8sv4XNeWtE5XcEL0uVih7Jo4Dkx1Q\nDmGHBH1zDfgs2qXiLb6xpw/CKQPypZW1JssOTMIfQppNQ87K75Ya0p25Y3ePS2t2\nGtvHxNjUV6kjOZjEn2yWEcBdpOVCUYBVFBNMB4YBHkNRDa/+S4uywAoaTWnCJLUi\ncvTlHmMw6xSQQn1UfRQHk50DMCEJ7Cy1RxrZJrkXXRP3LqQL2ijJ6F4yMfh+Gyb4\nO4XajoVj/+R4GwywKYrrS8PrSNtwxr5StlQO8zIQUSMiq26wM8mgELFlS/32Uclt\nNaQ1xBRizkzpZct9DwIDAQABo2AwXjALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFKjX\nuXY32CztkhImng4yJNUtaUYsMB8GA1UdIwQYMBaAFKjXuXY32CztkhImng4yJNUt\naUYsMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAB8spzNn+4VU\ntVxbdMaX+39Z50sc7uATmus16jmmHjhIHz+l/9GlJ5KqAMOx26mPZgfzG7oneL2b\nVW+WgYUkTT3XEPFWnTp2RJwQao8/tYPXWEJDc0WVQHrpmnWOFKU/d3MqBgBm5y+6\njB81TU/RG2rVerPDWP+1MMcNNy0491CTL5XQZ7JfDJJ9CCmXSdtTl4uUQnSuv/Qx\nCea13BX2ZgJc7Au30vihLhub52De4P/4gonKsNHYdbWjg7OWKwNv/zitGDVDB9Y2\nCMTyZKG3XEu5Ghl1LEnI3QmEKsqaCLv12BnVjbkSeZsMnevJPs1Ye6TjjJwdik5P\no/bKiIz+Fq8=\n-----END CERTIFICATE-----\n"; readonly rejectUnauthorized: true; }; /** * 把打包的 CA 写到临时文件并返回路径:pg_dump(libpq)的 sslrootcert 只认文件路径,不认字符串。 * 缓存,进程内只写一次;内容是公开 CA(非密),mode 0600 足矣。 */ export declare function supabaseCaFile(): string; /** * 把连接串强制改写成 verify-full + 我们的 CA(给 pg_dump 用)。剥掉用户自带的所有 ssl* 参数 * ——libpq 里 URL 参数优先级高于环境变量,残留的 sslmode=require 会盖掉我们的 verify-full。 */ export declare function forceVerifyFull(databaseUrl: string, caPath: string): string; export declare function isSupabaseHost(databaseUrl: string): boolean; /** pg_dump 用:Supabase 主机才改写成 verify-full;其它原样透传。 */ /** * 把连接目标写死在连接串里:端口缺省 5432、库名缺省 postgres、并去掉**空的** options 参数。 * 为什么:node-pg 与 libpq 对"URL 里没写的字段"各自回退到环境变量(PGPORT / PGDATABASE / * PGOPTIONS…)。子进程环境已剔除 PG*,而 Node 侧的预检客户端仍会读——两边可能连到不同的 * 库/端口,空目标检查看的是一个库、pg_restore 写的是另一个(交叉审查)。Node 客户端与 * pg_dump/pg_restore 都用规范化后的串,谁也不再依赖环境变量补字段。 * 空的 `?options=` 会让 pg 的 val() 回退读 PGOPTIONS,同样去掉;非空 options 是用户显式意图, * 保留(pooler 主机上任何 options 已被 assertNoHostOverride 拒绝)。 * 解析不了的串原样返回:上游守卫(assertNoHostOverride / assertSafeDatabaseUrl)负责拒绝。 */ export declare function normalizeConnectionTarget(databaseUrl: string): string; export declare function dumpUrlFor(databaseUrl: string): string; /** * node-pg 连接选项:把"剥离连接串里的 SSL 参数"与"套上打包 CA"做成**原子**的一步。 * * 为什么必须一起做(2026-07-22 实测,pg 8.22):连接串里的 SSL 参数**会覆盖**显式传的 * `ssl` 选项——`?sslmode=disable` / `?ssl=0` 会让我们的 CA 被整个丢弃、退化成不加密, * `?ssl=true` 则加密但不带我们的 CA。而 sslmode 在产品的参数黑名单里是**放行**的(为兼容 * 存量连接串),所以不剥就等于强制 verify-full 可被用户参数静默绕过。 * * 非 Supabase 主机(本 CLI 不限制目标)原样返回:不剥、不套,尊重用户自己的 sslmode。 */ /** * Supabase 主机的 pg Client 显式带 startup `options`。为什么:node-postgres 只在 config.options * 为假值时才读环境变量 PGOPTIONS,而 Supavisor 会从 options 里解析 `reference=` 并让它优先于 * 用户名里的租户——继承的 PGOPTIONS 能把连接悄悄路由到别的项目(交叉审查)。给一个无害的真值就把 * 环境变量挡在门外(Supavisor 与直连 Postgres 都实测接受);顺带在 pg_stat_activity 里能认出是谁在连。 * 只对 Supabase 主机做:租户覆盖只存在于 Supavisor,而 PgBouncer 这类中间件默认拒绝陌生的启动参数 * (unsupported startup parameter: options),别给自带 Postgres 的用户制造回归。 */ export declare const PG_CLIENT_OPTIONS = "-c application_name=backupdrill"; export declare function pgConnectOptions(databaseUrl: string): { connectionString: string; options?: string; ssl?: typeof SUPABASE_SSL; }; /** * 给一个 pg Client 挂上 error 监听。**必须在 connect() 之前挂。** * * 为什么:node-postgres 在连接建立后被对端掐断(Supabase 抖动、项目被暂停、网络抖)时, * 除了让正在等的查询 reject,还会在 client 上 emit "error";EventEmitter 没有监听者的 "error" * 直接抛成 uncaughtException。本引擎跑在 BackupDrill worker 进程里,一个客户库断连就能杀掉 * 整个 worker,连带打断其他客户正在跑的任务(2026-08-28 复盘)。挂上之后查询照常 reject、 * 调用方的 try/finally 照常收尾,进程活着。只记一行日志:断连的原因会从 reject 的错误里报出来。 */ export declare function attachPgErrorGuard(client: { on(event: "error", listener: (error: Error) => void): unknown; }): void; /** 连接一个 Postgres:pgConnectOptions(TLS 口径)+ error 监听 + connect(),三步不可分开。 */ export declare function connectPg(databaseUrl: string): Promise;