name: Build and Push Image on: push: branches: - main workflow_dispatch: env: REGISTRY_HOST: registry.campbellwireless.net IMAGE_NAME: ${{ github.repository }} jobs: docker-build-and-push: runs-on: ubuntu-latest steps: - name: Checkout env: REPO_URL: https://cloud.campbellwireless.net/git/${{ github.repository }}.git run: | set -eux git init . git remote add origin "$REPO_URL" auth="$(printf '%s' '${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}' | base64 | tr -d '\n')" git config --local http.https://cloud.campbellwireless.net/.extraheader "AUTHORIZATION: basic $auth" git fetch --prune --no-recurse-submodules origin +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/* git checkout --detach "${{ github.sha }}" - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Gitea registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY_HOST }} username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: Compute image tags id: tags run: | echo "sha_short=$(echo '${{ github.sha }}' | cut -c1-12)" >> "$GITHUB_OUTPUT" - name: Build and push image uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile push: true tags: | ${{ env.REGISTRY_HOST }}/${{ env.IMAGE_NAME }}:latest ${{ env.REGISTRY_HOST }}/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.sha_short }} - name: Preflight Portainer deploy config env: PORTAINER_URL: ${{ secrets.PORTAINER_URL }} PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }} PORTAINER_STACK_ID: ${{ secrets.PORTAINER_STACK_ID }} PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }} PORTAINER_INSECURE_TLS: ${{ secrets.PORTAINER_INSECURE_TLS }} run: | set -eu for required in PORTAINER_URL PORTAINER_API_KEY PORTAINER_STACK_ID PORTAINER_ENDPOINT_ID; do if [ -z "$(eval "printf '%s' \"\${$required:-}\"")" ]; then echo "Missing required secret: $required" >&2 exit 1 fi done CURL_ARGS=(--fail --show-error --silent --retry 3 --retry-all-errors) if [ "${PORTAINER_INSECURE_TLS:-false}" = "true" ]; then CURL_ARGS+=(--insecure) fi API_BASE="${PORTAINER_URL%/}/api" ENDPOINT_JSON="$( curl "${CURL_ARGS[@]}" \ --header "X-API-Key: ${PORTAINER_API_KEY}" \ "${API_BASE}/endpoints/${PORTAINER_ENDPOINT_ID}" )" echo "${ENDPOINT_JSON}" | jq -e --arg id "${PORTAINER_ENDPOINT_ID}" \ '((.Id // .id) | tostring) == $id' >/dev/null STACK_JSON="$( curl "${CURL_ARGS[@]}" \ --header "X-API-Key: ${PORTAINER_API_KEY}" \ "${API_BASE}/stacks/${PORTAINER_STACK_ID}?endpointId=${PORTAINER_ENDPOINT_ID}" )" echo "${STACK_JSON}" | jq -e --arg id "${PORTAINER_STACK_ID}" \ '((.Id // .id) | tostring) == $id' >/dev/null echo "Portainer preflight checks passed." - name: Trigger Portainer stack redeploy env: PORTAINER_URL: ${{ secrets.PORTAINER_URL }} PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }} PORTAINER_STACK_ID: ${{ secrets.PORTAINER_STACK_ID }} PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }} PORTAINER_INSECURE_TLS: ${{ secrets.PORTAINER_INSECURE_TLS }} run: | set -eu for required in PORTAINER_URL PORTAINER_API_KEY PORTAINER_STACK_ID PORTAINER_ENDPOINT_ID; do if [ -z "$(eval "printf '%s' \"\${$required:-}\"")" ]; then echo "Missing required secret: $required" >&2 exit 1 fi done CURL_ARGS=(--fail --show-error --silent --retry 3 --retry-all-errors) if [ "${PORTAINER_INSECURE_TLS:-false}" = "true" ]; then CURL_ARGS+=(--insecure) fi STACK_BASE="${PORTAINER_URL%/}/api/stacks/${PORTAINER_STACK_ID}" QUERY="endpointId=${PORTAINER_ENDPOINT_ID}" # Git-based stacks can be redeployed directly. if curl "${CURL_ARGS[@]}" \ --header "X-API-Key: ${PORTAINER_API_KEY}" \ --request POST \ "${STACK_BASE}/git/redeploy?${QUERY}" >/dev/null; then echo "Portainer deploy: git stack redeploy triggered." exit 0 fi # Non-git stacks: fetch current stack file and redeploy with pullImage=true. STACK_FILE_CONTENT="$( curl "${CURL_ARGS[@]}" \ --header "X-API-Key: ${PORTAINER_API_KEY}" \ "${STACK_BASE}/file" \ | jq -r '.StackFileContent' )" PAYLOAD_LOWER="$(jq -cn --arg stackFileContent "${STACK_FILE_CONTENT}" \ '{stackFileContent: $stackFileContent, prune: false, pullImage: true}')" if curl "${CURL_ARGS[@]}" \ --header "X-API-Key: ${PORTAINER_API_KEY}" \ --header "Content-Type: application/json" \ --request PUT \ --data "${PAYLOAD_LOWER}" \ "${STACK_BASE}?${QUERY}" >/dev/null; then echo "Portainer deploy: stack updated with lower-camel payload." exit 0 fi PAYLOAD_UPPER="$(jq -cn --arg StackFileContent "${STACK_FILE_CONTENT}" \ '{StackFileContent: $StackFileContent, Prune: false, PullImage: true}')" curl "${CURL_ARGS[@]}" \ --header "X-API-Key: ${PORTAINER_API_KEY}" \ --header "Content-Type: application/json" \ --request PUT \ --data "${PAYLOAD_UPPER}" \ "${STACK_BASE}?${QUERY}" >/dev/null echo "Portainer deploy: stack updated with upper-camel payload."