From 29ee65e1aed89dc2c13a898c56ae69a7138c9939 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 28 Sep 2026 20:31:13 +0300 Subject: [PATCH] Add HAProxy CI/CD --- .gitea/workflows/haproxy.yml | 38 +++++++++ scripts/deploy-haproxy.sh | 158 +++++++++++++++++++++++++++++++++++ scripts/validate-haproxy.sh | 89 ++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100644 .gitea/workflows/haproxy.yml create mode 100755 scripts/deploy-haproxy.sh create mode 100755 scripts/validate-haproxy.sh diff --git a/.gitea/workflows/haproxy.yml b/.gitea/workflows/haproxy.yml new file mode 100644 index 0000000..76b8fd1 --- /dev/null +++ b/.gitea/workflows/haproxy.yml @@ -0,0 +1,38 @@ +name: HAProxy CI/CD + +on: + push: + paths: + - "stacks/haproxy/**" + - "scripts/validate-haproxy.sh" + - "scripts/deploy-haproxy.sh" + - ".gitea/workflows/haproxy.yml" + + workflow_dispatch: + +permissions: + contents: read + +jobs: + + haproxy: + + name: Validate and deploy HAProxy + + runs-on: docker-home-01 + + steps: + + - name: Checkout repository + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Validate configuration + run: | + ./scripts/validate-haproxy.sh + + - name: Deploy to production + if: ${{ gitea.ref == 'refs/heads/main' }} + run: | + ./scripts/deploy-haproxy.sh diff --git a/scripts/deploy-haproxy.sh b/scripts/deploy-haproxy.sh new file mode 100755 index 0000000..f2f40d0 --- /dev/null +++ b/scripts/deploy-haproxy.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +SRC="${ROOT}/stacks/haproxy" +LIVE="/srv/nfs/infra/haproxy" +ENV_FILE="${LIVE}/.env" + +BACKUP_ROOT="/srv/nfs/infra/.deploy-backups/haproxy" +STAMP="$(date '+%Y%m%d-%H%M%S')" +BACKUP="${BACKUP_ROOT}/${STAMP}" + +echo "=== HAProxy deployment ===" + + +# +# 1. Pre-deployment validation +# + +"${ROOT}/scripts/validate-haproxy.sh" + + +# +# 2. Backup current configuration +# + +echo +echo "--- Backup current configuration ---" + +mkdir -p "${BACKUP}/conf" + +cp -a \ + "${LIVE}/docker-compose.yml" \ + "${BACKUP}/docker-compose.yml" + +rsync -a \ + "${LIVE}/conf/" \ + "${BACKUP}/conf/" + +echo "Backup: ${BACKUP}" + + +# +# Rollback function +# + +rollback() +{ + rc=$? + + echo + echo "!!! Deployment failed" + echo "!!! Restoring ${BACKUP}" + + cp -a \ + "${BACKUP}/docker-compose.yml" \ + "${LIVE}/docker-compose.yml" + + rsync -a --delete \ + "${BACKUP}/conf/" \ + "${LIVE}/conf/" + + ( + cd "${LIVE}" + + docker compose \ + --env-file "${ENV_FILE}" \ + up -d --remove-orphans || true + + docker compose \ + --env-file "${ENV_FILE}" \ + restart haproxy || true + ) + + exit "${rc}" +} + +trap rollback ERR + + +# +# 3. Deploy files +# + +echo +echo "--- Deploy configuration ---" + +install \ + -m 0644 \ + "${SRC}/docker-compose.yml" \ + "${LIVE}/docker-compose.yml" + +rsync -a --delete \ + "${SRC}/conf/" \ + "${LIVE}/conf/" + + +# +# 4. Apply Docker Compose +# + +echo +echo "--- Apply Docker Compose ---" + +cd "${LIVE}" + +docker compose \ + --env-file "${ENV_FILE}" \ + up -d --remove-orphans + + +# +# HAProxy config is bind-mounted. +# docker compose will not automatically restart HAProxy +# just because haproxy.cfg changed. +# + +echo +echo "--- Restart HAProxy ---" + +docker compose \ + --env-file "${ENV_FILE}" \ + restart haproxy + + +# +# 5. Post-deployment validation +# + +sleep 2 + +RUNNING="$( + docker inspect \ + -f '{{.State.Running}}' \ + haproxy +)" + +if [[ "${RUNNING}" != "true" ]]; then + echo "ERROR: HAProxy container is not running" + exit 1 +fi + + +docker exec \ + haproxy \ + haproxy \ + -c \ + -f /usr/local/etc/haproxy/haproxy.cfg + + +trap - ERR + + +echo +echo "=== Deployment successful ===" +echo "Backup: ${BACKUP}" diff --git a/scripts/validate-haproxy.sh b/scripts/validate-haproxy.sh new file mode 100755 index 0000000..4d42afa --- /dev/null +++ b/scripts/validate-haproxy.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +SRC="${ROOT}/stacks/haproxy" +LIVE="/srv/nfs/infra/haproxy" +ENV_FILE="${LIVE}/.env" + +echo "=== HAProxy validation ===" + +if [[ ! -f "${ENV_FILE}" ]]; then + echo "ERROR: ${ENV_FILE} not found" + exit 1 +fi + +if [[ ! -f "${SRC}/docker-compose.yml" ]]; then + echo "ERROR: docker-compose.yml not found" + exit 1 +fi + +if [[ ! -f "${SRC}/conf/haproxy.cfg" ]]; then + echo "ERROR: haproxy.cfg not found" + exit 1 +fi + + +echo +echo "--- Validate docker-compose.yml ---" + +docker compose \ + --env-file "${ENV_FILE}" \ + -f "${SRC}/docker-compose.yml" \ + config >/dev/null + + +echo +echo "--- Detect HAProxy image ---" + +HAPROXY_IMAGE="$( + docker compose \ + --env-file "${ENV_FILE}" \ + -f "${SRC}/docker-compose.yml" \ + config --images | + grep -m1 -E '(^|/)haproxy:' +)" + +if [[ -z "${HAPROXY_IMAGE}" ]]; then + echo "ERROR: HAProxy image not found in compose" + exit 1 +fi + +echo "HAProxy image: ${HAPROXY_IMAGE}" + + +echo +echo "--- Validate haproxy.cfg ---" + +TMP_DIR="$(mktemp -d)" + +cleanup() +{ + rm -rf "${TMP_DIR}" +} + +trap cleanup EXIT + +mkdir -p "${TMP_DIR}/conf" + +rsync -a \ + "${SRC}/conf/" \ + "${TMP_DIR}/conf/" + + +docker run \ + --rm \ + -v "${TMP_DIR}/conf:/usr/local/etc/haproxy:ro" \ + -v "${LIVE}/certs:/etc/haproxy/certs:ro" \ + -v "${LIVE}/crowdsec/lua:/usr/lib/crowdsec-haproxy-spoa-bouncer/lua:ro" \ + -v "${LIVE}/crowdsec/templates:/var/lib/crowdsec-haproxy-spoa-bouncer/html:ro" \ + "${HAPROXY_IMAGE}" \ + haproxy \ + -c \ + -f /usr/local/etc/haproxy/haproxy.cfg + + +echo +echo "=== Validation successful ==="