name: Docker Build and Push permissions: contents: write packages: write on: workflow_dispatch: inputs: branch: type: choice description: Target branch options: [develop, experimental, master] default: develop choice: type: choice description: Version bump options: [none, major, minor, patch] default: none push: branches: [develop, experimental, master] # Avoid multiple parallel builds for the same branch to prevent race conditions concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false # Set to true if you want to abort old builds immediately jobs: docker-build: runs-on: ubuntu-latest if: >- (github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/experimental' || github.ref == 'refs/heads/master') && (github.event_name != 'push' || github.actor != 'github-actions[bot]') steps: - name: Checkout code uses: actions/checkout@v5 with: fetch-depth: 0 # Ensure we always have the right branch context ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || github.ref }} - name: Install Rust Tools (Binaries) uses: taiki-e/install-action@v2 with: tool: cross,trunk,wasm-bindgen-cli,cargo-edit,mdbook - name: Install Rust Toolchain uses: dtolnay/rust-toolchain@stable with: targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl,wasm32-unknown-unknown - name: Install wasm tools 128 run: | chmod +x ./bin/install_wasm_tools.sh WASM_TOOLS_BIN="$(./bin/install_wasm_tools.sh 128)" echo "${WASM_TOOLS_BIN}" >> "$GITHUB_PATH" - name: Rust Cache uses: Swatinem/rust-cache@v2 with: cache-targets: "true" - name: Handle Version Bump run: | chmod +x ./bin/inc_version.sh if [[ "${{ github.event_name }}" == "push" ]]; then case "${{ github.ref_name }}" in develop) ./bin/inc_version.sh ;; experimental) echo "🔬 Experimental builds do not bump versions; keeping version from develop." ;; master) echo "Master builds use the version already committed in the merged PR; skipping version bump." ;; *) echo "Unsupported push branch: ${{ github.ref_name }}" >&2; exit 1 ;; esac else if [[ "${{ github.event.inputs.branch }}" == "master" || "${{ github.event.inputs.branch }}" == "experimental" ]]; then echo "Manual ${{ github.event.inputs.branch }} builds skip version bump." else case "${{ github.event.inputs.choice }}" in major) ./bin/inc_version.sh m ;; minor) ./bin/inc_version.sh p ;; patch) ./bin/inc_version.sh ;; esac fi fi - name: Read project version id: version run: | VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' ./Cargo.toml) echo "value=${VERSION}" >> "$GITHUB_OUTPUT" - name: Enforce zero patch for master releases if: >- (github.event_name == 'workflow_dispatch' && github.event.inputs.branch == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master') run: | VERSION="${{ steps.version.outputs.value }}" IFS='.' read -r major minor patch <<< "${VERSION}" if [[ -z "${major}" || -z "${minor}" || -z "${patch}" ]]; then echo "🧨 Invalid semantic version: ${VERSION}" >&2 exit 1 fi if [[ "${patch}" != "0" ]]; then echo "🧨 Refusing master release for non-release version ${VERSION}. Patch component must be 0." >&2 exit 1 fi - name: Set up QEMU uses: docker/setup-qemu-action@v4.2.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4.2.0 - name: Cache built resources id: cache-resources uses: actions/cache@v5 with: path: resources/*.ts key: ${{ runner.os }}-resources-${{ hashFiles('resources/*.jpg') }} - name: Install ffmpeg if: steps.cache-resources.outputs.cache-hit != 'true' run: | sudo apt-get update sudo apt-get install -y ffmpeg - name: Build TS resources if: steps.cache-resources.outputs.cache-hit != 'true' run: | chmod +x ./bin/build_resources.sh ./bin/build_resources.sh - name: Verify TS resources run: | required_resources=( "channel_unavailable" "user_connections_exhausted" "provider_connections_exhausted" "user_account_expired" "panel_api_provisioning" ) for resource in "${required_resources[@]}"; do if [[ ! -f "resources/${resource}.ts" ]]; then echo "🧨 Missing resource: resources/${resource}.ts" exit 1 fi done - name: Commit version bump if: >- (github.event_name == 'push' && github.ref == 'refs/heads/develop') || (github.event_name == 'workflow_dispatch' && github.event.inputs.branch == 'develop' && github.event.inputs.choice != 'none') id: version_commit run: | git config user.name "euzu" git config user.email "euzu@proton.me" git add . if git diff --cached --quiet; then echo "did_commit=false" >> "$GITHUB_OUTPUT" else VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' ./Cargo.toml | head -n1) git commit -m "ci: bump version v$VERSION" # Delete local tag if it exists (from a previous failed run) git tag -d "v$VERSION" 2>/dev/null || true git tag -a "v$VERSION" -m "v$VERSION" echo "did_commit=true" >> "$GITHUB_OUTPUT" fi - name: Build and push Docker images env: GITHUB_IO_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO_OWNER: ${{ github.repository_owner }} run: | chmod +x ./bin/build_docker.sh ./bin/build_docker.sh ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || github.ref_name }} - name: Verify release bundle artifacts run: | ls -la artifacts for arch in x86_64-unknown-linux-musl aarch64-unknown-linux-musl; do artifact="artifacts/tuliprox-v${{ steps.version.outputs.value }}-${arch}.tar.gz" test -f "${artifact}" test -f "${artifact}.sha256" for required_path in tuliprox/tuliprox tuliprox/web/ tuliprox/resources/; do tar -tzf "${artifact}" | grep -qx "${required_path}" done done for sum_file in artifacts/*.sha256; do (cd artifacts && sha256sum -c "$(basename "$sum_file")") done - name: Build release checksum notes run: | { echo "## Release Bundle Checksums" echo echo "The checksums below were validated in CI via \`sha256sum -c\` before upload." echo echo "| File | SHA256 |" echo "| --- | --- |" for sum_file in artifacts/*.sha256; do checksum=$(awk '{print $1}' "$sum_file") filename=$(awk '{print $2}' "$sum_file") echo "| \`$filename\` | \`$checksum\` |" done echo echo "Verification command after download:" echo "\`\`\`bash" echo "sha256sum -c *.sha256" echo "\`\`\`" } > artifacts/RELEASE_NOTES_SHA256.md - name: Upload release bundle artifacts uses: actions/upload-artifact@v4 with: name: tuliprox-release-v${{ steps.version.outputs.value }} path: artifacts/* if-no-files-found: error - name: Push version bump if: >- success() && ( (github.event_name == 'push' && github.ref == 'refs/heads/develop') || (github.event_name == 'workflow_dispatch' && github.event.inputs.branch == 'develop') ) && steps.version_commit.outputs.did_commit == 'true' run: | # Use origin HEAD to push to the correct remote branch, and push tags git push origin HEAD --tags - name: Publish release bundles to GitHub Release if: >- success() && ( (github.event_name == 'workflow_dispatch' && github.event.inputs.branch == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master') ) uses: softprops/action-gh-release@v2 with: tag_name: v${{ steps.version.outputs.value }} name: v${{ steps.version.outputs.value }} files: | artifacts/tuliprox-v${{ steps.version.outputs.value }}-x86_64-unknown-linux-musl.tar.gz artifacts/tuliprox-v${{ steps.version.outputs.value }}-x86_64-unknown-linux-musl.tar.gz.sha256 artifacts/tuliprox-v${{ steps.version.outputs.value }}-aarch64-unknown-linux-musl.tar.gz artifacts/tuliprox-v${{ steps.version.outputs.value }}-aarch64-unknown-linux-musl.tar.gz.sha256 body_path: artifacts/RELEASE_NOTES_SHA256.md generate_release_notes: true