gripsack icongripsack

your whole environment, in one bag.

packages from any source plus your dotfiles — described once in Python, fetched, built, and deployed by a Rust core. roll back anytime.

rust core python modules hash-addressed store generations & rollback status: design
$ curl -fsSL https://gripsack.dev/install.sh | sh

nothing to install yet — this is the shape of it. planned: cargo install gripsack · brew install gripsack-dev/tap/gripsack · release binaries

pick a palette

pure CSS variables — the logo re-tints too. your pick persists per browser.

how it works

1

describe

modules are typed Python in your repo: sources, build steps, config files, ownership.

2

evaluate

modules evaluate to IR — pure data. host facts & tags resolve here, nowhere else.

3

lock

the resolved plan pins URLs and content hashes per host. reproducible, no sandbox.

4

build

fetchers and builders run as a DAG into hash-addressed store paths.

5

activate

one atomic symlink flip. dotfiles deployed per ownership mode.

6

roll back

flip current to any previous generation. that's the whole mechanism.

a module

myenv/modules/helix.py — sketch; the api is not final
from gripsack import module, github_release, symlink, tracked_copy

helix = module(
    "helix",
    source=github_release(
        repo="helix-editor/helix",
        asset="helix-{version}-x86_64-linux.tar.xz",
    ),
    install={"bin/hx": symlink("~/.local/bin/hx")},
    config={
        "config.toml": tracked_copy("~/.config/helix/config.toml"),
        "languages.toml": tracked_copy("~/.config/helix/languages.toml"),
    },
)

full IDE support — pyright gives you autocomplete, inline errors, and refactors, because a module is just typed Python. evaluation emits IR (JSON); the Rust core only ever consumes IR.

what it does

modules

everything is a module: how to get it, build it, where its files and configs live. modules depend on modules; build-only deps are ephemeral.

any source

github releases, tarballs, git builds, cargo, distro packages. fetchers are pluggable verbs, conditionally selected per host.

dotfiles, first-class

ownership per file: owned symlink, tracked-copy with drift detection, merge blocks, template for per-machine values.

generations

every apply — one module or the whole graph — is a new generation. activation is one atomic rename; rollback is flipping it back.

lockfiles, not sandboxes

impure by default, reproducible by pinned URLs and hashes. nix's ergonomics without nix's enforcement.

hosts & facts

one repo, many machines. fedora laptop, ubuntu desktop: facts auto-detected, tags declared, conditionals resolved at eval.

compare

gripsacknix + home-managerchezmoi · stowapt · brew · mise
packages from any sourcenixpkgs·own repos
dotfiles managed·
generations & rollbackgit history·
config ownership modesowned onlytemplates·
no custom languagepythonnix lang
reproducible without sandboxlockfilesflakes·
no daemon, no rootdaemon · /nixapt needs root
per-host conditionalsfacts + tagsmodulestemplates·

follow along

$ grip apply --repo git@github.com:you/myenv

the design is public and moving: 0001 — architecture. star the repo if this is the package manager you also wish existed.