diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 594d416..56fef76 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,30 +1,31 @@ -name: CI +name: test-build-publish on: - pull_request: push: branches: - main tags: - 'v*' - -env: - REGISTRY: gitea.reversed.dev - IMAGE_NAME: space/evil-wordle + workflow_dispatch: jobs: - test: - name: Lint and build app + docker: runs-on: ubuntu-latest - + container: + image: catthehacker/ubuntu:act-latest + env: + RUNNER_TOOL_CACHE: /toolcache + PACKAGE_OWNER: space + PACKAGE_NAME: evil-wordle + REPO_NAME: evil-wordle steps: - - name: Check out repository + - name: Checkout uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 22 + node-version: '22' cache: npm - name: Install dependencies @@ -36,61 +37,94 @@ jobs: - name: Build app run: npm run build - - name: Install Docker if missing - run: | - if ! command -v docker >/dev/null 2>&1; then - curl -fsSL https://get.docker.com | sh - fi - docker --version - docker compose version - - name: Validate compose file run: docker compose config - publish-image: - name: Publish container image - runs-on: ubuntu-latest - needs: test - if: ${{ github.event_name == 'push' }} - - steps: - - name: Check out repository - uses: actions/checkout@v4 - - - name: Install and start Docker if missing - run: | - if ! command -v docker >/dev/null 2>&1; then - curl -fsSL https://get.docker.com | sh - fi - - if ! docker info >/dev/null 2>&1; then - if command -v service >/dev/null 2>&1; then - service docker start || true - fi - - if ! docker info >/dev/null 2>&1; then - nohup dockerd >/tmp/dockerd.log 2>&1 & - fi - - timeout 60 sh -c 'until docker info >/dev/null 2>&1; do sleep 2; done' - fi - - docker --version - docker compose version + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Log in to Gitea registry - run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY" -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin + uses: docker/login-action@v3 + with: + registry: gitea.reversed.dev + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} - - name: Build image + - name: Compute image tags + id: meta + shell: bash run: | - SHORT_SHA="${GITHUB_SHA::12}" - docker build \ - --tag "$REGISTRY/$IMAGE_NAME:latest" \ - --tag "$REGISTRY/$IMAGE_NAME:$SHORT_SHA" \ - . + set -euo pipefail + short_sha="${GITHUB_SHA::7}" + tags="${{ secrets.REGISTRY_IMAGE }}:${short_sha}" + if [ "${GITHUB_REF_NAME}" = "main" ]; then + tags="${tags}\n${{ secrets.REGISTRY_IMAGE }}:latest" + fi + if [[ "${GITHUB_REF_TYPE}" = "tag" ]]; then + clean_tag="${GITHUB_REF_NAME#v}" + tags="${tags}\n${{ secrets.REGISTRY_IMAGE }}:${clean_tag}" + fi + { + echo 'tags<> "$GITHUB_OUTPUT" - - name: Push image + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + + - name: Link package to repository + shell: bash run: | - SHORT_SHA="${GITHUB_SHA::12}" - docker push "$REGISTRY/$IMAGE_NAME:latest" - docker push "$REGISTRY/$IMAGE_NAME:$SHORT_SHA" + set -euo pipefail + python3 - <<'PY' + import json + import os + import sys + import urllib.error + import urllib.request + + owner = os.environ['PACKAGE_OWNER'] + package = os.environ['PACKAGE_NAME'] + repo = os.environ['REPO_NAME'] + token = os.environ['REGISTRY_PASSWORD'] + base = 'https://gitea.reversed.dev/api/v1' + headers = { + 'Authorization': f'token {token}', + 'Accept': 'application/json', + } + + req = urllib.request.Request( + f'{base}/packages/{owner}/container/{package}/-/latest', + headers=headers, + ) + with urllib.request.urlopen(req) as resp: + current = json.load(resp) + + linked_repo = (current.get('repository') or {}).get('name') + if linked_repo == repo: + print(f'package already linked to {owner}/{repo}') + sys.exit(0) + + link_req = urllib.request.Request( + f'{base}/packages/{owner}/container/{package}/-/link/{repo}', + data=b'', + method='POST', + headers=headers, + ) + try: + with urllib.request.urlopen(link_req) as resp: + print(f'linked package to {owner}/{repo}, status={resp.status}') + except urllib.error.HTTPError as exc: + body = exc.read().decode(errors='replace') + print(f'link failed: status={exc.code} body={body}') + raise + PY + env: + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} diff --git a/README.md b/README.md index 395a1e4..ed654f3 100644 --- a/README.md +++ b/README.md @@ -37,15 +37,18 @@ Gitea Actions workflow: `.gitea/workflows/ci.yml` Required repository or organization secrets: - `REGISTRY_USERNAME`: Gitea username allowed to publish packages -- `REGISTRY_TOKEN`: Gitea personal access token with package read/write access +- `REGISTRY_PASSWORD`: Gitea personal access token with package read/write access +- `REGISTRY_IMAGE`: full image name, for example `gitea.reversed.dev/space/evil-wordle` -The workflow installs Docker if it is missing. If your Gitea runner is itself containerized, it still needs either privileged mode for Docker-in-Docker or a mounted host Docker socket. +The workflow uses `catthehacker/ubuntu:act-latest`, Docker Buildx, and links the published package back to the `space/evil-wordle` repository through the Gitea API. On pushes to `main`, CI publishes: ```bash gitea.reversed.dev/space/evil-wordle:latest -gitea.reversed.dev/space/evil-wordle: +gitea.reversed.dev/space/evil-wordle: ``` +Tags like `v1.2.3` also publish `gitea.reversed.dev/space/evil-wordle:1.2.3`. + The app is built with React, Tailwind CSS, and Vite. Progress, settings, and stats are stored in `localStorage`. Guess validation uses `word-list-json`; curated local word buckets in `src/data/words.ts` control target selection and provide the fallback pool.