d

command module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 15 Imported by: 0

README

Logo

d stands for dead config linker. It is a small filesystem linker for source trees. It walks a source directory, maps regular files into a target root, and creates exact symlinks.

The source tree is the manifest. The destination tree is the filesystem. d does not care what made the directory. Git repo, tarball, copied folder, generated tree, mounted disk, whatever. It reads a filesystem tree and links regular files.

Features

  • Treats a directory tree as the manifest
  • Links regular files into a destination tree with exact symlink targets
  • Supports direct trees and per-OS platform folders
  • Plans the full operation before mutating the filesystem
  • Refuses user-owned files, directories, and mismatched symlinks
  • Removes only exact managed symlinks when unlinking
  • Can adopt an existing destination symlink explicitly
  • Orders actions deterministically by destination path
  • Ignores common repository, editor, and license metadata by default
  • Uses openat2 on Linux and Android for stricter rooted path resolution
  • Preserves ownership from the nearest parent when platform metadata allows it

Non-Features

  • Repository awareness. VCS metadata, manifests, lockfiles, and history are not consulted.
  • Machine inventory, synchronization, or remote state.
  • Config files. The directory tree is the config.
  • Host-specific templates, conditionals, variables, or generated files.
  • Secret managers, encrypted sources, or decrypt-on-apply workflows.
  • Hooks, plugins, bootstrap scripts, package downloads, or external dependency fetching.
  • Partial-file edits, structured config patching, or content merges.
  • Managed file attributes like executable, private, read-only, create-only, or exact-directory state.
  • Copy mode, hardlink mode, junction mode, or fallback link strategies.
  • Git-like commands for add, edit, diff, pull, commit, push, or apply-after-update.
  • Implicit string splitting, path alias guessing, or filesystem-specific merge logic.
  • Transactional rollback after an operational syscall failure.
  • Accidental root filesystem deployment.
  • Deleting ordinary files or cleaning empty directories during unlink.

Installation

Requires Go 1.26+.

env CGO_ENABLED=0 go install -trimpath -ldflags="-s -w" github.com/enzv/d@latest

From a checkout:

./make.sh build

Usage

d /path/to/source

Example

Flat layout is the default. source/ already looks like the destination, so d links it straight there. Platform layout only adds one folder in front, like linux, darwin, windows, or plan9, when one source tree needs per-system files. Without an explicit target, the destination is Larry's home.

$ pwd
/home/larry

