Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
63 lines
3.7 KiB
Bash
Executable File
63 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Деплой на VPS. Образы собираются на самом сервере (нативный amd64; под эмуляцией на Mac pnpm падает),
|
|
# после сборки кеш чистится — сервер общий и диск небольшой.
|
|
# infra/prod/deploy.sh — синхронизировать код, собрать, перезапустить
|
|
# ONLY=app infra/prod/deploy.sh — собрать только образ app (API/воркер/боты/лендинг)
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/../.."
|
|
|
|
HOST=${DEPLOY_HOST:-root@69.62.120.231}
|
|
KEY=${DEPLOY_KEY:-$HOME/.ssh/vps_claude}
|
|
DIR=/opt/barev
|
|
DOMAIN=${BAREV_DOMAIN:-barevclub.com}
|
|
BOT=${CLIENT_BOT_USERNAME:-barevclub_bot}
|
|
r() { ssh -i "$KEY" "$HOST" "$@"; }
|
|
|
|
# один деплой за раз: два параллельных запуска ломают пересоздание контейнеров (конфликт имён → 502)
|
|
LOCK=$DIR/.deploy-lock
|
|
if ! r "mkdir -p $DIR && mkdir $LOCK 2>/dev/null"; then
|
|
echo "✗ на сервере уже идёт деплой (есть $LOCK). Дождитесь его окончания."
|
|
echo " Если прошлый деплой оборвался, снимите блокировку: ssh -i $KEY $HOST rmdir $LOCK"
|
|
exit 1
|
|
fi
|
|
trap 'r "rmdir $LOCK" 2>/dev/null || true' EXIT
|
|
|
|
echo "→ синхронизация кода"
|
|
r "mkdir -p $DIR/src"
|
|
rsync -az --delete -e "ssh -i $KEY" \
|
|
--exclude node_modules --exclude .next --exclude dist --exclude .turbo --exclude '*.tsbuildinfo' \
|
|
--exclude .git --exclude .env --exclude docs/design --exclude i18n-review --exclude '/*.pdf' \
|
|
./ "$HOST:$DIR/src/"
|
|
scp -q -i "$KEY" infra/prod/docker-compose.yml "$HOST:$DIR/docker-compose.yml"
|
|
|
|
# секреты генерируются один раз и дальше не перезаписываются
|
|
if ! r "test -f $DIR/.env"; then
|
|
echo "→ создаю $DIR/.env с новыми секретами"
|
|
rnd() { openssl rand -hex 24; }
|
|
DBP=$(rnd); S3K=$(rnd); ADMINP=$(openssl rand -base64 12 | tr -d '/+=')
|
|
sed -e "s|^DB_PASSWORD=.*|DB_PASSWORD=$DBP|" \
|
|
-e "s|^DATABASE_URL=.*|DATABASE_URL=postgresql://barev:$DBP@barev-db:5432/barev|" \
|
|
-e "s|^ADMIN_SESSION_SECRET=.*|ADMIN_SESSION_SECRET=$(rnd)|" \
|
|
-e "s|^SEED_OWNER_PASSWORD=.*|SEED_OWNER_PASSWORD=$ADMINP|" \
|
|
-e "s|^S3_SECRET_KEY=.*|S3_SECRET_KEY=$S3K|" \
|
|
-e "s|^CLIENT_BOT_TOKEN=.*|CLIENT_BOT_TOKEN=$(grep ^CLIENT_BOT_TOKEN= .env | cut -d= -f2-)|" \
|
|
-e "s|^PRO_BOT_TOKEN=.*|PRO_BOT_TOKEN=$(grep ^PRO_BOT_TOKEN= .env | cut -d= -f2-)|" \
|
|
infra/prod/env.example | r "umask 077 && cat > $DIR/.env"
|
|
sed "s|barev-secret|$S3K|" infra/prod/seaweed-s3.json.template | r "cat > $DIR/seaweed-s3.json"
|
|
# ключ шифрования файлов в S3 (SSE-S3)
|
|
printf '[s3.sse]\nkek = "%s"\n' "$(openssl rand -hex 32)" | r "cat > $DIR/security.toml && chmod 644 $DIR/security.toml"
|
|
echo " пароль владельца админки: $ADMINP"
|
|
fi
|
|
|
|
for target in ${ONLY:-web app}; do
|
|
echo "→ сборка barev-$target"
|
|
r "cd $DIR/src && nice -n 10 docker build --network host -f infra/prod/Dockerfile --target $target \
|
|
--build-arg LANDING_URL=https://$DOMAIN --build-arg API_PUBLIC_URL=https://api.$DOMAIN \
|
|
--build-arg CLIENT_BOT_USERNAME=$BOT -t barev-$target:latest . > /tmp/barev-build-$target.log 2>&1 \
|
|
|| (tail -40 /tmp/barev-build-$target.log; exit 1)"
|
|
done
|
|
|
|
echo "→ запуск"
|
|
r "cd $DIR && docker compose up -d --remove-orphans && docker builder prune -af >/dev/null && docker image prune -f >/dev/null"
|
|
r "cd $DIR && docker compose ps --format '{{.Name}}\t{{.Status}}' && df -h / | tail -1"
|