Files
harbor-net/.gitea/workflows/gateway-build.yml
T
dokril d4f228284e
Build and Deploy Gateway / build-and-push (push) Successful in 24s
Build and Deploy Gateway / deploy (push) Successful in 14s
Add hard deploy option for Gateway workflow
2026-08-19 18:40:01 +03:00

230 lines
10 KiB
YAML

name: Build and Deploy Gateway
on:
push:
branches: [master]
workflow_dispatch:
inputs:
hard_deploy:
description: Always rebuild and deploy both Gateway images
required: false
default: false
type: boolean
env:
DEPLOY_PATH: /opt/vpn-proxy
BASE_IMAGE: vpn-proxy-runtime-base:bookworm-slim
NODE_BUILD_IMAGE: mirror.gcr.io/library/node:20.19-bookworm
RUNTIME_BASE_SOURCE_IMAGE: mirror.gcr.io/library/debian:bookworm-slim
APT_MIRROR: http://mirror.yandex.ru/debian
APT_SECURITY_MIRROR: http://mirror.yandex.ru/debian-security
SINGBOX_VERSION: 1.13.18
jobs:
build-and-push:
runs-on: ubuntu-22.04
outputs:
affected_components: ${{ steps['gateway-build'].outputs.affected_components }}
restart_scope: ${{ steps['gateway-build'].outputs.restart_scope }}
steps:
- name: Clone repository
env:
GIT_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -euo pipefail
SERVER_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||')
rm -rf repo
git clone "http://${{ gitea.actor }}:${GIT_TOKEN}@${SERVER_HOST}/${{ gitea.repository }}.git" repo
cd repo
git checkout ${{ gitea.sha }}
- name: Build and push gateway image
id: gateway-build
env:
HARD_DEPLOY_INPUT: ${{ inputs.hard_deploy }}
run: |
set -euo pipefail
cd repo
REGISTRY_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||')
IMAGE="${REGISTRY_HOST}/${{ gitea.repository }}/gateway"
CONTROL_IMAGE="${IMAGE}-control"
DATAPLANE_IMAGE="${IMAGE}-dataplane"
EVENT_NAME="${{ gitea.event_name }}"
case "${EVENT_NAME}:${HARD_DEPLOY_INPUT}" in
workflow_dispatch:true) HARD_DEPLOY=true ;;
workflow_dispatch:false|workflow_dispatch:|push:false|push:) HARD_DEPLOY=false ;;
*) echo "Invalid hard deploy request: ${EVENT_NAME}:${HARD_DEPLOY_INPUT}" >&2; exit 1 ;;
esac
BEFORE_SHA="${{ gitea.event.before }}"
ZERO_SHA="0000000000000000000000000000000000000000"
if [ "$EVENT_NAME" = "push" ] \
&& [ -n "$BEFORE_SHA" ] \
&& [ "$BEFORE_SHA" != "$ZERO_SHA" ] \
&& git cat-file -e "${BEFORE_SHA}^{commit}" 2>/dev/null; then
CHANGED_FILES="$(git diff --no-renames --name-only "$BEFORE_SHA" "${{ gitea.sha }}")"
elif [ "$EVENT_NAME" = "push" ]; then
CHANGED_FILES="package.json"
else
CHANGED_FILES="$(git diff-tree --no-renames --no-commit-id --name-only -r -m HEAD)"
fi
if command -v node >/dev/null 2>&1; then
RUNTIME_IMPACT="$(printf '%s\n' "$CHANGED_FILES" | node scripts/runtime-impact.mjs --stdin)"
else
if ! docker image inspect "${{ env.BASE_IMAGE }}" >/dev/null 2>&1 \
|| ! docker run --rm "${{ env.BASE_IMAGE }}" sh -lc 'command -v node >/dev/null'; then
echo "Cannot classify runtime impact: Node and the existing runtime base are unavailable." >&2
exit 1
fi
RUNTIME_IMPACT="$(printf '%s\n' "$CHANGED_FILES" | docker run --rm -i \
-v "$PWD:/work" \
-w /work \
"${{ env.BASE_IMAGE }}" \
node scripts/runtime-impact.mjs --stdin)"
fi
AFFECTED_COMPONENTS="$(printf '%s\n' "$RUNTIME_IMPACT" | sed -n 's/^affected-components=//p')"
RESTART_SCOPE="$(printf '%s\n' "$RUNTIME_IMPACT" | sed -n 's/^restart-scope=//p')"
case "${AFFECTED_COMPONENTS}:${RESTART_SCOPE}" in
none:none|control:control|dataplane:both|control+dataplane:both) ;;
*) echo "Invalid runtime impact: ${RUNTIME_IMPACT}" >&2; exit 1 ;;
esac
DOCKER_BUILD_OPTIONS=()
if [ "$HARD_DEPLOY" = "true" ]; then
echo "Hard deploy requested: forcing no-cache rebuild and deploy of both Gateway images."
AFFECTED_COMPONENTS="control+dataplane"
RESTART_SCOPE="both"
DOCKER_BUILD_OPTIONS=(--no-cache)
fi
echo "Affected components: ${AFFECTED_COMPONENTS}"
echo "Restart scope: ${RESTART_SCOPE}"
echo "affected_components=${AFFECTED_COMPONENTS}" >> "$GITHUB_OUTPUT"
echo "restart_scope=${RESTART_SCOPE}" >> "$GITHUB_OUTPUT"
if command -v npm >/dev/null 2>&1; then
npm ci --no-audit --no-fund
npm run typecheck
npm run check:boundaries
npm test
npm run build:production
else
if ! docker run --rm "${{ env.NODE_BUILD_IMAGE }}" sh -lc 'command -v npm >/dev/null && command -v git >/dev/null && test -x /bin/bash'; then
echo "Cannot validate change: host npm and the Node 20.19 build toolchain are unavailable." >&2
exit 1
fi
echo "Host npm not found; validating inside ${{ env.NODE_BUILD_IMAGE }}"
docker run --rm \
--network host \
-v "$PWD:/work" \
-w /work \
"${{ env.NODE_BUILD_IMAGE }}" \
sh -lc '
npm ci --no-audit --no-fund
npm_status=$?
if [ "$npm_status" -ne 0 ] || [ ! -x node_modules/.bin/tsc ]; then
echo "npm ci failed to install the validation toolchain." >&2
tail -n 200 /root/.npm/_logs/*-debug-0.log >&2 || true
exit 1
fi
npm run typecheck && npm run check:boundaries && npm test && npm run build:production
'
fi
if [ "$RESTART_SCOPE" = "none" ]; then
echo "Image build and push skipped: no Gateway runtime impact."
exit 0
fi
echo "Build runner: $(hostname)"
echo "Base image: ${{ env.BASE_IMAGE }}"
echo "Docker context: $(docker context show 2>/dev/null || true)"
docker info 2>/dev/null | sed -n '/HTTP Proxy:/p;/HTTPS Proxy:/p;/Name:/p'
if ! docker image inspect "${{ env.BASE_IMAGE }}" >/dev/null 2>&1 \
|| ! docker run --rm "${{ env.BASE_IMAGE }}" sh -lc \
'command -v npm >/dev/null && sing-box version 2>&1 | grep -Fx "sing-box version ${{ env.SINGBOX_VERSION }}"'; then
echo "Runtime base image ${{ env.BASE_IMAGE }} is missing npm or sing-box ${{ env.SINGBOX_VERSION }}; building it now."
BASE_IMAGE="${{ env.RUNTIME_BASE_SOURCE_IMAGE }}" \
RUNTIME_BASE_IMAGE="${{ env.BASE_IMAGE }}" \
APT_MIRROR="${{ env.APT_MIRROR }}" \
APT_SECURITY_MIRROR="${{ env.APT_SECURITY_MIRROR }}" \
SINGBOX_VERSION="${{ env.SINGBOX_VERSION }}" \
./scripts/build-runtime-base.sh
fi
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY_HOST" -u "${{ gitea.actor }}" --password-stdin
DOCKER_BUILDKIT=1 docker build \
"${DOCKER_BUILD_OPTIONS[@]}" \
--network host \
--pull=false \
--build-arg NODE_BUILD_IMAGE="${{ env.NODE_BUILD_IMAGE }}" \
--build-arg BASE_IMAGE="${{ env.BASE_IMAGE }}" \
--build-arg SINGBOX_VERSION="${{ env.SINGBOX_VERSION }}" \
--build-arg INSTALL_RUNTIME_DEPS=false \
--build-arg INSTALL_SINGBOX=false \
-t "${CONTROL_IMAGE}:latest" \
-t "${CONTROL_IMAGE}:${{ gitea.sha }}" \
-t "${DATAPLANE_IMAGE}:latest" \
-t "${DATAPLANE_IMAGE}:${{ gitea.sha }}" \
.
docker run --rm --entrypoint sing-box "${CONTROL_IMAGE}:${{ gitea.sha }}" version 2>&1 \
| grep -Fx "sing-box version ${{ env.SINGBOX_VERSION }}"
docker run --rm --entrypoint sing-box "${DATAPLANE_IMAGE}:${{ gitea.sha }}" version 2>&1 \
| grep -Fx "sing-box version ${{ env.SINGBOX_VERSION }}"
docker push "${CONTROL_IMAGE}:latest"
docker push "${CONTROL_IMAGE}:${{ gitea.sha }}"
docker push "${DATAPLANE_IMAGE}:latest"
docker push "${DATAPLANE_IMAGE}:${{ gitea.sha }}"
deploy:
runs-on: lxc-111
needs: build-and-push
steps:
- name: Clone repository
env:
GIT_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -euo pipefail
SERVER_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||')
rm -rf repo
git clone --depth 2 "http://${{ gitea.actor }}:${GIT_TOKEN}@${SERVER_HOST}/${{ gitea.repository }}.git" repo
cd repo
git checkout ${{ gitea.sha }}
- name: Pull and deploy gateway image
run: |
set -euo pipefail
cd repo
REGISTRY_HOST=$(echo "${{ gitea.server_url }}" | sed 's|https\?://||')
IMAGE="${REGISTRY_HOST}/${{ gitea.repository }}/gateway"
CONTROL_IMAGE="${IMAGE}-control:${{ gitea.sha }}"
DATAPLANE_IMAGE="${IMAGE}-dataplane:${{ gitea.sha }}"
AFFECTED_COMPONENTS="${{ needs['build-and-push'].outputs.affected_components }}"
RESTART_SCOPE="${{ needs['build-and-push'].outputs.restart_scope }}"
case "${AFFECTED_COMPONENTS}:${RESTART_SCOPE}" in
none:none|control:control|dataplane:both|control+dataplane:both) ;;
*) echo "Invalid runtime impact output: ${AFFECTED_COMPONENTS}:${RESTART_SCOPE}" >&2; exit 1 ;;
esac
if [ "$RESTART_SCOPE" = "none" ]; then
echo "Deploy skipped: no Gateway runtime impact."
exit 0
fi
UPDATE_DATAPLANE=false
if [ "$RESTART_SCOPE" = "both" ]; then
UPDATE_DATAPLANE=true
fi
echo "Deploy runner: $(hostname)"
echo "Affected components: ${AFFECTED_COMPONENTS}"
echo "Restart scope: ${RESTART_SCOPE}"
echo "Update dataplane: ${UPDATE_DATAPLANE}"
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY_HOST" -u "${{ gitea.actor }}" --password-stdin
DEPLOY_PATH="${{ env.DEPLOY_PATH }}" \
CONTROL_IMAGE="${CONTROL_IMAGE}" \
DATAPLANE_IMAGE="${DATAPLANE_IMAGE}" \
UPDATE_DATAPLANE="${UPDATE_DATAPLANE}" \
bash scripts/deploy-gateway.sh
VERSION_JSON="$(curl --noproxy '*' -fsS http://127.0.0.1:3456/api/version)"
printf '%s\n' "$VERSION_JSON"
printf '%s\n' "$VERSION_JSON" \
| grep -F "\"singBox\":\"${{ env.SINGBOX_VERSION }}\""