name: Deploy Environment Monitoring to Production Environment on: push: branches: - prod-env concurrency: group: prod-monitoring-deploy cancel-in-progress: false jobs: deploy: runs-on: prod-runner steps: - name: Checkout Branch uses: actions/checkout@v4 - name: Connect Runner to Overlay Network run: | docker network connect iklimco-net $(hostname) || true - name: Install Required Tools run: | sudo sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.hetzner.com/ubuntu/packages|g' /etc/apt/sources.list.d/ubuntu.sources || true sudo sed -i 's|http://archive.ubuntu.com/ubuntu|http://mirror.hetzner.com/ubuntu/packages|g' /etc/apt/sources.list || true sudo sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.hetzner.com/ubuntu/packages|g' /etc/apt/sources.list.d/ubuntu.sources || true sudo sed -i 's|http://security.ubuntu.com/ubuntu|http://mirror.hetzner.com/ubuntu/packages|g' /etc/apt/sources.list || true sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list sudo rm -f /etc/apt/sources.list.d/git-core-ubuntu-ppa*.list sudo rm -f /etc/apt/sources.list.d/github_git-lfs.list sudo apt-get update sudo apt-get install -y gettext jq - name: Set up SSH Key and Add to known_hosts run: | mkdir -p ~/.ssh echo "${{ secrets.STORAGEBOX_SSH_PRIV }}" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -p 23 ${{ vars.STORAGEBOX_USER }}.your-storagebox.de >> ~/.ssh/known_hosts - name: Download Deploy Inputs run: | source ./common-functions-base.sh export SPRING_PROFILES_ACTIVE=PROD rm -f .env .env.secrets.swag health-agent/.env health-agent/.env.setup scp -P 23 ${{ vars.STORAGEBOX_USER }}@${{ vars.STORAGEBOX_USER }}.your-storagebox.de:prod/secrets/iklim.co/.env ./.env scp -P 23 ${{ vars.STORAGEBOX_USER }}@${{ vars.STORAGEBOX_USER }}.your-storagebox.de:prod/secrets/iklim.co/.env.secrets.swag ./.env.secrets.swag scp -P 23 ${{ vars.STORAGEBOX_USER }}@${{ vars.STORAGEBOX_USER }}.your-storagebox.de:prod/secrets/iklim.co/.env.monitoring.health-agent-runtime ./health-agent/.env scp -P 23 ${{ vars.STORAGEBOX_USER }}@${{ vars.STORAGEBOX_USER }}.your-storagebox.de:prod/secrets/iklim.co/.env.monitoring.health-agent-setup ./health-agent/.env.setup require_env_file ./.env "Main env file" require_env_file ./.env.secrets.swag "SWAG secrets" require_env_file ./health-agent/.env "Health-agent runtime env" require_env_file ./health-agent/.env.setup "Health-agent setup env" - name: Promote Health Agent Image run: | source ./common-functions-base.sh export SPRING_PROFILES_ACTIVE=PROD source_env_file ./health-agent/deploy/prod.env if [ -z "${SOURCE_IMAGE_DIGEST:-}" ] || [ -z "${PROD_IMAGE_TAG:-}" ]; then log_message "INFO" "health-agent/deploy/prod.env is empty — skipping health-agent promotion" exit 0 fi case "$SOURCE_IMAGE_DIGEST" in registry.tarla.io/iklimco/health-agent@sha256:*) ;; *) log_message "ERROR" "SOURCE_IMAGE_DIGEST must be registry.tarla.io/iklimco/health-agent@sha256:"; exit 1 ;; esac case "$PROD_IMAGE_TAG" in *-rc*) log_message "ERROR" "PROD_IMAGE_TAG must not contain -rc"; exit 1 ;; esac PROD_IMAGE="registry.tarla.io/iklimco/health-agent:${PROD_IMAGE_TAG}" echo "${{ secrets.HARBOR_CI_TOKEN }}" | \ docker login registry.tarla.io -u robot-ci-push-iklimco --password-stdin docker pull "${SOURCE_IMAGE_DIGEST}" docker tag "${SOURCE_IMAGE_DIGEST}" "${PROD_IMAGE}" docker push "${PROD_IMAGE}" if grep -q "^IMAGE_HEALTH_AGENT=" .env; then sed -i "s|^IMAGE_HEALTH_AGENT=.*$|IMAGE_HEALTH_AGENT=health-agent:${PROD_IMAGE_TAG}|" .env else echo "IMAGE_HEALTH_AGENT=health-agent:${PROD_IMAGE_TAG}" >> .env fi echo "HEALTH_AGENT_IMAGE=${PROD_IMAGE}" >> $GITHUB_ENV log_message "SUCCESS" "Promoted: ${PROD_IMAGE}" - name: Run Uptime Kuma Setup run: | source ./common-functions-base.sh export SPRING_PROFILES_ACTIVE=PROD source_env_file ./health-agent/.env mkdir -p "${HEALTH_AGENT_CONFIG_GENERATED_DIR}" if [ ! -s "${HEALTH_AGENT_CONFIG_GENERATED_DIR}/uk_tokens.yml" ]; then docker run --rm \ -v "${HEALTH_AGENT_CONFIG_GENERATED_DIR}:/app/config/generated" \ --env-file "$(pwd)/health-agent/.env" \ --env-file "$(pwd)/health-agent/.env.setup" \ "${HEALTH_AGENT_IMAGE}" \ python scripts/setup_uptime_kuma.py log_message "SUCCESS" "Uptime Kuma setup complete, tokens written to ${HEALTH_AGENT_CONFIG_GENERATED_DIR}" else log_message "INFO" "uk_tokens.yml already exists, skipping Uptime Kuma setup" fi - name: Deploy Monitoring Stack run: | source ./common-functions-base.sh export SPRING_PROFILES_ACTIVE=PROD source_env_file ./.env source_env_file ./health-agent/.env export HEALTH_AGENT_ENV_FILE="$(pwd)/health-agent/.env" echo "${{ secrets.HARBOR_PULL_TOKEN }}" | \ docker login registry.tarla.io -u robot-swarm-pull-iklimco --password-stdin # Remove leftover dozzle_users Docker secret from previous setup docker secret rm dozzle_users 2>/dev/null || true docker stack deploy \ --with-registry-auth \ --resolve-image changed \ -c docker-stack-monitoring.yml \ monitoring - name: Wait for Loki run: | source ./common-functions-base.sh export SPRING_PROFILES_ACTIVE=PROD for i in $(seq 1 36); do REPLICAS=$(docker service ls --filter name=monitoring_loki --format "{{.Replicas}}" | head -1) if echo "$REPLICAS" | awk -F'[/ ]' '$1>0 && $1==$2{found=1} END{exit !found}'; then log_message "SUCCESS" "Loki is ready: $REPLICAS" exit 0 fi log_message "INFO" "Loki not ready yet (${REPLICAS:-missing}), waiting 5s..." sleep 5 done docker service ps monitoring_loki || true exit 1 - name: Configure SWAG Reverse Proxy run: | source ./common-functions-base.sh export SPRING_PROFILES_ACTIVE=PROD source_env_file ./.env source_env_file ./.env.secrets.swag export PORTAINER_SUBDOMAIN="${PORTAINER_SUBDOMAIN:-portainer.iklim.co}" export RESTRICTED_IPS_BLOCK="$(echo "$RESTRICTED_IPS" | tr ',' '\n' | sed 's|.*| allow &;|')" SWAG_VARS='${PORTAINER_SUBDOMAIN}${RESTRICTED_IPS_BLOCK}' for tpl in swag/site-confs/*.conf.tpl; do fname=$(basename "${tpl%.tpl}") envsubst "$SWAG_VARS" < "$tpl" | docker run --rm -i \ -v "${SWAG_SITE_CONFS_DIR}:/output" \ alpine sh -c "cat > /output/${fname}" log_message "SUCCESS" "${fname} written" done SWAG_CTR=$(docker ps -q -f name=iklimco_swag 2>/dev/null | head -1) if [ -n "$SWAG_CTR" ]; then docker exec "$SWAG_CTR" nginx -t && docker exec "$SWAG_CTR" nginx -s reload log_message "SUCCESS" "SWAG nginx reloaded" fi - name: Update DNS Records run: | source ./common-functions-base.sh export SPRING_PROFILES_ACTIVE=PROD source_env_file ./.env source_env_file ./.env.secrets.swag FLOATING_IP="${{ vars.PROD_FLOATING_IP }}" DOMAIN="iklim.co" for record in portainer; do CURRENT=$(curl -s \ -H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \ "https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${record}" \ 2>/dev/null | jq -r '.[0].data // empty' 2>/dev/null || true) if [ "$CURRENT" = "$FLOATING_IP" ]; then log_message "INFO" "${record}.${DOMAIN} -> ${FLOATING_IP} exists, skipping" else curl -sf -X PUT \ -H "Authorization: sso-key ${GODADDY_KEY}:${GODADDY_SECRET}" \ -H "Content-Type: application/json" \ "https://api.godaddy.com/v1/domains/${DOMAIN}/records/A/${record}" \ -d "[{\"data\":\"${FLOATING_IP}\",\"ttl\":600}]" log_message "SUCCESS" "${record}.${DOMAIN} -> ${FLOATING_IP} added/updated" fi done - name: Verify Deployment run: | docker service ps monitoring_loki \ --filter "desired-state=running" \ --format "table {{.Name}}\t{{.Node}}\t{{.CurrentState}}\t{{.Image}}" | head -20