Everything this machine holds that is yours — what it is, whose it is, and how it gets onto the next machine.
English · 한국어
$ kitbag status
personal (12)
├── = env:couchdb COUCHDB_URI COUCHDB_DB COUCHDB_USER COUCHDB_PASSWORD
├── = ssh:id_ed25519 SHA256:XhCQXT9l7zas… (ED25519)
└── + ssh:authorized_keys 5 keys: laptop desktop mini server phone
shared · a friend (4)
└── = env:llm-proxy PROXY_URL PROXY_TOKEN MODEL
work · acme (17)
├── ~ env:ci CI_TOKEN CI_URL DEPLOY_KEY_ID … +5 more
└── + file:aws-keychain binary, 25788 bytes → ~/Library/Keychains/…
mixed (3)
└── ? app:accounts one bundle — personal, work
+ 3 new ~ 1 changed = 32 unchanged ? 1 to buildStatus: early, and working. Every command below does what it says, against a real machine and a real store —
bw,op,passor anage-encrypted file. What is missing is the installer half: recipes cover packages, links, macOS defaults and commands, and the rest of a machine's setup is still ahead. See DESIGN.md for the argument and the plan.
Setting up a machine is two jobs handled by two kinds of tool. Dotfile managers move your configuration. Password managers move your secrets. Neither can answer the one that matters once you have more than one machine and more than one employer:
What on this machine belongs to my company, and what happens to it when I leave?
kitbag treats packages, configuration, system settings, credentials and app data as the same kind of thing — a desired state, a source, a way to apply it, and an owner — and filters every one of them through that owner.
| scope | meaning |
|---|---|
personal |
yours |
work |
an employer's or a client's |
shared |
an account someone else owns that you were given |
mixed |
one artifact holding several — it has to say which |
local |
this machine only, never leaves it |
A machine declares which scopes it takes. A personal laptop never restores work credentials. A work machine does not install your personal toys. The same filter decides what is sent, what is written, and what a report shows.
curl -LsSf https://raw.githubusercontent.com/Open330/kitbag/main/install.sh | shDetects the platform, checks the download against the checksums published with
it, and puts one binary in ~/.local/bin. Or cargo install --path crates/kitbag-cli.
kitbag status what this machine has, marked against the store
kitbag plan what apply would change, and nothing else
kitbag apply make the machine match the recipes
(packages, links, defaults, downloads, clones, merges)
kitbag discover find personal state nothing is tracking yet
kitbag track <path> --scope work
kitbag push / restore move it, one scope at a time
kitbag doctor permissions, reachability, unscoped files, orphans
kitbag lint refuse the things that must not be committed
kitbag trust sync the machines that may log in here
kitbag completions zsh …bash, fish, elvish, powershell--json on everything, --color auto|always|never, NO_COLOR respected, and
no command ever prints a secret's value.
A public dotfiles repository is safe only if it leaves out the list of what
exists. ~/.envs/kibana.env → work names an employer, a stack and a target,
and nobody needs the secret to make use of that.
repo (public) recipes and providers — no employer, no service name
inventory (private) what exists, where it goes, which scope — in the store
secret store the values — a vault, or an encrypted file
Collection is by pattern, never by name. Scope is a marker the file carries
(# scope: work), not a table in the repo. kitbag lint fails a commit that
breaks either rule — and runs over this repository on every
CI run, because a tool that leaked its own author's machine would have argued
against its design.
kitbag does not implement one. It borrows yours, so that losing interest in kitbag never strands your secrets inside it.
| backend | store | |
|---|---|---|
bw |
Bitwarden / Vaultwarden — free tier, self-hostable | v0.1 |
op |
1Password — the best developer CLI in the category | v0.1 |
pass |
pass / gopass — GPG and a git repo |
v0.2 |
age |
an age-encrypted file — no server at all |
v0.2 |
Adding one is a list/get/put adapter. Everything kitbag needs to know
about an item travels inside its own envelope, so a store that can keep bytes
under a name is enough:
kitbag/1
scope: work
owner: acme
encoding: utf8
sha256: 1f0e3d…
export TOKEN=…
Why there is no delete in the backend trait
A store holds things your machine knows nothing about — another machine's key, an account someone else added. A tool that removes what it does not recognise eventually removes something that mattered. Removal is a person's decision, taken with the store's own client.
chezmoi and yadm manage dotfiles and do it well; neither models ownership or moves credentials. 1Password CLI and SOPS manage secrets and not the machine. Mackup moved app state and is unmaintained. kitbag is the overlap: personal state, wherever it lives, with an owner attached.
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warningsContributions welcome — read CONTRIBUTING.md first; the first rule is that nobody's machine goes in this repository.