All checks were successful
Deploy Infra Host / sync-infra-host (push) Successful in 2s
Deploy / changes (push) Successful in 9s
Deploy Trade-In / changes (push) Successful in 12s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 52s
Deploy / build-worker (push) Successful in 52s
Deploy / build-frontend (push) Successful in 52s
Deploy Trade-In / build-browser (push) Successful in 41s
Deploy Obsidian / deploy-obsidian (push) Successful in 2m15s
Deploy / deploy (push) Successful in 1m53s
Deploy Trade-In / build-frontend (push) Successful in 2m44s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 12s
Deploy Trade-In / test (push) Successful in 4m8s
Deploy Trade-In / build-backend (push) Successful in 47s
Deploy Trade-In / deploy (push) Successful in 1m42s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
165 lines
10 KiB
YAML
165 lines
10 KiB
YAML
name: Deploy Obsidian
|
||
|
||
# Деплой ТОЛЬКО obsidian-стека (CouchDB).
|
||
# Триггерится при изменениях:
|
||
# - docker-compose.obsidian.yml (compose сервиса CouchDB)
|
||
# - scripts/setup-couchdb.sh (bootstrap)
|
||
# - docs/obsidian-livesync.md (документация — для history-watcher'а)
|
||
# - этот workflow
|
||
#
|
||
# Не пересобирает никаких Docker-образов (CouchDB официальный с DockerHub).
|
||
# Не трогает main-стек (backend / frontend / postgres / worker / beat / caddy).
|
||
#
|
||
# ИСТОРИЯ (2026-07-05): жил в .github/workflows/ с момента миграции с GitHub
|
||
# (16.05.2026), помечен в README как «остался на GitHub» — но живого зеркала
|
||
# на github.com с настроенными секретами не оказалось: 0 запусков за всю
|
||
# историю Forgejo Actions (12000+ прогонов остальных workflow), контейнер
|
||
# не пересоздавался с 17.05 до ручного SSH-фикса 04.07. Перенесён сюда —
|
||
# единственная директория, которую реально исполняет этот инстанс.
|
||
# См. issue #2416.
|
||
|
||
# ── ПОДЛИННОСТЬ ХОСТА (#3029) ────────────────────────────────────────────────
|
||
# Переезд 30.08 (#3057) уводит цель деплоя на Selectel, а раннеры оставляет на
|
||
# Beget — SSH становится междоузловым, через интернет. Поэтому у вызова
|
||
# appleboy/ssh-action ниже появился вход `fingerprint`.
|
||
# ЧТО ЗАДАТЬ: секрет DEPLOY_SSH_FINGERPRINT =
|
||
# ssh-keyscan -t ecdsa -p <порт> <хост> | ssh-keygen -lf - | awk '{print $2}'
|
||
# (значение с префиксом `SHA256:`; именно ecdsa — см. разбор в deploy.yml).
|
||
# ПОБАЙТОВО: значение сравнивается как есть, без trim — лишний пробел/перевод
|
||
# строки при копипасте включает проверку и роняет ssh-шаг с `host key
|
||
# fingerprint mismatch`.
|
||
# ПОКА СЕКРЕТ НЕ ЗАДАН — поведение прежнее: пустой fingerprint у easyssh-proxy
|
||
# v1.5.0 означает ssh.InsecureIgnoreHostKey(), то есть ровно как до этого PR.
|
||
# Включается одной настройкой, как INFRA_DEPLOY_HOST (#3059) и fail-open у
|
||
# TRADEIN_INTERNAL_AUTH_SECRET (#2989).
|
||
# NB: этот workflow ходит на secrets.DEPLOY_HOST, поэтому и отпечаток берётся
|
||
# DEPLOY_SSH_FINGERPRINT — пара «хост+отпечаток» обязана меняться вместе. Но
|
||
# CouchDB по плану ОСТАЁТСЯ на Beget вместе с Forgejo/GlitchTip (#3062), а
|
||
# DEPLOY_HOST после 30.08 будет указывать на Selectel. Значит у этого файла есть
|
||
# отдельная, ещё не закрытая проблема адресата — тот же класс, что #3059. Здесь
|
||
# намеренно НЕ трогаем: правка про подлинность хоста, а не про маршрутизацию.
|
||
# При переводе на INFRA_DEPLOY_HOST не забыть переключить и отпечаток.
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
paths:
|
||
- "docker-compose.obsidian.yml"
|
||
- "scripts/setup-couchdb.sh"
|
||
- "docs/obsidian-livesync.md"
|
||
- ".forgejo/workflows/deploy-obsidian.yml"
|
||
workflow_dispatch:
|
||
|
||
concurrency:
|
||
group: deploy-obsidian
|
||
cancel-in-progress: false
|
||
|
||
jobs:
|
||
deploy-obsidian:
|
||
runs-on: ubuntu-latest
|
||
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
# #3029: ВИДИМОСТЬ, А НЕ БЛОКИРОВКА. Отсутствие проверки хоста обязано быть
|
||
# громким: easyssh-proxy v1.5.0 при пустом fingerprint молча оставляет
|
||
# ssh.InsecureIgnoreHostKey(), и незащищённый деплой выглядит ровно как
|
||
# защищённый — зелёным. Шаг намеренно НЕ падает: секрета сегодня нет ни у
|
||
# кого, отказ сломал бы деплой в момент мержа этого PR, а правило здесь —
|
||
# «инертно по умолчанию, включается одной настройкой». Заведут секрет —
|
||
# предупреждение исчезнет само.
|
||
- name: Подлинность хоста — статус проверки (#3029)
|
||
env:
|
||
HOST_FINGERPRINT: ${{ secrets.DEPLOY_SSH_FINGERPRINT }}
|
||
run: |
|
||
set -euo pipefail
|
||
if [ -n "${HOST_FINGERPRINT:-}" ]; then
|
||
echo "Подлинность хоста: сверяется по DEPLOY_SSH_FINGERPRINT."
|
||
else
|
||
echo '::warning title=SSH без проверки подлинности хоста::DEPLOY_SSH_FINGERPRINT не задан — ключ хоста НЕ проверяется (#3029). По каналу едет DEPLOY_SSH_KEY и разворачивается стек CouchDB/Obsidian. После переезда на Selectel (#3057) соединение идёт через интернет. Как снять отпечаток — см. шапку этого файла.'
|
||
echo '###############################################################'
|
||
echo '# ВНИМАНИЕ (#3029): DEPLOY_SSH_FINGERPRINT не задан.'
|
||
echo '# Ключ хоста НЕ проверяется — канал уязвим к MITM.'
|
||
echo '# Как снять отпечаток — см. шапку этого файла.'
|
||
echo '###############################################################'
|
||
fi
|
||
|
||
- name: Deploy obsidian stack via SSH
|
||
uses: appleboy/ssh-action@v1.0.3
|
||
with:
|
||
host: ${{ secrets.DEPLOY_HOST }}
|
||
username: ${{ secrets.DEPLOY_USER }}
|
||
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||
port: ${{ secrets.DEPLOY_PORT || 22 }}
|
||
# #3029: подлинность хоста. Секрет НЕ задан → пустая строка → easyssh-proxy
|
||
# оставляет ssh.InsecureIgnoreHostKey(), то есть сегодняшнее поведение.
|
||
fingerprint: ${{ secrets.DEPLOY_SSH_FINGERPRINT }}
|
||
script: |
|
||
set -euo pipefail
|
||
cd /opt/gendesign
|
||
|
||
# Свежие конфиги из репо
|
||
git fetch origin main
|
||
git reset --hard origin/main
|
||
|
||
# Создать shared network если её ещё нет (idempotent).
|
||
docker network inspect gendesign_shared >/dev/null 2>&1 \
|
||
|| docker network create gendesign_shared
|
||
|
||
# Загрузить COUCHDB_USER/PASSWORD из .env.runtime до compose up
|
||
# (compose также читает .env.runtime через env_file, но
|
||
# `${COUCHDB_PASSWORD:?...}` валидация требует переменную в shell).
|
||
if [ -f backend/.env.runtime ]; then
|
||
set -a
|
||
# shellcheck source=/dev/null
|
||
source backend/.env.runtime
|
||
set +a
|
||
fi
|
||
if [ -z "${COUCHDB_PASSWORD:-}" ]; then
|
||
echo "ERROR: COUCHDB_PASSWORD не задан в backend/.env.runtime"
|
||
exit 1
|
||
fi
|
||
|
||
# Стек CouchDB поднимается с собственным project-name.
|
||
docker compose -p gendesign-obsidian \
|
||
-f docker-compose.obsidian.yml pull
|
||
docker compose -p gendesign-obsidian \
|
||
-f docker-compose.obsidian.yml up -d
|
||
|
||
# Bootstrap CouchDB (CORS, db, лимиты) — idempotent
|
||
if [ -f scripts/setup-couchdb.sh ]; then
|
||
# Загружаем COUCHDB_PASSWORD из backend/.env.runtime если есть
|
||
if [ -f backend/.env.runtime ]; then
|
||
set -a; source backend/.env.runtime; set +a
|
||
fi
|
||
# Ждём CouchDB up, потом bootstrap
|
||
for i in $(seq 1 30); do
|
||
if docker compose -p gendesign-obsidian \
|
||
-f docker-compose.obsidian.yml \
|
||
exec -T couchdb curl -fsS http://localhost:5984/_up >/dev/null 2>&1; then
|
||
break
|
||
fi
|
||
sleep 2
|
||
done
|
||
COUCHDB_HOST=http://localhost:5984 \
|
||
COUCHDB_USER="${COUCHDB_USER:-obsidian}" \
|
||
COUCHDB_PASSWORD="${COUCHDB_PASSWORD:?must be set in backend/.env.runtime}" \
|
||
docker compose -p gendesign-obsidian \
|
||
-f docker-compose.obsidian.yml \
|
||
exec -T couchdb bash -c "$(cat scripts/setup-couchdb.sh)" \
|
||
|| echo "(bootstrap warnings ignored — script is idempotent)"
|
||
fi
|
||
|
||
# Caddy в main-stack — НЕ перезапускаем тут (другой workflow),
|
||
# но reload конфига полезен на случай если в Caddyfile добавили
|
||
# новый obsidian-route только что (главное: image main caddy уже
|
||
# запущен и подключён к gendesign_shared network).
|
||
if docker compose -p gendesign -f docker-compose.prod.yml ps caddy --quiet \
|
||
| grep -q .; then
|
||
docker compose -p gendesign -f docker-compose.prod.yml \
|
||
exec -T caddy caddy reload --config /etc/caddy/Caddyfile \
|
||
|| echo "(caddy reload skipped — main stack not running)"
|
||
fi
|
||
|
||
sleep 3
|
||
curl -fsS https://obsidian.gendsgn.ru/_up | head -c 200 || true
|