mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-05 01:14:58 +03:00
✨ feat(release): установка на чистый хост одной командой
Раньше compose монтировал с хоста Caddyfile, init-скрипты базы и генератор секретов. Из-за этого установка требовала рядом распакованный репозиторий, а файл, забытый при сборке релиза, Docker молча подменял пустым каталогом — и стек падал на первом старте у человека. Плюс production-образ backend вообще не собирался: COPY content ссылался на каталог, которого в репозитории нет, так что релиза не существовало ни на GitHub, ни на GitLab. Теперь весь дистрибутив — один compose.yaml со страницы релиза: curl -fsSL .../compose.yaml -o compose.yaml docker compose up -d --wait - Caddyfile переехал в свой образ шлюза (caddy validate — в сборке), init-скрипты базы — в свой образ postgres, генератор секретов — в backend-образ. Bind-mount'ов в production-манифесте не осталось. - Состояние установки — именованные тома вместо каталогов рабочего каталога. Заодно чинит загрузку файлов на Linux: том наследует владельца из образа (hub), тогда как bind-mount доставался контейнеру как root:root. - scripts/pin-release-compose.py закрепляет ссылки на образы по digest и падает, если хоть один ключ остался подстановкой. - GitHub-workflow собирает четыре образа и прикладывает к релизу compose.yaml (основной путь) и release.env (для `chatballs deploy`). - tests/cli/test_release_compose.py держит свойство: манифест без bind-mount'ов и полностью закрепляем по digest. - Из окружения шлюза убраны CHATBALLS_APP_DOMAIN и CHATBALLS_ACME_EMAIL — Caddyfile их не читает. Dev-контур не меняется по смыслу: compose.dev.yaml по-прежнему собирает всё из исходников и держит состояние в ./data. AUDIT-TODO.md — временный список остального из аудита; удаляется целиком, когда закрыт последний пункт. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a141cd4f50
commit
8fe66385d6
19 files changed
+832
-137
No files matched your search
+102
-29
@@ -1,9 +1,16 @@
|
||||
# Публикация образов Chatballs в GitHub Container Registry.
|
||||
# Публикация Chatballs: образы в GitHub Container Registry + релизный compose.yaml.
|
||||
#
|
||||
# Смысл: человек не должен собирать продукт, чтобы его запустить. По тегу vX.Y.Z
|
||||
# workflow собирает backend и frontend, публикует их в ghcr.io и прикладывает к
|
||||
# релизу release.env — файл со ссылками на образы по digest. С ним запуск
|
||||
# сводится к «docker compose up» без единой сборки.
|
||||
# Смысл: человек не должен ни собирать продукт, ни распаковывать бандл, чтобы
|
||||
# его запустить. По тегу vX.Y.Z workflow собирает четыре образа (backend,
|
||||
# frontend, gateway с Caddyfile внутри, postgres с init-скриптами внутри),
|
||||
# публикует их в ghcr.io и прикладывает к релизу один файл — compose.yaml с
|
||||
# ссылками по digest. Установка на чистый хост:
|
||||
#
|
||||
# curl -fsSL <ссылка на compose.yaml> -o compose.yaml
|
||||
# docker compose up -d --wait
|
||||
#
|
||||
# release.env кладётся рядом для тех, кто разворачивает через `chatballs`
|
||||
# (закрытый контур): там нужны те же digest'ы отдельным файлом.
|
||||
#
|
||||
# Ничего настраивать не нужно: путь образов выводится из github.repository,
|
||||
# публикация идёт встроенным GITHUB_TOKEN.
|
||||
@@ -28,9 +35,9 @@ env:
|
||||
APP_DIR: .
|
||||
REGISTRY: ghcr.io
|
||||
# Сторонние образы пинуются тем же способом, что и свои: по digest.
|
||||
POSTGRES_IMAGE: pgvector/pgvector:pg16
|
||||
POSTGRES_BASE_IMAGE: pgvector/pgvector:pg16
|
||||
GATEWAY_BASE_IMAGE: caddy:2.8.4
|
||||
REDIS_IMAGE: redis:7-alpine
|
||||
GATEWAY_IMAGE: caddy:2.8.4
|
||||
COTURN_IMAGE: coturn/coturn:4.6
|
||||
|
||||
jobs:
|
||||
@@ -58,6 +65,20 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Базовые образы закрепляем до сборки: тогда digest наших образов
|
||||
# однозначно отвечает известному входу, а не плавающему тегу.
|
||||
- name: Digest базовых образов
|
||||
id: bases
|
||||
run: |
|
||||
set -eu
|
||||
digest_of() {
|
||||
docker buildx imagetools inspect "$1" --format '{{ "{{json .Manifest.Digest}}" }}' | tr -d '"'
|
||||
}
|
||||
echo "postgres=${POSTGRES_BASE_IMAGE}@$(digest_of "$POSTGRES_BASE_IMAGE")" >> "$GITHUB_OUTPUT"
|
||||
echo "gateway=${GATEWAY_BASE_IMAGE}@$(digest_of "$GATEWAY_BASE_IMAGE")" >> "$GITHUB_OUTPUT"
|
||||
echo "redis=${REDIS_IMAGE}@$(digest_of "$REDIS_IMAGE")" >> "$GITHUB_OUTPUT"
|
||||
echo "coturn=${COTURN_IMAGE}@$(digest_of "$COTURN_IMAGE")" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Backend
|
||||
id: backend
|
||||
uses: docker/build-push-action@v6
|
||||
@@ -84,40 +105,92 @@ jobs:
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: release.env со ссылками по digest
|
||||
# Шлюз и база — свои образы: Caddyfile и init-скрипты живут внутри них,
|
||||
# а не монтируются с хоста. Ради этого установка и стала одним файлом.
|
||||
- name: Gateway
|
||||
id: gateway
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.APP_DIR }}
|
||||
file: ${{ env.APP_DIR }}/deploy/docker/gateway.Dockerfile
|
||||
build-args: |
|
||||
CHATBALLS_GATEWAY_BASE_IMAGE=${{ steps.bases.outputs.gateway }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/gateway:${{ steps.version.outputs.value }}
|
||||
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/gateway:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Postgres
|
||||
id: postgres
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ env.APP_DIR }}
|
||||
file: ${{ env.APP_DIR }}/deploy/docker/postgres.Dockerfile
|
||||
build-args: |
|
||||
CHATBALLS_POSTGRES_BASE_IMAGE=${{ steps.bases.outputs.postgres }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/postgres:${{ steps.version.outputs.value }}
|
||||
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/postgres:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Релизный compose.yaml и release.env
|
||||
id: artifacts
|
||||
run: |
|
||||
set -eu
|
||||
version="${{ steps.version.outputs.value }}"
|
||||
repo="${{ steps.version.outputs.repo }}"
|
||||
prefix="${{ env.REGISTRY }}/$repo"
|
||||
prefix="${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}"
|
||||
|
||||
# Digest своих образов отдаёт сам build-push-action; сторонние
|
||||
# спрашиваем у реестра — :latest в релизе недопустим.
|
||||
third_party_digest() {
|
||||
docker buildx imagetools inspect "$1" --format '{{ "{{json .Manifest.Digest}}" }}' | tr -d '"'
|
||||
}
|
||||
backend="$prefix/backend:$version@${{ steps.backend.outputs.digest }}"
|
||||
frontend="$prefix/frontend:$version@${{ steps.frontend.outputs.digest }}"
|
||||
gateway="$prefix/gateway:$version@${{ steps.gateway.outputs.digest }}"
|
||||
postgres="$prefix/postgres:$version@${{ steps.postgres.outputs.digest }}"
|
||||
redis="${{ steps.bases.outputs.redis }}"
|
||||
coturn="${{ steps.bases.outputs.coturn }}"
|
||||
|
||||
python3 scripts/pin-release-compose.py \
|
||||
--source compose.yaml \
|
||||
--output dist/compose.yaml \
|
||||
--version "$version" \
|
||||
--pin "CHATBALLS_BACKEND_IMAGE=$backend" \
|
||||
--pin "CHATBALLS_FRONTEND_IMAGE=$frontend" \
|
||||
--pin "CHATBALLS_GATEWAY_IMAGE=$gateway" \
|
||||
--pin "CHATBALLS_POSTGRES_IMAGE=$postgres" \
|
||||
--pin "CHATBALLS_REDIS_IMAGE=$redis" \
|
||||
--pin "CHATBALLS_COTURN_IMAGE=$coturn"
|
||||
|
||||
{
|
||||
echo "# release.env — сгенерирован ${{ github.workflow }} для $version."
|
||||
echo "# Все образы закреплены по digest: :latest источником релиза не является."
|
||||
echo "# release.env — digest-пины релиза $version для \`chatballs deploy\`."
|
||||
echo "# Тем, кто ставит одной командой, он не нужен: всё уже внутри compose.yaml."
|
||||
echo "CHATBALLS_VERSION=$version"
|
||||
echo "CHATBALLS_BACKEND_IMAGE=$prefix/backend:$version@${{ steps.backend.outputs.digest }}"
|
||||
echo "CHATBALLS_FRONTEND_IMAGE=$prefix/frontend:$version@${{ steps.frontend.outputs.digest }}"
|
||||
echo "CHATBALLS_POSTGRES_IMAGE=${POSTGRES_IMAGE}@$(third_party_digest "$POSTGRES_IMAGE")"
|
||||
echo "CHATBALLS_REDIS_IMAGE=${REDIS_IMAGE}@$(third_party_digest "$REDIS_IMAGE")"
|
||||
echo "CHATBALLS_GATEWAY_IMAGE=${GATEWAY_IMAGE}@$(third_party_digest "$GATEWAY_IMAGE")"
|
||||
echo "CHATBALLS_COTURN_IMAGE=${COTURN_IMAGE}@$(third_party_digest "$COTURN_IMAGE")"
|
||||
} > release.env
|
||||
echo "CHATBALLS_BACKEND_IMAGE=$backend"
|
||||
echo "CHATBALLS_FRONTEND_IMAGE=$frontend"
|
||||
echo "CHATBALLS_GATEWAY_IMAGE=$gateway"
|
||||
echo "CHATBALLS_POSTGRES_IMAGE=$postgres"
|
||||
echo "CHATBALLS_REDIS_IMAGE=$redis"
|
||||
echo "CHATBALLS_COTURN_IMAGE=$coturn"
|
||||
} > dist/release.env
|
||||
|
||||
cat release.env
|
||||
# Файл, который скачает человек, обязан быть валидным сам по себе —
|
||||
# без переменных окружения и без чего-либо рядом.
|
||||
( cd dist && docker compose -f compose.yaml config -q )
|
||||
|
||||
cat dist/compose.yaml
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release.env
|
||||
path: release.env
|
||||
name: release-compose
|
||||
path: |
|
||||
dist/compose.yaml
|
||||
dist/release.env
|
||||
|
||||
- name: Приложить release.env к релизу
|
||||
- name: Приложить к релизу
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: release.env
|
||||
files: |
|
||||
dist/compose.yaml
|
||||
dist/release.env
|
||||
Reference in new issue
Block a user