diff --git a/.env.example b/.env.example index 4411ac6..d6e859f 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,9 @@ ADMIN_SECRET=мой_секрет_для_создания_юзеров # Порт публикации контейнера на хосте для docker compose APP_PORT=3000 +# Имя production-образа для docker compose deploy +IMAGE_NAME=registry.example.com/team/intradesk-monitor + # Порт сервера (по умолчанию 3000) PORT=3000 diff --git a/.woodpecker.yml b/.woodpecker.yml index 66e5abc..1891db4 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,22 +1,87 @@ -# If you use multiple agents, uncomment labels below and set the same labels on the target agent. -# labels: -# deploy-host: intradesk-monitor +# ───────────────────────────────────────────────────────────────────────────── +# Woodpecker CI Pipeline +# +# Шаги: +# 1. docker-build — сборка и push образа при push в main +# 2. docker-build-tag — сборка и push образа при git tag +# 3. deploy — деплой на сервер на агенте role=work_woodpecker +# +# Секреты (Settings -> Secrets): +# forgejo_host — хост registry +# forgejo_image — полный путь образа +# forgejo_username — пользователь registry +# forgejo_token — токен для push +# ───────────────────────────────────────────────────────────────────────────── steps: - - name: deploy - image: docker:27-cli - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /opt/intradesk-monitor:/opt/intradesk-monitor - commands: - - mkdir -p /opt/intradesk-monitor/deploy /opt/intradesk-monitor/data - - cp docker-compose.deploy.yml /opt/intradesk-monitor/docker-compose.deploy.yml - - cp deploy.sh /opt/intradesk-monitor/deploy.sh - - cp deploy/remote-deploy.sh /opt/intradesk-monitor/deploy/remote-deploy.sh - - chmod +x /opt/intradesk-monitor/deploy.sh /opt/intradesk-monitor/deploy/remote-deploy.sh - - test -f /opt/intradesk-monitor/.env || (echo "Missing /opt/intradesk-monitor/.env on deploy host" && exit 1) - - docker build -t intradesk-monitor:$CI_COMMIT_SHA -t intradesk-monitor:latest . - - APP_DIR=/opt/intradesk-monitor IMAGE_REPO=intradesk-monitor IMAGE_TAG=$CI_COMMIT_SHA /opt/intradesk-monitor/deploy/remote-deploy.sh + - name: docker-build + image: woodpeckerci/plugin-docker-buildx + settings: + registry: + from_secret: forgejo_host + repo: + from_secret: forgejo_image + username: + from_secret: forgejo_username + password: + from_secret: forgejo_token + tags: + - latest + - "sha-${CI_COMMIT_SHA:0:8}" + build_args: + - "BUILDKIT_INLINE_CACHE=1" when: - event: push - branch: ${CI_REPO_DEFAULT_BRANCH} + branch: main + + - name: docker-build-tag + image: woodpeckerci/plugin-docker-buildx + settings: + registry: + from_secret: forgejo_host + repo: + from_secret: forgejo_image + username: + from_secret: forgejo_username + password: + from_secret: forgejo_token + tags: + - "${CI_COMMIT_TAG}" + - latest + - "sha-${CI_COMMIT_SHA:0:8}" + build_args: + - "BUILDKIT_INLINE_CACHE=1" + when: + - event: tag + + - name: deploy + image: docker:cli + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /opt/intradesk-monitor:/deploy + commands: + - | + set -e + + cp docker-compose.deploy.yml /deploy/docker-compose.yml + test -f /deploy/.env || (echo "Missing /opt/intradesk-monitor/.env on deploy host" && exit 1) + + cd /deploy + + CONTAINER=$(docker compose ps -q intradesk-monitor 2>/dev/null | head -1) + if [ -n "$CONTAINER" ]; then + OLD_IMAGE=$(docker inspect --format='{{.Image}}' "$CONTAINER") + printf '%s' "$OLD_IMAGE" > .previous_image + echo "Snapshot for rollback: $OLD_IMAGE" + fi + + docker compose pull + docker compose up -d --remove-orphans + + echo "Deployed: ${CI_COMMIT_SHA:0:8} (${CI_COMMIT_MESSAGE})" + labels: + role: work_woodpecker + when: + - event: push + branch: main + - event: tag diff --git a/README.md b/README.md index 7de5ed2..4f37d13 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # Монитор заявок - CleverParking -Node/Express приложение со встроенным фронтендом и SQLite. Проект подготовлен для: +Node/Express приложение со встроенным фронтендом и SQLite. -- локального запуска через Docker Compose; -- автоматической сборки Docker-образа в Woodpecker; -- deploy прямо на хост, где установлен Woodpecker agent. +Проект теперь настроен по той же схеме, что и другой рабочий сервис на этом сервере: + +- образ собирается в Woodpecker и публикуется в registry; +- deploy выполняется на агенте с label `role=work_woodpecker`; +- на сервере запускается `docker compose pull && docker compose up -d`. ## Локальный запуск @@ -13,69 +15,47 @@ cp .env.example .env docker compose up -d --build ``` -Приложение будет доступно на `http://localhost:3000`, а база SQLite сохранится в папке `./data`. +Локальный compose собирает образ из текущего каталога и хранит SQLite в `./data`. -## Что добавлено для production +## Production deploy -- `Dockerfile` с production `NODE_ENV` и `HEALTHCHECK`; -- `/healthz` endpoint для проверки контейнера; -- fail-fast проверка `JWT_SECRET` и `ADMIN_SECRET` в production; -- `docker-compose.deploy.yml` для запуска заранее собранного локального образа; -- `.woodpecker.yml` для сборки и deploy на хосте агента; -- `deploy/remote-deploy.sh` и `deploy.sh` для повторного запуска того же сценария уже на сервере. +На сервере должен существовать каталог `/opt/intradesk-monitor` с файлом `.env`. -## Как теперь работает deploy - -1. Woodpecker забирает код из Git. -2. На push в default branch step `deploy` запускается на агенте, который стоит на целевой машине. -3. Step собирает локальный образ `intradesk-monitor:$CI_COMMIT_SHA` и тег `latest`. -4. В `/opt/intradesk-monitor` копируются deploy-файлы из репозитория. -5. Затем выполняется `docker compose up -d` уже с локально собранным образом. - -Registry и SSH для такого сценария не нужны. - -## Что нужно на машине с агентом - -- установленный Docker с `docker compose`; -- Woodpecker agent на этой же машине; -- каталог `/opt/intradesk-monitor`; -- файл `/opt/intradesk-monitor/.env`; -- доверенный (`trusted`) репозиторий в Woodpecker, потому что pipeline использует bind-mount хостовых путей и Docker socket. - -Пример `/opt/intradesk-monitor/.env`: +Минимальный пример `/opt/intradesk-monitor/.env`: ```dotenv JWT_SECRET=long_random_secret ADMIN_SECRET=another_long_secret APP_PORT=3000 +IMAGE_NAME=git.example.com/user/intradesk-monitor ``` -`PORT` и `DB_PATH` в production задаются через compose автоматически. +Во время deploy pipeline копирует [docker-compose.deploy.yml](c:/Users/Professional/Documents/VSCode/intra_git/intradesk-monitor/docker-compose.deploy.yml) в `/opt/intradesk-monitor/docker-compose.yml`, затем выполняет стандартный `docker compose pull` и `docker compose up -d`. -## Настройка агента +## Секреты Woodpecker -Текущий `.woodpecker.yml` ожидает, что на хосте доступны: +Нужны секреты репозитория: -- `/var/run/docker.sock` -- `/opt/intradesk-monitor` +- `forgejo_host` +- `forgejo_image` +- `forgejo_username` +- `forgejo_token` -Если у тебя несколько агентов, добавь на нужный агент собственный label и раскомментируй блок `labels` в `.woodpecker.yml`, чтобы deploy уходил строго на нужную машину. +`forgejo_image` должен совпадать с `IMAGE_NAME` в серверном `.env`. -## Первый деплой +## Что делает pipeline -1. Создать каталог `/opt/intradesk-monitor`. -2. Положить в него `.env`. -3. Убедиться, что агент Woodpecker может работать с Docker на хосте. -4. Пометить репозиторий как `trusted` в Woodpecker. -5. Запушить изменения в default branch. +[.woodpecker.yml](c:/Users/Professional/Documents/VSCode/intra_git/intradesk-monitor/.woodpecker.yml) содержит три шага: -После этого Woodpecker сам соберёт образ на целевой машине и перезапустит контейнер. +- `docker-build` — сборка и push `latest` и `sha-` при push в `main`; +- `docker-build-tag` — сборка и push git tag, `latest` и `sha-` при событии tag; +- `deploy` — deploy на прод-сервере через Docker socket на агенте `role=work_woodpecker`. ## Полезные команды на сервере ```bash cd /opt/intradesk-monitor -docker ps --filter name=intradesk-monitor -docker logs -f intradesk-monitor -./deploy.sh +docker compose ps +docker compose logs -f +cat .previous_image ``` diff --git a/deploy.sh b/deploy.sh deleted file mode 100644 index 6111c67..0000000 --- a/deploy.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env sh -set -eu - -SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) - -export APP_DIR="${APP_DIR:-$SCRIPT_DIR}" - -exec "$SCRIPT_DIR/deploy/remote-deploy.sh" diff --git a/deploy/remote-deploy.sh b/deploy/remote-deploy.sh deleted file mode 100644 index 3f24cd8..0000000 --- a/deploy/remote-deploy.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env sh -set -eu - -APP_DIR=${APP_DIR:-/opt/intradesk-monitor} -COMPOSE_FILE=${COMPOSE_FILE:-docker-compose.deploy.yml} - -cd "$APP_DIR" -mkdir -p data deploy - -if [ ! -f .env ]; then - echo ".env is missing in $APP_DIR. Create it from .env.example before the first deploy." >&2 - exit 1 -fi - -if [ -n "${IMAGE_REPO:-}" ] && [ -n "${IMAGE_TAG:-}" ]; then - cat > .env.runtime <&2 - exit 1 -fi - -set -a -. ./.env.runtime -set +a - -if [ "${PULL_IMAGE:-0}" = "1" ]; then - docker compose -f "$COMPOSE_FILE" pull -fi - -docker compose -f "$COMPOSE_FILE" up -d --remove-orphans -docker image prune -f >/dev/null 2>&1 || true diff --git a/docker-compose.deploy.yml b/docker-compose.deploy.yml index 6dea7bf..5007162 100644 --- a/docker-compose.deploy.yml +++ b/docker-compose.deploy.yml @@ -1,15 +1,14 @@ services: intradesk-monitor: - image: ${IMAGE_REPO}:${IMAGE_TAG} + image: ${IMAGE_NAME}:latest container_name: intradesk-monitor - restart: unless-stopped + restart: always + env_file: .env ports: - "${APP_PORT:-3000}:3000" volumes: - ./data:/data environment: NODE_ENV: production - JWT_SECRET: ${JWT_SECRET} - ADMIN_SECRET: ${ADMIN_SECRET} PORT: 3000 DB_PATH: /data/intradesk.db diff --git a/docker-compose.yml b/docker-compose.yml index a68a608..704198d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,14 +3,13 @@ services: container_name: intradesk-monitor build: context: . - restart: unless-stopped + restart: always + env_file: .env ports: - "${APP_PORT:-3000}:3000" volumes: - ./data:/data environment: NODE_ENV: production - JWT_SECRET: ${JWT_SECRET} - ADMIN_SECRET: ${ADMIN_SECRET} PORT: 3000 DB_PATH: /data/intradesk.db diff --git a/intradesk-monitor.zip b/intradesk-monitor.zip new file mode 100644 index 0000000..eca1bde Binary files /dev/null and b/intradesk-monitor.zip differ