name: Signed Release on: push: tags: - "v*" permissions: contents: read releases: write jobs: release: runs-on: ubuntu-latest env: GITEA_BASE_URL: ${{ github.server_url }} GITEA_REPOSITORY: ${{ github.repository }} GITEA_ALLOW_INSECURE_HTTP: ${{ vars.ERP_RELEASE_ALLOW_INSECURE_HTTP }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} RELEASE_PRIVATE_KEY_B64: ${{ secrets.RELEASE_PRIVATE_KEY_B64 }} NODE_OPTIONS: --max-old-space-size=8192 steps: - name: Check out source uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Java 17 uses: actions/setup-java@v4 with: distribution: temurin java-version: "17" - name: Build, sign, and publish release shell: bash run: | set -euo pipefail tag="${GITHUB_REF_NAME:?missing tag name}" version="${tag#v}" [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]] \ || { printf 'invalid release tag: %s\n' "$tag" >&2; exit 2; } api_base="${GITEA_BASE_URL:?missing GITEA_BASE_URL repository variable}" repository="${GITEA_REPOSITORY:-awaioi/ERP}" case "$api_base" in https://*) curl_protocols=(--proto '=https' --proto-redir '=https') ;; http://*) [[ "${GITEA_ALLOW_INSECURE_HTTP:-0}" == "1" || "${GITEA_ALLOW_INSECURE_HTTP:-0}" == "true" ]] \ || { printf 'GITEA_BASE_URL must use HTTPS\n' >&2; exit 1; } curl_protocols=(--proto '=http,https' --proto-redir '=http,https') ;; *) printf 'invalid GITEA_BASE_URL\n' >&2; exit 1 ;; esac [[ "$repository" =~ ^[^/[:space:]]+/[^/[:space:]]+$ ]] \ || { printf 'invalid GITEA_REPOSITORY\n' >&2; exit 1; } [[ -n "${GITEA_TOKEN:-}" ]] || { printf 'missing built-in GITEA_TOKEN\n' >&2; exit 1; } [[ -n "${RELEASE_PRIVATE_KEY_B64:-}" ]] \ || { printf 'missing RELEASE_PRIVATE_KEY_B64 secret\n' >&2; exit 1; } umask 077 key_file="${RUNNER_TEMP:-/tmp}/kaidi-erp-release-key.pem" cleanup() { rm -f "$key_file" release.json release-payload.json release-notes.md; } trap cleanup EXIT printf '%s' "$RELEASE_PRIVATE_KEY_B64" | base64 --decode > "$key_file" export ERP_RELEASE_PRIVATE_KEY_FILE="$key_file" bash tests/release-scripts.test.sh ( cd oa-backend ./gradlew test installerTest ) bash scripts/package-release.sh "$version" previous_tag="$(git describe --tags --match 'v[0-9]*' --abbrev=0 "${tag}^" 2>/dev/null || true)" change_range="$tag" [[ -z "$previous_tag" ]] || change_range="$previous_tag..$tag" { printf '## 更新内容\n\n' if ! git log --no-merges --format='- %s (`%h`)' "$change_range"; then printf -- '- Kaidi ERP %s 正式发布\n' "$version" fi printf '\n## 安全校验\n\n' printf -- '- 安装包:`kaidi-erp-%s.tar.gz`\n' "$version" printf -- '- 完整性:SHA-256\n' printf -- '- 发布签名:Ed25519\n' if [[ -n "$previous_tag" ]]; then printf '\n上一个正式版本:`%s`\n' "$previous_tag" fi } > release-notes.md owner="${repository%%/*}" repo="${repository#*/}" release_api="${api_base%/}/api/v1/repos/$owner/$repo/releases" auth_header="Authorization: token $GITEA_TOKEN" status="$(curl "${curl_protocols[@]}" --silent --show-error --location \ --output release.json --write-out '%{http_code}' \ --header "$auth_header" --header 'Accept: application/json' \ "$release_api/tags/$tag")" python3 - "$tag" release-notes.md > release-payload.json <<'PY' import json, sys tag = sys.argv[1] with open(sys.argv[2], encoding="utf-8") as handle: notes = handle.read().strip() print(json.dumps({ "tag_name": tag, "name": f"Kaidi ERP {tag}", "body": notes, "draft": False, "prerelease": "-" in tag.split("+", 1)[0], }, separators=(",", ":"))) PY if [[ "$status" == "404" ]]; then curl "${curl_protocols[@]}" --fail-with-body --silent --show-error --location --retry 3 \ --header "$auth_header" --header 'Content-Type: application/json' \ --data-binary @release-payload.json --output release.json "$release_api" elif [[ "$status" == "200" ]]; then release_id="$(python3 -c 'import json; print(json.load(open("release.json"))["id"])')" curl "${curl_protocols[@]}" --fail-with-body --silent --show-error --location --retry 3 \ --request PATCH --header "$auth_header" --header 'Content-Type: application/json' \ --data-binary @release-payload.json --output release.json "$release_api/$release_id" else printf 'Gitea release lookup returned HTTP %s\n' "$status" >&2 cat release.json >&2 exit 1 fi release_id="$(python3 -c 'import json; print(json.load(open("release.json"))["id"])')" curl "${curl_protocols[@]}" --fail-with-body --silent --show-error --location --retry 3 \ --header "$auth_header" --header 'Accept: application/json' \ --output release.json "$release_api/$release_id" for asset in "dist/kaidi-erp-$version.tar.gz" "dist/kaidi-erp-installer-$version.jar" dist/SHA256SUMS dist/SHA256SUMS.sig; do name="$(basename "$asset")" encoded_name="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$name")" existing_id="$(python3 - "$name" <<'PY' import json, sys release = json.load(open("release.json")) print(next((item["id"] for item in release.get("assets", []) if item.get("name") == sys.argv[1]), "")) PY )" if [[ -n "$existing_id" ]]; then curl "${curl_protocols[@]}" --fail-with-body --silent --show-error --location --retry 3 \ --request DELETE --header "$auth_header" \ "$release_api/$release_id/assets/$existing_id" fi curl "${curl_protocols[@]}" --fail-with-body --silent --show-error --location --retry 3 \ --request POST --header "$auth_header" \ --form "attachment=@$asset" \ "$release_api/$release_id/assets?name=$encoded_name" >/dev/null done printf 'Published %s release %s\n' "$repository" "$tag"