name: Build / Fedora RPM concurrency: group: "build-fedora-${{ github.ref }}" cancel-in-progress: true on: push: branches: - '**' tags: - 'v*' paths: - 'fedora/**' - 'src/**' - 'Cargo.toml' - 'Cargo.lock' - '.forgejo/workflows/build-fedora.yml' workflow_dispatch: jobs: build: runs-on: fedora-latest steps: - name: Detect Fedora version id: fedora run: | VERSION=$(rpm -E %fedora) echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Fedora version: $VERSION" - name: Checkout repository with full history uses: https://code.forgejo.org/actions/checkout@v4 with: fetch-depth: 0 - name: Cache DNF packages uses: https://code.forgejo.org/actions/cache@v4 with: path: | /var/cache/dnf /var/cache/yum key: dnf-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('fedora/continuwuity.spec.rpkg') }}-v1 restore-keys: | dnf-fedora${{ steps.fedora.outputs.version }}- - name: Cache Cargo registry uses: https://code.forgejo.org/actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git key: cargo-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: | cargo-fedora${{ steps.fedora.outputs.version }}- - name: Cache Rust build dependencies uses: https://code.forgejo.org/actions/cache@v4 with: path: | ~/rpmbuild/BUILD/*/target/release/deps ~/rpmbuild/BUILD/*/target/release/build ~/rpmbuild/BUILD/*/target/release/.fingerprint ~/rpmbuild/BUILD/*/target/release/incremental key: rust-deps-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: | rust-deps-fedora${{ steps.fedora.outputs.version }}- - name: Setup sccache uses: https://github.com/mozilla-actions/sccache-action@v0.0.9 with: token: ${{ secrets.GH_PUBLIC_RO }} - name: Configure sccache environment run: | echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV echo "SCCACHE_CACHE_SIZE=10G" >> $GITHUB_ENV # Aggressive GC since cache restores don't increment counter echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV - name: Install build dependencies run: | dnf install -y --setopt=keepcache=1 \ wget \ rpm-build \ rpm-sign \ rpmdevtools \ rpkg \ cargo-rpm-macros \ systemd-rpm-macros \ clang \ liburing-devel \ rust \ cargo \ gcc \ gcc-c++ \ make \ openssl-devel \ pkg-config \ python3-pip - name: Setup build environment and build SRPM run: | # Configure git for rpkg git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global user.email "ci@continuwuity.org" git config --global user.name "Continuwuity" # Setup RPM build tree rpmdev-setuptree cd "$GITHUB_WORKSPACE" # Determine release suffix and version based on ref type and branch if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then # Tags get clean version numbers for stable releases RELEASE_SUFFIX="" TAG_NAME="${{ github.ref_name }}" # Extract version from tag (remove v prefix if present) TAG_VERSION=$(echo "$TAG_NAME" | sed 's/^v//') # Create spec file with tag version sed -e "s/^Version:.*$/Version: $TAG_VERSION/" \ -e "s/^Release:.*$/Release: 1%{?dist}/" \ fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg elif [ "${{ github.ref_name }}" = "main" ]; then # Main branch gets .dev suffix RELEASE_SUFFIX=".dev" # Replace the Release line to include our suffix sed "s/^Release:.*$/Release: 1${RELEASE_SUFFIX}%{?dist}/" \ fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg else # Other branches get sanitized branch name as suffix SAFE_BRANCH=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/_/g' | cut -c1-20) RELEASE_SUFFIX=".${SAFE_BRANCH}" # Replace the Release line to include our suffix sed "s/^Release:.*$/Release: 1${RELEASE_SUFFIX}%{?dist}/" \ fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg fi # Build the SRPM rpkg srpm --outdir "$HOME/rpmbuild/SRPMS" # Show SRPM info ls -la $HOME/rpmbuild/SRPMS/ - name: Setup GPG for RPM signing run: | # Skip if no signing key is configured if [ -z "${{ secrets.RPM_SIGNING_KEY }}" ]; then echo "No RPM signing key configured - skipping signing setup" exit 0 fi # Import the signing key echo "${{ secrets.RPM_SIGNING_KEY }}" | gpg --batch --import # Get the key ID (look for the sec line, not the uid line) KEY_ID=$(gpg --list-secret-keys --keyid-format=long | grep "^sec" | head -1 | awk '{print $2}' | cut -d'/' -f2) echo "Using GPG key: $KEY_ID" # Configure RPM macros for signing cat > ~/.rpmmacros << EOF %_signature gpg %_gpg_name $KEY_ID %__gpg /usr/bin/gpg EOF - name: Build RPM from SRPM run: | # Find the SRPM file SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1) if [ -z "$SRPM" ]; then echo "Error: No SRPM file found" exit 1 fi echo "Building from SRPM: $SRPM" # Build the binary RPM rpmbuild --rebuild "$SRPM" \ --define "_topdir $HOME/rpmbuild" \ --define "_sourcedir $GITHUB_WORKSPACE" \ --nocheck # Skip %check section to avoid test dependencies - name: Sign RPM packages run: | # Skip if no signing key is configured if [ -z "${{ secrets.RPM_SIGNING_KEY }}" ]; then echo "No RPM signing key configured - skipping package signing" exit 0 fi # Track signing failures FAILED_COUNT=0 TOTAL_COUNT=0 # Export GPG_TTY to avoid terminal warnings export GPG_TTY=/dev/null # Sign all RPMs (binary and source) for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do echo "Signing: $(basename $rpm)" TOTAL_COUNT=$((TOTAL_COUNT + 1)) # Use expect or provide empty passphrase via stdin for batch signing if ! echo "" | rpmsign --addsign "$rpm" 2>&1; then echo "ERROR: Failed to sign $rpm" FAILED_COUNT=$((FAILED_COUNT + 1)) fi done # Fail if any RPMs failed to sign if [ "$FAILED_COUNT" -gt 0 ]; then echo "ERROR: Failed to sign $FAILED_COUNT out of $TOTAL_COUNT RPMs" exit 1 fi echo "Successfully signed all $TOTAL_COUNT RPMs" - name: Verify RPM signatures run: | # Skip if no signing key is configured if [ -z "${{ secrets.RPM_SIGNING_KEY }}" ]; then echo "No RPM signing key configured - skipping signature verification" exit 0 fi # Import our public key for verification echo "Importing GPG public key for verification..." rpm --import fedora/RPM-GPG-KEY-continuwuity.asc # Track verification failures FAILED_COUNT=0 TOTAL_COUNT=0 # Verify all RPMs for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do echo -n "Verifying $(basename $rpm): " TOTAL_COUNT=$((TOTAL_COUNT + 1)) if rpm --checksig "$rpm"; then echo " ✓" else echo " ✗ FAILED" FAILED_COUNT=$((FAILED_COUNT + 1)) fi done # Fail if any RPMs failed verification if [ "$FAILED_COUNT" -gt 0 ]; then echo "ERROR: $FAILED_COUNT out of $TOTAL_COUNT RPMs failed signature verification" exit 1 fi echo "Successfully verified all $TOTAL_COUNT RPM signatures" - name: Test RPM installation run: | # Find the main binary RPM (exclude debug and source RPMs) RPM=$(find "$HOME/rpmbuild/RPMS" -name "continuwuity-*.rpm" \ ! -name "*debuginfo*" \ ! -name "*debugsource*" \ ! -name "*.src.rpm" | head -1) if [ -z "$RPM" ]; then echo "Error: No binary RPM file found" exit 1 fi echo "Testing installation of: $RPM" # Dry run first rpm -qpi "$RPM" echo "" rpm -qpl "$RPM" # Actually install it (would need --nodeps if dependencies aren't met) dnf install -y "$RPM" || rpm -ivh --nodeps "$RPM" # Verify installation rpm -qa | grep continuwuity # Check that the binary exists [ -f /usr/bin/conduwuit ] && echo "✅ Binary installed successfully" [ -f /usr/lib/systemd/system/conduwuit.service ] && echo "✅ Systemd service installed" [ -f /etc/conduwuit/conduwuit.toml ] && echo "✅ Config file installed" - name: List built packages run: | echo "Binary RPMs:" find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec ls -la {} \; echo "" echo "Source RPMs:" find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec ls -la {} \; - name: Collect artifacts run: | mkdir -p artifacts # Copy all RPMs to artifacts directory find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; # Create metadata file cd artifacts echo "Build Information:" > BUILD_INFO.txt echo "==================" >> BUILD_INFO.txt echo "Git commit: ${{ github.sha }}" >> BUILD_INFO.txt echo "Git branch: ${{ github.ref_name }}" >> BUILD_INFO.txt echo "Build date: $(date -u +%Y-%m-%d_%H:%M:%S_UTC)" >> BUILD_INFO.txt echo "" >> BUILD_INFO.txt echo "Package contents:" >> BUILD_INFO.txt echo "-----------------" >> BUILD_INFO.txt for rpm in *.rpm; do echo "" >> BUILD_INFO.txt echo "File: $rpm" >> BUILD_INFO.txt rpm -qpi "$rpm" 2>/dev/null | grep -E "^(Name|Version|Release|Architecture|Size)" >> BUILD_INFO.txt done ls -la - name: Upload binary RPM artifact run: | # Find the main binary RPM (exclude debug and source RPMs) BIN_RPM=$(find artifacts -name "continuwuity-*.rpm" \ ! -name "*debuginfo*" \ ! -name "*debugsource*" \ ! -name "*.src.rpm" \ -type f) # Create temp directory for this artifact mkdir -p upload-bin cp $BIN_RPM upload-bin/ - name: Upload binary RPM uses: https://code.forgejo.org/actions/upload-artifact@v3 with: name: continuwuity path: upload-bin/ - name: Upload debug RPM artifact uses: https://code.forgejo.org/actions/upload-artifact@v3 with: name: continuwuity-debug path: artifacts/*debuginfo*.rpm - name: Publish to RPM Package Registry if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} run: | # Find the main binary RPM (exclude debug and source RPMs) RPM=$(find artifacts -name "continuwuity-*.rpm" \ ! -name "*debuginfo*" \ ! -name "*debugsource*" \ ! -name "*.src.rpm" \ -type f | head -1) if [ -z "$RPM" ]; then echo "No binary RPM found to publish" exit 0 fi # Extract version from RPM filename RPM_BASENAME=$(basename "$RPM") echo "Publishing: $RPM_BASENAME" # Determine the group based on ref type and branch if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then GROUP="stable" # For tags, extract the tag name for version info TAG_NAME="${{ github.ref_name }}" elif [ "${{ github.ref_name }}" = "main" ]; then GROUP="dev" else # Use sanitized branch name as group for feature branches GROUP=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30) fi # Extract package info from RPM for deletion PACKAGE_INFO=$(rpm -qpi "$RPM" 2>/dev/null) PACKAGE_NAME=$(echo "$PACKAGE_INFO" | grep "^Name" | awk '{print $3}') PACKAGE_VERSION=$(echo "$PACKAGE_INFO" | grep "^Version" | awk '{print $3}') PACKAGE_RELEASE=$(echo "$PACKAGE_INFO" | grep "^Release" | awk '{print $3}') PACKAGE_ARCH=$(echo "$PACKAGE_INFO" | grep "^Architecture" | awk '{print $2}') # Full version includes release FULL_VERSION="${PACKAGE_VERSION}-${PACKAGE_RELEASE}" # Try to delete existing package first (ignore errors if it doesn't exist) echo "Removing any existing package: $PACKAGE_NAME-$FULL_VERSION.$PACKAGE_ARCH" curl -X DELETE \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ "https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/package/$PACKAGE_NAME/$FULL_VERSION/$PACKAGE_ARCH" \ || echo "Package didn't exist or deletion failed (this is OK)" # Upload to Forgejo package registry # Using the RPM registry endpoint with group support curl --fail-with-body \ -X PUT \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ -H "Content-Type: application/x-rpm" \ -T "$RPM" \ "https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/upload" echo "" echo "✅ Published binary RPM to: https://forgejo.ellis.link/continuwuation/-/packages/rpm/continuwuity/" echo "Group: $GROUP" # Upload debug RPMs to separate group DEBUG_RPMS=$(find artifacts -name "*debuginfo*.rpm" -o -name "*debugsource*.rpm") if [ -n "$DEBUG_RPMS" ]; then echo "" echo "Publishing debug RPMs to group: ${GROUP}-debug" for DEBUG_RPM in $DEBUG_RPMS; do echo "Publishing: $(basename "$DEBUG_RPM")" # Extract debug RPM info DEBUG_INFO=$(rpm -qpi "$DEBUG_RPM" 2>/dev/null) DEBUG_NAME=$(echo "$DEBUG_INFO" | grep "^Name" | awk '{print $3}') DEBUG_VERSION=$(echo "$DEBUG_INFO" | grep "^Version" | awk '{print $3}') DEBUG_RELEASE=$(echo "$DEBUG_INFO" | grep "^Release" | awk '{print $3}') DEBUG_ARCH=$(echo "$DEBUG_INFO" | grep "^Architecture" | awk '{print $2}') DEBUG_FULL_VERSION="${DEBUG_VERSION}-${DEBUG_RELEASE}" # Try to delete existing debug package first curl -X DELETE \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/package/$DEBUG_NAME/$DEBUG_FULL_VERSION/$DEBUG_ARCH" \ || echo "Debug package didn't exist or deletion failed (this is OK)" # Upload debug RPM curl --fail-with-body \ -X PUT \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ -H "Content-Type: application/x-rpm" \ -T "$DEBUG_RPM" \ "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/upload" done echo "✅ Published debug RPMs to group: ${GROUP}-debug" fi # Also upload the SRPM to separate group SRPM=$(find artifacts -name "*.src.rpm" | head -1) if [ -n "$SRPM" ]; then echo "" echo "Publishing source RPM: $(basename "$SRPM")" echo "Publishing to group: ${GROUP}-src" # Extract SRPM info SRPM_INFO=$(rpm -qpi "$SRPM" 2>/dev/null) SRPM_NAME=$(echo "$SRPM_INFO" | grep "^Name" | awk '{print $3}') SRPM_VERSION=$(echo "$SRPM_INFO" | grep "^Version" | awk '{print $3}') SRPM_RELEASE=$(echo "$SRPM_INFO" | grep "^Release" | awk '{print $3}') SRPM_FULL_VERSION="${SRPM_VERSION}-${SRPM_RELEASE}" # Try to delete existing SRPM first echo "Removing any existing SRPM: $SRPM_NAME-$SRPM_FULL_VERSION.src" curl -X DELETE \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/package/$SRPM_NAME/$SRPM_FULL_VERSION/src" \ || echo "SRPM didn't exist or deletion failed (this is OK)" curl --fail-with-body \ -X PUT \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ -H "Content-Type: application/x-rpm" \ -T "$SRPM" \ "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/upload" echo "✅ Published source RPM to group: ${GROUP}-src" fi