Skip to main content

Punix

Install tools, set up machines, deploy apps. Nothing breaks.

Punix is a package manager and service deployer for macOS and Linux. Every install is reversible, every deploy can be rolled back in one second, and the same workflow runs on your laptop, your VPS, or any host you can reach over SSH.

What you get

  • Reproducible, from-source builds. Each package builds into its own hash-named directory in a content-addressed store. The same recipe and sources produce a bit-for-bit identical result: the build you run today and the one you run in two years match, even if the upstream tarball server is long gone.
  • Atomic installs and updates. Your active profile is a directory of symlinks on your $PATH. Installing, updating, or switching to "the set I had last week" flips the live state in a single syscall. Uninstall is exact: no leftover files, no broken links. Two versions of the same tool can coexist.
  • One-second rollback. Every deploy is a complete numbered snapshot; the previous one stays whole on disk. Rollback just repoints a symlink. No rebuild, no reconfigure, no replay. There is no half-deployed state, even after a power loss mid-deploy.
  • Deploy anywhere, one command. Describe your stack once (config files, binaries, and service units) and deploy it locally or to any host you can reach over plain SSH — real ssh/rsync, no agent and no daemon on the target, and only content that's actually new is transferred. The shipping supervisor backend is systemd; the backend seam is pluggable (a renderer plus an activator) but a second one lands when there's a reason, not for the list.
  • A configuration language that catches mistakes early. Recipes and stacks are written in PCL and type-checked end-to-end before any build runs. Every typo, missing dependency, and bad URL is caught at file:line:col before a single build command executes.
  • Try a tool without installing it. punix shell ripgrep builds the package, puts it on your $PATH, and drops you in a shell. Leave, and your machine is exactly as it was: no profile generation, nothing linked, nothing to undo. The store is shared, so a later punix install of the same tool costs nothing.
  • Change one package of a collection without forking it. A recipe declares three overridable options (its version, url and hash), so your own tree can pin a package back, take a patched fork, or point at a mirror, while the rest of the collection keeps updating underneath you. Everything else in a recipe stays private to it.
  • Your own machine, no root. A stack can declare scope = "user" and write under your home directory as you: dotfiles, ~/.config trees, and background services under systemctl --user. Paths are relative to your home, so one file works on every machine you own. It is the same deploy command and the same one-second rollback you'd use on a server — and Punix still refuses to write the files in your home that execute code as you.
  • Config you can derive instead of copy-paste. Describe a fleet once and fold per-host config out of it, so editing one place re-renders every host that depends on it. Structured formats (YAML/JSON) need no code at all — you write the data and Punix serializes it, which means a value of yours can't escape into syntax.
  • Provenance you can interrogate. punix service why <file> names the recipe that produced a config file and a hash of that recipe's code, so you can tell whether the thing that generated it changed even where the output didn't.
  • Extensible without a fork. Config recipes for daemons Punix has never heard of can live outside the source tree: vendor one, pin its sha256 in a manifest, and select it from PCL. Punix reads the file once — verifying, running, and identifying the same bytes — and refuses anything the manifest doesn't admit.

Install

curl -fsSL https://punix.lab.abilian.com/install.sh | sh

The installer prints what it will do and pauses before doing it. It only writes under ~/.punix/, and it plays nicely with an existing Homebrew setup. Prefer Python packaging? pip install punix works too (Python 3.13+). See the install guide for other paths.

A quick taste

Install a few tools into your profile:

punix install ripgrep fd wget

Or describe a package or stack in PCL:

module curl {
  option version : Str = "8.20.0"
  option url : Str = f"https://curl.se/download/curl-{version}.tar.xz"
  option hash : Str = "63fe2dc1...e2b896"
  recipe  = "std.autotools"
  source  = { type = "url"  url = url  hash = hash  hashType = "sha256" }
  deps = [openssl.pname, zlib.pname]
}

…then check it, deploy it, and roll back if you need to:

punix check stack.pcl                          # type-check, no build
punix service deploy MyStack --file stack.pcl  # → gen-002
punix service rollback MyStack                 # → gen-001, instantly

Deploy to a server instead: same file, same command, just add a target:

punix service deploy MyStack --file stack.pcl --target ssh://you@server

Or point the same machinery at your own home directory — no root, nothing to install on a server:

module Dotfiles {
  backend = "systemd"
  scope   = "user"
  configFiles = [
    { path = ".config/myapp/settings.conf"  content = "theme = dark\n" }
  ]
}
punix service deploy Dotfiles --file dotfiles.pcl   # writes ~/.config/myapp/settings.conf
punix service rollback Dotfiles                     # byte-exact, without re-reading the .pcl

Why not just use…?

  • Homebrew: an excellent single-tool installer. It lacks service deployment, and brew update can move installed paths underneath you. Punix coexists with it.
  • Stow / chezmoi / home-manager: dotfile managers. Punix gives you what the lightweight ones don't — rollback to the exact previous bytes, and a refusal to write the files in your home that execute code — without asking you to adopt Nix, and it manages your laptop and your servers from the same file.
  • Nix: the same content-addressed store and correctness model, with a typed, lighter configuration language.
  • Ansible: built to mutate existing hosts step by step. Punix replaces a host's app stack atomically and rolls the whole thing back in one move.
  • Docker Compose: different job. Compose runs containers; Punix builds from source into a content-addressed store and manages the host's service units. A Compose backend is a plausible future addition, not something that ships today.

There's a fuller comparison in the docs.

Documentation

Full docs live at punix.lab.abilian.com:

Runnable demos live in examples/. Start with examples/hello for a first build, or examples/tangled-deploy for a real server built from source with HTTPS, secrets, and reboot-persistence.

Contributing

uv sync            # set up the environment (needs uv + Python 3.13+)
make test          # run the test suite
make lint          # ruff + type checkers

That is the whole setup for the test suite and the quality gate. One optional extra, needed only to reproduce the config-emitter measurement under notes/experiments/: brew install augeas (macOS) / apt install augeas-tools (Debian) / apk add augeas (Alpine). It is a development and CI dependency only — it never enters the runtime path. See the contributing guide.

Every advertised property is backed by a conformance test that gates each release. Punix is under active development. Contributions and issues are welcome.

License

Apache 2.0.

Metadata

Release files for punix 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for punix 0.4.0
File Size Uploaded
punix-0.4.0.tar.gz 331.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for punix 0.4.0
File Interpreter ABI Platform
punix-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 734.0 kB

Release files / punix-0.4.0.tar.gz

Download URL punix-0.4.0.tar.gz
Size 331.8 kB
Tags Source
SHA-256 checksum
How to use checksums
4cc20305ad7f74666aaa7df8cea1226d35a81c1c8328387587f1e9822b5c73c9
BLAKE2b-256 checksum
How to use checksums
b8414f81eecd526284c29bb979c41b812a4c9aa6ab4ee10b677489c5d77c6012
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / punix-0.4.0-py3-none-any.whl

Download URL punix-0.4.0-py3-none-any.whl
Size 402.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f35c18b7f44ebd282c2a9f567f12c54f6f28b4a88797cefefcf2da9c9b260f1c
BLAKE2b-256 checksum
How to use checksums
ce67b01a6c1b83a2f60175b053b4b38fc55a1d6bd91570803329466db447f98b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page