mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-05 09:14:58 +03:00
Первичное требование: поднял докер — прошёл мастер — дальше всё в интерфейсе. Ни одной переменной окружения задавать не нужно и негде. Запуск и секреты: - .env.example удалён, мёртвые переменные вычищены; секреты инстанса генерирует одноразовый сервис secrets в именованный том, пароли БД — через POSTGRES_*_PASSWORD_FILE; - Caddy: catch-all :80 и on_demand TLS вместо хостов в конфиге — свежая коробка отвечает по IP и по любому домену, до мастера дойти можно; - адрес установки, SMTP и TURN переехали в настройки (InstanceSettings, миграции 0027–0030), внешние ссылки строятся от него; - deploy/cli больше не читает .env; сборка образов в ghcr через GitHub Actions, release.env с digest-пинами; - куки Secure/__Host- выставляются по факту TLS запроса, а не настройкой. Порталы (дизайн-базлайн v2, кадры PT1–PT8): - список, карточка портала, библиотека материалов, редактор статьи и настройки — ширины и ритм как в остальных разделах; - файлы статьи: изображение вставляется в текст своим механизмом, файл прикрепляется вложением и выводится на портале списком с иконкой формата; - ссылки на файлы приводятся к относительным: абсолютный хост резал CSP портала и картинка не появлялась; - «Опубликовать» сверяется с сервером и публикует то, что на экране, а не ранее выбранную редакцию; - колонка «Оценки» в списке статей и блок оценок в редакторе. Первый запуск: полоса «демо-данные устанавливаются» — установка идёт в worker, и без неё человек видел пустые разделы без объяснения. Починен фон: вторичное хранилище стало best-effort — при живых остаточных S3-ключах повторная загрузка файла падала в 500. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
4.8 KiB
Python
94 lines
4.8 KiB
Python
import os
|
||
|
||
|
||
|
||
from django.core.exceptions import ImproperlyConfigured
|
||
|
||
|
||
|
||
from chatballs_backend.settings_env import env_secret
|
||
|
||
|
||
|
||
|
||
|
||
def _credentials() -> tuple[dict[str, str], dict[str, str]]:
|
||
|
||
# Имена ролей — константы продукта, а не настройка установки: их создаёт
|
||
|
||
# deploy/postgres/init-runtime-roles.sh при первом старте. Переменные
|
||
|
||
# окружения остаются переопределением для нестандартных установок.
|
||
|
||
users = {
|
||
|
||
"app": os.environ.get("POSTGRES_APP_USER", "chatballs_app"),
|
||
|
||
"platform": os.environ.get("POSTGRES_PLATFORM_USER", "chatballs_platform"),
|
||
|
||
"migration": os.environ.get(
|
||
|
||
"POSTGRES_MIGRATION_USER", "chatballs_migration"
|
||
|
||
),
|
||
|
||
}
|
||
|
||
# Пароли ролей генерирует первый старт стека в том с секретами; человек их
|
||
|
||
# не вводит и не хранит. Переменные окружения остаются переопределением.
|
||
|
||
fallback = env_secret("POSTGRES_PASSWORD", "postgres_password", "chatballs")
|
||
|
||
passwords = {
|
||
|
||
"app": env_secret("POSTGRES_APP_PASSWORD", "postgres_app_password", fallback),
|
||
|
||
"platform": env_secret(
|
||
|
||
"POSTGRES_PLATFORM_PASSWORD", "postgres_platform_password", fallback
|
||
|
||
),
|
||
|
||
"migration": env_secret(
|
||
|
||
"POSTGRES_MIGRATION_PASSWORD", "postgres_migration_password", fallback
|
||
|
||
),
|
||
|
||
}
|
||
|
||
return users, passwords
|
||
|
||
|
||
|
||
|
||
|
||
def _pool_options(*, testing: bool) -> dict | None:
|
||
|
||
# ASGI-серверы исполняют ORM в короткоживущих потоках sync_to_async;
|
||
|
||
# persistent-соединения (CONN_MAX_AGE > 0) в таких потоках осиротевают и
|
||
|
||
# исчерпывают max_connections Postgres. Вместо них — psycopg pool на процесс:
|
||
|
||
# соединения возвращаются в пул независимо от потока и ограничены сверху.
|
||
|
||
# CHATBALLS_DB_POOL_MAX=0 отключает пул (короткоживущие соединения на запрос).
|
||
|
||
if testing:
|
||
|
||
return None
|
||
|
||
max_size = int(os.environ.get("CHATBALLS_DB_POOL_MAX", "4"))
|
||
|
||
if max_size <= 0:
|
||
|
||
return None
|
||
|
||
return {
|
||
|
||
"min_size": int(os.environ.get("CHATBALLS_DB_POOL_MIN", "1")),
|
||
|
||
"max_size": max_size,
|
||
|
||
"timeout": float(os.environ.get("CHATBALLS_DB_POOL_TIMEOUT", "10")),
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
def build_databases(*, debug: bool, testing: bool) -> dict[str, dict]:
|
||
|
||
role = os.environ.get("CHATBALLS_DB_ROLE", "app").lower()
|
||
|
||
if role not in {"app", "platform", "migration"}:
|
||
|
||
raise ImproperlyConfigured("CHATBALLS_DB_ROLE must be app, platform or migration")
|
||
|
||
users, passwords = _credentials()
|
||
|
||
if not debug and not testing and len(set(users.values())) != 3:
|
||
|
||
raise ImproperlyConfigured(
|
||
|
||
"App, platform and migration database users must be distinct"
|
||
|
||
)
|
||
|
||
pool = _pool_options(testing=testing)
|
||
|
||
|
||
|
||
def config(selected_role: str) -> dict:
|
||
|
||
return {
|
||
|
||
"ENGINE": "django.db.backends.postgresql",
|
||
|
||
"NAME": os.environ.get("POSTGRES_DB", "chatballs"),
|
||
|
||
"USER": users[selected_role],
|
||
|
||
"PASSWORD": passwords[selected_role],
|
||
|
||
"HOST": os.environ.get("POSTGRES_HOST", "postgres"),
|
||
|
||
"PORT": os.environ.get("POSTGRES_PORT", "5432"),
|
||
|
||
# Пул несовместим с persistent-соединениями: с ним CONN_MAX_AGE
|
||
|
||
# обязан быть 0, а без пула persistent-режим возвращать нельзя
|
||
|
||
# (см. _pool_options).
|
||
|
||
"CONN_MAX_AGE": 0,
|
||
|
||
"OPTIONS": {"pool": dict(pool)} if pool else {},
|
||
|
||
}
|
||
|
||
|
||
|
||
databases = {
|
||
|
||
"default": config("migration" if testing else role),
|
||
|
||
"platform": config("platform"),
|
||
|
||
}
|
||
|
||
if testing:
|
||
|
||
# Тесты создают свою БД и подключаются владельцем кластера. Его пароль
|
||
|
||
# приходит оттуда же, откуда у остальных ролей: файл секрета инстанса,
|
||
|
||
# переменная окружения — переопределение.
|
||
|
||
databases["default"]["USER"] = os.environ.get(
|
||
|
||
"POSTGRES_USER", "chatballs_bootstrap"
|
||
|
||
)
|
||
|
||
databases["default"]["PASSWORD"] = env_secret(
|
||
|
||
"POSTGRES_PASSWORD", "postgres_password", "chatballs"
|
||
|
||
)
|
||
|
||
databases["platform"]["TEST"] = {"MIRROR": "default"}
|
||
|
||
return databases
|
||
|