feat: add Gitea registry login and update Docker image build process
Some checks failed
Build And Publish Production Image / Build And Publish Production Image (push) Failing after 1m2s
Some checks failed
Build And Publish Production Image / Build And Publish Production Image (push) Failing after 1m2s
This commit is contained in:
@@ -26,155 +26,15 @@ jobs:
|
|||||||
if: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }}
|
if: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }}
|
||||||
run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login docker.io -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
|
run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login docker.io -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
|
||||||
|
|
||||||
|
- name: Log in to Gitea registry
|
||||||
|
run: echo "${REGISTRY_PASSWORD}" | docker login "${REGISTRY}" -u "${REGISTRY_USERNAME}" --password-stdin
|
||||||
|
|
||||||
- name: Build all-in-one image
|
- 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: |
|
run: |
|
||||||
set -u
|
docker build -t "${REGISTRY}/${IMAGE_NAME}:latest" -f Dockerfile.allinone .
|
||||||
set +e
|
docker tag "${REGISTRY}/${IMAGE_NAME}:latest" "${REGISTRY}/${IMAGE_NAME}:${{ gitea.sha }}"
|
||||||
|
|
||||||
PORTAINER_BASE_URL=$(printf '%s' "${PORTAINER_URL}" | sed -E 's/[[:space:]]+$//; s#/*$##')
|
- name: Push image tags
|
||||||
|
run: |
|
||||||
echo "Portainer deploy debug"
|
docker push "${REGISTRY}/${IMAGE_NAME}:latest"
|
||||||
echo "PORTAINER_URL=${PORTAINER_URL}"
|
docker push "${REGISTRY}/${IMAGE_NAME}:${{ gitea.sha }}"
|
||||||
echo "PORTAINER_BASE_URL=${PORTAINER_BASE_URL}"
|
|
||||||
echo "STACK_NAME=${STACK_NAME}"
|
|
||||||
echo "PORTAINER_ENDPOINT_ID=${PORTAINER_ENDPOINT_ID}"
|
|
||||||
echo "HTTP_PROXY=${HTTP_PROXY:-<empty>}"
|
|
||||||
echo "HTTPS_PROXY=${HTTPS_PROXY:-<empty>}"
|
|
||||||
echo "NO_PROXY=${NO_PROXY:-<empty>}"
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|||||||
149
.gitea/workflows/deploy.yml
Normal file
149
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
name: Deploy Production Stack
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Build And Publish Production Image"]
|
||||||
|
types: [completed]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy Stack Via Portainer
|
||||||
|
if: ${{ gitea.event_name == 'workflow_dispatch' || gitea.event.workflow_run.conclusion == 'success' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
STACK_NAME: condado-newsletter-stack
|
||||||
|
PORTAINER_URL: http://portainer.lab/
|
||||||
|
PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }}
|
||||||
|
PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }}
|
||||||
|
ENV_VARS: ${{ secrets.ENV_VARS }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
github-server-url: http://gitea.lab
|
||||||
|
|
||||||
|
- name: Validate ENV_VARS secret
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
if [ -z "${ENV_VARS}" ]; then
|
||||||
|
echo "ENV_VARS secret is empty."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Deploy stack via Portainer API
|
||||||
|
run: |
|
||||||
|
set -u
|
||||||
|
set +e
|
||||||
|
|
||||||
|
PORTAINER_BASE_URL=$(printf '%s' "${PORTAINER_URL}" | sed -E 's/[[:space:]]+$//; s#/*$##')
|
||||||
|
|
||||||
|
ENV_JSON=$(printf '%s\n' "${ENV_VARS}" | jq -R -s '
|
||||||
|
split("\n")
|
||||||
|
| map(gsub("\r$"; ""))
|
||||||
|
| map(select(length > 0))
|
||||||
|
| map(select(startswith("#") | not))
|
||||||
|
| map(select(test("^[A-Za-z_][A-Za-z0-9_]*=.*$")))
|
||||||
|
| map(capture("^(?<name>[A-Za-z_][A-Za-z0-9_]*)=(?<value>.*)$"))
|
||||||
|
| map({name: .name, value: .value})
|
||||||
|
')
|
||||||
|
|
||||||
|
echo "Loaded $(printf '%s' "${ENV_JSON}" | jq 'length') env entries from ENV_VARS"
|
||||||
|
|
||||||
|
PORTAINER_HOST=$(printf '%s' "${PORTAINER_BASE_URL}" | sed -E 's#^[a-zA-Z]+://##; s#/.*$##; s/:.*$//')
|
||||||
|
PORTAINER_IP=""
|
||||||
|
ACTIVE_PORTAINER_BASE_URL="${PORTAINER_BASE_URL}"
|
||||||
|
|
||||||
|
if command -v getent >/dev/null 2>&1; then
|
||||||
|
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}}"
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
if [ "${STACKS_CURL_EXIT}" -ne 0 ]; then
|
||||||
|
cat "${STACKS_ERR}" || true
|
||||||
|
exit "${STACKS_CURL_EXIT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${STACKS_HTTP_CODE}" -lt 200 ] || [ "${STACKS_HTTP_CODE}" -ge 300 ]; then
|
||||||
|
cat "${STACKS_BODY}" || true
|
||||||
|
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
|
||||||
|
PAYLOAD=$(jq -n \
|
||||||
|
--rawfile stack_file docker-compose.prod.yml \
|
||||||
|
--argjson env_vars "${ENV_JSON}" \
|
||||||
|
'{StackFileContent: $stack_file, Env: $env_vars, Prune: false, PullImage: true}')
|
||||||
|
|
||||||
|
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
|
||||||
|
PAYLOAD=$(jq -n \
|
||||||
|
--arg name "${STACK_NAME}" \
|
||||||
|
--rawfile stack_file docker-compose.prod.yml \
|
||||||
|
--argjson env_vars "${ENV_JSON}" \
|
||||||
|
'{Name: $name, StackFileContent: $stack_file, Env: $env_vars, 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
|
||||||
|
|
||||||
|
if [ "${APPLY_CURL_EXIT}" -ne 0 ]; then
|
||||||
|
cat "${APPLY_ERR}" || true
|
||||||
|
exit "${APPLY_CURL_EXIT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${APPLY_HTTP_CODE}" -lt 200 ] || [ "${APPLY_HTTP_CODE}" -ge 300 ]; then
|
||||||
|
cat "${APPLY_BODY}" || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Portainer deploy completed successfully"
|
||||||
@@ -59,7 +59,7 @@ services:
|
|||||||
- "homepage.group=Hyperlink"
|
- "homepage.group=Hyperlink"
|
||||||
- "homepage.name=Condado Newsletter"
|
- "homepage.name=Condado Newsletter"
|
||||||
- "homepage.description=Automated newsletter generator using AI"
|
- "homepage.description=Automated newsletter generator using AI"
|
||||||
- "homepage.logo=claude-dark.png"
|
- "homepage.logo=claude-ai.png"
|
||||||
- "homepage.href=http://condado-newsletter.lab"
|
- "homepage.href=http://condado-newsletter.lab"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Reference in New Issue
Block a user