Skip to content
Copied!
published on 2026-07-18

How It Works

This article explains the core concepts behind Rigo (the vault layout, entries and the state model, OS layers, per-machine selection). For day-to-day procedures, see the tutorial.

The Vault

The vault is a plain directory that mirrors your home directory. vault/.zshrc corresponds to ~/.zshrc, and vault/.config/git/config to ~/.config/git/config.

Vault/
├── .config/
│   └── rigo/
│       └── rigo.toml      # Rigo's own config, managed like any entry
├── .zshrc                 # → ~/.zshrc
├── .gitconfig             # → ~/.gitconfig
├── .os/                   # OS-specific layer (see below)
└── .trash/                # forget's safety net (see below)

.os and .trash are structural directories that Rigo treats specially and excludes from normal scanning (their names can be changed with os_dir / trash_dir / abs_dir in rigo.toml).

Config Location and Vault Discovery

The config file rigo.toml sits at the fixed location <vault>/.config/rigo/rigo.toml inside the vault and is linked to ~/.config/rigo/rigo.toml like any other managed file. Rigo discovers the vault by following this symlink backwards.

  1. Read the symlink at $XDG_CONFIG_HOME/rigo/rigo.toml (~/.config/rigo/rigo.toml when unset)
  2. Strip the trailing .config/rigo/rigo.toml from its target; what remains is the vault root

Because "where the config sits inside the vault" is fixed, the vault location never needs to be recorded anywhere. On a new machine the symlink does not exist yet, so only the first run names the rigo.toml inside the vault with -f (if that first command is apply, rigo.toml itself gets linked on the spot and automatic discovery works from then on).

Switching Vaults

Multiple vaults (say, a daily one and a lab one) work by the same mechanism: re-pointing the discovery symlink switches the environment. Name the target vault's rigo.toml with -f and link the discovery symlink itself.

sh
# Point the discovery symlink at another vault
rigo -f ~/Lab/Vault/.config/rigo/rigo.toml link .config/rigo/rigo.toml
replacing symlink that pointed to /Users/ada/Sync/Vault/.config/rigo/rigo.toml

Subsequent rigo commands use the new vault. Re-pointing the discovery symlink by hand with ln -sf works just as well.

After switching, symlinks of other entries still pointing into the old vault show up in status as broken when the new vault has an entry of the same name; re-point them with rigo link. Links to entries that exist only in the old vault are invisible from the new vault, so if you do not want them left behind, unlink (materialize) them on the old vault before switching.

Note that a vault directly at a volume root (/ or D:\ itself) is not supported. It would drown in OS-generated entries.

Entries

The unit of management is an entry. There are two kinds.

  • File entries
    One file in the vault becomes one symlink; the basic form.
  • Directory-unit entries
    A whole directory becomes one symlink. Declared in rigo.toml via dirs (or with a trailing / inside tags). New files created inside come under management automatically.

A directory that is not declared is not itself managed or linked. Rigo descends into it and manages the files inside as individual entries. .config/ usually works this way: each file such as .config/git/config becomes its own entry.

Paths inside a directory-unit entry cannot be declared individually in dirs or tags (the whole directory is one link, so nothing inside can be addressed separately; scanning reports an error).

The State Model

On a given machine, every entry is in one of five states. Detection only observes the actual state of the deploy location (the target); there is no separate state file.

StateMeaning
linkedThe target is a symlink to the vault copy (converged)
pendingThe target does not exist (not deployed yet)
unlinkedThe target is a real file identical to the vault
conflictThe target is a real file that differs from the vault
brokenThe target is a symlink, but not to the vault copy (including dangling links)

status additionally shows excluded for entries that do not deploy on this machine (see "Per-Machine Selection" below).

Directory-unit entries compare recursively (only a matching file set with matching contents counts as unlinked).

Safety Principles

State transitions follow consistent principles.

  • Conflicts are never resolved silently
    apply only lists conflicts and leaves them alone. The only resolutions are the interactive rigo link dialog (show the diff, then choose the vault side or adopt the local side into the vault) or an explicit --force (take the vault side).
  • Local content is never lost
    Linking stages the existing content aside before replacing it, and restores it on failure. unlink prepares the copy first, then swaps it in.
  • forget goes through the trash
    Un-managing an entry does not delete the vault copy immediately; it moves to the in-vault trash (see below).

