diff --git a/.woodpecker.yml b/.woodpecker.yml index 6f84127..b589345 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -1,21 +1,13 @@ -# ───────────────────────────────────────────────────────────────────────────── -# Woodpecker CI Pipeline +# Woodpecker CI/CD # -# Шаги: -# 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 -# ───────────────────────────────────────────────────────────────────────────── +# Required secrets: +# forgejo_host - registry host (example: git.example.com) +# forgejo_image - full image path (example: git.example.com/user/intradesk-monitor) +# forgejo_username - registry username +# forgejo_token - registry token with package:write steps: - - # ── 1а. Сборка при push в main ───────────────────────────────────────────── + # Build and push on main branch updates. - name: docker-build image: woodpeckerci/plugin-docker-buildx settings: @@ -29,51 +21,45 @@ steps: from_secret: forgejo_token tags: - latest - - "sha-${CI_COMMIT_SHA:0:8}" - # Берём inline-кэш из latest; ARG GIT_COMMIT теперь в конце Dockerfile — кэш не сбивается + - "sha-${CI_COMMIT_SHA}" cache_from: from_secret: forgejo_image - # mode=max кэширует все промежуточные слои (apk, npm install) в отдельный манифест - cache_to: - from_secret: forgejo_image_cache # значение: /:buildcache rewrite_timestamp: false build_args: - - "GIT_COMMIT=${CI_COMMIT_SHA:0:8}" + - "GIT_COMMIT=${CI_COMMIT_SHA}" - "BUILDKIT_INLINE_CACHE=1" when: - event: push branch: main - # ── 1б. Сборка при git tag v* (semver) ──────────────────────────────────── + # Build and push on Git tag (release) events. - name: docker-build-tag image: woodpeckerci/plugin-docker-buildx settings: registry: from_secret: forgejo_host repo: - from_secret: forgejo_image_2 + from_secret: forgejo_image username: from_secret: forgejo_username password: from_secret: forgejo_token tags: - - "${CI_COMMIT_TAG}" # v1.2.3 + - "${CI_COMMIT_TAG}" - latest - - "sha-${CI_COMMIT_SHA:0:8}" # всегда — нужно для отката + - "sha-${CI_COMMIT_SHA}" cache_from: - from_secret: forgejo_image # значение: git.example.com/user/intradesk-monitor:latest + from_secret: forgejo_image + rewrite_timestamp: false build_args: - - "GIT_COMMIT=${CI_COMMIT_SHA:0:8}" + - "GIT_COMMIT=${CI_COMMIT_SHA}" - "BUILDKIT_INLINE_CACHE=1" when: - event: tag - # ── 2. Деплой на продакшн-сервер ────────────────────────────────────────── - # Выполняется на Woodpecker-агенте с меткой role=work_woodpecker. - # Требует: репозиторий помечен «trusted» в настройках Woodpecker-сервера - # (иначе монтирование volumes запрещено). + # Deploy for both push(main) and tag events. - name: deploy - image: docker:cli + image: docker:27-cli volumes: - /var/run/docker.sock:/var/run/docker.sock - /opt/intra_monitor:/deploy @@ -82,10 +68,8 @@ steps: 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") @@ -93,16 +77,14 @@ steps: echo "Snapshot for rollback: $OLD_IMAGE" fi - # Удаляем legacy-контейнер со статичным именем, чтобы избежать name conflict if docker ps -a --format '{{.Names}}' | grep -Eq '^intradesk-monitor$'; then docker rm -f intradesk-monitor || true fi - # Обновляем образ и перезапускаем - docker compose -p intradesk-monitor pull + docker compose -p intradesk-monitor pull intradesk-monitor docker compose -p intradesk-monitor up -d --remove-orphans - echo "Deployed: ${CI_COMMIT_SHA:0:8} (${CI_COMMIT_MESSAGE})" + echo "Deployed commit: ${CI_COMMIT_SHA} tag: ${CI_COMMIT_TAG}" labels: role: work_woodpecker when: diff --git a/Dockerfile b/Dockerfile index c76b15b..049bfbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,35 +1,42 @@ -FROM node:20-alpine +# syntax=docker/dockerfile:1.7 +FROM node:20-alpine AS deps WORKDIR /app -ENV NODE_ENV=production -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 - -# Зависимости для better-sqlite3 (нужна компиляция) -# Слой кэшируется пока не меняется базовый образ +# Build dependencies for native Node modules (better-sqlite3). RUN apk add --no-cache python3 make g++ COPY package*.json ./ -RUN npm install --omit=dev --no-audit --no-fund \ - && npm cache clean --force +# Use npm cache between builds; fallback to npm install when lock file is absent. +RUN --mount=type=cache,target=/root/.npm \ + if [ -f package-lock.json ]; then \ + npm ci --omit=dev --no-audit --no-fund; \ + else \ + npm install --omit=dev --no-audit --no-fund; \ + fi -# Код приложения — копируется после зависимостей -COPY server.js ./ -COPY refresh-service.js ./ -COPY public/ ./public/ +FROM node:20-alpine AS runtime +WORKDIR /app -# Build-аргумент объявлен ПОСЛЕ тяжёлых слоёв — не сбивает кэш при каждом коммите -ARG GIT_COMMIT=unknown -ENV GIT_COMMIT=${GIT_COMMIT} +ENV NODE_ENV=production \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + PORT=3000 \ + DB_PATH=/data/intradesk.db + +# Runtime libs required by native modules. +RUN apk add --no-cache libstdc++ + +COPY --from=deps /app/node_modules ./node_modules +COPY server.js refresh-service.js ./ +COPY public ./public -# Папка для базы данных RUN mkdir -p /data -EXPOSE 3000 +ARG GIT_COMMIT=unknown +LABEL org.opencontainers.image.revision=$GIT_COMMIT -ENV PORT=3000 -ENV DB_PATH=/data/intradesk.db +EXPOSE 3000 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));"