name: Deploy to Production # Manual production deploy — pulls pre-built images from the registry on: workflow_dispatch: inputs: image_tag: description: "Docker image tag to deploy (commit SHA or version tag, e.g. v1.0.0)" required: true default: "latest" type: string env: FRONTEND_IMAGE: git.koppkb.com/kopkb/my-kopkb-fe jobs: deploy: runs-on: mykopkb-production steps: - name: Checkout repository uses: actions/checkout@v4 with: ref: ${{ inputs.image_tag }} - name: Verify image exists in registry run: | set -e echo "${{ secrets.REGISTRY_PASSWORD }}" | \ docker login git.koppkb.com \ -u "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin IMAGE="${FRONTEND_IMAGE}:${{ inputs.image_tag }}" echo "Checking if image exists: ${IMAGE}" docker manifest inspect "${IMAGE}" > /dev/null echo "✓ ${IMAGE} found" - 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: | set -euo pipefail if ! command -v ssh >/dev/null 2>&1; then apt-get update && apt-get install -y openssh-client fi mkdir -p ~/.ssh 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="${FRONTEND_IMAGE}:${IMAGE_TAG}" "${SSH[@]}" "mkdir -p '${PROD_DEPLOY_DIR}'" scp -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes \ docker/docker-compose.production.yml \ "${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_DIR}/docker-compose.frontend.yml" "${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 frontend IMAGE=${IMAGE}" 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}" docker rm -f mykopkb-fe-production || true docker compose -f docker-compose.frontend.yml -p mykopkb-fe pull docker compose -f docker-compose.frontend.yml -p mykopkb-fe up -d sleep 10 STATUS=\$(docker compose -f docker-compose.frontend.yml -p mykopkb-fe ps --status running --format '{{.Name}}' frontend || true) if [ -z "\$STATUS" ]; then docker compose -f docker-compose.frontend.yml -p mykopkb-fe logs --tail=50 frontend || true exit 1 fi FE_PORT=\$(grep -E '^FE_PORT=' .env 2>/dev/null | cut -d= -f2- | tr -d '"' || true) FE_PORT=\${FE_PORT:-8081} curl -sf "http://127.0.0.1:\${FE_PORT}/health" echo "✓ Deployed frontend IMAGE_TAG=${IMAGE_TAG}" EOF