Settings
gripsack is configured with plain TOML in two files, and that's the whole story:
env.toml— lives in your env repo, committed. It makes the repo self-describing: any machine that clones it knows how to evaluate it.~/.config/gripsack/config.toml— lives on one machine, never committed. For machine-local things you don't want in git.
If you only remember one rule: configuration is data, and it is read before any of your module code runs. Settings can never depend on what a module computes — so there's no bootstrap paradox, ever.
Heads up: gripsack is alpha. The schema below is stable, but some keys are wired up only as
grip applylands. This page documents the config surface; settings reference has every key in table form.
A minimal env.toml
[env]
name = "tarek"
That's enough for a working repo. Everything else is optional.
The frontend
Modules are TypeScript — modules/*.ts and hosts/*.ts are the
frontend, and there is nothing to declare. (A stale
frontend = "python" from older releases fails with a migration
hint; the key is gone.)
Eval runs sandboxed under a pinned Deno — no env vars, no network, no
subprocesses — and the runtime provisions itself: the first eval
downloads it once (sha256-verified, ~40MB, cached; 2.9.6 today),
grip doctor checks it, and GRIPSACK_DENO points at your own. Eval
platforms: glibc Linux, including WSL2's Linux environment — Deno ships no musl
build. macOS and native Windows are not supported.
The repo's installed node_modules/@gripsack/core shadows the embedded
frontend. grip doctor reads that installed package's version, not just the
range declared in package.json: a stale installed copy is a MISS even
when the declaration is current. Update the local install to
@gripsack/core@^0.39.0, or remove the shadowing copy to use the embedded
frontend. With no local install, an old declaration remains a warning.
File permission policy needs no setting. Copies/templates follow payload executability, track content plus full mode, and preserve user chmod drift; merge markers track their host's mode. See ownership modes.
Build-time environment
[eval.env]
CARGO_NET_GIT_FETCH_WITH_CLI = "true"
Injected into the apply process for the run's duration — build steps,
fetchers, and plugins inherit it (SSL_CERT_FILE for a corporate CA
is the canonical case). The sandboxed eval itself sees no environment
at all; what your config learns about the machine arrives through the
host entrypoint's ctx.
Custom sources (fetchers)
For transports built-ins can't do (mTLS, exotic protocols), point a named source at its plugin:
[fetchers.artifactory]
plugin = "gripfetch-artifactory"
Usually you don't need this section at all — plugin discovery is
automatic for gripfetch-<name> executables on your PATH. Declare it
when you want an explicit path or a different name.
Housekeeping
[settings]
keep_generations = 20
How many generations to keep before grip gc reclaims store paths.
Rollback depth vs disk. Default keeps everything until you gc manually.
Acquisition has its own budget, separate from module concurrency. By default,
at most two payloads acquire at once, with 512 MiB downloaded, 4 GiB expanded,
100,000 entries and a 128 MiB decoder budget per payload. Downloads use private
disk spools, not payload-sized RAM buffers. The positive integer settings
acquisition_jobs, download_limit_bytes, expanded_limit_bytes,
archive_entry_limit and decoder_memory_bytes are listed in the
settings reference.
Temporary files and store publication
TMPDIR selects ordinary temporary storage; when it is unset,
Linux normally uses /tmp. The temporary directory need not be on
the same filesystem as GRIPSACK_HOME. This is the core-owned
publication contract, not a requirement to put all scratch under the
store:
| operation | staging and final publication |
|---|---|
| GitHub releases and other built-in archive fetches | private download/unpack staging; checked payload published into the store |
native conda.environment |
helper solve scratch, archive spools/extraction and prefix staging use temporary storage; the core validates the frozen result before store publication |
pixi.fromLock |
parses captured manifest/lock files without running a pixi subprocess, then uses the same frozen archive/materialization path as native Conda |
The publisher seals and syncs staged content, then renames it. When
that rename crosses filesystems (EXDEV), it streams a copy into a
fresh sibling on the destination filesystem, preserves file
modes and symlinks, syncs the copied files/directories, then atomically
renames the completed sibling to the final name and syncs its parent.
It never exposes the partially copied tree under the final store name.
Conda's Rattler helper uses copy-only linking and patches for the final
prefix; it does not require hard links from /tmp into the store.
Provision sufficient space on both filesystems for staging and
publication. Setting TMPDIR to suitable private scratch is a
capacity/performance choice, not an ownership or integrity bypass.
External pixi lock generation, custom fetcher plugins, and arbitrary
user commands retain their own scratch/publication rules; this
guarantee does not repair their cross-device rename assumptions.
Measured with public 0.45.0 in an isolated Linux container: native
Conda and Pixi import each published 42 real frozen archives with
TMPDIR, TMP and TEMP unset, /tmp on tmpfs and the private
state/store on a different filesystem. Both apply and retained
reapply succeeded; retained store bytes, modes and directory/file
identity remained unchanged. Acquisition used local fixture HTTPS,
not external network access; materialization and retained reuse made
no requests. No pixi executable was on PATH. The external
reviewer's archive/GitHub separate-filesystem success is separate
reviewer evidence. These observations do not qualify arbitrary
filesystems or third-party commands.
Machine-local config
~/.config/gripsack/config.toml accepts [settings] and [fetchers.*]
— the same keys as env.toml minus [env] and [eval]. Use it for
things that shouldn't be committed: a plugin that only exists on this
machine, a lower keep_generations on a small disk.
When the same key is set in both files, the repo wins — a cloned repo behaves identically everywhere, and your local file can only fill gaps or add new entries. (Environment variables and CLI flags override both, for one-off overrides.)