No description
Find a file
Marvin Mees fd44e3507f
All checks were successful
Continous Integration / build-and-test (push) Successful in 26s
Merge pull request 'Added install scripts and badges in README.md' (#1) from devops/install.sh into main
Reviewed-on: #1
2026-06-23 11:52:05 +00:00
.forgejo/workflows Updated cd.yml 2026-06-23 08:42:15 +02:00
src Fixed initialization 2026-06-23 09:20:24 +02:00
tests Removed tests that required interactive-mode 2026-06-22 20:05:53 +02:00
.gitignore First version 2026-06-22 14:58:24 +02:00
CLAUDE.md First version 2026-06-22 14:58:24 +02:00
Directory.Build.props Added pipelines 2026-06-22 19:27:59 +02:00
Directory.Packages.props First version 2026-06-22 14:58:24 +02:00
global.json Fixed .NET version to minor versions 2026-06-22 19:53:22 +02:00
lncfg.slnx First version 2026-06-22 14:58:24 +02:00
README.md Added install scripts and badges in README.md 2026-06-23 13:51:28 +02:00

lncfg

Release Last commit .NET Platforms

lncfg ("link config") manages your UNIX config files through a git repository. It moves a config file or directory into a tracked repository and leaves a symlink in its place, so your dotfiles live in version control while remaining where the apps that use them expect them.

~/.gitconfig ──symlink──▶ ~/dotfiles/.gitconfig   (tracked in git)

Installation

The install script downloads the latest release for your platform, drops the binary in a user-level location (no sudo/admin required), and—on Linux and macOS—marks it executable.

Linux / macOS — installs to ~/.local/bin/lncfg:

curl -fsSL https://git.mmtsc.de/marvinmees/lncfg/raw/branch/main/install.sh | bash

Windows (PowerShell) — installs to %LOCALAPPDATA%\Programs\lncfg\lncfg.exe:

iwr https://git.mmtsc.de/marvinmees/lncfg/raw/branch/main/install.bat -OutFile install.bat; .\install.bat

If the install directory isn't on your PATH, the script prints the line to add it. A few environment variables tweak the behaviour: LNCFG_INSTALL_DIR (install location), LNCFG_VERSION (a specific tag instead of the latest release) and LNCFG_TOKEN (API token for a private mirror). You can also download a binary directly from the releases page.

Commands

Command What it does
lncfg init -c -d <dir> -r <remote> Create a new repository in <dir>, configure the git identity, make the first commit and push to <remote> (the remote is optional).
lncfg init -d <dir> -r <remote> Clone <remote> into <dir> and apply its links, resolving any conflicts with files already on the machine.
lncfg add <path> Move an existing file or directory into the repository, symlink it back, and commit + push.
lncfg sync Pull the latest configs for the managed repository and re-apply the links.

Conflict handling (init clone / sync)

When a config already exists on the machine, lncfg shows a diff and asks what to do:

  • Replace use the repository version (delete the local file, create the symlink).
  • Keep leave the local file untouched and skip linking it.
  • Backup move the local file aside to *.bak, then create the symlink.

For non-interactive use, pass --force, --keep or --backup to apply one choice to every conflict. A real directory is never silently replaced; it is always treated as a conflict.

Git identity (init -c)

If a gcm ("Git Configuration Manager") executable is on PATH, lncfg runs gcm auto. If that is unavailable or fails, it prompts for a name and email and configures the repository directly.

How it works

Each managed item is recorded in a committed manifest, lncfg.json, at the repository root:

{
  "version": 1,
  "entries": [
    { "source": ".gitconfig",   "target": "~/.gitconfig",      "kind": "File" },
    { "source": ".config/nvim",  "target": "~/.config/nvim",   "kind": "Directory" }
  ]
}
  • source is the item's location inside the repository.
  • target is its location on the machine, stored relative to $HOME as ~/… so the manifest is portable across machines and users. Paths outside $HOME are stored absolute.

lncfg's own per-machine state (which repository this machine uses) is kept separately and not committed, at $XDG_CONFIG_HOME/lncfg/config.json (default ~/.config/lncfg/config.json).

Architecture

Clean Architecture, with dependencies pointing inward:

Lncfg.Cli  ─▶ Lncfg.Application ─▶ Lncfg.Domain ◀─ Lncfg.Infrastructure
   (Spectre.Console.Cli,            (use cases)      (entities, ports)    (filesystem, git,
    composition root)                                                      gcm, JSON, Spectre UI)
  • Domain entities (LinkEntry, Manifest), pure services (PathMapper, LinkStatusEvaluator) and ports (IFileSystem, IGitClient, IGcmClient, IUserInteraction, …). No external dependencies.
  • Application the use cases (InitCreate, InitClone, Add, Sync) and the shared LinkApplier. Depends only on the domain. add performs its filesystem mutation with strict rollback, so a failed add never leaves your config missing.
  • Infrastructure adapters over the real filesystem (symlink-aware), the git and gcm CLIs, JSON persistence, and the Spectre.Console UI.
  • Cli Spectre.Console.Cli commands and the dependency-injection composition root; builds the lncfg executable.

Building and testing

dotnet build                 # build everything (warnings are errors)
dotnet test                  # run all four test projects
dotnet run --project src/Lncfg.Cli -- --help

Test strategy

Because lncfg manipulates real config files, it is tested intensively:

  • Domain tests pure unit tests for path mapping, the manifest, and conflict classification.
  • Infrastructure tests the symlink semantics of the real filesystem (detection, deleting a symlink without touching its target, moves), the git client against a temporary bare remote, and JSON round-trips.
  • Application tests every use case against the real filesystem and git in a hermetic sandbox, the full conflict-resolution matrix, and the add rollback paths (fault injection).
  • Integration tests the compiled lncfg executable, end to end, with an isolated $HOME and a local bare remote (create → add → push, and clone-onto-another-machine with a backed-up conflict).

Tech stack

.NET 10 · Spectre.Console · Clean Architecture · xUnit v3 (Microsoft Testing Platform)