Operations
Packaging guide
Audience: distribution maintainers. If you are packaging dot for Debian, Fedora, Arch, Homebrew, nixpkgs, Scoop, or anything else, this page is written for you. Everything a packager normally has to reverse-engineer — the licence grant, the toolchain floor, how the dependencies are pinned, how to build and test offline, and how to verify what you downloaded — is here.
Existing packaging inputs live in pkg/, one directory per format.
What this software is
A shell framework, not a compiled program. The shipped artefact is:
bin/dot— the CLI dispatcher (bash)lib/dot/,scripts/— sourced bash libraries and subcommandsshare/man/man1/dot.1— the man page, generated from the CLI's command registry at build timeshare/{bash-completion,zsh,fish}/…— completions, also generateddefaults/— the chezmoi source tree the CLI applies to$HOME
There is nothing to compile and no architecture-specific output. The release archive is noarch/any.
Licence grant
Apache-2.0 OR MIT — the SPDX expression declared in package.json and in REUSE.toml. Both full texts ship in the repository and in the release archive as LICENSE-APACHE and LICENSE-MIT, and make install places both under $(PREFIX)/share/doc/dotfiles/.
This is a dual grant, not a conjunction: a downstream recipient may comply with either licence, at their option. For distributions that require a single choice, MIT is the permissive default; choose Apache-2.0 if you want the explicit patent grant.
Per-file licensing is machine-readable via REUSE: REUSE.toml at the repository root annotates the tree, and reuse lint runs in CI. Vendored third-party material, where present, is annotated separately and keeps its own licence.
Minimum toolchain
Full policy, per-distro mapping, and the rules for when a floor may rise: MINIMUM-TOOLCHAIN.md. The short form for a packaging recipe:
| Dependency | Minimum | Kind |
|---|---|---|
bash | 3.2 | required at runtime |
git | 2.34 | required at runtime |
curl | any | required by the bootstrap path only |
chezmoi | 2.72.2 | required at runtime — the CLI is a control plane over chezmoi |
zsh / fish / nushell | 5.8 / 4.0 / 0.98 | optional; only if the user wants that shell's integration |
jq, gum, starship, fzf | any | optional; enable JSON output, rich prompts, and interactive pickers |
go | 1.23 | build/test only — fuzz harnesses and the two Go TUIs. Not needed to build or run the package. |
Suggested dependency split for a distro package: Depends: bash (>= 3.2), git (>= 2.34), chezmoi (>= 2.72.2); Recommends: jq, zsh; Suggests: fish, nushell, gum, starship, fzf.
Dependency pin model
Every dependency this project builds and tests against is pinned, and every pin is committed:
| Surface | Pinned by | Notes |
|---|---|---|
| Development toolchain (node, go, rust, and ~20 CLI tools) | mise.toml + mise.lock + mise-versions.lock.json | mise is the package manager; the lock carries checksums |
| Documentation build (Python) | requirements-docs.txt | Hash-pinned (--require-hashes-compatible), compiled from requirements-docs.in |
| Node tooling | package.json | Dev-only |
| Nix | flake.lock, nix/flake.lock | Root flake provides the dev shell; nix/flake.nix provides packages.default |
| GitHub Actions | Same-commit paths for local reusable workflows; 40-hex SHA for external uses: | Enforced by tools/ci/lint-reusable-pins.sh and OpenSSF Scorecard |
| Container bases | FROM image:tag@sha256:<digest> | Policy in security/CI_PINNING.md |
| Binaries fetched during CI | sha256sum -c against a committed manifest | security/remote-installers.sha256 |
None of these are needed to build the package: the release archive is self-contained and its build has no network step. They exist so that the tests you may want to run are reproducible.
Full provenance policy: supply-chain/README.md.
Building the package
From a release tarball (recommended — it is the attested artefact):
From a git checkout:
Both honour PREFIX (default /usr/local) and DESTDIR, and install to FHS paths:
$(DESTDIR)$(PREFIX)/bin/dot -> symlink into libexec
$(DESTDIR)$(PREFIX)/lib/dotfiles/ program tree
$(DESTDIR)$(PREFIX)/share/man/man1/dot.1
$(DESTDIR)$(PREFIX)/share/bash-completion/completions/dot
$(DESTDIR)$(PREFIX)/share/zsh/site-functions/_dot
$(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/dot.fish
$(DESTDIR)$(PREFIX)/share/doc/dotfiles/{LICENSE-APACHE,LICENSE-MIT,README.md,CHANGELOG.md}
Every directory is individually overridable — BINDIR, MANDIR, DOCDIR, LIBEXECDIR, BASHCOMPDIR, ZSHCOMPDIR, FISHCOMPDIR — for distributions whose layout differs. If your BINDIR and LIBEXECDIR are not siblings under PREFIX, also set DOT_LINK to the correct relative or absolute symlink target; make install fails loudly rather than leaving a dangling link.
make uninstall removes exactly what make install placed.
Testing offline
There are no vendored build dependencies to unbundle, and the checks a packager cares about need no network:
installcheck verifies the bin symlink resolves, the man page and all three completions are present, the licences are installed, and the installed binary reports the expected version and renders help — from a sandboxed HOME, with no source checkout and no network.
The full suite also runs offline:
make test-integration and anything under install/provision/ do touch the network and modify $HOME; do not run them in a build chroot.
Verifying what you downloaded
Do not package an unverified tarball. Every release carries four independent attestations, and the consumer-side recipe with copy-paste commands is in pkg/VERIFY.md and security/VERIFY_RELEASE.md.
Minimum a packager should do:
TAG=v0.2.527
REPO=sebastienrousseau/dotfiles
# SLSA build provenance on the tarball itself
# Or, without the gh CLI: the signed manifest covering every asset
Tags are signed with an SSH ed25519 key published in KEYS.asc, which is itself a git allowed_signers file:
An SBOM ships with every release in both CycloneDX and SPDX JSON.
Reproducibility
The release archives are built deterministically — tar --sort=name --mtime='1970-01-01' --owner=0 --group=0 --numeric-owner, and zip -X -D over a normalised tree — so two builds of the same tag produce identical bytes.
This is a statement about the archive build, which is exercised by CI on every release. It is not a verified end-to-end reproducible-builds claim: no diffoscope comparison of two independent rebuilds runs in CI today. Treat it as "deterministic archiving", not as a reproducible-builds certification.
Channel notes
| Channel | File | How it is produced |
|---|---|---|
| Homebrew | pkg/brew/dot.rb | release-distribute-homebrew.yml rewrites url + sha256, opens a PR on sebastienrousseau/homebrew-tap |
| Scoop | pkg/scoop/dot.json | release-distribute-scoop.yml rewrites version + hashes via jq, opens a PR on sebastienrousseau/scoop-bucket |
| AUR | pkg/aur/PKGBUILD | release-distribute-aur.yml rewrites pkgver + sha256sums, regenerates .SRCINFO, pushes to the AUR |
| Nix | pkg/nix/ → nix/flake.nix | packages.default in nix/flake.nix |
| deb / rpm | not yet provided | Contributions welcome; make install already produces a correct DESTDIR tree, so a recipe is thin |
If you are packaging for a distribution not listed here, please open an issue — a link to your package will be added, and the pkg/ directory is the right place for the recipe to live so it stays in lockstep with the release build.