$ tree source
source/
|-- .config/nvim/init.lua
`-- .gitconfig

$ d source
LINK     /home/larry/.config/nvim/init.lua -> /home/larry/source/.config/nvim/init.lua
LINK     /home/larry/.gitconfig -> /home/larry/source/.gitconfig

$ readlink /home/larry/.gitconfig
/home/larry/source/.gitconfig

Full command documentation, flags, and examples live at pkg.go.dev/github.com/enzv/d.

Build & Testing

We do not use make. make.sh is a POSIX shell runner for local checks and release builds.

./make.sh lint
./make.sh test
./make.sh bench
./make.sh release
./make.sh clean

Documentation

Overview

D is the dead config linker. It links regular files from a source tree into a target tree.

It is a small command for linking filesystem-shaped source trees by symlink. The source tree is the manifest. The destination tree is the filesystem. D does not care what made the source directory. It can be a Git repo, tarball, copied folder, generated tree, mounted disk, or anything else that appears as a filesystem tree. There is no database, no templating pass, no bootstrap ritual, and no hidden state to debug at 2 a.m.

D plans before it mutates anything. If the plan contains a conflict, D prints the plan and exits without changing the filesystem. That is the contract.

Usage

d [flags] [source]

If source is omitted, D reads the current directory. If -t is omitted, D uses the home directory reported by the operating system. The -t flag takes a real path; D does not expand shell syntax such as ~ or $HOME. Pass those values expanded by your shell, or quote them if you want them treated literally.

The flags are:

-p
	Print the plan without changing the filesystem.

-u
	Unlink managed symlinks.

-i
	Replace an existing destination symlink with the source link instead of
	reporting it as a conflict.
	It cannot be combined with -u.

-x pattern
	Exclude an additional source path.
	The flag may be repeated.
	A pattern without a slash matches any path component.
	A pattern with a slash matches the slash-separated relative path.

-t path
	Set the target root.

-l name
	Set the source layout.
	The supported layouts are flat and platform.
	The default is flat.

Layouts

In flat layout, D walks source directly. Every regular file below source maps to the same relative path below the target root. This is the default layout.

For example:

source/.gitconfig -> $HOME/.gitconfig
source/.config/nvim/init.lua -> $HOME/.config/nvim/init.lua

In platform layout, D walks source/GOOS, where GOOS is the current Go operating system name, such as linux, darwin, windows, or plan9.

For example, on Linux:

source/linux/.gitconfig -> $HOME/.gitconfig
source/linux/.config/nvim/init.lua -> $HOME/.config/nvim/init.lua

Use flat layout for a tree that already looks like the destination tree. Use platform layout when one source tree contains separate per-OS trees.

Files

D only links regular source files. It walks directories, ignores empty directories, and rejects source symlinks and other non-regular entries. Destination paths are computed as:

destination = target root + source relative path

D creates missing parent directories with mode 0700. It creates symlinks to absolute source paths. It leaves an existing correct symlink alone. It refuses to overwrite ordinary files, directories, and symlinks that point somewhere else, unless -i is set for an existing symlink.

D treats managed symlinks by exact link text. A symlink that reaches the same file through a relative path, ., .., or another filesystem alias is still a conflict unless its stored target text is exactly the expected absolute source path.

D orders planned actions by destination path. Duplicate destination detection is textual; D does not try to merge paths that only collide through filesystem-specific aliases.

The default excludes are deliberately boring. At any depth, D ignores:

.git
.hg
.DS_Store
Thumbs.db
*.swp
*~

At the source root only, D ignores:

README
README.*
LICENSE
LICENSE.*
COPYING
COPYING.*

D treats the source as a filesystem directory. It does not require repository awareness, package metadata, VCS history, or any other tool-specific structure. It reads directory entries and file metadata from the filesystem, then applies the layout and exclude rules above. The built-in excludes only skip a small set of common metadata and junk paths. Use -x for any other metadata your tree carries.

Target Roots

The target root must be explicit when the source tree looks like a filesystem root. For example, a flat source tree containing top-level directories such as etc, home, usr, var, Users, ProgramData, or rc can be real system configuration, not a home-directory tree. D refuses to guess.

Use -t / for a root filesystem deployment. Use -t "$HOME" when you want the home directory and the source tree happens to look root-shaped.

When targeting /, spell home paths fully. If the real home is /home/alice, write home/alice/.config/app, not home/.config/app.

With -u, D removes only managed symlinks: links whose destination path matches the source tree layout and whose link target points exactly at the expected source file. If the source tree has already been removed, D can still scan the target root for exact managed links using the source path that would have been used for the current run. It does not delete regular files. It does not clean up directories. It does not guess.

Safety

D rejects source and target roots that are symlinks or have symlink ancestors. The target root must already exist and must not be inside the active source tree.

A conflict prevents all filesystem mutation. Operational failures during execution are not transactional; D reports the error and does not roll back earlier successful operations from the same run.

On Linux and Android, D uses openat2-based target operations to reject symlink resolution, magic links, and filesystem boundary crossings under the target root. On other supported platforms, D uses the standard library rooted filesystem operations available to Go.

When D needs to create parent directories or replacement symlinks, it tries to preserve ownership from the nearest existing parent directory on platforms that expose that metadata. If the operating system refuses symlink creation, D returns that error instead of switching to a weaker link type.

Examples

d /path/to/source
d -l=platform /path/to/source
d -t /tmp/stage -p system
d -x .cache -x private /path/to/source
d -u /path/to/source
d -p -u /path/to/source
d -i /path/to/source
d -t "$HOME" /path/to/source

Output

D writes human-readable action lines to standard output. Errors go to standard error.

Typical actions are:

LINK     /home/alice/.gitconfig -> /home/alice/source/.gitconfig
OK       /home/alice/.config/nvim/init.lua
IMPORT   /home/alice/.ssh/config: symlink points to /old/config -> /home/alice/source/.ssh/config
CONFLICT /home/alice/.ssh/config: regular file exists
UNLINK   /home/alice/.config/nvim/init.lua
MISSING  /home/alice/.config/nvim/init.lua

The exit codes are:

0 success
1 conflict or operational error
2 invalid usage

Jump to

Keyboard shortcuts

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