Files
chatballs/.github/workflows/release.yml
AndreyandClaude Fable 5.1 5396dda8ad ✨ feat(updates): обновление установки из интерфейса
Установка раз в шесть часов и по кнопке проверяет страницу релизов
GitHub. Администратор установки видит баннер о новой версии и ставит
её одной кнопкой; карточка «Обновления» — в «Настройки → Платформа».

Установку выполняет отдельный сервис updater с Docker-сокетом:
backend-app общается с ним только файлами в томе chatballs-updates
(heartbeat, request.json, status.json). Updater принимает лишь релизы
своего репозитория с образами по digest и применяет compose.yaml в
одноразовом контейнере-помощнике, после чего кладёт файл в каталог
установки. Запрос установки попадает в аудит.

Релизный workflow собирает образ updater и пришпиливает его в
compose.yaml; версия бэкенда зашивается в образ (CHATBALLS_VERSION).
Миграции updates.0001 и tenancy.0034 (гранты на таблицу состояния).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 00:43:11 +03:00

221 lines
9.4 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Публикация Chatballs: образы в GitHub Container Registry + релизный compose.yaml.
#
# Смысл: человек не должен ни собирать продукт, ни распаковывать бандл, чтобы
# его запустить. По тегу 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.
name: release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
version:
description: "Версия релиза (например 1.0.0)"
required: true
permissions:
contents: write
packages: write
env:
APP_DIR: .
REGISTRY: ghcr.io
# Сторонние образы пинуются тем же способом, что и свои: по digest.
POSTGRES_BASE_IMAGE: pgvector/pgvector:pg16
GATEWAY_BASE_IMAGE: caddy:2.8.4
REDIS_IMAGE: redis:7-alpine
COTURN_IMAGE: coturn/coturn:4.6
UPDATER_BASE_IMAGE: docker:27-cli
jobs:
images:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Версия релиза
id: version
run: |
version="${{ inputs.version }}"
[ -n "$version" ] || version="${GITHUB_REF_NAME#v}"
case "$version" in
*[!A-Za-z0-9._-]*|"") echo "Недопустимая версия: $version" >&2; exit 1 ;;
esac
echo "value=$version" >> "$GITHUB_OUTPUT"
echo "repo=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT"
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
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"
echo "updater=${UPDATER_BASE_IMAGE}@$(digest_of "$UPDATER_BASE_IMAGE")" >> "$GITHUB_OUTPUT"
- name: Backend
id: backend
uses: docker/build-push-action@v7
with:
context: ${{ env.APP_DIR }}
file: ${{ env.APP_DIR }}/apps/backend/Dockerfile.production
build-args: |
CHATBALLS_VERSION=${{ steps.version.outputs.value }}
push: true
tags: |
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/backend:${{ steps.version.outputs.value }}
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/backend:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Frontend
id: frontend
uses: docker/build-push-action@v7
with:
context: ${{ env.APP_DIR }}
file: ${{ env.APP_DIR }}/deploy/docker/frontend.Dockerfile
push: true
tags: |
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/frontend:${{ steps.version.outputs.value }}
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/frontend:latest
cache-from: type=gha
cache-to: type=gha,mode=max
# Шлюз и база — свои образы: Caddyfile и init-скрипты живут внутри них,
# а не монтируются с хоста. Ради этого установка и стала одним файлом.
- name: Gateway
id: gateway
uses: docker/build-push-action@v7
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@v7
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
# Сервис обновления по кнопке (ADR-CHATBALLS-0049): docker CLI с compose
# и два сценария внутри; единственный, кому монтируется docker.sock.
- name: Updater
id: updater
uses: docker/build-push-action@v7
with:
context: ${{ env.APP_DIR }}
file: ${{ env.APP_DIR }}/deploy/docker/updater.Dockerfile
build-args: |
CHATBALLS_UPDATER_BASE_IMAGE=${{ steps.bases.outputs.updater }}
push: true
tags: |
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/updater:${{ steps.version.outputs.value }}
${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}/updater: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 }}"
prefix="${{ env.REGISTRY }}/${{ steps.version.outputs.repo }}"
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 }}"
updater="$prefix/updater:$version@${{ steps.updater.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" \
--pin "CHATBALLS_UPDATER_IMAGE=$updater"
{
echo "# release.env — digest-пины релиза $version для \`chatballs deploy\`."
echo "# Тем, кто ставит одной командой, он не нужен: всё уже внутри compose.yaml."
echo "CHATBALLS_VERSION=$version"
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"
echo "CHATBALLS_UPDATER_IMAGE=$updater"
} > dist/release.env
# Файл, который скачает человек, обязан быть валидным сам по себе —
# без переменных окружения и без чего-либо рядом.
( cd dist && docker compose -f compose.yaml config -q )
cat dist/compose.yaml
- uses: actions/upload-artifact@v7
with:
name: release-compose
path: |
dist/compose.yaml
dist/release.env
- name: Приложить к релизу
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v3
with:
files: |
dist/compose.yaml
dist/release.env