diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6399e39 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +node_modules +dist +dist-check +.git +.env.local +.claude +.agents +.cursor +.gemini +.opencode +.mcp.json diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..07f8dcd --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,80 @@ +name: CI + +on: + push: + branches: [main] + tags: ['v*'] + pull_request: + branches: [main] + workflow_dispatch: + +concurrency: + group: ci-${{ gitea.workflow }}-${{ gitea.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + + - run: npm ci + + # The console goes in front of a DNS server, so a build that fails its own + # checks must not exist. + - run: npm run typecheck + - run: npm run lint + - run: npm run lint:language + - run: npm run lint:colours + - run: npm run lint:dashes + - run: npm test + - run: npm run build + + - name: Routes emitted + run: echo "Routes emitted: $(find dist -name index.html | wc -l)" + + image: + needs: check + if: gitea.event_name != 'pull_request' && (gitea.ref_name == 'main' || startsWith(gitea.ref, 'refs/tags/v')) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: git.shx.one + username: ${{ gitea.actor }} + password: ${{ secrets.ACTIONS_PAT }} + + # Registry names are lowercase. + - name: Resolve image name + id: image + run: | + echo "name=git.shx.one/$(echo '${{ gitea.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ steps.image.outputs.name }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable=${{ gitea.ref_name == 'main' }} + type=sha,prefix=sha-,enable=${{ gitea.ref_name == 'main' }} + + - uses: docker/build-push-action@v5 + with: + context: . + file: docker/Dockerfile.gitea + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=${{ steps.image.outputs.name }}:buildcache + cache-to: type=registry,ref=${{ steps.image.outputs.name }}:buildcache,mode=max diff --git a/docker/Dockerfile.gitea b/docker/Dockerfile.gitea new file mode 100644 index 0000000..9bfee13 --- /dev/null +++ b/docker/Dockerfile.gitea @@ -0,0 +1,20 @@ +# The console image built by Gitea CI (git.shx.one). Same contract as the upstream +# init image: it copies the console into the volume mounted at /target and exits; +# the official DNS server mounts that volume read-only and serves it. +# +# docker run --rm -v technitium-console:/target git.shx.one/denozord/technitium-console:latest +# +# Unlike docker/Dockerfile (which packs a release tarball), this one builds the +# console from the sources in the context. +FROM node:22-bookworm-slim AS build +WORKDIR /src +COPY package.json package-lock.json ./ +RUN npm ci +COPY . . +RUN npm run build + +FROM busybox:stable +COPY --from=build /src/dist /console +# Everything the console ships is replaced on each run, except the administrator's +# own lists (json/*-custom.json), which live in the same folder. +ENTRYPOINT ["/bin/sh", "-c", "test -d /target || { echo 'mount a volume at /target' >&2; exit 1; }; find /target -depth -mindepth 1 ! -name '*-custom.json' -delete 2>/dev/null; cp -a /console/. /target/"]