PkgOps · the packaging companion

Build. Sign. Ship. No FreeBSD builder.

Write 30 lines of YAML, run one command, and get a signed FreeBSD package repository that stock pkg understands. PkgOps cross-compiles your Go project on Linux or macOS (or on a GitHub Actions runner), writes the same .pkg files and catalogs FreeBSD's own tools write, and leaves installs and upgrades to the pkg your servers already have.

PkgOps on GitHub JailOps docs

Why PkgOps

  • No builder

    Go cross-compiles for FreeBSD with CGO_ENABLED=0. No ports tree, no build jail, no FreeBSD machine in your CI.

  • Native and signed

    Real .pkg files, pkg-compatible catalogs, RSA signatures. Tampered catalogs and packages are refused by stock pkg.

  • Reproducible

    Two builds of the same inputs are byte-identical, binary included, so a package is something you can audit and re-create.

  • Static hosting

    A repository is a directory of files: nginx, any static host, GitHub, or the small optional registry that takes uploads from CI.

  • Two dependencies

    Pure Go with zstd and YAML, shipped as a CLI and an importable Go package. It shares no code with JailOps.

  • Packages that behave

    rc.d scripts, service users, owned directories and @config files that survive upgrades, all from the manifest.

The right tool for each job. About 95% of packaging work is a small daemon, a CLI tool, some config and an rc.d script: that is PkgOps. The heavy 5%, such as large builds from the ports tree, software that needs CGO or native libraries, custom port options and bulk builds across jails, belongs to poudriere, and PkgOps does not try to replace it. The two coexist: a PkgOps repository is one more entry in /usr/local/etc/pkg/repos.

How it fits

From source to pkg upgrade.

PkgOps architecture A Linux or macOS machine or a GitHub Actions runner runs pkgops build, which cross-compiles Go into a native pkg file. pkgops repo signs it into a static repository. The repository is published to nginx, a pkgops registry or GitHub. FreeBSD hosts and jails run pkg update, install and upgrade against it. Laptop or CILinux, macOS, Actionspkgops build Signed repository.pkg files and catalogpkgops repo, verify Hostingnginx, registry, GitHubstatic files over HTTPS FreeBSDpkg installpkg upgrade Optional registry: pkgops servetoken-protected upload, re-sign, retention; web UI coming The signing key stays with you; FreeBSD hosts only need the public key.

Walkthrough

A Go daemon with an rc.d script.

on your machine
$ git clone https://github.com/jailops/pkgops && cd pkgops
$ go install ./cmd/pkgops
$ pkgops key generate --out repo.key --bits 4096
$ pkgops build --abi FreeBSD:15:amd64 -o dist
$ pkgops build --abi FreeBSD:15:aarch64 -o dist
$ pkgops repo --repo site --key repo.key dist/*/*.pkg
$ pkgops verify site/FreeBSD:15:amd64/latest --pubkey repo.pub
$ pkgops publish --from site --to /var/www/pkg
pkgops.yaml, abridged
name: hello
version: 0.1.0
comment: A tiny HTTP daemon
www: https://example.org
maintainer: you@example.org
license: BSD2CLAUSE
build: {type: go, package: ./cmd/hello}
files:
  - {source: build/${ARCH}/hello, destination: bin/hello}
services:
  - {name: hello, source: packaging/rc.d-hello}
on the FreeBSD host, as root
$ fetch -o /usr/local/etc/pkg/keys/hello.pub https://pkg.example.org/repo.pub
$ pkgops conf --name hello --url 'https://pkg.example.org/${ABI}/latest' \
    --pubkey /usr/local/etc/pkg/keys/hello.pub > /usr/local/etc/pkg/repos/hello.conf
$ pkg update
$ pkg install hello
$ sysrc hello_enable=YES
$ pkg upgrade                            # later, after a new release

The complete manifest reference, a runnable example and the format notes are in the PkgOps repository.

Hosting

Pick what fits.

  • nginx or any static server. pkgops publish copies packages first and the catalog last, atomically, so clients never see a catalog that points at a missing package.
  • The optional registry. pkgops serve serves the repository read-only and accepts uploads with scoped tokens: pkgops push --url ... --token ... dist/*/*.pkg from CI. Every upload is verified, re-indexed and re-signed, with retention. It speaks plain HTTP, so put TLS in front. A web UI for the registry is coming.
  • GitHub, no FreeBSD machine. A composite Action builds for both architectures, signs with a key kept in a secret and publishes to Pages or Releases.

With JailOps

One infrastructure story.

  • JailOps itself ships as FreeBSD packages (jailops-client, jailops, jailops-backupd) from a signed repository, with rc.d scripts and jailops-priv init. The public repository at pkg.jailops.net is not live yet.
  • Your own software follows the same path: build with PkgOps, publish a signed repository, and install it in a jail with pkg install, or through a jail's provisioning packages list.
  • Jails stay clean: the OS comes from an image generation, your application from a package you built and signed.

Verified and next

Verified on FreeBSD 15.1 arm64 with pkg 2.8.4: pkg add, info, check and delete on PkgOps packages; pkg update, install and upgrade from a static repository, including dependencies and @config files; signature checks in both directions; users, groups and directories; pkgops serve and push running on FreeBSD. Not yet verified: amd64 packages on real amd64 hardware, publishing through GitHub Pages and Releases, and rc.d service start at boot. Limits: pure Go only (no CGO), amd64 and aarch64, FreeBSD 15 and newer. PkgOps is pre-1.0 software.