name: Build and Release on: workflow_dispatch: inputs: version: description: '版本号 (留空则自动生成 v-)' required: false default: '' prerelease: description: '标记为预发布 (Pre-release)' required: false default: 'false' type: choice options: - 'false' - 'true' draft: description: '以草稿方式创建 Release (不对外公开)' required: false default: 'false' type: choice options: - 'false' - 'true' skip_tests: description: '跳过单元测试 (紧急构建时使用)' required: false default: 'false' type: choice options: - 'false' - 'true' jobs: build-release: runs-on: ubuntu-latest steps: - name: Checkout (full history) uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install toolchain run: | set -euo pipefail sudo apt-get update -qq DEBIAN_FRONTEND=noninteractive sudo apt-get install -y -qq \ shellcheck jq curl tar coreutils - name: Run shellcheck run: | set -euo pipefail shellcheck iptables-forward.sh install.sh lib/*.sh tests/run_all.sh - name: Run unit tests if: ${{ inputs.skip_tests != 'true' }} run: | set -euo pipefail bash tests/run_all.sh --skip-integration - name: Compute version metadata id: ver run: | set -euo pipefail SHORT_SHA=$(git rev-parse --short=7 HEAD) FULL_SHA=$(git rev-parse HEAD) DATE=$(date -u +%Y.%m.%d) BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) if [[ -n "${{ inputs.version }}" ]]; then VERSION="${{ inputs.version }}" else VERSION="v${DATE}-${SHORT_SHA}" fi TAG="${VERSION}" TITLE="IPTables 端口转发管理工具 ${VERSION}" ARTIFACT_BASE="iptables-forward-${VERSION}" { echo "version=${VERSION}" echo "tag=${TAG}" echo "title=${TITLE}" echo "artifact_base=${ARTIFACT_BASE}" echo "short_sha=${SHORT_SHA}" echo "full_sha=${FULL_SHA}" echo "build_time=${BUILD_TIME}" } >> "$GITHUB_OUTPUT" echo "准备构建: ${VERSION}" - name: Ensure tag does not already exist run: | set -euo pipefail TAG='${{ steps.ver.outputs.tag }}' if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then echo "::error::Tag ${TAG} 已存在于远端。请手动传入新的 version 输入。" exit 1 fi - name: Build release tarball id: build run: | set -euo pipefail NAME='${{ steps.ver.outputs.artifact_base }}' mkdir -p "dist/${NAME}" cp iptables-forward.sh install.sh README.md "dist/${NAME}/" cp -r lib "dist/${NAME}/" [[ -f LICENSE ]] && cp LICENSE "dist/${NAME}/" || true chmod 755 "dist/${NAME}/iptables-forward.sh" "dist/${NAME}/install.sh" tar -czf "dist/${NAME}.tar.gz" -C dist "${NAME}" ( cd dist && sha256sum "${NAME}.tar.gz" > "${NAME}.tar.gz.sha256" ) ls -la dist/ - name: Generate changelog id: changelog run: | set -euo pipefail PREV_TAG=$(git tag --list 'v*' --sort=-creatordate | head -n 1 || true) { echo "## 构建信息" echo "" echo "- **版本**: \`${{ steps.ver.outputs.version }}\`" echo "- **提交**: \`${{ steps.ver.outputs.full_sha }}\`" echo "- **构建时间 (UTC)**: ${{ steps.ver.outputs.build_time }}" echo "- **触发者**: @${{ github.actor }}" echo "" echo "## 变更记录" echo "" if [[ -n "${PREV_TAG}" ]]; then echo "自 \`${PREV_TAG}\` 以来的提交:" echo "" git log "${PREV_TAG}..HEAD" --pretty='- %s (`%h`)' --no-merges || true else echo "首次发布,近 30 条提交:" echo "" git log -30 --pretty='- %s (`%h`)' --no-merges fi echo "" echo "## 安装" echo "" echo '```bash' echo "# 下载" echo "wget ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.ver.outputs.tag }}/${{ steps.ver.outputs.artifact_base }}.tar.gz" echo "wget ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.ver.outputs.tag }}/${{ steps.ver.outputs.artifact_base }}.tar.gz.sha256" echo "" echo "# 校验" echo "sha256sum -c ${{ steps.ver.outputs.artifact_base }}.tar.gz.sha256" echo "" echo "# 解压并安装 (可选)" echo "tar -xzf ${{ steps.ver.outputs.artifact_base }}.tar.gz" echo "cd ${{ steps.ver.outputs.artifact_base }}" echo "sudo ./install.sh # 链接到 /usr/local/bin/iptables-forward" echo "" echo "# 直接运行" echo "sudo ./iptables-forward.sh" echo '```' } > dist/RELEASE_NOTES.md cat dist/RELEASE_NOTES.md - name: Create tag and Release via Gitea API env: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITEA_URL: ${{ github.server_url }} REPO: ${{ github.repository }} TAG: ${{ steps.ver.outputs.tag }} TITLE: ${{ steps.ver.outputs.title }} SHA: ${{ steps.ver.outputs.full_sha }} ARTIFACT_BASE: ${{ steps.ver.outputs.artifact_base }} PRERELEASE: ${{ inputs.prerelease }} DRAFT: ${{ inputs.draft }} run: | set -euo pipefail BODY=$(jq -Rs . < dist/RELEASE_NOTES.md) PAYLOAD=$(jq -nc \ --arg tag "$TAG" \ --arg sha "$SHA" \ --arg name "$TITLE" \ --argjson body "$BODY" \ --argjson prerelease "$PRERELEASE" \ --argjson draft "$DRAFT" \ '{tag_name: $tag, target_commitish: $sha, name: $name, body: $body, prerelease: $prerelease, draft: $draft}') echo "Creating release: $TAG" RESP=$(curl -sSfL -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "${PAYLOAD}" \ "${GITEA_URL}/api/v1/repos/${REPO}/releases") RELEASE_ID=$(echo "$RESP" | jq -r '.id') if [[ -z "${RELEASE_ID}" || "${RELEASE_ID}" == "null" ]]; then echo "::error::创建 Release 失败。响应: ${RESP}" exit 1 fi echo "Release ID: ${RELEASE_ID}" for asset in "dist/${ARTIFACT_BASE}.tar.gz" "dist/${ARTIFACT_BASE}.tar.gz.sha256"; do fname=$(basename "$asset") echo "Uploading $fname" curl -sSfL -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -F "attachment=@${asset};filename=${fname}" \ "${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${fname}" \ > /dev/null done echo "完成: ${GITEA_URL}/${REPO}/releases/tag/${TAG}" - name: Upload artifacts to workflow run (备份) uses: actions/upload-artifact@v3 with: name: ${{ steps.ver.outputs.artifact_base }} path: | dist/${{ steps.ver.outputs.artifact_base }}.tar.gz dist/${{ steps.ver.outputs.artifact_base }}.tar.gz.sha256 dist/RELEASE_NOTES.md retention-days: 30