Compare commits
3 commits
464d53c70b
...
66d436b7aa
| Author | SHA1 | Date | |
|---|---|---|---|
| 66d436b7aa | |||
| 2cb2b3f53e | |||
| 299f69abd5 |
9 changed files with 241 additions and 135 deletions
|
|
@ -7,6 +7,9 @@ ADMIN_SECRET=мой_секрет_для_создания_юзеров
|
|||
# Порт публикации контейнера на хосте для docker compose
|
||||
APP_PORT=3000
|
||||
|
||||
# Имя production-образа для docker compose deploy
|
||||
IMAGE_NAME=git.ihateamerica.ru/dv/intra_monitor
|
||||
|
||||
# Порт сервера (по умолчанию 3000)
|
||||
PORT=3000
|
||||
|
||||
|
|
|
|||
115
.woodpecker.yml
115
.woodpecker.yml
|
|
@ -1,22 +1,101 @@
|
|||
# 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 в main)
|
||||
# 2. docker-build-tag — сборка и пуш образа с semver-тегом (git tag v*)
|
||||
# 3. deploy — деплой на сервер (выполняется на агенте role=work_woodpecker)
|
||||
#
|
||||
# Секреты (Settings → Secrets репозитория):
|
||||
# forgejo_host — хост реестра, например git.example.com
|
||||
# forgejo_image — полный путь образа, например git.example.com/user/intradesk-monitor
|
||||
# forgejo_username — логин (обычно = gitea.actor)
|
||||
# forgejo_token — токен с правом package:write
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
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
|
||||
|
||||
# ── 1а. Сборка при push в main ─────────────────────────────────────────────
|
||||
- name: docker-build
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
registry:
|
||||
from_secret: forgejo_host
|
||||
repo:
|
||||
from_secret: forgejo_image_2
|
||||
username:
|
||||
from_secret: forgejo_username
|
||||
password:
|
||||
from_secret: forgejo_token
|
||||
tags:
|
||||
- latest
|
||||
- "sha-${CI_COMMIT_SHA:0:8}"
|
||||
cache_from:
|
||||
from_secret: forgejo_image_cache # значение: git.example.com/user/intradesk-monitor:latest
|
||||
build_args:
|
||||
- "GIT_COMMIT=${CI_COMMIT_SHA:0:8}"
|
||||
- "BUILDKIT_INLINE_CACHE=1"
|
||||
when:
|
||||
- event: push
|
||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||
branch: main
|
||||
|
||||
# ── 1б. Сборка при git tag v* (semver) ────────────────────────────────────
|
||||
- name: docker-build-tag
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
registry:
|
||||
from_secret: forgejo_host
|
||||
repo:
|
||||
from_secret: forgejo_image_2
|
||||
username:
|
||||
from_secret: forgejo_username
|
||||
password:
|
||||
from_secret: forgejo_token
|
||||
tags:
|
||||
- "${CI_COMMIT_TAG}" # v1.2.3
|
||||
- latest
|
||||
- "sha-${CI_COMMIT_SHA:0:8}" # всегда — нужно для отката
|
||||
cache_from:
|
||||
from_secret: forgejo_image_cache # значение: git.example.com/user/intradesk-monitor:latest
|
||||
build_args:
|
||||
- "GIT_COMMIT=${CI_COMMIT_SHA:0:8}"
|
||||
- "BUILDKIT_INLINE_CACHE=1"
|
||||
when:
|
||||
- event: tag
|
||||
|
||||
# ── 2. Деплой на продакшн-сервер ──────────────────────────────────────────
|
||||
# Выполняется на Woodpecker-агенте с меткой role=work_woodpecker.
|
||||
# Требует: репозиторий помечен «trusted» в настройках Woodpecker-сервера
|
||||
# (иначе монтирование volumes запрещено).
|
||||
- name: deploy
|
||||
image: docker:cli
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /opt/intra_monitor:/deploy
|
||||
commands:
|
||||
- |
|
||||
set -e
|
||||
cd /deploy
|
||||
|
||||
# Проверяем наличие .env на сервере
|
||||
test -f .env || (echo "Missing /opt/intra_monitor/.env on deploy host" && exit 1)
|
||||
|
||||
# Сохраняем ID текущего образа (для отката)
|
||||
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
|
||||
|
|
@ -6,6 +6,10 @@ ENV NODE_ENV=production
|
|||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
# Build-аргумент для трассировки коммита (используется при сборке образа)
|
||||
ARG GIT_COMMIT=unknown
|
||||
ENV GIT_COMMIT=${GIT_COMMIT}
|
||||
|
||||
# Зависимости для better-sqlite3 (нужна компиляция)
|
||||
RUN apk add --no-cache python3 make g++
|
||||
|
||||
|
|
@ -27,4 +31,4 @@ ENV DB_PATH=/data/intradesk.db
|
|||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD node -e "const http=require('http');const req=http.get('http://127.0.0.1:3000/healthz',res=>process.exit(res.statusCode===200?0:1));req.on('error',()=>process.exit(1));"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
CMD ["node", "server.js"]
|
||||
179
README.md
179
README.md
|
|
@ -1,81 +1,156 @@
|
|||
# Монитор заявок - CleverParking
|
||||
# Монитор заявок — CleverParking
|
||||
|
||||
Node/Express приложение со встроенным фронтендом и SQLite. Проект подготовлен для:
|
||||
Node/Express приложение со встроенным фронтендом и SQLite.
|
||||
|
||||
- локального запуска через Docker Compose;
|
||||
- автоматической сборки Docker-образа в Woodpecker;
|
||||
- deploy прямо на хост, где установлен Woodpecker agent.
|
||||
Образ собирается в Woodpecker CI, публикуется в Forgejo registry и деплоится на сервер через Docker socket на агенте с меткой `role=work_woodpecker`.
|
||||
|
||||
---
|
||||
|
||||
## Локальный запуск
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Отредактировать .env под себя
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Приложение будет доступно на `http://localhost:3000`, а база SQLite сохранится в папке `./data`.
|
||||
Compose собирает образ из текущего каталога, данные SQLite хранятся в `./data`.
|
||||
|
||||
## Что добавлено для production
|
||||
---
|
||||
|
||||
- `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` для повторного запуска того же сценария уже на сервере.
|
||||
## Первоначальная настройка сервера
|
||||
|
||||
## Как теперь работает deploy
|
||||
Выполняется **один раз** вручную перед первым деплоем через CI.
|
||||
|
||||
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` уже с локально собранным образом.
|
||||
### 1. Установить Docker
|
||||
|
||||
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`:
|
||||
|
||||
```dotenv
|
||||
JWT_SECRET=long_random_secret
|
||||
ADMIN_SECRET=another_long_secret
|
||||
APP_PORT=3000
|
||||
```bash
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
```
|
||||
|
||||
`PORT` и `DB_PATH` в production задаются через compose автоматически.
|
||||
Проверить:
|
||||
|
||||
## Настройка агента
|
||||
```bash
|
||||
docker version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
Текущий `.woodpecker.yml` ожидает, что на хосте доступны:
|
||||
### 2. Создать рабочий каталог и файлы
|
||||
|
||||
- `/var/run/docker.sock`
|
||||
- `/opt/intradesk-monitor`
|
||||
```bash
|
||||
mkdir -p /opt/intradesk-monitor/data
|
||||
cd /opt/intradesk-monitor
|
||||
```
|
||||
|
||||
Если у тебя несколько агентов, добавь на нужный агент собственный label и раскомментируй блок `labels` в `.woodpecker.yml`, чтобы deploy уходил строго на нужную машину.
|
||||
Создать `docker-compose.yml`:
|
||||
|
||||
## Первый деплой
|
||||
```bash
|
||||
cat > docker-compose.yml << 'EOF'
|
||||
services:
|
||||
intradesk-monitor:
|
||||
image: git.example.com/user/intradesk-monitor:latest
|
||||
container_name: intradesk-monitor
|
||||
restart: always
|
||||
env_file: .env
|
||||
ports:
|
||||
- "${APP_PORT:-3000}:3000"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
DB_PATH: /data/intradesk.db
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "const http=require('http');const req=http.get('http://127.0.0.1:3000/healthz',res=>process.exit(res.statusCode===200?0:1));req.on('error',()=>process.exit(1));"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
EOF
|
||||
```
|
||||
|
||||
1. Создать каталог `/opt/intradesk-monitor`.
|
||||
2. Положить в него `.env`.
|
||||
3. Убедиться, что агент Woodpecker может работать с Docker на хосте.
|
||||
4. Пометить репозиторий как `trusted` в Woodpecker.
|
||||
5. Запушить изменения в default branch.
|
||||
> Заменить `git.example.com/user/intradesk-monitor` на актуальный путь образа.
|
||||
|
||||
После этого Woodpecker сам соберёт образ на целевой машине и перезапустит контейнер.
|
||||
### 3. Создать .env
|
||||
|
||||
```bash
|
||||
# Сгенерировать секреты
|
||||
JWT_SECRET=$(openssl rand -hex 32)
|
||||
ADMIN_SECRET=$(openssl rand -hex 32)
|
||||
|
||||
cat > /opt/intradesk-monitor/.env << EOF
|
||||
JWT_SECRET=${JWT_SECRET}
|
||||
ADMIN_SECRET=${ADMIN_SECRET}
|
||||
APP_PORT=3000
|
||||
EOF
|
||||
```
|
||||
|
||||
Проверить:
|
||||
|
||||
```bash
|
||||
cat /opt/intradesk-monitor/.env
|
||||
```
|
||||
|
||||
### 4. Авторизоваться в Forgejo registry
|
||||
|
||||
Чтобы сервер мог скачать образ при `docker compose pull`:
|
||||
|
||||
```bash
|
||||
docker login git.example.com
|
||||
```
|
||||
|
||||
Ввести логин и токен с правом `package:read`.
|
||||
|
||||
Учётные данные сохраняются в `~/.docker/config.json` и используются автоматически при каждом `pull`.
|
||||
|
||||
### 5. Проверить вручную до первого CI-деплоя
|
||||
|
||||
```bash
|
||||
cd /opt/intradesk-monitor
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
docker compose ps
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
Если контейнер поднялся и healthcheck зелёный — сервер готов принимать деплои от CI.
|
||||
|
||||
---
|
||||
|
||||
## Секреты Woodpecker
|
||||
|
||||
Добавить в Settings → Secrets репозитория:
|
||||
|
||||
| Секрет | Значение |
|
||||
|--------------------|---------------------------------------------------|
|
||||
| `forgejo_host` | хост registry, например `git.example.com` |
|
||||
| `forgejo_image` | полный путь образа, например `git.example.com/user/intradesk-monitor` |
|
||||
| `forgejo_username` | логин пользователя Forgejo |
|
||||
| `forgejo_token` | токен с правом `package:write` |
|
||||
|
||||
---
|
||||
|
||||
## Что делает pipeline
|
||||
|
||||
`.woodpecker.yml` содержит три шага:
|
||||
|
||||
- `docker-build` — сборка и push тегов `latest` и `sha-<shortsha>` при push в `main`
|
||||
- `docker-build-tag` — сборка и push git-тега, `latest` и `sha-<shortsha>` при событии `tag`
|
||||
- `deploy` — на сервере делает `docker compose pull` + `up -d` через 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
|
||||
|
||||
# Откат на предыдущий образ
|
||||
docker compose stop
|
||||
docker compose run --rm intradesk-monitor docker tag $(cat .previous_image) intradesk-monitor:rollback
|
||||
docker compose up -d
|
||||
```
|
||||
|
|
@ -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"
|
||||
|
|
@ -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 <<EOF
|
||||
IMAGE_REPO=${IMAGE_REPO}
|
||||
IMAGE_TAG=${IMAGE_TAG}
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ ! -f .env.runtime ]; then
|
||||
echo ".env.runtime is missing. Provide IMAGE_REPO and IMAGE_TAG for the deploy." >&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
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
services:
|
||||
intradesk-monitor:
|
||||
image: ${IMAGE_REPO}:${IMAGE_TAG}
|
||||
container_name: intradesk-monitor
|
||||
restart: unless-stopped
|
||||
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
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
services:
|
||||
intradesk-monitor:
|
||||
image: ${IMAGE_NAME}:latest
|
||||
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
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "const http=require('http');const req=http.get('http://127.0.0.1:3000/healthz',res=>process.exit(res.statusCode===200?0:1));req.on('error',()=>process.exit(1));"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
BIN
intradesk-monitor.zip
Normal file
BIN
intradesk-monitor.zip
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue