dotdrift

command module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 5 Imported by: 0

README

dotdrift

A CLI tool for managing Linux configuration through git-backed profiles.

Principles

  • Presence = managed: every modules/<id>/module.toml is selected; no enable list.
  • Apply always resumes: dotdrift apply continues from the first incomplete step.
  • Mise owns files: symlink, copy, and template operations are performed by mise.
  • Selection precedence: module → host → user; user wins, disable is unioned across layers.

Installation

go install github.com/thedataflows/dotdrift@latest

dotdrift requires mise on PATH. It will install a user-local copy via https://mise.run when missing or too old.

Quick start

# Create a profile
dotdrift init ./my-profile
cd ./my-profile

# Onboard an existing config path into a module
dotdrift onboard ~/.bashrc

# See what would change
dotdrift plan

# Apply the profile (always resumes)
dotdrift apply --yes

# Show current state
dotdrift status

Profile layout

profile/
├── dotdrift.toml
├── modules/
│   └── <id>/
│       ├── module.toml
│       └── home/...          # files referenced by dotfile entries
├── hosts/<hostname>/
│   ├── dotdrift.toml           # host layer: disable list unioned
│   └── modules/<id>/...        # host overlays
└── users/<username>/
    ├── dotdrift.toml           # user layer: disable list unioned
    └── modules/<id>/...        # user overlays (highest precedence)

The dotdrift.toml in each layer may carry a [modules] disable list; disables are unioned across base, host, and user layers.

Hooks

A module may declare shell commands to run around the apply pipeline:

# modules/<id>/module.toml
[hooks]
pre = ["echo about to apply"]
post = ["systemctl --user daemon-reload"]

pre commands run as the hooks-pre step before packages are installed; post commands run as the hooks-post step after dotfiles. Commands execute as mise tasks from the profile root with DOTDRIFT_PROFILE, DOTDRIFT_HOSTNAME, DOTDRIFT_USERNAME, DOTDRIFT_OS, and DOTDRIFT_BACKEND in the environment. Unlike other sections, hooks merge by append across layers (base → host → user) and modules, in selection order. A failing hook fails its step and resume re-runs it, so write post-hooks to be idempotent. Hooks are listed in dotdrift plan; skip them with dotdrift apply --no-hooks or DOTDRIFT_NO_HOOKS=1.

Scope

A module applies to the home directory by default. Set scope = "system" in its module.toml to manage system dotfiles (e.g. targets under /etc):

# modules/<id>/module.toml
scope = "system"

[dotfiles]
"/etc/demo.conf" = { source = "demo.conf", mode = "copy" }

One dotdrift apply covers both scopes: user dotfiles apply as usual, and system dotfiles apply in a dotfiles-system step via sudo (one password prompt per apply, thanks to sudo's timestamp cache; no sudo at all when already running as root). System-scope entries are marked [system] in dotdrift plan. Packages self-elevate via the distro backend and hooks carry their own inline privilege, so neither needs scope machinery.

Generate

dotdrift generate mounts writes a system-scoped module holding systemd .mount units (plus .service/.timer for --startat), and dotdrift generate smb writes one holding shares.conf and a one-time smb.conf seed. Mounts and shares are declared as [mounts.<name>] and [smb]/[smb.shares.<name>] tables in the module's module.toml (merged whole-entry by name across layers); the rendered files are derived artifacts. On a terminal with no input flags an interactive wizard runs (same byte-identical result as the flag mode); with any input flag the strict CLI mode applies. --layer base|host|user picks where the module lands — a host-layer-only module needs no base stub. One CLI run writes one mount (the [mounts] section is replaced wholesale, so use the wizard's "add another mount?" loop or one --module per mount for several); --share is repeatable. The regular apply pipeline then places the files via mise and activates them in the conditional mounts/smb steps (unit enablement, timers, samba group/users/service). See docs/product/cli-surface.md for the full flag reference and docs/product/migrate-pimp-my-cachyos.md for a worked migration.

See examples/simple/ for a minimal single-module profile, and examples/profile/ for a multi-layer example with host and user overlays.

Note: dotdrift apply stores resume state and generated mise config under the XDG state directory ($XDG_STATE_HOME/dotdrift/, defaulting to ~/.local/state/dotdrift/) so the profile directory is never polluted with runtime state. dotdrift onboard does the same (.../profiles/<hash>/onboard/mise.toml); pass --yes to answer mise prompts non-interactively.

sudo warning: dotdrift resolves the username from the OS account, not $USER. Running sudo dotdrift apply selects root's overlays and writes into root's HOME. To manage your own dotfiles, run dotdrift as your normal user; use sudo only if you intentionally maintain a users/root/ overlay.

