# Build the container image and publish it to the Gitea container registry. # # Why this lives in .gitea/workflows and not .github/workflows: Gitea looks for # .gitea/workflows first and only falls back to .github/workflows when that # directory does not exist. So the presence of this file makes Gitea ignore # .github/workflows/publish-image.yml entirely. That file is still correct for # github.com/ghcr.io and is deliberately left in place -- the two cannot race, # and neither is dead code. Deleting this directory silently re-activates the # ghcr.io workflow, which will fail here for want of a GitHub PAT. # # The unit suite runs *inside* the image build (the Dockerfile's builder stage # ends with ./build/tests/ovg_tests), so a failing test fails the publish. There # is deliberately no separate test job duplicating that -- see the note in # .github/workflows/publish-image.yml, which makes the same choice. # # Tags produced: # push to master -> master, sha- # push tag v1.2.3 -> 1.2.3, 1.2, latest, sha- # pull request -> built and smoke-tested, never pushed # # `latest` follows the newest release tag, not the branch head. Until the first # v* tag exists, the tag to pull is `master`. name: build image on: push: branches: [master] tags: ["v*"] pull_request: branches: [master] workflow_dispatch: permissions: contents: read packages: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} # Superseding a PR build is free; killing a release build half way through is # not. cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: image: runs-on: ubuntu-latest # A cold build compiles the openvpn3 core and lwIP from source. Configure + # compile alone measured 1m39s on 4 cores; on top of that the image build # also does an apt install, two git clones, and the test suite. The ceiling # is set well above any of that because the failure mode it guards against # -- a cache miss on a runner that is also being slow -- is the one where a # tight limit turns a slow build into a red one. timeout-minutes: 60 steps: - uses: actions/checkout@v4 # Registry paths must be lowercase and github.repository is not guaranteed # to be. Both halves are overridable: REGISTRY for a different Gitea host, # IMAGE for an owner/name that differs from this repository's. - name: Resolve image reference id: img env: REGISTRY: ${{ vars.REGISTRY || 'git.sfclub.cc' }} IMAGE: ${{ vars.IMAGE || github.repository }} run: | set -eu printf 'ref=%s\n' \ "$(printf '%s/%s' "$REGISTRY" "$IMAGE" | tr '[:upper:]' '[:lower:]')" \ >> "$GITHUB_OUTPUT" printf 'registry=%s\n' "$REGISTRY" >> "$GITHUB_OUTPUT" - uses: docker/setup-buildx-action@v3 # Skipped on pull requests: nothing is pushed from a PR, and a PR from a # fork has no access to secrets anyway. # # REGISTRY_USER/REGISTRY_TOKEN match the naming in cloud/workspace-image. # The fallback is Gitea's own per-job token, which can push to this # instance's registry under the actor's namespace -- enough for the common # case, so the workflow is not dead on arrival before secrets are set. - name: Log in to the Gitea registry if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ${{ steps.img.outputs.registry }} username: ${{ secrets.REGISTRY_USER || github.actor }} password: ${{ secrets.REGISTRY_TOKEN || secrets.GITEA_TOKEN }} - name: Derive tags and labels id: meta uses: docker/metadata-action@v5 with: images: ${{ steps.img.outputs.ref }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha # The default flavor (latest=auto) adds `latest` on a semver tag push # and nowhere else, which is the intent stated at the top of the file. - name: Build and push id: build uses: docker/build-push-action@v6 with: context: . push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} # amd64 only, matching the project's stated target. arm64 is not known # to be broken -- it is untested, and cross-building the openvpn3 core # under QEMU costs the better part of an hour per run. Enabling it # means adding docker/setup-qemu-action above and verifying lwIP's # unaligned-access assumptions, not just editing this line. platforms: linux/amd64 build-args: | OVG_WITH_TUNNEL=ON OVG_RUN_TESTS=1 # Registry-backed cache rather than type=gha: the GitHub Actions cache # backend needs ACTIONS_CACHE_URL/ACTIONS_RUNTIME_TOKEN, which a # self-hosted act_runner supplies inconsistently. A registry cache # needs only the login two steps up. Without one of the two, every run # recompiles openvpn3 from scratch. cache-from: type=registry,ref=${{ steps.img.outputs.ref }}:buildcache cache-to: ${{ github.event_name != 'pull_request' && format('type=registry,ref={0}:buildcache,mode=max', steps.img.outputs.ref) || '' }} # Off on purpose. Attestations turn the push into a multi-manifest # index with unknown/unknown platform entries, which Gitea's registry # renders as junk "images" in the package view and which some pull # paths mishandle. ghcr.io copes; this does not need to. provenance: false # Verifies the artifact that was actually published, by digest rather than # by tag. Cheap, and it covers the two things a green build still would # not: that the runtime stage carries the shared libraries the binary # needs, and that the config baked into the image parses. # # --check needs the credentials file, which is deliberately not in the # image; a throwaway one is enough to get the config validated. - name: Smoke test the published image if: github.event_name != 'pull_request' env: IMAGE: ${{ steps.img.outputs.ref }}@${{ steps.build.outputs.digest }} run: | set -eux docker run --rm "$IMAGE" --version printf 'ci:changeme\n' > "$RUNNER_TEMP/socks5.auth" docker run --rm \ -v "$RUNNER_TEMP/socks5.auth:/etc/openvpngate/socks5.auth:ro" \ "$IMAGE" -c /etc/openvpngate/openvpngate.conf --check - name: Summary if: github.event_name != 'pull_request' run: | { echo "### Published" echo echo '```' echo "${{ steps.meta.outputs.tags }}" echo '```' echo echo "digest: \`${{ steps.build.outputs.digest }}\`" } >> "$GITHUB_STEP_SUMMARY"