Upload to Launchpad PPA #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # .github/workflows/launchpad_ppa.yml | |
| name: Upload to Launchpad PPA | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_tag: | |
| description: 'Release tag to upload to PPA (e.g. v0.5.0). Empty = latest release' | |
| required: false | |
| distributions: | |
| description: 'Target distributions (comma-separated: jammy,noble,resolute)' | |
| required: false | |
| default: 'jammy,noble,resolute' | |
| permissions: | |
| contents: read | |
| jobs: | |
| upload-ppa: | |
| name: Upload to PPA (${{ matrix.distro }}) | |
| runs-on: ubuntu-latest | |
| environment: packaging | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - distro: jammy | |
| vendor_rust: "1.92.0" | |
| toolchain_source: "Requires backend-ai PPA dependency on ~lablup/+archive/ubuntu/rustc-release" | |
| - distro: noble | |
| vendor_rust: "1.92.0" | |
| toolchain_source: "Requires backend-ai PPA dependency on ~lablup/+archive/ubuntu/rustc-release" | |
| - distro: resolute | |
| vendor_rust: "1.92.0" | |
| toolchain_source: "Uses the official Ubuntu 26.04 archive Rust 1.92+ toolchain" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check distribution | |
| id: check_distro | |
| run: | | |
| DISTRIBUTIONS="${{ github.event.inputs.distributions }}" | |
| if [[ ",$DISTRIBUTIONS," == *",${{ matrix.distro }},"* ]]; then | |
| echo "should_run=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "should_run=false" >> "$GITHUB_OUTPUT" | |
| echo "Skipping ${{ matrix.distro }} - not in requested distributions" | |
| fi | |
| - name: Get release tag | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| id: get_tag | |
| run: | | |
| if [ -n "${{ github.event.inputs.release_tag }}" ]; then | |
| echo "tag=${{ github.event.inputs.release_tag }}" >> "$GITHUB_OUTPUT" | |
| else | |
| TAG=$(gh release list --limit 1 --json tagName -q '.[0].tagName') | |
| echo "tag=${TAG}" >> "$GITHUB_OUTPUT" | |
| fi | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Preserve current packaging overlay | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| rm -rf /tmp/bssh-packaging | |
| mkdir -p /tmp/bssh-packaging | |
| cp -a debian /tmp/bssh-packaging/debian | |
| - name: Check out release tag | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.get_tag.outputs.tag }} | |
| fetch-depth: 0 | |
| - name: Restore current packaging overlay | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| rm -rf debian | |
| cp -a /tmp/bssh-packaging/debian debian | |
| - name: Describe Rust toolchain source | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| echo "Target distro: ${{ matrix.distro }}" | |
| echo "Vendoring toolchain: Rust ${{ matrix.vendor_rust }}" | |
| echo "Launchpad build toolchain: ${{ matrix.toolchain_source }}" | |
| - name: Update debian/changelog from release | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| sudo apt-get update && sudo apt-get install -y curl jq | |
| bash ./debian/update-changelog.sh \ | |
| -d ${{ matrix.distro }} \ | |
| -p lablup/backend-ai \ | |
| --auto-increment \ | |
| ${{ steps.get_tag.outputs.tag }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Install build dependencies | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| sudo apt update | |
| sudo apt install -y \ | |
| devscripts \ | |
| debhelper \ | |
| dh-make \ | |
| fakeroot \ | |
| dput \ | |
| gpg \ | |
| dpkg-dev \ | |
| build-essential | |
| - name: Install Rust ${{ matrix.vendor_rust }} for vendoring | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ matrix.vendor_rust }} | |
| - name: Validate lockfile with Rust ${{ matrix.vendor_rust }} | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| cargo --version | |
| rustc --version | |
| cargo metadata --format-version 1 --locked --no-deps > /dev/null | |
| awk '/^version = /{print; found=1} END{ if(!found) exit 1 }' Cargo.lock | |
| - name: Vendor Rust dependencies for offline Launchpad build | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| rm -rf vendor .cargo | |
| mkdir -p .cargo | |
| cargo vendor --locked vendor > .cargo/config.toml | |
| python3 debian/sanitize-vendor.py | |
| python3 - <<'PY' | |
| import json | |
| from pathlib import Path, PurePosixPath | |
| offenders = [] | |
| for checksum_file in Path("vendor").rglob(".cargo-checksum.json"): | |
| data = json.loads(checksum_file.read_text()) | |
| for rel_path in data.get("files", {}): | |
| if rel_path.endswith(".orig") or any( | |
| part.startswith(".") for part in PurePosixPath(rel_path).parts | |
| ): | |
| offenders.append(f"{checksum_file}: {rel_path}") | |
| if offenders: | |
| raise SystemExit( | |
| "vendor checksum still references hidden/orig files:\n" | |
| + "\n".join(offenders[:50]) | |
| ) | |
| PY | |
| grep -q 'replace-with = "vendored-sources"' .cargo/config.toml | |
| grep -q '\[source.vendored-sources\]' .cargo/config.toml | |
| grep -q '^directory = "vendor"$' .cargo/config.toml | |
| test -d vendor | |
| ! find vendor -name '*.orig' -print | grep -q . | |
| - name: Prepare package version | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| VERSION="${{ steps.get_tag.outputs.tag }}" | |
| VERSION="${VERSION#v}" | |
| echo "PACKAGE_VERSION=${VERSION}" >> "$GITHUB_ENV" | |
| CHANGELOG_VERSION=$(head -n 1 debian/changelog | awk '{print $2}' | tr -d '()') | |
| echo "FULL_PACKAGE_VERSION=${CHANGELOG_VERSION}" >> "$GITHUB_ENV" | |
| echo "Package version: ${CHANGELOG_VERSION}" | |
| head -n 1 debian/changelog | grep -q "${VERSION}-" || { | |
| echo "Error: Changelog doesn't contain expected version ${VERSION}" | |
| exit 1 | |
| } | |
| - name: Import GPG key | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import | |
| gpg --list-secret-keys --keyid-format LONG | |
| FINGERPRINT=$(gpg --list-secret-keys --with-colons | grep '^fpr:' | head -1 | cut -d: -f10) | |
| echo "GPG_FINGERPRINT=$FINGERPRINT" >> "$GITHUB_ENV" | |
| echo "${FINGERPRINT}:6:" | gpg --import-ownertrust | |
| - name: Configure GPG | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| export GNUPGHOME=/home/runner/.gnupg | |
| mkdir -p $GNUPGHOME | |
| chmod 700 $GNUPGHOME | |
| cat > $GNUPGHOME/gpg-agent.conf << 'EOF' | |
| allow-loopback-pinentry | |
| default-cache-ttl 300 | |
| max-cache-ttl 3600 | |
| EOF | |
| cat > $GNUPGHOME/gpg.conf << 'EOF' | |
| use-agent | |
| pinentry-mode loopback | |
| batch | |
| no-tty | |
| EOF | |
| if [ -n "${{ secrets.GPG_PASSPHRASE }}" ]; then | |
| echo "${{ secrets.GPG_PASSPHRASE }}" > $GNUPGHOME/passphrase | |
| chmod 600 $GNUPGHOME/passphrase | |
| fi | |
| gpgconf --kill gpg-agent || true | |
| - name: Build source package | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| rm -f bssh | |
| rm -rf target/ | |
| test -d vendor | |
| test -f .cargo/config.toml | |
| sed -i 's/Architecture: .*/Architecture: any/' debian/control | |
| chmod +x debian/rules | |
| export GNUPGHOME=/home/runner/.gnupg | |
| if [ -n "${{ secrets.GPG_PASSPHRASE }}" ]; then | |
| echo "Building and signing source package with passphrase..." | |
| cat > /tmp/gpg-sign.sh << 'EOSCRIPT' | |
| #!/bin/bash | |
| echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 --pinentry-mode loopback "$@" | |
| EOSCRIPT | |
| chmod +x /tmp/gpg-sign.sh | |
| export GPG_PASSPHRASE="${{ secrets.GPG_PASSPHRASE }}" | |
| dpkg-buildpackage -S -sa -k"$GPG_FINGERPRINT" -d --sign-command="/tmp/gpg-sign.sh" | |
| else | |
| echo "Building and signing source package without passphrase..." | |
| dpkg-buildpackage -S -sa -k"$GPG_FINGERPRINT" -d | |
| fi | |
| echo "Source package built and signed successfully" | |
| - name: Upload to Ubuntu PPA | |
| if: steps.check_distro.outputs.should_run == 'true' | |
| run: | | |
| cat > ~/.dput.cf << 'EOF' | |
| [backend-ai-ppa] | |
| fqdn = ppa.launchpad.net | |
| method = ftp | |
| incoming = ~lablup/ubuntu/backend-ai/ | |
| login = anonymous | |
| allow_unsigned_uploads = 0 | |
| EOF | |
| CHANGES_FILE=$(ls ../*_source.changes 2>/dev/null | head -1) | |
| if [ -z "$CHANGES_FILE" ]; then | |
| echo "Error: No source .changes file found" | |
| exit 1 | |
| fi | |
| echo "Uploading $CHANGES_FILE to PPA lablup/backend-ai" | |
| dput backend-ai-ppa "$CHANGES_FILE" | |
| echo "✅ Successfully uploaded to PPA" | |
| echo "📦 Version: $FULL_PACKAGE_VERSION" | |
| echo "📦 Package will be available at: https://launchpad.net/~lablup/+archive/ubuntu/backend-ai" |