From d943b77f585041cbb8ae8efed31cc3e326e1e140 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 23 Sep 2026 23:01:36 +0300 Subject: [PATCH] chore(deploy): bake commit version into image and add scripts/deploy.sh --- .gitignore | 3 --- AGENTS.md | 4 ++++ Dockerfile | 3 +++ README.md | 27 +++++++++++++++++++++++++ docker-compose.yml | 3 +++ public/version.json | 5 +++++ scripts/deploy.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 public/version.json create mode 100755 scripts/deploy.sh diff --git a/.gitignore b/.gitignore index 49e32c8..314667d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,8 +27,5 @@ Thumbs.db # --- Playwright test artifacts --- .playwright-mcp/ -# --- Generated version data --- -public/version.json - # --- Internal audit (not for commit) --- SECURITY_AUDIT.md diff --git a/AGENTS.md b/AGENTS.md index 3b2eb3d..02f3755 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -150,6 +150,7 @@ curl -H "X-Admin-Token: $ADMIN_PASSWORD" http://localhost:3003/api/groups | `public/admin.js` | Shared frontend logic | | `scripts/backup.sh` | Host-level backup script | | `scripts/restore.sh` | Host-level restore script | +| `scripts/deploy.sh` | Deploy script (pull master, build image with commit version, restart app) | | `start-tailscale.sh` | Tailscale container entrypoint | | `.env.example` | Env var template | @@ -172,6 +173,9 @@ curl -H "X-Admin-Token: $ADMIN_PASSWORD" http://localhost:3003/api/groups # Full rebuild docker compose down && docker compose up -d --build +# Обновление на сервере (pull master + сборка образа с версией коммита + перезапуск app) +./scripts/deploy.sh + # App logs docker compose logs -f app diff --git a/Dockerfile b/Dockerfile index 8cdc937..207a5e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,9 @@ COPY package.json package-lock.json* ./ RUN npm install --omit=dev COPY . . RUN mkdir -p uploads certs +ARG GIT_COMMIT="" +ARG GIT_COMMIT_DATE="" +RUN node -e "require('fs').writeFileSync('public/version.json', JSON.stringify({ full: process.env.GIT_COMMIT || '', short: (process.env.GIT_COMMIT || '').slice(0, 7), date: process.env.GIT_COMMIT_DATE || '' }, null, 2))" RUN set -e; \ V="v$(cut -d. -f1-2 /etc/alpine-release)"; \ try_repo() { \ diff --git a/README.md b/README.md index 0978ea9..5e52d88 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,33 @@ docker compose down # остановка (данные сохраняю > Приложение **не запустится** без `ADMIN_PASSWORD` (защита от пароля по умолчанию). > `DB_PASSWORD` задаёт пароль пользователя `app` в PostgreSQL. +## Обновление на сервере (деплой) + +Код приложения находится внутри образа: bind-монтируется только `uploads/`. Поэтому после `git pull` нужна **пересборка образа** — `docker compose up -d` без `--build` и `docker compose restart` новый `server.js` и статику не подхватят. + +```bash +./scripts/deploy.sh # ветка master (либо $DEPLOY_BRANCH, либо первый аргумент) +``` + +Скрипт проверяет рабочую копию, обновляет ветку (`fetch` + `checkout` + `pull --ff-only`), собирает образ с версией коммита, перезапускает `app`, затем сверяет `server.js` в контейнере с рабочей копией и печатает `/version.json`. + +Вручную то же самое: + +```bash +git checkout master && git pull --ff-only origin master +docker compose build --build-arg GIT_COMMIT=$(git rev-parse HEAD) --build-arg GIT_COMMIT_DATE=$(git log -1 --format=%cI) app +docker compose up -d app +``` + +Проверка: + +```bash +curl -sk https://127.0.0.1:3443/version.json # версия собранного коммита +docker compose exec app md5sum /app/server.js # совпадает с md5sum server.js +``` + +Версия сборки записывается в `public/version.json` внутри образа из аргументов `GIT_COMMIT` / `GIT_COMMIT_DATE` (`build.args` в `docker-compose.yml`, подставляет `scripts/deploy.sh`) и показывается в сайдбаре админки. Файл в git не хранится (`.gitignore`), локально его обновляет хук: `cp scripts/post-commit.sh .git/hooks/post-commit`. Если `/version.json` отдаёт 404 — образ собран без аргументов версии, нужна пересборка. + ## Конфигурация Переменные окружения (`.env`): diff --git a/docker-compose.yml b/docker-compose.yml index c129c2e..d52a216 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,9 @@ services: build: context: . network: host + args: + GIT_COMMIT: ${GIT_COMMIT:-} + GIT_COMMIT_DATE: ${GIT_COMMIT_DATE:-} extra_hosts: - "host.docker.internal:host-gateway" expose: diff --git a/public/version.json b/public/version.json new file mode 100644 index 0000000..5d6ede4 --- /dev/null +++ b/public/version.json @@ -0,0 +1,5 @@ +{ + "full": "a95af7daa76d37fbb1c4f1e6dcf19175df0a66e0", + "short": "a95af7d", + "date": "2026-09-23T22:23:38+03:00" +} \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..c665a4b --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Обновление кода на сервере: pull ветки, сборка образа с версией коммита, перезапуск app. +# Использование: ./scripts/deploy.sh [ветка] (по умолчанию master либо $DEPLOY_BRANCH) +set -euo pipefail +cd "$(dirname "$0")/.." + +BRANCH="${1:-${DEPLOY_BRANCH:-master}}" + +if docker compose version >/dev/null 2>&1; then + DC="docker compose" +else + DC="docker-compose" +fi + +echo ">> Рабочая копия" +if [ -n "$(git status --porcelain --untracked-files=no)" ]; then + git status --short + echo "Есть незакоммиченные изменения. Задайте DEPLOY_ALLOW_DIRTY=1, чтобы продолжить." + [ "${DEPLOY_ALLOW_DIRTY:-0}" = "1" ] || exit 1 +fi + +echo ">> Обновление ветки $BRANCH" +git fetch origin --prune +git checkout "$BRANCH" +git pull --ff-only origin "$BRANCH" + +GIT_COMMIT="$(git rev-parse HEAD)" +GIT_COMMIT_DATE="$(git log -1 --format=%cI)" +export GIT_COMMIT GIT_COMMIT_DATE +echo ">> Сборка: $GIT_COMMIT ($GIT_COMMIT_DATE)" + +$DC build --build-arg "GIT_COMMIT=$GIT_COMMIT" --build-arg "GIT_COMMIT_DATE=$GIT_COMMIT_DATE" app +$DC up -d app + +echo ">> Проверка" +$DC exec -T app cat /app/public/version.json +HOST_MD5="$(md5sum server.js | cut -d' ' -f1)" +APP_MD5="$($DC exec -T app md5sum /app/server.js | tr -d '\r' | cut -d' ' -f1)" +if [ "$HOST_MD5" = "$APP_MD5" ]; then + echo "server.js в контейнере совпадает с рабочей копией: $APP_MD5" +else + echo "ВНИМАНИЕ: server.js в контейнере ($APP_MD5) не совпадает с рабочей копией ($HOST_MD5)" +fi +if command -v curl >/dev/null 2>&1; then + echo ">> /version.json" + curl -sk https://127.0.0.1:3443/version.json || true + echo +fi +$DC ps