# Release (GitHub) — pipeline release primaria # # Strategia hosting: # - Repo primario su Gitea (push iniziale) # - Push mirror automatico Gitea -> GitHub # - GitHub esegue TUTTA la CI (Gitea non esegue Actions) # # Trigger: # - push di un tag v* (propagato dal mirror Gitea) # # - run manuale via UI (con input 'tag') name: Release on: push: tags: - 'v*' workflow_dispatch: inputs: tag: description: 'Tag versione (es. v1.7.0)' required: true type: string permissions: contents: write jobs: build: runs-on: windows-latest strategy: fail-fast: false matrix: config: [x86-ansi, x86-unicode, x64-unicode] steps: - name: Checkout uses: actions/checkout@v6 with: submodules: recursive - name: Setup Python uses: actions/setup-python@v6 with: python-version: '3.11' - name: Setup MSBuild uses: microsoft/setup-msbuild@v3 - name: Build ${{ matrix.config }} run: python build_plugin.py --configs ${{ matrix.config }} --verbosity normal - name: Collect DLL shell: pwsh run: | $map = @{ 'x86-ansi' = 'x86-ansi'; 'x86-unicode' = 'x86-unicode'; 'x64-unicode' = 'amd64-unicode' } $dir = $map['${{ matrix.config }}'] $src = "plugins\$dir\nsis7z.dll" $dst = "dll-bundle\$dir" New-Item -ItemType Directory -Force -Path $dst | Out-Null Copy-Item $src $dst - name: Upload DLL uses: actions/upload-artifact@v7 with: name: dll-win-${{ matrix.config }} path: dll-bundle/** if-no-files-found: error build-linux: runs-on: ubuntu-latest strategy: fail-fast: false matrix: config: [x86-ansi, x86-unicode, x64-unicode] steps: - name: Checkout uses: actions/checkout@v6 with: submodules: recursive - name: Setup Python uses: actions/setup-python@v6 with: python-version: '3.11' - name: Install MinGW toolchain run: | sudo apt-get update sudo apt-get install -y \ make \ binutils-mingw-w64-i686 \ binutils-mingw-w64-x86-64 \ g++-mingw-w64-i686 \ g++-mingw-w64-x86-64 - name: Build ${{ matrix.config }} on Linux run: python build_plugin.py --host linux --configs ${{ matrix.config }} --verbose publish: needs: [build, build-linux] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Resolve release tag id: tag shell: bash run: | tag="${{ github.ref_name }}" if [ -z "$tag" ] || [ "$tag" = "main" ]; then tag="${{ inputs.tag }}" fi if [ -z "$tag" ]; then echo "Tag non disponibile. Imposta l'input 'tag' (es. v2.2.1)." >&2 exit 1 fi case "$tag" in v*) ;; *) tag="v$tag" ;; esac echo "tag=$tag" >> "$GITHUB_OUTPUT" - name: Download DLL artifacts uses: actions/download-artifact@v8 with: path: dll-bundle pattern: dll-win-* merge-multiple: true - name: Extract changelog section run: | tag="${{ steps.tag.outputs.tag }}" version="${tag#v}" awk -v ver="[$version]" ' /^## / && index($0, ver) { found=1; next } found && /^## / { exit } found { print } ' CHANGELOG.md > release_body.md echo "" >> release_body.md echo "--" >> release_body.md - name: Package plugins ZIP id: pkg run: | tag="${{ steps.tag.outputs.tag }}" version="${tag#v}" zip_name="${{ github.event.repository.name }}-${version}-bundle.zip" (cd dll-bundle && zip -r "../$zip_name" .) echo "zip_name=$zip_name" >> "$GITHUB_OUTPUT" - name: SHA256SUMS run: | sha256sum "${{ steps.pkg.outputs.zip_name }}" > SHA256SUMS.txt cat SHA256SUMS.txt - name: Create GitHub Release uses: softprops/action-gh-release@v3 with: tag_name: ${{ steps.tag.outputs.tag }} name: ${{ steps.tag.outputs.tag }} files: | ${{ steps.pkg.outputs.zip_name }} SHA256SUMS.txt body_path: release_body.md - name: Update Gitea Release and upload assets env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TAG: ${{ steps.tag.outputs.tag }} ZIP_NAME: ${{ steps.pkg.outputs.zip_name }} run: | python3 - <<'PY' import json import mimetypes import os import urllib.error import urllib.request from pathlib import Path base = "https://gitea.emulab.it/api/v1" repo = "Simone/nsis-plugin-ns7zip" tag = os.environ.get("GITEA_TAG", "").strip() token = os.environ.get("GITEA_TOKEN", "").strip() zip_name = os.environ.get("ZIP_NAME", "").strip() if not tag: raise SystemExit("Tag non disponibile: imposta input 'tag' in workflow_dispatch") if not token: raise SystemExit("GITEA_TOKEN is missing or empty") with open("release_body.md", "r", encoding="utf-8") as f: body = f.read() def request(method, path, payload=None, auth_scheme="token"): data = json.dumps(payload).encode("utf-8") if payload is not None else None req = urllib.request.Request( f"{base}{path}", data=data, method=method, headers={ "Authorization": f"{auth_scheme} {token}", "Content-Type": "application/json", "Accept": "application/json", "User-Agent": "GitHubActions-GiteaReleaseSync/1.0", }, ) with urllib.request.urlopen(req) as resp: raw = resp.read() return json.loads(raw) if raw else {} def upload_asset(rid, path, auth_scheme): p = Path(path) mime = mimetypes.guess_type(p.name)[0] or "application/octet-stream" data = p.read_bytes() boundary = "----FormBoundary7MA4YWxkTrZu0gW" body_parts = ( f"--{boundary}\r\n" f'Content-Disposition: form-data; name="attachment"; filename="{p.name}"\r\n' f"Content-Type: {mime}\r\n\r\n" ).encode() + data + f"\r\n--{boundary}--\r\n".encode() req = urllib.request.Request( f"{base}/repos/{repo}/releases/{rid}/assets?name={p.name}", data=body_parts, method="POST", headers={ "Authorization": f"{auth_scheme} {token}", "Content-Type": f"multipart/form-data; boundary={boundary}", "User-Agent": "GitHubActions-GiteaReleaseSync/1.0", }, ) with urllib.request.urlopen(req) as resp: raw = resp.read() return json.loads(raw) if raw else {} auth_scheme = None last_error = None for scheme in ("token", "Bearer"): try: user = request("GET", "/user", auth_scheme=scheme) auth_scheme = scheme print(f"Gitea auth OK via '{scheme}' as user: {user.get('login', '')}") break except urllib.error.HTTPError as e: try: details = e.read().decode("utf-8", errors="replace") except Exception: details = "" last_error = f"HTTP {e.code} {e.reason} ({scheme}) {details}".strip() if not auth_scheme: raise SystemExit( "Gitea authentication failed for both 'token' and 'Bearer'. " "Check secret GITEA_TOKEN value and permissions (Repository Read+Write). " f"Last error: {last_error}" ) try: release = request("GET", f"/repos/{repo}/releases/tags/{tag}", auth_scheme=auth_scheme) rid = release["id"] request("PATCH", f"/repos/{repo}/releases/{rid}", {"body": body}, auth_scheme=auth_scheme) print(f"Updated existing Gitea release id={rid} for tag {tag}") except urllib.error.HTTPError as e: if e.code != 404: try: details = e.read().decode("utf-8", errors="replace") except Exception: details = "" raise SystemExit(f"Gitea release lookup/update failed: HTTP {e.code} {e.reason}. {details}".strip()) created = request( "POST", f"/repos/{repo}/releases", { "tag_name": tag, "name": tag, "body": body, "draft": False, "prerelease": False, }, auth_scheme=auth_scheme, ) rid = created["id"] print(f"Created Gitea release id={rid} for tag {tag}") # Rimuovi asset già presenti con lo stesso nome (retag idempotente) existing = request("GET", f"/repos/{repo}/releases/{rid}/assets", auth_scheme=auth_scheme) upload_names = {zip_name, "SHA256SUMS.txt"} for asset in existing: if asset["name"] in upload_names: request("DELETE", f"/repos/{repo}/releases/{rid}/assets/{asset['id']}", auth_scheme=auth_scheme) print(f"Deleted existing asset: {asset['name']}") # Carica ZIP e SHA256SUMS for f in [zip_name, "SHA256SUMS.txt"]: if Path(f).exists(): result = upload_asset(rid, f, auth_scheme) print(f"Uploaded {f} -> {result.get('browser_download_url', result.get('id'))}") else: print(f"WARNING: {f} not found, skipping upload") PY - name: Push back dist/ DLLs to Gitea env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} run: | tag="${{ steps.tag.outputs.tag }}" # Copia le DLL compilate in dist/ for dir in amd64-unicode x86-ansi x86-unicode; do mkdir -p "dist/$dir" cp "dll-bundle/$dir/nsis7z.dll" "dist/$dir/nsis7z.dll" done # Configura git con le credenziali Gitea git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git remote add gitea "https://x-token:${GITEA_TOKEN}@gitea.emulab.it/Simone/nsis-plugin-ns7zip.git" git add dist/ git diff --cached --quiet || git commit -m "chore: update dist/ DLLs for ${tag} [skip ci]" git push gitea HEAD:main