This commit is contained in:
@@ -12,11 +12,10 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
BACKEND_IMAGE: git.koppkb.com/kopkb/my-kopkb-be
|
BACKEND_IMAGE: git.koppkb.com/kopkb/my-kopkb-be
|
||||||
DEPLOY_DIR: /home/arrahn/Project
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: host
|
runs-on: mykopkb-production
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -24,107 +23,133 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ inputs.image_tag }}
|
ref: ${{ inputs.image_tag }}
|
||||||
|
|
||||||
- name: Login to Docker registry
|
- name: Verify image exists in registry
|
||||||
run: |
|
run: |
|
||||||
|
set -e
|
||||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
|
||||||
docker login git.koppkb.com \
|
docker login git.koppkb.com \
|
||||||
-u "${{ secrets.REGISTRY_USERNAME }}" \
|
-u "${{ secrets.REGISTRY_USERNAME }}" \
|
||||||
--password-stdin
|
--password-stdin
|
||||||
|
|
||||||
- name: Verify images exist in registry
|
IMAGE="${BACKEND_IMAGE}:${{ inputs.image_tag }}"
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
IMAGE_TAG="${{ inputs.image_tag }}"
|
|
||||||
|
|
||||||
IMAGE="${BACKEND_IMAGE}:${IMAGE_TAG}"
|
|
||||||
echo "Checking if image exists: ${IMAGE}"
|
echo "Checking if image exists: ${IMAGE}"
|
||||||
if ! docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then
|
docker manifest inspect "${IMAGE}" > /dev/null
|
||||||
echo "ERROR: Image ${IMAGE} does not exist in registry!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✓ ${IMAGE} found"
|
echo "✓ ${IMAGE} found"
|
||||||
|
|
||||||
- name: Pull Docker images
|
- name: Deploy over SSH
|
||||||
|
env:
|
||||||
|
PROD_HOST: ${{ secrets.PROD_HOST }}
|
||||||
|
PROD_USER: ${{ secrets.PROD_USER }}
|
||||||
|
PROD_SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
|
||||||
|
PROD_DEPLOY_DIR: ${{ secrets.PROD_DEPLOY_DIR }}
|
||||||
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
IMAGE_TAG: ${{ inputs.image_tag }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -euo pipefail
|
||||||
IMAGE_TAG="${{ inputs.image_tag }}"
|
|
||||||
docker pull "${BACKEND_IMAGE}:${IMAGE_TAG}"
|
|
||||||
echo "✓ Image pulled successfully"
|
|
||||||
|
|
||||||
- name: Sync compose file and deploy
|
if [ ! -f .env.production ]; then
|
||||||
run: |
|
echo "ERROR: .env.production is missing from the repository checkout"
|
||||||
set -e
|
|
||||||
IMAGE_TAG="${{ inputs.image_tag }}"
|
|
||||||
|
|
||||||
mkdir -p "${DEPLOY_DIR}"
|
|
||||||
cp docker/production/docker-compose.production.yml "${DEPLOY_DIR}/docker-compose.yml"
|
|
||||||
|
|
||||||
ENV_FILE=".env.production"
|
|
||||||
if [ ! -f "${ENV_FILE}" ]; then
|
|
||||||
echo "ERROR: ${ENV_FILE} is missing from the repository checkout"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
cp "${ENV_FILE}" "${DEPLOY_DIR}/.env"
|
if [ ! -f docker/production/nginx/nginx.conf ]; then
|
||||||
chmod 600 "${DEPLOY_DIR}/.env"
|
echo "ERROR: docker/production/nginx/nginx.conf is missing from the repository checkout"
|
||||||
|
|
||||||
NGINX_CONF="$(pwd)/docker/production/nginx/nginx.conf"
|
|
||||||
if [ ! -f "${NGINX_CONF}" ]; then
|
|
||||||
echo "ERROR: nginx config not found at ${NGINX_CONF}"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd "${DEPLOY_DIR}"
|
if ! command -v ssh >/dev/null 2>&1; then
|
||||||
|
apt-get update && apt-get install -y openssh-client
|
||||||
|
fi
|
||||||
|
|
||||||
export IMAGE_TAG
|
mkdir -p ~/.ssh
|
||||||
export NGINX_CONF
|
umask 077
|
||||||
|
# Gitea often stores a multiline key as one line. Prefer a base64
|
||||||
|
# secret (one line). A raw key that still contains BEGIN is accepted too.
|
||||||
|
if printf '%s' "$PROD_SSH_KEY" | grep -q -- '-----BEGIN'; then
|
||||||
|
printf '%b\n' "$PROD_SSH_KEY" | tr -d '\r' > ~/.ssh/id_ed25519
|
||||||
|
else
|
||||||
|
printf '%s' "$PROD_SSH_KEY" | tr -d '[:space:]' | base64 -d > ~/.ssh/id_ed25519
|
||||||
|
fi
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
if ! ssh-keygen -y -f ~/.ssh/id_ed25519 >/dev/null; then
|
||||||
|
echo "ERROR: PROD_SSH_KEY is not a readable private key."
|
||||||
|
echo "On the runner, run: base64 -w0 ~/.ssh/mykopkb_prod"
|
||||||
|
echo "Put that single line in the PROD_SSH_KEY secret."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
ssh-keyscan -H "$PROD_HOST" >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
SSH=(ssh -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes "${PROD_USER}@${PROD_HOST}")
|
||||||
|
IMAGE="${BACKEND_IMAGE}:${IMAGE_TAG}"
|
||||||
|
NGINX_CONF="${PROD_DEPLOY_DIR}/nginx/nginx.conf"
|
||||||
|
|
||||||
|
"${SSH[@]}" "mkdir -p '${PROD_DEPLOY_DIR}/nginx'"
|
||||||
|
scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \
|
||||||
|
docker/production/docker-compose.production.yml \
|
||||||
|
"${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_DIR}/docker-compose.yml"
|
||||||
|
scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \
|
||||||
|
.env.production \
|
||||||
|
"${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_DIR}/.env"
|
||||||
|
scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \
|
||||||
|
docker/production/nginx/nginx.conf \
|
||||||
|
"${PROD_USER}@${PROD_HOST}:${NGINX_CONF}"
|
||||||
|
|
||||||
|
"${SSH[@]}" "bash -s" << EOF
|
||||||
|
set -euo pipefail
|
||||||
|
echo "${REGISTRY_PASSWORD}" | docker login git.koppkb.com \
|
||||||
|
-u "${REGISTRY_USERNAME}" --password-stdin
|
||||||
|
|
||||||
|
echo "Checking if image exists: ${IMAGE}"
|
||||||
|
docker manifest inspect "${IMAGE}" > /dev/null
|
||||||
|
echo "✓ ${IMAGE} found"
|
||||||
|
|
||||||
|
echo "Deploying backend IMAGE=${IMAGE}"
|
||||||
|
chmod 600 "${PROD_DEPLOY_DIR}/.env"
|
||||||
|
|
||||||
|
docker network inspect mykopkb-production-network >/dev/null 2>&1 \
|
||||||
|
|| docker network create mykopkb-production-network
|
||||||
|
|
||||||
|
cd "${PROD_DEPLOY_DIR}"
|
||||||
|
export IMAGE_TAG="${IMAGE_TAG}"
|
||||||
|
export NGINX_CONF="${NGINX_CONF}"
|
||||||
|
|
||||||
docker rm -f mykopkb-be-production || true
|
docker rm -f mykopkb-be-production || true
|
||||||
docker compose -f docker-compose.yml pull backend
|
docker compose -f docker-compose.yml pull backend
|
||||||
docker compose -f docker-compose.yml up -d backend
|
docker compose -f docker-compose.yml up -d backend
|
||||||
|
|
||||||
echo "✓ Deployed backend IMAGE_TAG=${IMAGE_TAG}"
|
echo "Waiting for the backend to start..."
|
||||||
|
|
||||||
- name: Verify deployment
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
cd "${DEPLOY_DIR}"
|
|
||||||
|
|
||||||
echo "Waiting for services to start..."
|
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
STATUS=$(docker compose -f docker-compose.yml ps --status running --format '{{.Name}}' backend 2>/dev/null || true)
|
STATUS=\$(docker compose -f docker-compose.yml ps --status running --format '{{.Name}}' backend || true)
|
||||||
if [ -z "$STATUS" ]; then
|
if [ -z "\$STATUS" ]; then
|
||||||
echo "ERROR: backend is not running"
|
echo "ERROR: backend is not running"
|
||||||
docker compose -f docker-compose.yml ps
|
docker compose -f docker-compose.yml ps
|
||||||
docker compose -f docker-compose.yml logs --tail=50 backend || true
|
docker compose -f docker-compose.yml logs --tail=50 backend || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "✓ backend is running (${STATUS})"
|
echo "✓ backend is running (\$STATUS)"
|
||||||
|
|
||||||
BE_CONTAINER=$(docker compose -f docker-compose.yml ps -q backend)
|
BE_CONTAINER=\$(docker compose -f docker-compose.yml ps -q backend)
|
||||||
if docker exec "$BE_CONTAINER" test -f /var/www/.env; then
|
if docker exec "\$BE_CONTAINER" test -f /var/www/.env; then
|
||||||
echo "✓ Backend .env file exists"
|
echo "✓ Backend .env file exists"
|
||||||
else
|
else
|
||||||
echo "WARNING: Backend .env file not found"
|
echo "WARNING: Backend .env file not found"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
API_PORT=$(grep -E '^API_PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || true)
|
API_PORT=\$(grep -E '^API_PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || true)
|
||||||
API_PORT=${API_PORT:-8080}
|
API_PORT=\${API_PORT:-8080}
|
||||||
|
|
||||||
check_health() {
|
|
||||||
NAME=$1
|
|
||||||
URL=$2
|
|
||||||
for i in 1 2 3 4 5 6; do
|
for i in 1 2 3 4 5 6; do
|
||||||
if curl -sf "$URL" > /dev/null; then
|
if curl -sf "http://127.0.0.1:\${API_PORT}/health" > /dev/null; then
|
||||||
echo "✓ ${NAME} health check passed (${URL})"
|
echo "✓ backend health check passed (http://127.0.0.1:\${API_PORT}/health)"
|
||||||
return 0
|
echo "✓ Deployed backend IMAGE_TAG=${IMAGE_TAG}"
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
echo "Waiting for ${NAME}... attempt ${i}/6"
|
echo "Waiting for backend... attempt \${i}/6"
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
echo "ERROR: ${NAME} health check failed (${URL})"
|
|
||||||
docker compose -f docker-compose.yml logs --tail=50 "${NAME}" || true
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
check_health "backend" "http://127.0.0.1:${API_PORT}/health"
|
echo "ERROR: backend health check failed (http://127.0.0.1:\${API_PORT}/health)"
|
||||||
|
docker compose -f docker-compose.yml logs --tail=50 backend || true
|
||||||
|
exit 1
|
||||||
|
EOF
|
||||||
|
|||||||
Reference in New Issue
Block a user