name: CI on: push: branches: [ "main", "ci-test" ] tags: [ "v*" ] pull_request: branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest container: image: ubuntu:26.04 options: --privileged --cap-add SYS_ADMIN --security-opt apparmor:unconfined steps: - name: Set up container image run: | apt-get update apt-get install -y nodejs sudo curl wget ca-certificates build-essential - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - name: Check format run: cargo fmt --check - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y pkg-config libssl-dev libgpg-error-dev libgpgme-dev - name: Restore cargo/target cache uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ci-build-${{ github.sha }} restore-keys: | ci-build- - name: Build run: cargo build env: RUSTFLAGS: -Dwarnings - name: Lint run: cargo clippy --all-targets --all-features env: RUSTFLAGS: -Dwarnings test: # Disabled: test suite is too heavy for current CI infra (CPU/RAM exhaustion). # Re-enable by removing the `if: false` line below. if: false needs: build runs-on: ubuntu-latest container: image: ubuntu:26.04 options: --privileged --cap-add SYS_ADMIN --security-opt apparmor:unconfined steps: - name: Set up container image run: | apt-get update apt-get install -y nodejs sudo curl wget ca-certificates build-essential - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - name: Restore cargo/target cache uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ci-build-${{ github.sha }} restore-keys: | ci-build- - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y pkg-config libssl-dev libgpg-error-dev libgpgme-dev - name: Install runtime system dependencies run: | sudo apt-get update sudo apt-get install -y git pristine-tar mmdebstrap util-linux dpkg-dev - name: Setup subuid/subgid run: | usermod --add-subuids 100000-200000 --add-subgids 100000-200000 ${USER:-root} - name: Run tests with verbose logging (timeout 30min) env: RUST_LOG: debug run: timeout 30m cargo test -- --nocapture snap: needs: build runs-on: ubuntu-latest container: # Official snapcraft image: an Ubuntu userland matching the snap base, # with snapcraft preinstalled (no snapd/systemd/LXD required). image: ghcr.io/canonical/snapcraft:8_core24 steps: - name: Install build prerequisites run: | apt-get update -q apt-get install -y -q --no-install-recommends git curl nodejs - uses: actions/checkout@v6 - name: Build snap run: | # GitHub Actions overrides the image entrypoint (pebble), which is # what normally puts craftctl on PATH; restore it, allow git to run # in the checked-out tree, and provide rustup for the rust plugin. git config --global --add safe.directory '*' curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable export PATH="/usr/libexec/snapcraft:$HOME/.cargo/bin:$PATH" snapcraft pack --destructive-mode - name: Upload snap artifact # v4 refuses to run outside github.com (GHESNotSupportedError); Gitea # implements the artifact API used by v3, so v3 is the supported choice. uses: actions/upload-artifact@v3 with: name: snap path: ./*.snap if-no-files-found: error release: # Publishes the release artifacts on a release tag (a v* tag without # a prerelease suffix — RC tags publish nothing, like the crates.io # job): the upstream tarball, the packed snap, and their checksums. # Gitea's automatic per-tag archive is a git-tree snapshot named # after the raw tag; this produces the conventional upstream shape # (pkh-.tar.gz, versioned pkh-/ prefix) attached # to a release page. Nothing is published beyond this repo: no # crates.io (see the publish job), no snap store. The Debian # packaging renames the tarball to .orig.tar.gz on import, as # gbp/uscan always do for upstream releases. if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-') needs: [build, snap] runs-on: ubuntu-latest container: image: ubuntu:26.04 options: --privileged --cap-add SYS_ADMIN --security-opt apparmor:unconfined steps: - name: Set up container image run: | apt-get update apt-get install -y nodejs curl ca-certificates git - uses: actions/checkout@v6 - uses: actions/download-artifact@v3 with: name: snap - name: Build the upstream tarball run: | tag="${GITHUB_REF_NAME#v}" git archive --format=tar.gz --prefix="pkh-${tag}/" \ -o "pkh-${tag}.tar.gz" HEAD sha256sum "pkh-${tag}.tar.gz" pkh_*_amd64.snap > sha256sums.txt - name: Create or get the release id: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" tag="${GITHUB_REF_NAME#v}" body="{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"pkh $tag\",\"body\":\"Release artifacts for $tag: the source tarball, the packed snap and their checksums.\",\"draft\":false,\"prerelease\":false}" release_id=$(curl -sf -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Content-Type: application/json" \ -d "$body" "$api/releases" \ | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true) if [ -z "$release_id" ]; then # Already released (e.g. a re-push of the tag): reuse it. release_id=$(curl -sf "$api/releases/tags/$tag" \ | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) fi [ -n "$release_id" ] || { echo "cannot resolve the release id" >&2; exit 1; } echo "release_id=$release_id" >> "$GITHUB_OUTPUT" - name: Upload the release assets env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_ID: ${{ steps.release.outputs.release_id }} run: | api="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" for f in pkh-*.tar.gz pkh_*_amd64.snap sha256sums.txt; do echo "attaching $f" curl -sf -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ -F "attachment=@$f" \ "$api/releases/$RELEASE_ID/assets?name=$(basename "$f")" done publish: # Publishes the crate to crates.io on a release tag (a v* tag without # a prerelease suffix: v0.2.0 publishes, v0.2.0-rc1 does not — release # candidates are never published). Trusted publishing # (OIDC) is GitHub-Actions-only, so authentication goes through a # crates.io API token stored as the CARGO_REGISTRY_TOKEN secret, # scoped to the pkh crate. if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-') needs: build runs-on: ubuntu-latest container: image: ubuntu:26.04 options: --privileged --cap-add SYS_ADMIN --security-opt apparmor:unconfined steps: - name: Set up container image run: | apt-get update apt-get install -y nodejs sudo curl wget ca-certificates build-essential - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - name: Install build dependencies run: | sudo apt-get update sudo apt-get install -y pkg-config libssl-dev libgpg-error-dev libgpgme-dev - name: Check the tag matches the crate version # cargo publish ships whatever version Cargo.toml declares, # regardless of the tag: a mismatch must fail loudly instead of # publishing the wrong version under the release tag. run: | crate_version="$(awk -F'"' '/^version =/{print $2; exit}' Cargo.toml)" tag_version="${GITHUB_REF_NAME#v}" if [ "$crate_version" != "$tag_version" ]; then echo "tag $GITHUB_REF_NAME does not match crate version $crate_version" >&2 exit 1 fi - name: Publish run: cargo publish env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}