name: Web Release # Releases the FTStore Admin server (web/). Git tags are namespaced `web-v*` so # they never collide with the app's `v*` tags, which ios-release.yml and # app-repo.json own. The Docker images keep the plain `vX.Y` tag. on: workflow_dispatch: inputs: version: description: "Version tag (e.g. web-v0.1, web-v1.0.0)" required: true type: string env: REGISTRY: ghcr.io IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }}/ftstore-admin jobs: release: name: Build, Push & Release runs-on: [self-hosted, linux, x64] permissions: contents: write packages: write steps: - uses: actions/checkout@v5 with: fetch-depth: 0 # The app image compiles zsign from the manager/Zsign submodule, so # it has to come down with the checkout or the build fails. submodules: true - name: Validate version format id: version run: | if [[ ! "${{ inputs.version }}" =~ ^web-v[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then echo "::error::Version must match web-vX.Y or web-vX.Y.Z (e.g. web-v0.1, web-v1.0.0)" exit 1 fi # The image tag drops the namespace prefix: the registry only ever # holds these images, so ftstore-admin-app:v0.1 reads better than # ftstore-admin-app:web-v0.1 — and matches the tags already pushed. IMAGE_TAG="${{ inputs.version }}" echo "image_tag=${IMAGE_TAG#web-}" >> "$GITHUB_OUTPUT" - name: Check tag doesn't already exist run: | if git rev-parse "${{ inputs.version }}" >/dev/null 2>&1; then echo "::error::Tag ${{ inputs.version }} already exists" exit 1 fi - name: Generate changelog id: changelog run: | # --match keeps the app's `v*` tags out: without it the previous # iOS release would be picked as the baseline and the changelog would # span every commit since, most of them unrelated to web/. PREVIOUS_TAG=$(git describe --tags --abbrev=0 --match 'web-v*' 2>/dev/null || echo "") if [ -z "$PREVIOUS_TAG" ]; then COMMITS=$(git log --pretty=format:"- %s (%h)" --no-merges -- web '.github/workflows/web-*.yml') COMPARE_TEXT="Initial release" else COMMITS=$(git log "${PREVIOUS_TAG}..HEAD" --pretty=format:"- %s (%h)" --no-merges -- web '.github/workflows/web-*.yml') COMPARE_TEXT="**Full changelog**: [\`${PREVIOUS_TAG}...${{ inputs.version }}\`](https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${{ inputs.version }})" fi { echo "body<> "$GITHUB_OUTPUT" - name: Create and push tag run: | git tag "${{ inputs.version }}" git push origin "${{ inputs.version }}" - uses: docker/setup-buildx-action@v3 # A plain `docker login` with retries rather than `docker/login-action`, # which has none. ghcr.io answered one of the rolling builds with a TLS # handshake timeout and took the whole run with it, on a step that costs # nothing to try again. It matters more here than there: this one is # dispatched by hand and has already tagged the release by the time it # gets this far. Five attempts, backing off, then the failure is real. - name: Log in to GHCR env: GHCR_USER: ${{ github.actor }} GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | for attempt in 1 2 3 4 5; do if printf '%s' "$GHCR_TOKEN" \ | docker login ghcr.io --username "$GHCR_USER" --password-stdin then exit 0 fi echo "ghcr.io login attempt $attempt failed, waiting $((attempt * 5))s" sleep $((attempt * 5)) done echo "ghcr.io refused five logins in a row" >&2 exit 1 - name: Build and push app image uses: docker/build-push-action@v6 with: # Repo root, not ./web: the image compiles zsign from manager/Zsign. context: . file: ./web/Dockerfile push: true tags: | ${{ env.IMAGE_PREFIX }}-app:${{ steps.version.outputs.image_tag }} ${{ env.IMAGE_PREFIX }}-app:latest no-cache: true - name: Build and push worker image uses: docker/build-push-action@v6 with: context: ./web file: ./web/Dockerfile.worker push: true tags: | ${{ env.IMAGE_PREFIX }}-worker:${{ steps.version.outputs.image_tag }} ${{ env.IMAGE_PREFIX }}-worker:latest no-cache: true # The runner is self-hosted, so without this the credentials stay in # ~/.docker/config.json after the job is over. `docker/login-action` did # this in a post step; logging in by hand means logging out by hand. - name: Log out of GHCR if: always() run: docker logout ghcr.io - name: Create GitHub Release uses: softprops/action-gh-release@v3 with: tag_name: ${{ inputs.version }} name: ${{ inputs.version }} body: ${{ steps.changelog.outputs.body }} generate_release_notes: false