All checks were successful
CI / changes (pull_request) Successful in 6s
CI / changes (push) Successful in 7s
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 6m23s
CI / backend-tests (pull_request) Successful in 6m21s
- Создан tradein-mvp/ops/db-bootstrap/set_gendesign_reader_password.sql:
set_config GUC + format('%L') + \o /dev/null — зеркальный паттерн
ops/db-bootstrap/set_tradein_fdw_password.sql. Пароль не попадает
в stdout/logs (leak-protected).
- deploy-tradein.yml: шаг bootstrap переписан на stdin-redirect
(< ops/db-bootstrap/set_gendesign_reader_password.sql) вместо сломанного
-c "ALTER ROLE ... PASSWORD :'TRADEIN_READER_PASSWORD'", которое давало
syntax error at ':' (psql не интерполирует :'var' внутри -c строки).
- newbuilding_crossload.py ON CONFLICT DO UPDATE: yandex_jk_id и
cian_internal_house_id → COALESCE(EXCLUDED.col, table.col), чтобы
cian-запись не затирала yandex_jk_id NULL'ом и наоборот.
- Тест test_upsert_sql_coalesce_external_ids проверяет наличие COALESCE
в DO UPDATE секции для обоих id.
54 lines
3.2 KiB
SQL
54 lines
3.2 KiB
SQL
-- Set gendesign_reader password from env.
|
||
-- Applied by .forgejo/workflows/deploy-tradein.yml after migrations:
|
||
-- psql -v pw="$TRADEIN_READER_PASSWORD" -f ops/db-bootstrap/set_gendesign_reader_password.sql
|
||
-- cwd на деплое = /opt/gendesign/tradein-mvp (путь относительный).
|
||
--
|
||
-- Idempotent: ALTER если роль существует, NOTICE и продолжает если нет
|
||
-- (migration 101_gendesign_reader_role.sql может ещё не примениться на первом деплое).
|
||
-- Пароль НИКОГДА не хранится в этом файле или в git — только имя переменной.
|
||
--
|
||
-- Format %L экранирует пароль как SQL string literal — безопасно даже с кавычками.
|
||
--
|
||
-- psql variable substitution (:'pw') НЕ интерполируется внутри dollar-quoted
|
||
-- блока ($$...$$) — это правило psql, не bug. Поэтому password передаём в DO
|
||
-- через сессионный GUC (set_config), который psql интерполирует ВНЕ dollar
|
||
-- quote, и читаем внутри через current_setting().
|
||
-- Reference incident: deploy 2026-05-24 (post-merge PR #503) упал на
|
||
-- "syntax error at or near ':'" — :'pw' дошёл до сервера as literal вместо
|
||
-- интерполяции при использовании -c вместо stdin + файл.
|
||
--
|
||
-- Источник переменной pw: TRADEIN_READER_PASSWORD из tradein-mvp/.env.runtime.
|
||
-- Передаётся через: psql -v "pw=${TRADEIN_READER_PASSWORD}"
|
||
--
|
||
-- ⚠️ `set_config(name, value, is_local) -> text` ВОЗВРАЩАЕТ установленное
|
||
-- значение. Без `\o /dev/null` psql напечатал бы пароль на stdout → leak в
|
||
-- Forgejo Actions deploy logs (retained, visible всем с repo read access).
|
||
-- Поэтому оборачиваем оба set_config вызова в `\o /dev/null` / `\o` brackets
|
||
-- — output mutes только для этих строк, NOTICE-сообщения из DO block
|
||
-- (idempotency signal) остаются видимыми.
|
||
--
|
||
-- Rollback path: НЕ revert этого файла (вернёт сломанный :'pw' внутри $$).
|
||
-- Корректный rollback — unset TRADEIN_READER_PASSWORD в
|
||
-- /opt/gendesign/tradein-mvp/.env.runtime на VPS, deploy-tradein.yml
|
||
-- тогда пропустит этот шаг полностью.
|
||
|
||
\o /dev/null
|
||
SELECT set_config('app.reader_pw', :'pw', false);
|
||
\o
|
||
|
||
DO $$
|
||
BEGIN
|
||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'gendesign_reader') THEN
|
||
EXECUTE format('ALTER ROLE gendesign_reader WITH PASSWORD %L', current_setting('app.reader_pw'));
|
||
RAISE NOTICE 'gendesign_reader password set';
|
||
ELSE
|
||
RAISE NOTICE 'gendesign_reader role missing — migration 101_gendesign_reader_role.sql not applied yet';
|
||
END IF;
|
||
END $$;
|
||
|
||
-- Clear GUC after use (defense-in-depth — не оставляем password в session state
|
||
-- даже на short connection). Same \o trick — set_config return value is empty
|
||
-- string here, но всё равно лишний row в stdout не нужен.
|
||
\o /dev/null
|
||
SELECT set_config('app.reader_pw', '', false);
|
||
\o
|