name: Build And Publish Production Image on: push: branches: - main jobs: build: name: Build And Publish Production Image runs-on: ubuntu-latest env: REGISTRY: gitea.lab:80 IMAGE_NAME: sancho41/condado-newsletter REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} steps: - uses: actions/checkout@v4 with: github-server-url: http://gitea.lab - name: Verify Docker CLI run: docker version - name: Log in to Docker Hub (optional) if: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }} run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login docker.io -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Build all-in-one image run: docker build -t sancho41/condado-newsletter:latest -f Dockerfile.allinone . - name: Deploy stack via Portainer API env: STACK_NAME: codado-newsletter-stack PORTAINER_URL: http://portainer.lab/ PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }} PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }} run: | set -u set +e PORTAINER_BASE_URL=$(printf '%s' "${PORTAINER_URL}" | sed -E 's/[[:space:]]+$//; s#/*$##') echo "Portainer deploy debug" echo "PORTAINER_URL=${PORTAINER_URL}" echo "PORTAINER_BASE_URL=${PORTAINER_BASE_URL}" echo "STACK_NAME=${STACK_NAME}" echo "PORTAINER_ENDPOINT_ID=${PORTAINER_ENDPOINT_ID}" echo "HTTP_PROXY=${HTTP_PROXY:-}" echo "HTTPS_PROXY=${HTTPS_PROXY:-}" echo "NO_PROXY=${NO_PROXY:-}" echo "Current runner network info:" if command -v ip >/dev/null 2>&1; then ip -4 addr show || true ip route || true else hostname -I || true fi PORTAINER_HOST=$(printf '%s' "${PORTAINER_BASE_URL}" | sed -E 's#^[a-zA-Z]+://##; s#/.*$##; s/:.*$//') echo "Resolved host target: ${PORTAINER_HOST}" PORTAINER_IP="" ACTIVE_PORTAINER_BASE_URL="${PORTAINER_BASE_URL}" if command -v getent >/dev/null 2>&1; then echo "Host lookup (getent):" getent hosts "${PORTAINER_HOST}" || true PORTAINER_IP=$(getent hosts "${PORTAINER_HOST}" | awk 'NR==1{print $1}') if [ -n "${PORTAINER_IP}" ]; then PORTAINER_IP_BASE_URL="${PORTAINER_BASE_URL/${PORTAINER_HOST}/${PORTAINER_IP}}" echo "Portainer IP fallback URL: ${PORTAINER_IP_BASE_URL}" fi fi STACKS_BODY=$(mktemp) STACKS_ERR=$(mktemp) STACKS_HTTP_CODE=$(curl -sS \ --noproxy "*" \ -o "${STACKS_BODY}" \ -w "%{http_code}" \ "${ACTIVE_PORTAINER_BASE_URL}/api/stacks" \ -H "X-API-Key: ${PORTAINER_API_KEY}" \ 2>"${STACKS_ERR}") STACKS_CURL_EXIT=$? if [ "${STACKS_CURL_EXIT}" -eq 6 ] && [ -n "${PORTAINER_IP:-}" ]; then echo "Retrying GET /api/stacks with IP fallback due to DNS failure" STACKS_HTTP_CODE=$(curl -sS \ --noproxy "*" \ -o "${STACKS_BODY}" \ -w "%{http_code}" \ "${PORTAINER_IP_BASE_URL}/api/stacks" \ -H "X-API-Key: ${PORTAINER_API_KEY}" \ 2>"${STACKS_ERR}") STACKS_CURL_EXIT=$? if [ "${STACKS_CURL_EXIT}" -eq 0 ]; then ACTIVE_PORTAINER_BASE_URL="${PORTAINER_IP_BASE_URL}" fi fi echo "GET /api/stacks curl exit: ${STACKS_CURL_EXIT}" echo "GET /api/stacks http code: ${STACKS_HTTP_CODE}" echo "GET /api/stacks stderr:" cat "${STACKS_ERR}" || true echo "GET /api/stacks response (sanitized):" jq -r '.[] | "Id=\(.Id) Name=\(.Name) EndpointId=\(.EndpointId)"' "${STACKS_BODY}" || true if [ "${STACKS_CURL_EXIT}" -ne 0 ]; then echo "Failed to reach Portainer API while listing stacks." exit "${STACKS_CURL_EXIT}" fi if [ "${STACKS_HTTP_CODE}" -lt 200 ] || [ "${STACKS_HTTP_CODE}" -ge 300 ]; then echo "Portainer returned a non-success status for stack listing." exit 1 fi STACK_ID=$(jq -r --arg stack_name "${STACK_NAME}" '.[] | select(.Name == $stack_name) | .Id' "${STACKS_BODY}" | head -n 1) APPLY_BODY=$(mktemp) APPLY_ERR=$(mktemp) if [ -n "${STACK_ID}" ]; then echo "Existing stack found with id=${STACK_ID}; sending update request" PAYLOAD=$(jq -n \ --rawfile stack_file docker-compose.prod.yml \ '{StackFileContent: $stack_file, Env: [], Prune: false, PullImage: false}') APPLY_HTTP_CODE=$(curl -sS -X PUT \ --noproxy "*" \ -o "${APPLY_BODY}" \ -w "%{http_code}" \ "${ACTIVE_PORTAINER_BASE_URL}/api/stacks/${STACK_ID}?endpointId=${PORTAINER_ENDPOINT_ID}" \ -H "X-API-Key: ${PORTAINER_API_KEY}" \ -H "Content-Type: application/json" \ -d "${PAYLOAD}" \ 2>"${APPLY_ERR}") APPLY_CURL_EXIT=$? else echo "Stack not found; sending create request" PAYLOAD=$(jq -n \ --arg name "${STACK_NAME}" \ --rawfile stack_file docker-compose.prod.yml \ '{Name: $name, StackFileContent: $stack_file, Env: [], FromAppTemplate: false}') APPLY_HTTP_CODE=$(curl -sS -X POST \ --noproxy "*" \ -o "${APPLY_BODY}" \ -w "%{http_code}" \ "${ACTIVE_PORTAINER_BASE_URL}/api/stacks/create/standalone/string?endpointId=${PORTAINER_ENDPOINT_ID}" \ -H "X-API-Key: ${PORTAINER_API_KEY}" \ -H "Content-Type: application/json" \ -d "${PAYLOAD}" \ 2>"${APPLY_ERR}") APPLY_CURL_EXIT=$? fi echo "Apply curl exit: ${APPLY_CURL_EXIT}" echo "Apply http code: ${APPLY_HTTP_CODE}" echo "Apply stderr:" cat "${APPLY_ERR}" || true echo "Apply response body:" cat "${APPLY_BODY}" || true if [ "${APPLY_CURL_EXIT}" -ne 0 ]; then echo "Failed to reach Portainer API while applying stack changes." exit "${APPLY_CURL_EXIT}" fi if [ "${APPLY_HTTP_CODE}" -lt 200 ] || [ "${APPLY_HTTP_CODE}" -ge 300 ]; then echo "Portainer returned a non-success status while applying stack changes." exit 1 fi echo "Portainer deploy step completed successfully"