Skip to content

Punix v2

# Punix v2

Install tools, set up machines, deploy apps — without breaking anything.

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

  01 — Install

Get Punix in one command.

macOS, Linux, or WSL. The installer prints what it will do and pauses before doing it.

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

Or pip install punix — see other installation paths.

  02 — How it works

What Does Punix Do?

Punix installs the command-line tools and applications you need across macOS, Linux, and WSL — into a per-user profile, atomically. It won't install files outside ~/.punix/.

Each package lands in its own hash-named directory inside ~/.punix/store/<hash>-<name>-<version>/. The hash binds the recipe and its sources; two versions of the same tool can coexist because they have different hashes and different directories.

Your active profile is a directory of symlinks at ~/.punix/current/bin/, which you add to your $PATH once. A binary on your $PATH points into its store entry. Multiple deps can require different openssl versions without conflict.

Switch profiles ("the set I had last week") with one command — the live state changes in a single syscall. Uninstall is exact and reversible. No leftover files, no broken symlinks. Plays nice with Homebrew if you have it.

Read the install guide →  ·  Content addressing →

~
$ punix install ghq ruff wget installing 3 modules: ghq, ruff, wget (+7 deps) ghq (1 binary) ruff (1 binary) wget (1 binary) installed 3 modules in ~/.punix/current/bin $ ls -l ~/.punix/current/bin/ruff ~/.punix/current/bin/ruff -> ~/.punix/store/4b310705...-ruff-0.15.14/bin/ruff $ find ~/.punix/store -maxdepth 1 -name "*-ghq-*" ~/.punix/store/cbd1c600...-ghq-1.10.1
  03 — Reliable builds

The same recipe, every time. For years.

Recipes are written in PCL — the Punix Configuration Language. The type checker reads them end-to-end before any build runs. Typo? Missing dep? Bad URL? You see it instantly, with a file:line:col location — not at minute 11 of a 12-minute build.

When a recipe and its sources are bytewise identical, the output is too. Bit-for-bit. Your install today and your install in 2027 produce the same binary, even if the upstream tarball server is long gone.

PCL overview →  ·  How content addressing works →

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

Roll back any deploy in one second.

Deploy your stack — config files, binaries, systemd or launchd units — in one command. Each deploy is a complete numbered snapshot. The previous one stays whole on disk.

If something breaks, punix service rollback flips you back instantly. It doesn't rebuild, doesn't reconfigure, doesn't replay anything — just points the live symlink at the previous snapshot. The whole operation is one os.replace syscall.

Power loss mid-deploy? The previous snapshot stays live. There is no half-deployed state.

How rollback works →

~ — deploy
$ punix service deploy MyStack --file stack.pcl deployed: MyStack → gen-002 3 config files, 2 store paths pinned $ punix service rollback MyStack rolled back: MyStack → gen-001 (was gen-002) $ punix profile list gen-001 2026-06-08 active gen-002 2026-06-09
  05 — Same workflow, anywhere

Local, SSH, or your VPS — one command.

The deploy command takes a --target flag. Point it at local to deploy to your laptop, or ssh://you@server to deploy to your server. The workflow is identical: same config file, same build, same atomic flip, same one-second rollback.

Punix uses real ssh and rsync under the hood — no agent on the target, no daemon to install. The build closure is pushed by content hash, so only what's actually new gets transferred.

Deploy over SSH →  ·  More backends →

~ — deploy --target ssh://
$ punix service deploy MyStack \ --file stack.pcl \ --target ssh://deploy@prod.example.com pushing closure to prod.example.com caddy (already on target) postgres (rsync'd 12.3 MB) flipping symlink deployed: MyStack@prod → gen-005
  06 — Compare

Why not just use…?

HomebrewGreat for installing tools — not a service deployer. brew update can change installed paths underneath you. Punix coexists fine with brew. NixRight shape, hard to learn, runtime-typed config. Punix has the same content-addressed store with a typed, lighter language. AnsibleBuilt for orchestrating change on existing hosts. Punix replaces a host's app stack atomically — different job. Docker ComposeHash-addressed layers, but YAML config and manual down/up rollback. Punix can drive a Compose stack as a backend.

Full comparison: vs Nix, Guix, SlapOS, Homebrew, Ansible, Docker →

  07 — Status

Verified by tests, not marketing copy.

595tests green 386recipes shipping 63 %brew top-500 ~15 klines of Python 6 / 8engine stages

Every major property has a dedicated conformance test under tests/c_e2e/test_conformance_stage*.py. Any regression is a CI release-blocker — we don't ship if it fails.

What works today →  ·  Corpus status →  ·  Roadmap →

  08 — Read on

Pick your path.