name: Distribute Release # Auto-pushes new releases to package managers. Triggers on: # - tag push (e.g. v5.9.21) — the normal release flow # - workflow_dispatch — manual run for testing or backports # # Each step that needs a secret checks for it and skips gracefully with a # clear warning if it's not set, so the workflow can be merged and tested # before secrets are configured. on: push: tags: ['v*'] workflow_dispatch: inputs: version: description: 'Override version (e.g. 5.9.21). Leave blank to use tag.' required: false type: string permissions: contents: read jobs: version: name: Resolve version runs-on: ubuntu-22.04 if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') outputs: version: ${{ steps.v.outputs.version }} steps: - id: v run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ inputs.version }}" ]; then echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT else echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT fi - run: echo "Distributing v${{ steps.v.outputs.version }}" docker: name: Docker Hub needs: version if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-22.04 permissions: contents: read packages: write env: DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} VERSION: ${{ needs.version.outputs.version }} steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub run: | if [ -z "$DOCKERHUB_TOKEN" ]; then echo "::warning::DOCKERHUB_TOKEN secret not set — skipping Docker push. Add it at Settings → Secrets → Actions." exit 0 fi echo "$DOCKERHUB_TOKEN" | docker login -u samiahmed7777 --password-stdin - name: Wait for release artifacts run: | # The Dockerfile downloads the daemon .deb from the release URL. # On tag-push the release is created first, but the assets get # uploaded a few seconds/minutes later by the build job — without # this wait, the Docker build races and fails with curl 22 / 404 # (saw this on v5.9.24 run #24, dist #24, Docker Hub job # step #5 — release was published 8 min after the workflow fired). for i in {1..90}; do URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/cryptographic-triangles-daemon_${VERSION}_amd64.deb" if curl -fsSL --head "$URL" >/dev/null 2>&1; then echo "✓ Release .deb available: $URL" exit 0 fi echo " waiting for release v${VERSION} daemon .deb... ($i/90)" sleep 20 done echo "::error::Release v${VERSION} daemon .deb never became available after 30 minutes" exit 1 - name: Build and push run: | if [ -z "$DOCKERHUB_TOKEN" ]; then exit 0; fi docker buildx build \ --push \ --tag samiahmed7777/trianglesd:$VERSION \ --tag samiahmed7777/trianglesd:latest \ --cache-from type=gha \ --cache-to type=gha,mode=max \ --provenance=false \ ./packaging/docker - name: Verify pushed image run: | if [ -z "$DOCKERHUB_TOKEN" ]; then exit 0; fi docker pull samiahmed7777/trianglesd:$VERSION echo "--- trianglesd -version ---" docker run --rm samiahmed7777/trianglesd:$VERSION trianglesd -version 2>&1 | head -3 echo "--- triangles-cli getinfo (will fail without RPC, expected) ---" docker run --rm samiahmed7777/trianglesd:$VERSION triangles-cli getinfo 2>&1 | head -3 aur: name: AUR (triangles-qt-bin) needs: version if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-22.04 container: image: archlinux:latest options: --privileged env: AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }} VERSION: ${{ needs.version.outputs.version }} steps: - uses: actions/checkout@v4 - name: Check AUR_SSH_KEY run: | if [ -z "$AUR_SSH_KEY" ]; then echo "::warning::AUR_SSH_KEY secret not set — skipping AUR push. Add it at Settings → Secrets → Actions." echo "::warning::The key should be the contents of ~/.ssh/aur_key (private key, not .pub)." fi - name: Install build tools + create non-root user if: env.AUR_SSH_KEY != '' run: | pacman -Syu --noconfirm --needed git openssh base-devel python sudo # makepkg refuses to run as root — create a build user useradd -m -s /bin/bash build echo 'build ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers chown -R build:build "$GITHUB_WORKSPACE" - name: Wait for release artifacts if: env.AUR_SSH_KEY != '' run: | for i in {1..90}; do URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/cryptographic-triangles_${VERSION}_amd64.deb" if curl -fsSL --head "$URL" >/dev/null 2>&1; then echo "✓ Release .deb available: $URL" exit 0 fi echo " waiting for release v${VERSION}... ($i/90)" sleep 20 done echo "::error::Release v${VERSION} .deb never became available after 30 minutes" exit 1 - name: Download source .debs if: env.AUR_SSH_KEY != '' run: | cd /tmp curl -fsSL -o full.deb "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/cryptographic-triangles_${VERSION}_amd64.deb" curl -fsSL -o daemon.deb "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/cryptographic-triangles-daemon_${VERSION}_amd64.deb" ls -la /tmp/*.deb sha256sum /tmp/full.deb /tmp/daemon.deb - name: Update PKGBUILD with version + SHA256s if: env.AUR_SSH_KEY != '' run: | cp "$GITHUB_WORKSPACE/packaging/aur/PKGBUILD" /tmp/PKGBUILD chown build:build /tmp/PKGBUILD /tmp/full.deb /tmp/daemon.deb sudo -u build bash -c ' set -e cd /tmp FULL_SHA=$(sha256sum full.deb | awk "{print \$1}") DAEMON_SHA=$(sha256sum daemon.deb | awk "{print \$1}") echo "version='"$VERSION"' full=$FULL_SHA daemon=$DAEMON_SHA" python3 - < .SRCINFO echo "--- generated .SRCINFO ---" cat .SRCINFO ' - name: Setup SSH key for AUR if: env.AUR_SSH_KEY != '' run: | mkdir -p /home/build/.ssh printf '%s\n' "$AUR_SSH_KEY" > /home/build/.ssh/aur_key chmod 600 /home/build/.ssh/aur_key ssh-keyscan -t ed25519 aur.archlinux.org > /home/build/.ssh/known_hosts 2>/dev/null chown -R build:build /home/build/.ssh - name: Clone AUR repo if: env.AUR_SSH_KEY != '' run: | sudo -u build bash -c ' cd /tmp GIT_SSH_COMMAND="ssh -i ~/.ssh/aur_key -o IdentitiesOnly=yes" \ git clone ssh://aur@aur.archlinux.org/triangles-qt-bin.git ls -la /tmp/triangles-qt-bin ' - name: Stage updated files if: env.AUR_SSH_KEY != '' run: | cp /tmp/PKGBUILD /tmp/triangles-qt-bin/PKGBUILD cp /tmp/.SRCINFO /tmp/triangles-qt-bin/.SRCINFO cp "$GITHUB_WORKSPACE/packaging/aur/triangles-qt.desktop" /tmp/triangles-qt-bin/triangles-qt.desktop chown -R build:build /tmp/triangles-qt-bin sudo -u build bash -c ' cd /tmp/triangles-qt-bin git --no-pager diff --stat ' - name: Commit and push to AUR if: env.AUR_SSH_KEY != '' run: | sudo -u build bash -c ' cd /tmp/triangles-qt-bin git config user.name "Sami Ahmed" git config user.email "SamiAhmed7777@users.noreply.github.com" git add PKGBUILD .SRCINFO triangles-qt.desktop if git diff --cached --quiet; then echo "No changes to commit (AUR already at this version)" exit 0 fi git commit -m "triangles-qt-bin '"$VERSION"'-1" GIT_SSH_COMMAND="ssh -i ~/.ssh/aur_key -o IdentitiesOnly=yes" \ git push origin master ' - name: ✓ Summary if: always() run: | if [ -z "$AUR_SSH_KEY" ]; then echo "::notice::AUR job was skipped because AUR_SSH_KEY is not set." else echo "::notice::AUR distribution completed." fi homebrew: name: Homebrew tap (SamiAhmed7777/homebrew-triangles) needs: version if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-22.04 env: HOMEBREW_GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_TOKEN }} VERSION: ${{ needs.version.outputs.version }} steps: - name: Check HOMEBREW_GITHUB_TOKEN run: | if [ -z "$HOMEBREW_GITHUB_TOKEN" ]; then echo "::warning::HOMEBREW_GITHUB_TOKEN secret not set — skipping Homebrew push. Add it at Settings → Secrets → Actions." echo "::warning::Use a GitHub PAT with 'repo' scope for SamiAhmed7777/homebrew-triangles." fi - name: Wait for release artifacts if: env.HOMEBREW_GITHUB_TOKEN != '' run: | for i in {1..90}; do URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/Cryptographic-Triangles-v${VERSION}-macos-arm64.dmg" if curl -fsSL --head "$URL" >/dev/null 2>&1; then echo "✓ Release .dmg available: $URL" exit 0 fi echo " waiting for release v${VERSION}... ($i/90)" sleep 20 done echo "::error::Release v${VERSION} macOS .dmg never became available after 30 minutes" exit 1 - name: Compute macOS .dmg SHA256 if: env.HOMEBREW_GITHUB_TOKEN != '' id: sha run: | curl -fsSL -o /tmp/triangles.dmg \ "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/Cryptographic-Triangles-v${VERSION}-macos-arm64.dmg" SHA=$(sha256sum /tmp/triangles.dmg | awk '{print $1}') echo "sha=$SHA" >> $GITHUB_OUTPUT echo "macOS .dmg SHA256: $SHA" - name: Clone homebrew-triangles if: env.HOMEBREW_GITHUB_TOKEN != '' run: | git clone https://x-access-token:$HOMEBREW_GITHUB_TOKEN@github.com/SamiAhmed7777/homebrew-triangles.git /tmp/homebrew-triangles cd /tmp/homebrew-triangles git --no-pager log --oneline | head -3 - name: Update Formula and Cask if: env.HOMEBREW_GITHUB_TOKEN != '' env: VERSION: ${{ needs.version.outputs.version }} SHA: ${{ steps.sha.outputs.sha }} run: | cd /tmp/homebrew-triangles # Update Casks/cryptographic-triangles.rb python3 - </dev/null 2>&1; then echo "✓ Release .exe available: $URL" exit 0 fi echo " waiting for release v${VERSION}... ($i/90)" sleep 20 done echo "::error::Release v${VERSION} Windows installer never became available after 30 minutes" exit 1 - name: Compute installer SHA256 if: env.CHOCO_API_KEY != '' && env.CHOCO_SKIP_WACATAC != '' shell: bash id: sha run: | curl -fsSL -o /tmp/triangles-setup.exe \ "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/Cryptographic-Triangles-${VERSION}-win-x64-setup.exe" SHA=$(sha256sum /tmp/triangles-setup.exe | awk '{print $1}') echo "sha=$SHA" >> $GITHUB_OUTPUT echo "Chocolatey installer SHA256: $SHA" - name: Update nuspec version if: env.CHOCO_API_KEY != '' && env.CHOCO_SKIP_WACATAC != '' shell: bash working-directory: ${{ github.workspace }}/packaging/chocolatey run: | python3 -c " import re with open('triangles.nuspec') as f: c = f.read() c = re.sub(r'[\d.]+', f'${VERSION}', c) with open('triangles.nuspec', 'w') as f: f.write(c) print('updated nuspec version to', '${VERSION}') " grep -E "|" triangles.nuspec - name: Update nuspec version + install script SHA if: env.CHOCO_API_KEY != '' && env.CHOCO_SKIP_WACATAC != '' shell: bash working-directory: ${{ github.workspace }}/packaging/chocolatey run: | python3 -c " import re with open('triangles.nuspec') as f: c = f.read() c = re.sub(r'[\d.]+', f'${VERSION}', c) with open('triangles.nuspec', 'w') as f: f.write(c) with open('tools/chocolateyInstall.ps1') as f: c = f.read() c = c.replace('__CHECKSUM_PLACEHOLDER__', '${{ steps.sha.outputs.sha }}') with open('tools/chocolateyInstall.ps1', 'w') as f: f.write(c) print('updated nuspec version + install script checksum') " grep -E "|" triangles.nuspec grep checksum64 tools/chocolateyInstall.ps1 - name: Pack Chocolatey package if: env.CHOCO_API_KEY != '' && env.CHOCO_SKIP_WACATAC != '' shell: pwsh working-directory: ${{ github.workspace }}/packaging/chocolatey run: | choco pack Get-ChildItem *.nupkg - name: Push to Chocolatey if: env.CHOCO_API_KEY != '' && env.CHOCO_SKIP_WACATAC != '' shell: pwsh working-directory: ${{ github.workspace }}/packaging/chocolatey run: | $apiKey = [System.Environment]::GetEnvironmentVariable('CHOCO_API_KEY', 'Process') choco apikey add --key="$apiKey" --source='https://push.chocolatey.org/' Get-ChildItem *.nupkg | ForEach-Object { Write-Host "Pushing $($_.Name)..." choco push $_.Name --source='https://push.chocolatey.org/' } - name: ✓ Summary if: always() shell: bash run: | if [ -z "$CHOCO_API_KEY" ]; then echo "::notice::Chocolatey job skipped (CHOCO_API_KEY not set)." elif [ -n "$CHOCO_SKIP_WACATAC" ]; then echo "::notice::Chocolatey job skipped (Wacatac detection still active). Set CHOCO_SKIP_WACATAC='' and re-run after Microsoft clears the false-positive." else echo "::notice::Chocolatey push completed (subject to moderator review)." fi winget: name: WinGet (CryptographicTriangles.TrianglesQt) needs: version if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-22.04 env: WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }} VERSION: ${{ needs.version.outputs.version }} steps: - uses: actions/checkout@v4 - name: Check WINGET_TOKEN run: | if [ -z "$WINGET_TOKEN" ]; then echo "::warning::WINGET_TOKEN not set — skipping WinGet PR. Add a GitHub PAT with 'public_repo' scope at Settings → Secrets → Actions." fi - name: Wait for release artifacts if: env.WINGET_TOKEN != '' run: | for i in {1..90}; do URL="https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/Cryptographic-Triangles-${VERSION}-win-x64-setup.exe" if curl -fsSL --head "$URL" >/dev/null 2>&1; then echo "✓ Release .exe available: $URL" exit 0 fi echo " waiting for release v${VERSION}... ($i/90)" sleep 20 done echo "::error::Release v${VERSION} Windows installer never became available after 30 minutes" exit 1 - name: Compute installer SHA256 if: env.WINGET_TOKEN != '' id: sha run: | curl -fsSL -o /tmp/triangles-setup.exe \ "https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${VERSION}/Cryptographic-Triangles-${VERSION}-win-x64-setup.exe" SHA=$(sha256sum /tmp/triangles-setup.exe | awk '{print $1}') echo "sha=$SHA" >> $GITHUB_OUTPUT echo "WinGet installer SHA256: $SHA" - name: "Pre-flight check for existing failed WinGet PRs" if: env.WINGET_TOKEN != '' env: GH_TOKEN: ${{ secrets.WINGET_TOKEN }} run: | set -e # Don't pile up PRs if previous ones still have author-action-needed flags. # winget-pkgs moderators can read repeated unfixed failures as spam. # Skip the PR for this release if any existing SamiAhmed7777 PR against # microsoft/winget-pkgs has a blocker label. echo "Checking existing open PRs from SamiAhmed7777 on microsoft/winget-pkgs..." BLOCKING=$(gh api -X GET \ 'repos/microsoft/winget-pkgs/issues?state=open&labels=PullRequest-Error,Needs-Author-Feedback&per_page=30' \ --jq '.[] | select(.user.login=="SamiAhmed7777") | "#\(.number) [\(.state)] \(.title)"' \ || echo "") if [ -n "$BLOCKING" ]; then echo "::error::Existing WinGet PR(s) with blocker labels — fix or close those first:" echo "$BLOCKING" echo "::error::Aborting this WinGet submission to avoid piling up failed PRs." exit 1 fi echo "✓ No blocker-labelled PRs found — safe to submit." - name: Fork + update WinGet manifest + open PR if: env.WINGET_TOKEN != '' env: GH_TOKEN: ${{ secrets.WINGET_TOKEN }} SHA: ${{ steps.sha.outputs.sha }} PUBLISHER_INITIAL: c PACKAGE_ID: CryptographicTriangles.TrianglesQt PACKAGE_SHORT: TrianglesQt INSTALLER_URL: https://github.com/SamiAhmed7777/triangles_v5/releases/download/v${{ env.VERSION }}/Cryptographic-Triangles-${{ env.VERSION }}-win-x64-setup.exe run: | set -e # Install gh + jq if missing which gh >/dev/null 2>&1 || (curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null && sudo apt update && sudo apt install -y gh jq) # Skip if a PR for THIS version already exists (avoid duplicate submissions). echo "Checking for existing PR for version ${VERSION}..." if gh api 'repos/microsoft/winget-pkgs/pulls?state=open&per_page=30' \ --jq ".[] | select(.head.ref | startswith(\"triangles-${VERSION}-\")) | .number" \ | grep -q .; then echo "::notice::PR for v${VERSION} already exists — skipping to avoid duplicate." exit 0 fi echo "✓ No existing PR for v${VERSION}." VERSION="$VERSION" # Path convention (winget-pkgs): lowercase first letter of publisher, # then publisher folder (PascalCase), then short package folder name. # Example: manifests/c/CryptographicTriangles/TrianglesQt/5.9.20/ MANIFEST_DIR="manifests/$PUBLISHER_INITIAL/CryptographicTriangles/$PACKAGE_SHORT/$VERSION" # TrianglesQt is built with NSIS (Nullsoft). Standard silent flag is /S. # If the installer tech ever changes, update InstallerSwitches here. NSIS_SILENT="/S" # 1. Clone the winget-pkgs repo (Sami's fork) — auto-create fork if needed echo "Forking microsoft/winget-pkgs..." GH_REPO="SamiAhmed7777/winget-pkgs" if ! gh repo view "$GH_REPO" >/dev/null 2>&1; then gh repo fork microsoft/winget-pkgs --remote=false || true fi rm -rf winget-pkgs git clone --depth 1 "https://x-access-token:${WINGET_TOKEN}@github.com/${GH_REPO}.git" winget-pkgs cd winget-pkgs git config user.name "Sami Ahmed" git config user.email "SamiAhmed7777@users.noreply.github.com" BRANCH="triangles-${VERSION}-${{ github.run_number }}" git checkout -b "$BRANCH" mkdir -p "$MANIFEST_DIR" # 2. Generate the three manifest files (winget-pkgs schema 1.12.0) # # Schema rules (see doc/manifest/schema/1.12.0/*.md and # doc/ValidationFailureGuide.md): # - version file: PackageIdentifier, PackageVersion, DefaultLocale # (NOT PackageLocale — that's the old field name), ManifestType # "version", ManifestVersion "1.12.0" # - defaultLocale file: Publisher, PackageName, License, # ShortDescription are REQUIRED (no Publisher in version file) # - installer file: InstallModes array (not "InstallerMode: # interactive" — that's the old field name); ManifestVersion 1.12.0 # - All files: include # yaml-language-server: $schema=... comment # for editor + validator support SCHEMA_BASE="https://raw.githubusercontent.com/microsoft/winget-cli/master/schemas/JSON/manifests/v1.12.0" cat > "$MANIFEST_DIR/${PACKAGE_ID}.yaml" < "$MANIFEST_DIR/${PACKAGE_ID}.locale.en-US.yaml" < "$MANIFEST_DIR/${PACKAGE_ID}.installer.yaml" <