PackageForge extends the KernelForge philosophy to userspace. It is an automated CI/CD pipeline designed to dynamically fetch, customize, rebuild, and publish any custom .deb or .rpm package using a single unified YAML manifest.
PackageForge operates through an 8-Phase pipeline executed entirely within GitHub Actions.
Every package is defined by a declarative package.yml manifest stored in packages/<pkgname>/package.yml. This manifest acts as the absolute source of truth, dictating what upstream source to fetch, what the new identity will be, and how the files should be customized.
No code modifications are needed to add a new package—simply drop in a new YAML folder!
The pipeline parses the manifest and fetches the upstream source. It intelligently uses Dockerized package managers (apt or dnf) to download .deb or .rpm packages directly, or falls back to wget and git clone. It caches the artifact and generates an upstream.sha256 lockfile for reproducibility.
A format-agnostic unpacking system. It detects whether the artifact is a .deb (extracts using ar and tar) or an .rpm (extracts using rpm2cpio), and normalizes both into a unified intermediate directory structure:
unpacked/
files/ # Actual package filesystem contents
meta/
control # Normalized metadata (Name, Version, Arch, Desc)
scripts/ # Pre/Post install scripts
Bridging the gap. The pipeline parses the customize block of the package.yml to apply string replacements, inject new configuration files, and execute build-time bash hooks against the normalized unpacked/ layout.
Takes the normalized, customized layout and repacks it. It reuses the exact same Docker containers built by KernelForge (kernel-builder-deb and kernel-builder-rpm) to execute dpkg-deb --build or synthesize a .spec file on the fly for rpmbuild.
A fully automated GitHub Actions pipeline (.github/workflows/ci.yml). It uses a dynamic matrix strategy (jq scanning) to auto-discover all directories in packages/ and process them in parallel across the deb and rpm formats.
Spins up a clean debian:bookworm or fedora:latest container to install the newly built artifact. It verifies package registration (dpkg -l / rpm -q), tests the main binary execution (--version), and runs systemd-analyze verify on any injected .service files.
Cryptographically signs the packages using imported GPG keys (dpkg-sig and rpm --addsign). It then builds a full APT repository (reprepro) and YUM repository (createrepo_c) and deploys the index files directly to the gh-pages branch, ready for users to consume via /etc/apt/sources.list.
- Create a directory in
packages/:mkdir -p packages/my-custom-app/files
- Create
packages/my-custom-app/package.yml:source: type: apt name: htop version: "3.3.0" upstream_distro: ubuntu customize: package_name: "htop-acmecorp" vendor: "Acme Corp"
- Commit and push. The CI pipeline will auto-detect the folder and build it!
To maintain the integrity of the automated packaging pipeline, branch protection rules must be enforced on the main branch.
Navigate to your repository's settings on GitHub:
- Go to Settings > Branches > Add branch protection rule.
- Set the Branch name pattern to
main. - Check Require status checks to pass before merging and select the CI pipeline jobs.
Building a universal package customization pipeline introduces several complex edge cases:
- Fetching: Upstream package is renamed, removed, or auth rate-limits are hit. Checksum mismatches occur if upstream forces a hotfix without bumping the version tag.
- Unpacking: Packages using complex
dpkg-divertmechanisms or hardcoded paths in maintainer scripts. - Customization: Blind string replacements on binary executables will corrupt the files. Replacements must be strictly scoped to text/control files.
- Testing: Renaming a package without correctly handling
Provides/Conflicts/Replacesflags can break the dependency tree if both the original and custom package are installed simultaneously.