From 63cb9e7e020f76131b4c870ccd00c35c4f514277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=A1=D0=B0?= =?UTF-8?q?=D0=B1=D0=B8=D0=BB=D0=B8=D0=BD?= Date: Fri, 10 Jul 2026 14:33:19 +0300 Subject: [PATCH] Deploy IFT: optional observer-web/logrotate, bootstrap build on IFT server. Co-authored-by: Cursor --- .gitea/workflows/deploy-ift.yml | 22 +++++++ docker/ObserverWeb.Dockerfile | 10 ++-- docker/logrotate/Dockerfile | 5 +- scripts/build-optional-aux-on-server.sh | 76 +++++++++++++++++++++++++ scripts/publish-ift-aux-images.sh | 32 ++++++++--- 5 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 scripts/build-optional-aux-on-server.sh diff --git a/.gitea/workflows/deploy-ift.yml b/.gitea/workflows/deploy-ift.yml index 9bafa4d..ec2f051 100644 --- a/.gitea/workflows/deploy-ift.yml +++ b/.gitea/workflows/deploy-ift.yml @@ -73,6 +73,28 @@ jobs: FILES="$(git diff --name-only "${HEAD}^" "${HEAD}" 2>/dev/null || git show --pretty="" --name-only "${HEAD}")" bash scripts/publish-ift-aux-images.sh "${REGISTRY}" "${TAG}" "${FILES}" + - name: Bootstrap optional aux images on IFT server + uses: appleboy/ssh-action@v0.1.5 + with: + host: ${{ secrets.IFT_SSH_HOST }} + username: ${{ secrets.IFT_SSH_USER }} + key: ${{ secrets.IFT_SSH_PRIVATE_KEY }} + script: | + export REGISTRY_USER='${{ secrets.REGISTRY_USER }}' + export REGISTRY_PASSWORD='${{ secrets.REGISTRY_PASSWORD }}' + echo "${REGISTRY_PASSWORD}" | docker login git.sabilin.com -u "${REGISTRY_USER}" --password-stdin + if [ "${{ github.event_name }}" = "workflow_run" ]; then + COMMIT="${{ github.event.workflow_run.head_sha }}" + else + COMMIT="${GITHUB_SHA}" + fi + WORKDIR="$(mktemp -d)" + git clone --depth 1 "https://${REGISTRY_USER}:${REGISTRY_PASSWORD}@git.sabilin.com/EventHub/EventHubBack.git" "${WORKDIR}/back" + git -C "${WORKDIR}/back" fetch --depth 1 origin "${COMMIT}" + git -C "${WORKDIR}/back" checkout "${COMMIT}" + BACK_DIR="${WORKDIR}/back" bash "${WORKDIR}/back/scripts/build-optional-aux-on-server.sh" "${REGISTRY}" "${{ steps.meta.outputs.tag }}" "${COMMIT}" + rm -rf "${WORKDIR}" + - name: Deploy core on IFT via SSH uses: appleboy/ssh-action@v0.1.5 with: diff --git a/docker/ObserverWeb.Dockerfile b/docker/ObserverWeb.Dockerfile index e4992bd..225d01d 100644 --- a/docker/ObserverWeb.Dockerfile +++ b/docker/ObserverWeb.Dockerfile @@ -1,11 +1,9 @@ FROM erlang:28-alpine -RUN apk add --no-cache \ - elixir \ - nodejs \ - npm \ - git \ - inotify-tools +RUN for i in 1 2 3 4 5; do \ + apk add --no-cache elixir nodejs npm git inotify-tools && break; \ + echo "apk retry $i/5..."; sleep 15; \ + done WORKDIR /app diff --git a/docker/logrotate/Dockerfile b/docker/logrotate/Dockerfile index d5e2877..5fddced 100644 --- a/docker/logrotate/Dockerfile +++ b/docker/logrotate/Dockerfile @@ -1,5 +1,8 @@ FROM alpine:latest -RUN apk add --no-cache logrotate dcron tzdata +RUN for i in 1 2 3 4 5; do \ + apk add --no-cache logrotate dcron tzdata && break; \ + echo "apk retry $i/5..."; sleep 15; \ + done COPY logrotate.conf /etc/logrotate.conf COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh diff --git a/scripts/build-optional-aux-on-server.sh b/scripts/build-optional-aux-on-server.sh new file mode 100644 index 0000000..ee6f20a --- /dev/null +++ b/scripts/build-optional-aux-on-server.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Сборка observer-web / logrotate на сервере IFT (fallback, если runner не тянет Alpine). +# Usage: BACK_DIR=/path/to/back build-optional-aux-on-server.sh +set -euo pipefail + +REGISTRY="${1:?registry}" +TAG="${2:?sha-tag}" +COMMIT="${3:?commit-sha}" + +git_with_auth() { + if [[ -n "${REGISTRY_USER:-}" && -n "${REGISTRY_PASSWORD:-}" ]]; then + local askpass + askpass="$(mktemp)" + cat > "${askpass}" <<'EOF' +#!/bin/sh +case "$1" in + *[Pp]assword*) printf '%s\n' "$REGISTRY_PASSWORD" ;; + *) printf '%s\n' "$REGISTRY_USER" ;; +esac +EOF + chmod +x "${askpass}" + GIT_TERMINAL_PROMPT=0 GIT_ASKPASS="${askpass}" \ + REGISTRY_USER="${REGISTRY_USER}" REGISTRY_PASSWORD="${REGISTRY_PASSWORD}" \ + "$@" + rm -f "${askpass}" + else + GIT_TERMINAL_PROMPT=0 "$@" + fi +} + +need_build() { + docker buildx imagetools inspect "${REGISTRY}/$1:${TAG}" >/dev/null 2>&1 && return 1 + docker buildx imagetools inspect "${REGISTRY}/$1:ift" >/dev/null 2>&1 && return 1 + return 0 +} + +build_one() { + local name="$1" + local context="$2" + local dockerfile="$3" + + if ! need_build "${name}"; then + if docker buildx imagetools inspect "${REGISTRY}/${name}:${TAG}" >/dev/null 2>&1; then + echo "== ${name}: ${TAG} exists, skip" + else + echo "== ${name}: promote :ift -> ${TAG}" + docker buildx imagetools create -t "${REGISTRY}/${name}:${TAG}" "${REGISTRY}/${name}:ift" + fi + return 0 + fi + + echo "== ${name}: build on IFT server" + bash scripts/retry-docker-build.sh 5 60 -- \ + docker buildx build -f "${dockerfile}" --push \ + -t "${REGISTRY}/${name}:${TAG}" \ + -t "${REGISTRY}/${name}:ift" \ + --provenance=false --sbom=false \ + "${context}" || { + echo "WARN: ${name}: server build failed, deploy continues without it" + return 0 + } +} + +if [[ -n "${BACK_DIR:-}" ]]; then + cd "${BACK_DIR}" +else + WORKDIR="$(mktemp -d)" + trap 'rm -rf "${WORKDIR}"' EXIT + git_with_auth git clone --depth 1 "https://git.sabilin.com/EventHub/EventHubBack.git" "${WORKDIR}/back" + git_with_auth git -C "${WORKDIR}/back" fetch --depth 1 origin "${COMMIT}" + git -C "${WORKDIR}/back" checkout "${COMMIT}" + cd "${WORKDIR}/back" +fi + +build_one observer-web . docker/ObserverWeb.Dockerfile +build_one logrotate docker/logrotate docker/logrotate/Dockerfile diff --git a/scripts/publish-ift-aux-images.sh b/scripts/publish-ift-aux-images.sh index ec52bb6..120bd19 100644 --- a/scripts/publish-ift-aux-images.sh +++ b/scripts/publish-ift-aux-images.sh @@ -12,9 +12,11 @@ rebuild_image() { local context="$2" local dockerfile="$3" local reason="$4" + local attempts="${5:-3}" + local wait="${6:-30}" echo "== ${name}: rebuild (${reason})" - bash scripts/retry-docker-build.sh 3 30 -- \ + bash scripts/retry-docker-build.sh "${attempts}" "${wait}" -- \ docker buildx build -f "${dockerfile}" --push \ -t "${REGISTRY}/${name}:${TAG}" \ -t "${REGISTRY}/${name}:ift" \ @@ -22,11 +24,25 @@ rebuild_image() { "${context}" } +try_rebuild() { + local optional="$1" + shift + if rebuild_image "$@"; then + return 0 + fi + if [[ "${optional}" == "1" ]]; then + echo "WARN: $1: rebuild failed (optional), will try on IFT server or skip" + return 0 + fi + return 1 +} + publish_image() { local name="$1" local path_pattern="$2" local context="$3" local dockerfile="$4" + local optional="${5:-0}" if docker buildx imagetools inspect "${REGISTRY}/${name}:${TAG}" >/dev/null 2>&1; then echo "== ${name}: ${TAG} already in registry, skip" @@ -34,13 +50,13 @@ publish_image() { fi if echo "${FILES}" | grep -qE "${path_pattern}"; then - rebuild_image "${name}" "${context}" "${dockerfile}" "matched: ${path_pattern}" + try_rebuild "${optional}" "${name}" "${context}" "${dockerfile}" "matched: ${path_pattern}" return fi echo "== ${name}: unchanged, promote :ift -> ${TAG}" if ! docker buildx imagetools inspect "${REGISTRY}/${name}:ift" >/dev/null 2>&1; then - rebuild_image "${name}" "${context}" "${dockerfile}" "bootstrap: :ift not found" + try_rebuild "${optional}" "${name}" "${context}" "${dockerfile}" "bootstrap: :ift not found" return fi docker buildx imagetools create \ @@ -48,8 +64,8 @@ publish_image() { "${REGISTRY}/${name}:ift" } -publish_image traefik-coraza '^docker/traefik/' docker/traefik docker/traefik/Dockerfile -publish_image fallback '^docker/fallback/' docker/fallback docker/fallback/Dockerfile -publish_image bot-emulator-users '^test/emulate_users/' . test/emulate_users/Dockerfile -publish_image observer-web '^docker/ObserverWeb\.Dockerfile|^docker/observer_web/' . docker/ObserverWeb.Dockerfile -publish_image logrotate '^docker/logrotate/' docker/logrotate docker/logrotate/Dockerfile +publish_image traefik-coraza '^docker/traefik/' docker/traefik docker/traefik/Dockerfile 0 +publish_image fallback '^docker/fallback/' docker/fallback docker/fallback/Dockerfile 0 +publish_image bot-emulator-users '^test/emulate_users/' . test/emulate_users/Dockerfile 0 +publish_image observer-web '^docker/ObserverWeb\.Dockerfile|^docker/observer_web/' . docker/ObserverWeb.Dockerfile 1 +publish_image logrotate '^docker/logrotate/' docker/logrotate docker/logrotate/Dockerfile 1