Files
vending-machine-game/.gitea/workflows/ci-cd.yml
T
RyanandClaude Sonnet 5.5 894e3e25c6
CI/CD / test (push) Failing after 7m19s
CI/CD / publish (push) Skipped
CI/CD / deploy (push) Skipped
Deploy from a Gitea registry image instead of building on the server
CI now builds and pushes the image to the Gitea container registry, links
the package, and the deploy job ships only deploy/docker-compose.yml and an
.env naming the image to /opt/vending-machine-game.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 10:46:57 +03:30

153 lines
6.3 KiB
YAML

# Gitea Actions. While .gitea/workflows exists Gitea ignores .github/workflows (GitHub Pages only).
#
# Every push and pull request: install, test and build.
# Push to main: build the Docker image, push it to the Gitea registry, then have the server pull it.
# The server holds no source code, only deploy/docker-compose.yml and a .env naming the image.
#
# Repository settings → Actions → Secrets:
# REGISTRY_USERNAME Gitea user that publishes images
# REGISTRY_TOKEN its personal access token with package Read and Write
# (the built-in GITEA_TOKEN cannot publish packages)
# DEPLOY_HOST server address (deploy is skipped while this is empty)
# DEPLOY_SSH_KEY private key whose public key is in the server user's ~/.ssh/authorized_keys
# DEPLOY_USER SSH user, default root; it must be able to run docker
# DEPLOY_PORT SSH port, default 22
# DEPLOY_KNOWN_HOSTS optional output of `ssh-keyscan <host>`; otherwise the host key is scanned on each run
# Repository settings → Actions → Variables (optional):
# NPM_REGISTRY npm mirror used by the runner and the server's Docker build when
# registry.npmjs.org is unreachable, e.g. from servers in Iran
# DEPLOY_PATH directory on the server, default /opt/vending-machine-game
# HEALTH_URL URL checked after deploy, e.g. https://robotmarkets.ir/
name: CI/CD
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
# Deploys run one at a time, in push order.
concurrency:
group: ci-cd-${{ github.ref }}
cancel-in-progress: false
# Fail within minutes instead of hanging when the registry is slow or blocked.
env:
NPM_CONFIG_REGISTRY: ${{ vars.NPM_REGISTRY || 'https://registry.npmjs.org/' }}
NPM_CONFIG_FETCH_RETRIES: '3'
NPM_CONFIG_FETCH_TIMEOUT: '60000'
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: node:22-bookworm
steps:
- uses: actions/checkout@v4
- name: Install
run: npm ci --no-audit --no-fund
- name: Test
run: node --test tests/*.test.js
- name: Build
run: npm run build
publish:
needs: test
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
image: ${{ steps.push.outputs.image }}
steps:
- uses: actions/checkout@v4
- name: Build, push and link the image
id: push
shell: bash
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
SOURCE_REPOSITORY: ${{ gitea.repository }}
COMMIT_SHA: ${{ gitea.sha }}
RUN_ID: ${{ gitea.run_id }}
run: |
set -euo pipefail
REGISTRY='git.my-rm.com'
OWNER="${SOURCE_REPOSITORY%%/*}"
REPO="${SOURCE_REPOSITORY#*/}"
IMAGE="$REGISTRY/${OWNER,,}/${REPO,,}:build-$(date -u +%Y%m%d-%H%M%S)-${RUN_ID}-${COMMIT_SHA:0:7}"
# Keep this job's registry credentials away from the host's own logins.
DOCKER_CONFIG="$(mktemp -d)"
export DOCKER_CONFIG
trap 'rm -rf "$DOCKER_CONFIG"' EXIT
printf '%s' "$REGISTRY_TOKEN" | docker login "$REGISTRY" --username "$REGISTRY_USERNAME" --password-stdin
docker build \
--build-arg "NPM_REGISTRY=$NPM_CONFIG_REGISTRY" \
--label "org.opencontainers.image.source=https://$REGISTRY/$SOURCE_REPOSITORY" \
--label "org.opencontainers.image.revision=$COMMIT_SHA" \
--tag "$IMAGE" .
docker push "$IMAGE"
# Show the package under this repository's Packages tab.
curl --fail-with-body --silent --show-error --request POST \
--header "Authorization: token $REGISTRY_TOKEN" \
"https://$REGISTRY/api/v1/packages/$OWNER/container/${REPO,,}/-/link/$REPO"
echo "image=$IMAGE" >> "$GITEA_OUTPUT"
echo "Published and linked: $IMAGE"
deploy:
needs: publish
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: node:22-bookworm
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
DEPLOY_PATH: ${{ vars.DEPLOY_PATH }}
HEALTH_URL: ${{ vars.HEALTH_URL }}
IMAGE: ${{ needs.publish.outputs.image }}
steps:
- uses: actions/checkout@v4
- name: Deploy to the server
run: |
if [ -z "$DEPLOY_HOST" ] || [ -z "$DEPLOY_SSH_KEY" ]; then
echo "DEPLOY_HOST / DEPLOY_SSH_KEY secrets are not set: skipping deploy."
exit 0
fi
port="${DEPLOY_PORT:-22}"
target="${DEPLOY_USER:-root}@${DEPLOY_HOST}"
dir="${DEPLOY_PATH:-/opt/vending-machine-game}"
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key && chmod 600 ~/.ssh/deploy_key
if [ -n "$DEPLOY_KNOWN_HOSTS" ]; then
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > ~/.ssh/known_hosts
else
ssh-keyscan -p "$port" "$DEPLOY_HOST" > ~/.ssh/known_hosts 2>/dev/null
fi
# Only the compose file goes to the server; the app itself comes from the registry image.
# The server user must already be logged in to git.my-rm.com (docker login, package Read token).
ssh_opts="-i $HOME/.ssh/deploy_key -p $port"
ssh $ssh_opts "$target" "mkdir -p '$dir'"
scp -i ~/.ssh/deploy_key -P "$port" deploy/docker-compose.yml "$target:$dir/docker-compose.yml"
ssh $ssh_opts "$target" "cd '$dir' && cp -f .env .env.previous 2>/dev/null; printf 'IMAGE=%s\n' '$IMAGE' > .env && docker compose pull && docker compose up -d --remove-orphans && docker image prune -f"
- name: Check the site
if: env.HEALTH_URL != '' && env.DEPLOY_HOST != ''
run: |
for attempt in $(seq 1 20); do
if curl -fsS -o /dev/null "$HEALTH_URL"; then
echo "Site is up: $HEALTH_URL"
exit 0
fi
sleep 6
done
echo "Site did not answer at $HEALTH_URL" >&2
exit 1