-- Создание БД `auth` — единого хранилища доступов «Меры» и «Птицы» (идемпотентно). -- -- Applied by .forgejo/workflows/deploy.yml ПЕРЕД миграциями data/sql/auth/*.sql: -- docker compose ... exec -T postgres psql -U "$POSTGRES_USER" -d postgres \ -- -v ON_ERROR_STOP=on < ops/db-bootstrap/create_auth_db.sql -- Подключение обязательно к БД `postgres`: нельзя создать базу, находясь в ней самой. -- -- ПОЧЕМУ ЭТО НЕ МИГРАЦИЯ: -- CREATE DATABASE запрещён внутри транзакционного блока, а .claude/rules/sql.md требует -- от каждого файла в data/sql обёртки BEGIN/COMMIT. Плюс миграции `auth` по определению -- выполняются уже ВНУТРИ БД `auth` — то есть создать её собой они не могут. Отсюда -- отдельный bootstrap-шаг, по образцу scripts/bootstrap_glitchtip.sh (там так же -- заводится вторая БД на этом же сервере). -- -- ПОЧЕМУ \gexec, А НЕ DO-БЛОК: -- DO-блок — это функция, она выполняется внутри транзакции, значит CREATE DATABASE в ней -- недопустим. \gexec строит текст команды на стороне клиента и отправляет её отдельным -- стейтментом. Если WHERE NOT EXISTS отфильтровал строку, \gexec не получает ничего и -- молча ничего не делает — это и даёт идемпотентность без ошибки на повторном прогоне. -- ON_ERROR_STOP=on распространяется и на команды, выполненные через \gexec. -- -- ВЛАДЕЛЕЦ БД — $POSTGRES_USER (суперюзер кластера), НЕ auth_app. Владелец объекта имеет на -- него все права в обход GRANT'ов; если бы БД и таблицы принадлежали прикладной роли, -- точечные гранты в data/sql/auth/002_auth_app_role.sql были бы декорацией. Роль auth_app -- создаётся миграцией 002 и получает только нужные DML-права. -- -- TEMPLATE template0 — сознательно, а не template1 (шаблон по умолчанию): template0 -- гарантированно пуст и неизменяем, а в template1 любой может доустановить расширения или -- объекты, и они молча окажутся в хранилище паролей. На образе postgis:16-3.4 сегодня -- postgis лежит в template_postgis, а template1 чист (проверено локально на том же образе), -- но полагаться на это как на инвариант незачем — template0 снимает вопрос навсегда. -- ENCODING 'UTF8' указан явно (кластер и так UTF8 — вся кириллица gendesign лежит в нём), -- чтобы кодировка хранилища логинов не зависела от того, с какими аргументами когда-нибудь -- пересоздадут кластер. -- -- Пароля в этом файле нет и быть не может: роль создаётся passwordless в миграции 002, -- пароль ставится отдельным шагом из env (ops/db-bootstrap/set_auth_app_password.sql). SELECT 'CREATE DATABASE auth TEMPLATE template0 ENCODING ''UTF8'';' WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = 'auth') \gexec -- Единственная преграда для «любая login-роль кластера (glitchtip, tradein_fdw_reader, -- gendesign_reader) открывает сессию в хранилище паролей»: по умолчанию PostgreSQL выдаёт -- CONNECT роли PUBLIC при создании БД. -- -- ДУБЛЬ С data/sql/auth/002_auth_app_role.sql — НАМЕРЕННЫЙ, не копипаста. Инвариант держится -- в двух местах, потому что у файлов разный жизненный цикл: -- * здесь (bootstrap) — ради ПЕРЕПРИМЕНЯЕМОСТИ: этот файл гоняется на КАЖДОМ деплое, там же, -- где создаётся БД. Если `auth` восстановят из дампа или пересоздадут в обход миграций, -- база появится с дефолтным PUBLIC-CONNECT, а 002 уже числится применённой в -- _schema_migrations и второй раз не выполнится — REVOKE молча не вернётся. -- * в 002 — ради САМОДОСТАТОЧНОСТИ миграции: применённая на пустую БД (scratch/staging, -- ручной psql -f) она обязана давать полный периметр прав без чтения bootstrap-файлов. -- Удалять любую из двух копий нельзя: каждая закрывает сценарий, который другая не покрывает. -- -- Выполнимо из подключения к БД `postgres` (мы именно в ней): права на объект DATABASE живут -- в pg_database.datacl — это общий на кластер каталог, не локальный для БД, в отличие от -- грантов на таблицы/схемы. Проверено эмпирически на postgis:16-3.4 (REVOKE из сессии в -- `postgres` по другой БД убирает `=Tc/` из datacl, has_database_privilege('public', …, -- 'CONNECT') → false). Команда идемпотентна — повторный прогон бесплатен. REVOKE ALL ON DATABASE auth FROM PUBLIC; COMMENT ON DATABASE auth IS 'Единое хранилище доступов: «Мера» (trade-in) и «Птица» (Site Finder). Схема — ' 'data/sql/auth/*.sql, применяется отдельным циклом миграций в .forgejo/workflows/deploy.yml ' '(таблица _schema_migrations живёт внутри этой же БД).';