FormalOS

Files and paths

Every directory fspec reads or writes on a host, what lives there and which scope owns it.

FormalSpec writes to a small, fixed set of places. Everything else on the machine is either declared in your spec or left alone.

Your repository

PathMeaning
spec.starThe entry point. Sets hosts
fspec.lockEvery pinned version, written by fspec update
secrets.starRecipients of each .age file
dev.starA project environment, in any repository
.fspec/loads/gh: load files shipped with a remote apply

Store and generations

PathMeaning
/opt/fspec/store/<hash>-<name>-<version>/One unpacked package. Never rewritten; reused when present
/opt/fspec/generations/<n>/bin/A generation: symlinks into the store
/opt/fspec/currentSymlink to the active generation
/opt/fspec/current/binPut this on PATH
/opt/fspec/current/pkgs/<name>A package's whole tree, for absolute paths to libraries and data
/usr/local/share/<dest>Links made by pkg(share = ...) (Linux)
/etc/<dest>Links made by pkg(etc = ...) (Linux)
/Applications/<App>.appCopies made by pkg(apps = ...) (macOS; a clone on APFS)
/var/cache/fspec/pkgs/Cached distro package files the lock pins
/var/cache/fspec/keys/Repository signing keys for repo(...)

The same /opt/fspec root is used on macOS.

State

PathScope
/var/lib/fspec/state.jsonSystem: what FormalSpec owns machine-wide
~/.local/state/fspec/state.jsonUser: what it owns in your home
<state dir>/generations/<n>.jsonA snapshot of state per generation, used by rollback
<state dir>/journal.jsonThe undo journal of an apply in progress
<state dir>/secrets/<sha256>.ageKept ciphertext of each applied secret, for fspec secret install

Deleting a state file makes FormalSpec forget what it owns. The next apply then treats every resource as new, and removals stop working for anything it forgot.

Trash

PathHolds
/var/lib/fspec/trash/<generation>/App data the system scope removed
~/.local/state/fspec/trash/<generation>/App data the user scope removed
.../trash/<generation>/manifest.jsonEach item's package and original path

fspec gc --older-than empties old trash.

Caches

PathHolds
~/.cache/fspec/loads/gh: load files, by owner, repo and commit
~/.cache/fspec/spec/<commit>/On a remote host: the spec a remote apply shipped
~/.cache/fspec/build/<name>-<rev>/On a builder: a build job, deleted when it finishes
~/.cache/fspec/dev/farms/<hash>/binA project environment's commands
~/.cache/fspec/dev/run/<project>/logsLogs of a project's exec services
~/.local/share/fspec/store/Packages a project environment unpacked for you
~/.local/state/fspec/sync/<item>/Sync bases and previous versions

Services

InitSystem unitsUser units
systemd/etc/systemd/system/fspec-<name>.service (and .timer)~/.config/systemd/user/fspec-<name>.service
launchd/Library/LaunchDaemons/fspec.<name>.plist~/Library/LaunchAgents/fspec.<name>.plist
runit/etc/sv/fspec-<name>/~/.local/service/fspec-<name>/
OpenRC/etc/init.d/fspec-<name>none
dinit/etc/dinit.d/fspec-<name>~/.config/dinit.d/fspec-<name>
s6/etc/s6/sv/fspec-<name>/none
cron timers/etc/cron.d/fspec-<name>a marked block in your crontab

Keys

PathMeaning
~/.config/fspec/age.keyAn age identity, tried after the ssh keys when decrypting secrets
~/.config/direnv/lib/fspec.shWhere fspec dev direnv output goes

Logs

PathMeaning
<root>/var/log/fspec-commands.logCommands a --root run would have executed
/var/log/fspec-install.logThe installer's output