OS Layers and Overlays

The vault is scanned as a stack of layers. When multiple layers hold the same logical path, the later layer wins.

common (vault root) < .os/<goos>/ < .os/linux/<distro>/
  • Common layer
    Deploys on every OS.
  • OS layers
    Under .os/darwin/, .os/linux/, and .os/windows/, again mirroring home. Deploy only on machines running that OS.
  • Distribution overlays
    Under .os/linux/<distro>/. Only directory names declared in distros in rigo.toml are treated as overlays, deploying only on machines whose /etc/os-release ID field matches. Undeclared directories count as ordinary home content (with a warning if one happens to match the host's distribution).

For example, keep .zshrc in the common layer and add .os/darwin/.zshrc to give macOS its own version.

Absolute Paths Outside Home (.abs)

Paths outside the home directory go under .abs/ inside an OS layer, mirroring the filesystem root. Such paths cannot be shared between OSes, so they are inherently OS-specific.

vault/.os/linux/.abs/etc/systemd/user/foo.service  →  /etc/systemd/user/foo.service

Windows-Specific Mappings

On Windows, two more mappings apply.

  • Profile sections
    .os/windows/.appdata/ maps to %APPDATA%, and .os/windows/.local/ to %LOCALAPPDATA%.
  • Named volumes
    Absolute paths outside home never use drive letters directly; they go through .abs/<volume>/. The volume-to-drive-letter mapping is declared per host (or group) in [volumes] in rigo.toml. The built-in volume system resolves to %SystemDrive% and needs no declaration.
vault/.os/windows/.abs/data/Tools/x.ini  →  D:\Tools\x.ini
                                            (E:\Tools\x.ini on a host that maps "data" to e)

This is how the same content lands on different drives on different machines, absorbed by a name.

Logical paths under .config/ resolve to $XDG_CONFIG_HOME (~/.config when unset) on every OS.

Per-Machine Selection

Which entries deploy on which machine is controlled by [groups] / [include] / [exclude] in rigo.toml.

  • A machine's identifier (its host name) is the OS host name up to the first dot, lowercased. The rigo status header shows it.
  • [groups] names sets of hosts (the same idea as an Ansible inventory).
  • The keys of [include] / [exclude] are host or group names; the values are lists of paths or tag names.

Selection runs in one of two modes.

  • Exclude mode (default)
    Everything deploys except what matches the effective exclude (the union of the host's own excludes and those of its groups).
  • Include mode
    Kicks in as soon as the host has any effective include. Only what matches the effective include deploys, minus the effective exclude (an allowlist).

Each list item is a tag name (expanding to every member of the tag) or a path. A path matches the entry itself, and naming a directory also matches every entry beneath it.

Deselected entries show as excluded in status and are never touched by apply or link. forget is a vault-level operation, though, so it works on excluded entries too (it just skips the local materialization).

ignore

The following never become entries; scanning skips them.

  • Built-in ignores
    OS droppings (.DS_Store, Thumbs.db, and friends) and the artifacts of the major sync services (Syncthing, Dropbox, iCloud Drive, Google Drive, OneDrive, Nextcloud/ownCloud, Resilio, Synology, and so on). Rigo does not care how the vault is synced, so the usual suspects are covered out of the box.
  • User patterns
    gitignore-style globs in ignore in rigo.toml, matched against vault-relative paths (a pattern without / matches basenames at any depth, ** crosses directories, a trailing / restricts to directories).

For services that localize conflict-copy filenames (Dropbox, Nextcloud), only the English forms are built in; add user patterns for other locales.

The Trash

Content removed from the vault by rigo forget is not deleted immediately; it moves to .trash/ inside the vault.

  • The trash consists of generation directories stamped in UTC (for example .trash/20260716T093000Z/), one entry per generation.
  • Each generation holds a metadata file (.rigo-entry) recording the original vault-relative path; trash ls / trash restore use it.
  • The trash is part of the vault, so it rides the sync tool. A forget on one machine can be undone (trash restore) from another, but by the same token trash empty removes a safety net shared by all machines (it asks for confirmation).

When a forget from another machine syncs in, a dangling symlink remains on your machine. rigo clean cleans these up interactively, offering to restore a real file when a trash copy exists.

Next, the tutorial walks through these concepts as actual procedures.