Commands

Command Purpose
`dotdrift init [path git-url]`
dotdrift detect Print host/user/os/distro/gpu/backend facts
dotdrift modules [modules...] List selected and skipped modules (optionally limited to the listed modules)
dotdrift plan [--json] [modules...] Print the effective plan without side effects (--json for machine-readable output; optionally limited to the listed modules)
dotdrift apply [--yes] [--no-hooks] [modules...] Run the full pipeline and resume from state (optionally limited to the listed modules)
dotdrift status Show resume cursor, selection, and last error
dotdrift onboard <path>... Copy live paths into a module and apply (--force replaces a conflicting module copy with the live file)
dotdrift generate mounts|smb Generate a mounts module (systemd units) or smb module (samba shares) into a profile layer; interactive wizard on a terminal, strict flag mode otherwise
# Generate an NFS mount module with a nightly timer, then two samba shares
dotdrift generate mounts --name syn01 --source nas:/volume1/syn01 \
  --destination /mnt/synology/syn01 --type nfs --startat "*-*-* 18:05:00"
dotdrift generate smb --share media=/srv/media --share data=/mnt/data --no-avahi

Module filter

modules, plan, and apply accept optional positional module ids limiting the command's scope to those modules. Ids are space or comma separated (both forms mix freely):

dotdrift apply vim git      # only vim and git
dotdrift apply vim,git      # same
dotdrift plan shell --json  # plan for shell only

With no ids the behavior is unchanged. An unknown id is a loud error naming the unknown ids and listing the valid module ids. Naming a module that exists but is not selected (disabled via [modules] disable or excluded by a when filter) is also an error naming it and its skip reason — the filter never resurrects skipped modules.

Resume caveat: a filtered apply changes the selection fingerprint, so the resume state resets (with the standard warning) on the first scoped run. Alternating scoped and unscoped applies resets the cursor each time.

Testing

go test ./...

Unit tests run offline. Integration tests against real tools can be added with //go:build integration.

Integration tests
./tests/e2e/run.sh

Builds and runs a Docker end-to-end suite across three distros — the debian family (debian:bookworm-slim, ubuntu:24.04) and CachyOS (cachyos/cachyos); requires Docker and network access. Each container builds dotdrift from this repo, onboards a live file with a real mise.run bootstrap, and runs dotdrift apply against a fixture profile — then asserts a real package install (apt on the debian family, pacman/paru on CachyOS) of a leaf package (jq), dotfile symlinking, pre/post hooks executed as mise tasks, resume no-op on a second apply, complete state on disk, and no runtime pollution inside the profile. The CachyOS image is the first coverage of dotdrift's Arch backend (paru -S install + pacman -Q idempotency); it ships paru from the CachyOS binary repo, so no AUR build is needed. Runs on push to main via .github/workflows/e2e.yml; the offline go test ./... gate is unchanged.


License

MIT License

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
Package dotdrift implements the dotdrift CLI.
Package dotdrift implements the dotdrift CLI.
internal
apply
Package apply orchestrates the dotdrift pipeline with always-resume semantics.
Package apply orchestrates the dotdrift pipeline with always-resume semantics.
detect
Package detect gathers system facts.
Package detect gathers system facts.
facts
Package facts holds the system facts used for module selection.
Package facts holds the system facts used for module selection.
generate
Package generate renders systemd units from dotdrift configuration.
Package generate renders systemd units from dotdrift configuration.
mise
Package mise bootstraps the mise binary and runs mise operations.
Package mise bootstraps the mise binary and runs mise operations.
mounts
Package mounts implements the mounts apply step: activation of systemd mount units whose unit files are already placed by mise.
Package mounts implements the mounts apply step: activation of systemd mount units whose unit files are already placed by mise.
onboard
Package onboard materializes live paths into a module.
Package onboard materializes live paths into a module.
packages
Package packages provides package backend operations.
Package packages provides package backend operations.
profile
Package profile loads and selects modules from a profile.
Package profile loads and selects modules from a profile.
resolve
Package resolve merges profile layers into an execution plan.
Package resolve merges profile layers into an execution plan.
smb
Package smb implements the apply step that activates the Samba server: group and user membership, avahi, config validation, service enablement, and samba account passwords.
Package smb implements the apply step that activates the Samba server: group and user membership, avahi, config validation, service enablement, and samba account passwords.
state
Package state persists resume-only state for dotdrift apply.
Package state persists resume-only state for dotdrift apply.
tui
Package tui implements the interactive `dotdrift generate` wizard.
Package tui implements the interactive `dotdrift generate` wizard.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL