From 85767c836d35269caa0e07e64ddc439ecf51066e Mon Sep 17 00:00:00 2001 From: Alex Getman <216438466+alexgetmancom@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:42:45 +0300 Subject: [PATCH] Add immutable container delivery --- .github/workflows/cd.yml | 110 +++++++++++++++++++++++++++++++++++++++ compose.vm106.yaml | 5 +- 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/cd.yml diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..bb43cef --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,110 @@ +name: CD + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + packages: write + +env: + IMAGE_NAME: ghcr.io/alexgetmancom/miband-bot + +jobs: + publish: + runs-on: ubuntu-latest + outputs: + image: ${{ steps.image.outputs.image }} + steps: + - uses: actions/checkout@v4 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + - uses: docker/setup-buildx-action@v3 + - name: Build and publish image + id: build + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile.bun + platforms: linux/amd64 + push: true + tags: ${{ env.IMAGE_NAME }}:${{ github.sha }} + - name: Select immutable image + id: image + env: + DIGEST: ${{ steps.build.outputs.digest }} + run: echo "image=${IMAGE_NAME}@${DIGEST}" >> "$GITHUB_OUTPUT" + + deploy: + needs: publish + if: vars.DEPLOY_ENABLED == 'true' + runs-on: ubuntu-latest + env: + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_PORT: ${{ vars.DEPLOY_PORT || '22' }} + DEPLOY_SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_PRIVATE_KEY }} + DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }} + GHCR_TOKEN: ${{ github.token }} + IMAGE: ${{ needs.publish.outputs.image }} + steps: + - uses: actions/checkout@v4 + - name: Configure SSH + run: | + set -euo pipefail + mkdir -p ~/.ssh + printf '%s\n' "$DEPLOY_SSH_PRIVATE_KEY" > ~/.ssh/deploy_key + printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts + chmod 600 ~/.ssh/deploy_key ~/.ssh/known_hosts + - name: Upload production Compose + run: | + set -euo pipefail + scp -P "$DEPLOY_PORT" -i ~/.ssh/deploy_key \ + compose.vm106.yaml \ + "$DEPLOY_USER@$DEPLOY_HOST:/opt/miband-tracker/compose.vm106.yaml.next" + - name: Log in to GHCR on the server + run: | + set -euo pipefail + printf '%s' "$GHCR_TOKEN" | ssh -p "$DEPLOY_PORT" -i ~/.ssh/deploy_key \ + "$DEPLOY_USER@$DEPLOY_HOST" \ + "docker login ghcr.io --username ${{ github.actor }} --password-stdin" + - name: Activate image and verify readiness + run: | + set -euo pipefail + ssh -p "$DEPLOY_PORT" -i ~/.ssh/deploy_key \ + "DEPLOY_IMAGE='$IMAGE' bash -s" <<'REMOTE' + set -euo pipefail + cd /opt/miband-tracker + touch deploy-image.env + previous_image="$(sed -n 's/^MIBAND_IMAGE=//p' deploy-image.env || true)" + mv compose.vm106.yaml.next compose.vm106.yaml + printf 'MIBAND_IMAGE=%s\n' "$DEPLOY_IMAGE" > deploy-image.env.next + mv deploy-image.env.next deploy-image.env + compose=(docker compose --env-file deploy-image.env -f compose.vm106.yaml) + "${compose[@]}" config --quiet + "${compose[@]}" pull + "${compose[@]}" up -d + + ready=0 + for attempt in $(seq 1 30); do + if curl --fail --silent http://127.0.0.1:18080/readyz >/dev/null; then + ready=1 + break + fi + sleep 2 + done + + if [ "$ready" -ne 1 ]; then + if [ -n "$previous_image" ]; then + printf 'MIBAND_IMAGE=%s\n' "$previous_image" > deploy-image.env + "${compose[@]}" up -d + fi + exit 1 + fi + docker logout ghcr.io >/dev/null 2>&1 || true + REMOTE diff --git a/compose.vm106.yaml b/compose.vm106.yaml index a3b977d..69b3c7c 100644 --- a/compose.vm106.yaml +++ b/compose.vm106.yaml @@ -1,9 +1,8 @@ services: app: container_name: miband-bot-ts - build: - context: . - dockerfile: Dockerfile.bun + image: ${MIBAND_IMAGE:?set MIBAND_IMAGE} + pull_policy: always restart: unless-stopped env_file: - /opt/miband-tracker/secrets.env