diff --git a/.github/Dockerfile.act b/.github/Dockerfile.act index 45f65fee0..26f9a2e73 100644 --- a/.github/Dockerfile.act +++ b/.github/Dockerfile.act @@ -4,6 +4,13 @@ ARG GO_VERSION # but `actionlint` used below requires 1.25 FROM golang:1.25 +ARG ACTIONLINT_VERSION=v1.7.8 +ARG ACT_VERSION=v0.2.89 +ARG ACT_LINUX_X86_64_SHA256=0191d6f1f3b716b5c55820032605d05fc3c1cdbf581ebeff655019e5dd1524c0 +ARG ACT_LINUX_ARM64_SHA256=daa8679ba9615a74d2d0cec321dc593f21948a2a11bb65862b063d8b930f4bcb +ARG LUACHECK_VERSION=1.2.0-1 +ARG YAMLLINT_VERSION=1.38.0 + ENV DEBIAN_FRONTEND=noninteractive ENV GOBIN=/usr/local/bin ENV HOME=/home/gomud @@ -15,21 +22,35 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ - docker.io \ git \ + luarocks \ make \ nodejs \ npm \ python3 \ python3-pip \ + shellcheck \ && rm -rf /var/lib/apt/lists/* -RUN go install github.com/rhysd/actionlint/cmd/actionlint@latest +RUN go install github.com/rhysd/actionlint/cmd/actionlint@${ACTIONLINT_VERSION} + +RUN pip3 install --break-system-packages "yamllint==${YAMLLINT_VERSION}" -RUN pip3 install --break-system-packages yamllint +RUN luarocks install luacheck "${LUACHECK_VERSION}" -RUN curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/nektos/act/master/install.sh \ - | bash -s -- -b /usr/local/bin +RUN set -eux; \ + arch="$(dpkg --print-architecture)"; \ + case "$arch" in \ + amd64) act_arch=x86_64; act_sha256="${ACT_LINUX_X86_64_SHA256}" ;; \ + arm64) act_arch=arm64; act_sha256="${ACT_LINUX_ARM64_SHA256}" ;; \ + *) echo "Unsupported act architecture: $arch" >&2; exit 1 ;; \ + esac; \ + curl --proto '=https' --tlsv1.2 -sSfL \ + -o /tmp/act.tar.gz \ + "https://github.com/nektos/act/releases/download/${ACT_VERSION}/act_Linux_${act_arch}.tar.gz"; \ + printf '%s %s\n' "$act_sha256" /tmp/act.tar.gz | sha256sum -c -; \ + tar -xzf /tmp/act.tar.gz -C /usr/local/bin act; \ + rm -f /tmp/act.tar.gz RUN useradd --create-home --home-dir /home/gomud --shell /bin/bash gomud \ && mkdir -p /work /home/gomud/.cache/act /home/gomud/.cache/go-build /home/gomud/go/pkg/mod \ diff --git a/.github/actions/discord-webhook/action.yml b/.github/actions/discord-webhook/action.yml deleted file mode 100644 index 99e8a7cea..000000000 --- a/.github/actions/discord-webhook/action.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- -name: "Discord Webhook" -description: "Send PR info to Discord" -inputs: - webhook-url: - description: "Discord webhook URL" - required: false - pr-title: - description: "Title of the pull request" - required: false - pr-body: - description: "Body of the pull request" - required: false - pr-url: - description: "URL of the pull request" - required: false -runs: - using: "composite" - steps: - - name: Send Discord webhook - shell: bash - env: - WEBHOOK_URL: ${{ inputs.webhook-url }} - PR_TITLE: ${{ inputs.pr-title }} - PR_BODY: ${{ inputs.pr-body }} - PR_URL: ${{ inputs.pr-url }} - run: | - set -euo pipefail - - payload="$( - python3 - <<'PY' - import json - import os - - title = os.environ.get("PR_TITLE") or "Pull request notification" - description = ( - os.environ.get("PR_BODY") or - "No pull request description provided." - ) - url = os.environ.get("PR_URL") or "" - - print(json.dumps({ - "allowed_mentions": { - "parse": [], - }, - "embeds": [ - { - "title": title[:256], - "description": description[:4096], - "url": url, - }, - ], - })) - PY - )" - - curl \ - --fail-with-body \ - --silent \ - --show-error \ - --request POST \ - --header "Content-Type: application/json" \ - --data "$payload" \ - "$WEBHOOK_URL" diff --git a/.github/scripts/build-release-binaries.sh b/.github/scripts/build-release-binaries.sh index 6690f5283..aae6a369a 100755 --- a/.github/scripts/build-release-binaries.sh +++ b/.github/scripts/build-release-binaries.sh @@ -5,11 +5,18 @@ script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd -- "${script_dir}/../.." && pwd)" dist_dir="${RELEASE_DIST_DIR:-dist}" binary_version="${BINARY_VERSION:-}" +target_asset="${RELEASE_TARGET_ASSET:-}" +target_found=false cd "$repo_root" mkdir -p "$dist_dir" while IFS='|' read -r _label goos goarch goarm asset; do + if [ -n "$target_asset" ] && [ "$asset" != "$target_asset" ]; then + continue + fi + target_found=true + target="${goos}/${goarch}" if [ -n "$goarm" ]; then target="${target} GOARM=${goarm}" @@ -30,3 +37,8 @@ while IFS='|' read -r _label goos goarch goarm asset; do fi echo "::endgroup::" done < <("${script_dir}/release-assets.sh" targets) + +if [ -n "$target_asset" ] && [ "$target_found" != true ]; then + printf 'Unknown release target asset: %s\n' "$target_asset" >&2 + exit 2 +fi diff --git a/.github/scripts/ci-local-act.sh b/.github/scripts/ci-local-act.sh index 3e4185bb4..4faaafe16 100755 --- a/.github/scripts/ci-local-act.sh +++ b/.github/scripts/ci-local-act.sh @@ -8,6 +8,8 @@ ACT_FLAGS="${ACT_FLAGS:---pull=false -P ubuntu-24.04=catthehacker/ubuntu:act-lat ACT_DRYRUN_SECRETS="${ACT_DRYRUN_SECRETS:--s DISCORD_WEBHOOK_URL=https://example.invalid/webhook}" XDG_CONFIG_HOME="${ACT_CONFIG_HOME:-${repo_root}/.github}" export XDG_CONFIG_HOME +read -r -a act_flags <<<"$ACT_FLAGS" +read -r -a act_dryrun_secrets <<<"$ACT_DRYRUN_SECRETS" mkdir -p "${XDG_CONFIG_HOME}/act" touch "${XDG_CONFIG_HOME}/act/actrc" @@ -18,7 +20,7 @@ run_act() { local workflow="$3" shift 3 - act ${ACT_FLAGS:-} --dryrun "$event" "$@" \ + act "${act_flags[@]}" --dryrun "$event" "$@" \ -e "$event_file" \ -W "$workflow" } @@ -28,9 +30,9 @@ run_act() { run_act push .github/act/push_master.json .github/workflows/ci.yml run_act pull_request .github/act/pull_request.json .github/workflows/ci.yml run_act pull_request .github/act/pull_request.json \ - .github/workflows/discord-notify.yml ${ACT_DRYRUN_SECRETS:-} + .github/workflows/discord-notify.yml "${act_dryrun_secrets[@]}" run_act push .github/act/push_master.json .github/workflows/prerelease.yml run_act workflow_dispatch .github/act/stable_release.json \ .github/workflows/stable-release.yml run_act workflow_call .github/act/docker_package_call.json \ - .github/workflows/docker-package.yml ${ACT_DRYRUN_SECRETS:-} + .github/workflows/docker-package.yml "${act_dryrun_secrets[@]}" diff --git a/.github/scripts/release-assets.sh b/.github/scripts/release-assets.sh index 9e81e5763..081304e71 100755 --- a/.github/scripts/release-assets.sh +++ b/.github/scripts/release-assets.sh @@ -17,9 +17,9 @@ EOF } binary_names() { - local label goos goarch goarm asset + local _label _goos _goarch _goarm asset - while IFS='|' read -r label goos goarch goarm asset; do + while IFS='|' read -r _label _goos _goarch _goarm asset; do printf '%s\n' "$asset" done < <(release_targets) } @@ -49,13 +49,13 @@ attestation_paths() { } downloads_markdown() { - local label goos goarch goarm asset + local label _goos _goarch _goarm asset - while IFS='|' read -r label goos goarch goarm asset; do - printf -- '- %s: `%s`\n' "$label" "$asset" + while IFS='|' read -r label _goos _goarch _goarm asset; do + printf -- "- %s: \`%s\`\n" "$label" "$asset" done < <(release_targets) - printf -- '- Datafiles: `%s`\n' "$datafiles_archive" - printf -- '- Checksums: `%s`\n' "$checksums_file" + printf -- "- Datafiles: \`%s\`\n" "$datafiles_archive" + printf -- "- Checksums: \`%s\`\n" "$checksums_file" } case "${1:-}" in diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58402ffd0..7aa06d00a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,11 @@ name: CI # ci.yml performs the Continuous Integration (CI) workflow and performs the # following actions: +# - conditional GitHub automation lint # - Go format/vet # - conditional JS lint -# - Go race tests +# - conditional Lua lint +# - PR Go race tests "on": push: @@ -29,21 +31,27 @@ permissions: jobs: detect-changes: - name: Detect JavaScript Changes + name: Detect CI Inputs runs-on: ubuntu-24.04 timeout-minutes: 5 outputs: + automation_lint: >- + ${{ steps.filter.outputs.automation_lint }} + go_lint: >- + ${{ steps.filter.outputs.go_lint }} + go_tests: >- + ${{ steps.filter.outputs.go_tests }} js_lint: >- ${{ steps.filter.outputs.js_lint }} - runtime_build: >- - ${{ steps.filter.outputs.runtime_build }} + lua_lint: >- + ${{ steps.filter.outputs.lua_lint }} steps: - # Full history is needed so push and pull request events can diff against - # the actual base commit instead of whatever shallow checkout provides. - # actions/checkout v6.0.2 + # Depth 2 covers normal single-commit pushes and pull request merge + # parents; the script fetches the base commit for larger push ranges. + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: - fetch-depth: 0 + fetch-depth: 2 persist-credentials: false - id: filter @@ -51,6 +59,7 @@ jobs: env: BASE_REF: >- ${{ github.event.pull_request.base.sha || github.event.before }} + EVENT_NAME: ${{ github.event_name }} HEAD_REF: ${{ github.sha }} run: | set -eu @@ -60,6 +69,13 @@ jobs: BASE_REF="$(git rev-parse HEAD^)" fi + if ! git cat-file -e "${BASE_REF}^{commit}" 2>/dev/null; then + if ! git fetch --no-tags --depth=1 origin "${BASE_REF}" \ + 2>/dev/null; then + git fetch --no-tags --unshallow origin + fi + fi + changed_files="$(git diff --name-only "${BASE_REF}" "${HEAD_REF}")" printf '%s\n' "${changed_files}" @@ -70,26 +86,106 @@ jobs: echo "js_lint=false" >> "$GITHUB_OUTPUT" fi + if printf '%s\n' "${changed_files}" | grep -Eq '^\.github/'; then + echo "automation_lint=true" >> "$GITHUB_OUTPUT" + else + echo "automation_lint=false" >> "$GITHUB_OUTPUT" + fi + + lua_re='(^|/)[^/]+\.lua$' + lua_re="${lua_re}|^(Makefile|\.luacheckrc|\.github/workflows/ci\.yml)$" + if printf '%s\n' "${changed_files}" | grep -Eq "${lua_re}"; then + echo "lua_lint=true" >> "$GITHUB_OUTPUT" + else + echo "lua_lint=false" >> "$GITHUB_OUTPUT" + fi + go_re='(^|/)[^/]+\.go$' go_re="${go_re}|^(go\.mod|go\.sum|Makefile)$" + # Module files can be embedded through go:embed, so data-only module + # asset changes still need Go validation. + go_re="${go_re}|^modules/[^/]+/files/" go_re="${go_re}|^\.github/actions/(go-checks|setup-go)/" go_re="${go_re}|^\.github/workflows/ci\.yml" if printf '%s\n' "${changed_files}" | grep -Eq "${go_re}"; then - echo "runtime_build=true" >> "$GITHUB_OUTPUT" + echo "go_lint=true" >> "$GITHUB_OUTPUT" + # Pull requests run race tests here; master pushes are covered by + # the prerelease validation workflow. + if [ "${EVENT_NAME}" = "pull_request" ]; then + echo "go_tests=true" >> "$GITHUB_OUTPUT" + else + echo "go_tests=false" >> "$GITHUB_OUTPUT" + fi else - echo "runtime_build=false" >> "$GITHUB_OUTPUT" + echo "go_lint=false" >> "$GITHUB_OUTPUT" + echo "go_tests=false" >> "$GITHUB_OUTPUT" fi + automation-lint: + name: GitHub Automation Lint + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: detect-changes + if: >- + needs.detect-changes.outputs.automation_lint == 'true' + steps: + # actions/checkout v6.0.3 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + with: + persist-credentials: false + + - name: Set up Go for actionlint + # actions/setup-go v6.4.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c + with: + go-version: '1.25.x' + cache: false + + - name: Install automation linters + env: + ACTIONLINT_VERSION: v1.7.8 + YAMLLINT_VERSION: 1.38.0 + run: | + set -euo pipefail + + go install \ + "github.com/rhysd/actionlint/cmd/actionlint@${ACTIONLINT_VERSION}" + + need_apt=false + if ! python3 -m pip --version >/dev/null 2>&1; then + need_apt=true + fi + if ! command -v shellcheck >/dev/null 2>&1; then + need_apt=true + fi + if [ "$need_apt" = "true" ]; then + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + python3-pip \ + shellcheck + fi + + python3 -m pip install --user "yamllint==${YAMLLINT_VERSION}" + + - name: Run automation lint + run: | + set -euo pipefail + export PATH="$HOME/go/bin:$HOME/.local/bin:$PATH" + + actionlint .github/workflows/*.yml + yamllint .github + shellcheck .github/scripts/*.sh + go-lint: name: Go Format And Vet runs-on: ubuntu-24.04 timeout-minutes: 20 needs: detect-changes if: >- - needs.detect-changes.outputs.runtime_build == 'true' + needs.detect-changes.outputs.go_lint == 'true' steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false @@ -107,7 +203,7 @@ jobs: if: >- needs.detect-changes.outputs.js_lint == 'true' steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false @@ -121,15 +217,31 @@ jobs: - name: Run JavaScript lint checks run: make js-lint + lua-lint: + name: Lua Lint + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: detect-changes + if: >- + needs.detect-changes.outputs.lua_lint == 'true' + steps: + # actions/checkout v6.0.3 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + with: + persist-credentials: false + + - name: Run Lua lint checks + run: make lua-lint + test: name: Go Tests runs-on: ubuntu-24.04 timeout-minutes: 30 needs: detect-changes if: >- - needs.detect-changes.outputs.runtime_build == 'true' + needs.detect-changes.outputs.go_tests == 'true' steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 94afe506c..2abe50908 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -39,7 +39,9 @@ jobs: # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: - fetch-depth: 0 + # Keep normal runs shallow; the detect step fetches older bases only + # when a larger push range needs them. + fetch-depth: 2 persist-credentials: false - name: Detect changed CodeQL inputs @@ -70,6 +72,13 @@ jobs: diff_head=HEAD fi + if ! git cat-file -e "${diff_base}^{commit}" 2>/dev/null; then + if ! git fetch --no-tags --depth=1 origin "${diff_base}" \ + 2>/dev/null; then + git fetch --no-tags --unshallow origin + fi + fi + mapfile -t changed_files < <( git diff --name-only "$diff_base" "$diff_head" ) diff --git a/.github/workflows/discord-notify.yml b/.github/workflows/discord-notify.yml index d1feedbe2..5187eefc7 100644 --- a/.github/workflows/discord-notify.yml +++ b/.github/workflows/discord-notify.yml @@ -17,32 +17,56 @@ jobs: if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-24.04 timeout-minutes: 5 + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} steps: - # actions/checkout v6.0.2 - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 - with: - persist-credentials: false - - name: Resolve Discord webhook URL - id: webhook - run: | - webhook_url='${{ secrets.DISCORD_WEBHOOK_URL }}' - if [ -z "$webhook_url" ]; then - # Local act dry runs can pass this as an environment variable. - webhook_url="${DISCORD_WEBHOOK_URL:-}" - fi - echo "url=$webhook_url" >> "$GITHUB_OUTPUT" - - name: Skip when webhook is not configured - if: ${{ steps.webhook.outputs.url == '' }} + if: ${{ env.DISCORD_WEBHOOK_URL == '' }} run: > echo "DISCORD_WEBHOOK_URL is not configured; skipping Discord notification." - name: Send Discord Message - if: ${{ steps.webhook.outputs.url != '' }} - uses: ./.github/actions/discord-webhook - with: - webhook-url: ${{ steps.webhook.outputs.url }} - pr-title: ${{ github.event.pull_request.title }} - pr-body: ${{ github.event.pull_request.body }} - pr-url: ${{ github.event.pull_request.html_url }} + if: ${{ env.DISCORD_WEBHOOK_URL != '' }} + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + set -euo pipefail + + payload="$( + python3 - <<'PY' + import json + import os + + title = os.environ.get("PR_TITLE") or "Pull request notification" + description = ( + os.environ.get("PR_BODY") or + "No pull request description provided." + ) + url = os.environ.get("PR_URL") or "" + + print(json.dumps({ + "allowed_mentions": { + "parse": [], + }, + "embeds": [ + { + "title": title[:256], + "description": description[:4096], + "url": url, + }, + ], + })) + PY + )" + + curl \ + --fail-with-body \ + --silent \ + --show-error \ + --request POST \ + --header "Content-Type: application/json" \ + --data "$payload" \ + "$DISCORD_WEBHOOK_URL" diff --git a/.github/workflows/docker-package.yml b/.github/workflows/docker-package.yml index e0027ff13..48c1d0058 100644 --- a/.github/workflows/docker-package.yml +++ b/.github/workflows/docker-package.yml @@ -31,7 +31,7 @@ jobs: attestations: write id-token: write steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false @@ -54,21 +54,21 @@ jobs: run: echo "version=$(make go-version)" >> "$GITHUB_OUTPUT" - name: Set up Docker Buildx - # docker/setup-buildx-action v4.0.0 + # docker/setup-buildx-action v4.1.0 # yamllint disable-line rule:line-length uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - name: Log in to the Container registry ${{ env.REGISTRY }} - # docker/login-action v4.1.0 + # docker/login-action v4.2.0 uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + password: ${{ github.token }} - name: Extract metadata (tags, labels) for Docker id: meta - # docker/metadata-action v6.0.0 + # docker/metadata-action v6.1.0 uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} @@ -80,7 +80,7 @@ jobs: - name: Build and push Docker image id: push - # docker/build-push-action v7.1.0 + # docker/build-push-action v7.2.0 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf with: context: . @@ -100,4 +100,4 @@ jobs: with: subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} subject-digest: ${{ steps.push.outputs.digest }} - push-to-registry: false + push-to-registry: true diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 908929fb0..1f65f545f 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -30,7 +30,7 @@ jobs: outputs: binary_version: ${{ steps.meta.outputs.binary_version }} steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false @@ -57,7 +57,7 @@ jobs: permissions: contents: read steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false @@ -67,36 +67,52 @@ jobs: - uses: ./.github/actions/go-checks build: - name: Build Release Binaries + name: Build Release Binary (${{ matrix.label }}) runs-on: ubuntu-24.04 timeout-minutes: 20 needs: - metadata - test + strategy: + fail-fast: false + matrix: + include: + - label: Linux amd64 + asset: gomud-linux_x64 + - label: Linux arm64 + asset: gomud-linux_arm64 + - label: Linux arm/v7 + asset: gomud-linux_armv7 + - label: Windows amd64 + asset: gomud-windows_x64.exe + - label: Windows arm64 + asset: gomud-windows_arm64.exe + - label: macOS amd64 + asset: gomud-darwin_x64 + - label: macOS arm64 + asset: gomud-darwin_arm64 permissions: contents: read env: BINARY_VERSION: ${{ needs.metadata.outputs.binary_version }} + RELEASE_TARGET_ASSET: ${{ matrix.asset }} steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: ./.github/actions/setup-go - - name: Generate code - run: go generate ./... - - - name: Build release binaries + - name: Build release binary run: .github/scripts/build-release-binaries.sh - - name: Upload release binaries - # actions/uploadf-artifact v7.0.1 + - name: Upload release binary + # actions/upload-artifact v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: - name: release-binaries - path: dist/* + name: release-binary-${{ matrix.asset }} + path: dist/${{ matrix.asset }} if-no-files-found: error publish: @@ -117,7 +133,7 @@ jobs: BINARY_VERSION: ${{ needs.metadata.outputs.binary_version }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false @@ -126,8 +142,9 @@ jobs: # actions/download-artifact v8.0.1 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: - name: release-binaries + pattern: release-binary-* path: bin/ + merge-multiple: true - name: Assemble release assets run: .github/scripts/assemble-release-assets.sh diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml index cd48858db..7e6302af0 100644 --- a/.github/workflows/stable-release.yml +++ b/.github/workflows/stable-release.yml @@ -35,7 +35,7 @@ jobs: outputs: binary_version: ${{ steps.meta.outputs.binary_version }} steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false @@ -90,7 +90,7 @@ jobs: permissions: contents: read steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false @@ -100,36 +100,52 @@ jobs: - uses: ./.github/actions/go-checks build: - name: Build Release Binaries + name: Build Release Binary (${{ matrix.label }}) runs-on: ubuntu-24.04 timeout-minutes: 20 needs: - validate-release - test + strategy: + fail-fast: false + matrix: + include: + - label: Linux amd64 + asset: gomud-linux_x64 + - label: Linux arm64 + asset: gomud-linux_arm64 + - label: Linux arm/v7 + asset: gomud-linux_armv7 + - label: Windows amd64 + asset: gomud-windows_x64.exe + - label: Windows arm64 + asset: gomud-windows_arm64.exe + - label: macOS amd64 + asset: gomud-darwin_x64 + - label: macOS arm64 + asset: gomud-darwin_arm64 permissions: contents: read env: BINARY_VERSION: ${{ needs.validate-release.outputs.binary_version }} + RELEASE_TARGET_ASSET: ${{ matrix.asset }} steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false - uses: ./.github/actions/setup-go - - name: Generate code - run: go generate ./... - - - name: Build release binaries + - name: Build release binary run: .github/scripts/build-release-binaries.sh - - name: Upload release binaries + - name: Upload release binary # actions/upload-artifact v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: - name: release-binaries - path: dist/* + name: release-binary-${{ matrix.asset }} + path: dist/${{ matrix.asset }} if-no-files-found: error stage-release: @@ -152,7 +168,7 @@ jobs: BINARY_VERSION: ${{ needs.validate-release.outputs.binary_version }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false @@ -182,8 +198,9 @@ jobs: # actions/download-artifact v8.0.1 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: - name: release-binaries + pattern: release-binary-* path: bin/ + merge-multiple: true - name: Assemble release assets run: .github/scripts/assemble-release-assets.sh @@ -264,7 +281,6 @@ jobs: packages: write attestations: write id-token: write - secrets: inherit with: release_tag: ${{ inputs.release_tag }} @@ -290,3 +306,74 @@ jobs: "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" \ -F draft=false \ -F prerelease=false + + cleanup-failed-draft: + name: Cleanup Failed Stable Draft + runs-on: ubuntu-24.04 + timeout-minutes: 5 + needs: + - stage-release + - docker-image + - publish + if: >- + ${{ + always() && + ( + needs.stage-release.result == 'failure' || + needs.docker-image.result == 'failure' || + needs.publish.result == 'failure' + ) + }} + permissions: + contents: write + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_ID: ${{ needs.stage-release.outputs.release_id }} + RELEASE_TAG: ${{ inputs.release_tag }} + steps: + - name: Delete unpublished stable draft + run: | + set -euo pipefail + + # Only delete a draft whose ID came from this run. Looking it up by + # tag can delete a draft created while this run waited for approval. + if [ -z "$RELEASE_ID" ]; then + echo "No draft release ID was created by this workflow run." + exit 0 + fi + + release_data="$( + gh api "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" \ + --jq '[.id, .tag_name, .draft, .target_commitish] | @tsv' \ + 2>/dev/null || true + )" + if [ -z "$release_data" ]; then + echo "Draft release ${RELEASE_ID} was not found." + exit 0 + fi + + IFS=$'\t' read -r release_id tag_name draft target_commitish \ + <<<"$release_data" + if [ "$tag_name" != "$RELEASE_TAG" ] || + [ "$draft" != "true" ] || + [ "$target_commitish" != "$GITHUB_SHA" ]; then + echo "Release ${RELEASE_TAG} is not this run's draft; leaving it." + exit 0 + fi + + gh api \ + -X DELETE \ + "repos/${GITHUB_REPOSITORY}/releases/${release_id}" + + tag_sha="$( + gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${RELEASE_TAG}" \ + --jq '.object.sha' \ + 2>/dev/null || true + )" + if [ "$tag_sha" = "$GITHUB_SHA" ]; then + gh api \ + -X DELETE \ + "repos/${GITHUB_REPOSITORY}/git/refs/tags/${RELEASE_TAG}" + elif [ -n "$tag_sha" ]; then + echo "Tag ${RELEASE_TAG} does not point at this run; leaving it." + fi diff --git a/.github/workflows/update-go-toolchain.yml b/.github/workflows/update-go-toolchain.yml index 823dbe141..62b1ca12b 100644 --- a/.github/workflows/update-go-toolchain.yml +++ b/.github/workflows/update-go-toolchain.yml @@ -23,7 +23,7 @@ jobs: BASE_BRANCH: master PR_BRANCH: automation/update-go-toolchain steps: - # actions/checkout v6.0.2 + # actions/checkout v6.0.3 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 with: persist-credentials: false diff --git a/Makefile b/Makefile index e7ecb0e41..450b57582 100644 --- a/Makefile +++ b/Makefile @@ -198,8 +198,10 @@ ci-local: ci-local-image ## Run local CI validation in the CI tool container. ci-local-inner: ## Run CI checks from inside the local CI tool container. actionlint .github/workflows/*.yml yamllint .github + shellcheck .github/scripts/*.sh $(MAKE) validate $(MAKE) js-lint + $(MAKE) lua-lint ACT_FLAGS="$(ACT_FLAGS)" \ ACT_DRYRUN_SECRETS="$(ACT_DRYRUN_SECRETS)" \ .github/scripts/ci-local-act.sh