diff --git a/.gitea/scripts/ci.Dockerfile b/.gitea/scripts/ci.Dockerfile new file mode 100644 index 0000000..15a6f27 --- /dev/null +++ b/.gitea/scripts/ci.Dockerfile @@ -0,0 +1,21 @@ +# CI-only image for the dependency check. +# +# The report is a Node script because it has to compare semantic versions and +# read four different HTTP APIs, and the official `rust` image ships no Node — +# while the `node` image ships no cargo, which `cargo metadata` needs to resolve +# the workspace graph. Rather than maintain the parsing twice in two languages, +# this layers the two together. +# +# Node is copied from the official image rather than installed from a +# third-party apt repository: no extra signing key, no extra network +# dependency at build time, and the same binaries everyone else runs. +ARG RUST_VERSION=1.95 +ARG NODE_VERSION=22-bookworm-slim + +FROM node:${NODE_VERSION} AS node + +FROM rust:${RUST_VERSION}-bookworm +COPY --from=node /usr/local/bin/node /usr/local/bin/node +COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules +RUN ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \ + && node --version diff --git a/.gitea/workflows/dependency-check.yml b/.gitea/workflows/dependency-check.yml index 3dec59e..ff4d733 100644 --- a/.gitea/workflows/dependency-check.yml +++ b/.gitea/workflows/dependency-check.yml @@ -53,8 +53,11 @@ jobs: # The workspace is streamed into the container over stdin rather than # bind-mounted, so this works whether the runner shares a filesystem with - # the daemon or not. The official rust image already carries the - # toolchain, so nothing is downloaded inside. + # the daemon or not. + # + # The image is rust + node, built from .gitea/scripts/ci.Dockerfile: the + # report needs cargo to resolve the workspace graph and Node to read the + # registries, and neither official image has both. # # RUSTUP_TOOLCHAIN overrides rust-toolchain.toml: the image's own # toolchain is enough to read Cargo.lock, and honouring the file would @@ -66,10 +69,17 @@ jobs: - name: Generate report run: | set -eu + VERSION="${{ steps.rust.outputs.version }}" + docker build \ + --build-arg "RUST_VERSION=${VERSION}" \ + --tag "dep-check:${VERSION}" \ + --file .gitea/scripts/ci.Dockerfile \ + .gitea/scripts > /dev/null + tar cf - --exclude=./.git --exclude=./target . | docker run --rm -i \ - -e RUSTUP_TOOLCHAIN="${{ steps.rust.outputs.version }}" \ + -e RUSTUP_TOOLCHAIN="${VERSION}" \ -e CARGO_TERM_COLOR=never \ - "rust:${{ steps.rust.outputs.version }}-bookworm" \ + "dep-check:${VERSION}" \ sh -ec 'mkdir -p /work && cd /work && tar xf - && exec node .gitea/scripts/dependency-report.mjs' \ > /tmp/report.md 2> /tmp/report.err || { echo "::error::the dependency report failed to generate" diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index fe44de8..0e157c2 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -143,18 +143,22 @@ jobs: exit 1 fi - # A normal boot must have created the vault and the push state, and - # the binary must report the version we tagged — proof that the tag, - # the Cargo.toml bump and the image all agree. - docker exec "$NAME" sh -ec ' - test -f /var/lib/discord-authenticator/vault.key - test -f /var/lib/discord-authenticator/push-state.json - /usr/local/bin/discord-authenticator --version - ' | tee /tmp/smoke-version - + # The binary must report the version we tagged — proof that the tag, + # the Cargo.toml bump and the image all agree. A wrong version here + # means someone tagged without bumping the workspace version. + docker exec "$NAME" /usr/local/bin/discord-authenticator --version | tee /tmp/smoke-version grep -q "${{ steps.version.outputs.version }}" /tmp/smoke-version \ || { echo "::error::the image reports a different version than the tag"; exit 1; } + # The entrypoint's chown must have made the volume writable by the + # service user. A fresh boot only mints vault.key — vault.json and + # push-state.json appear once there is something to store — so key + # creation is the honest proof that a first run succeeded. + docker exec "$NAME" sh -ec ' + test -f /var/lib/discord-authenticator/vault.key + test -s /var/lib/discord-authenticator/vault.key + ' || { echo "::error::the vault key was not created — check volume permissions"; exit 1; } + docker logs "$NAME" # LOGIN uses the repository owner rather than a fixed name: Gitea's diff --git a/.gitea/workflows/temp-diagnose.yml b/.gitea/workflows/temp-diagnose.yml deleted file mode 100644 index 5dd9a3c..0000000 --- a/.gitea/workflows/temp-diagnose.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: TEMP diagnose smoke test - -# Throwaway: diagnoses why the smoke test in release.yml fails silently. -# Deleted once the cause is known. -on: - workflow_dispatch: - -jobs: - diagnose: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Build image - run: | - set -eu - docker build --file deploy/docker/Dockerfile -t da-diag:latest . 2>&1 | tail -5 - - - name: Run it and dump everything - run: | - set -u - echo "### docker version" - docker version --format '{{.Server.Version}}' || true - echo - echo "### starting container" - docker rm -f da-diag >/dev/null 2>&1 || true - docker volume rm -f da-diag-vol >/dev/null 2>&1 || true - docker run -d --name da-diag \ - --mount type=volume,src=da-diag-vol,dst=/var/lib/discord-authenticator \ - da-diag:latest - echo "run exit: $?" - echo - sleep 8 - echo "### docker ps -a" - docker ps -a --filter name=da-diag || true - echo - echo "### docker inspect state" - docker inspect --format '{{json .State}}' da-diag || true - echo - echo "### docker logs" - docker logs da-diag 2>&1 || true - echo - echo "### exec: does the binary exist and run?" - docker exec da-diag /usr/local/bin/discord-authenticator --version || echo "exec --version FAILED rc=$?" - echo - echo "### exec: wget present?" - docker exec da-diag sh -c 'command -v wget && wget --version 2>&1 | head -1' || echo "wget check FAILED rc=$?" - echo - echo "### exec: files created?" - docker exec da-diag sh -c 'ls -la /var/lib/discord-authenticator' || echo "ls FAILED rc=$?" - echo - echo "### exec: healthz from inside" - docker exec da-diag sh -c 'wget -qO- http://127.0.0.1:8710/healthz; echo " (rc=$?)"' || echo "healthz FAILED rc=$?" - echo - echo "### exec: whoami / process list" - docker exec da-diag sh -c 'id; ps aux 2>/dev/null | head -10' || echo "ps FAILED rc=$?" - echo - echo "### cleanup" - docker rm -f da-diag || true - docker volume rm -f da-diag-vol || true