{"id":28369868,"url":"https://github.com/yarlson/lnk","last_synced_at":"2026-01-21T13:25:26.979Z","repository":{"id":295182714,"uuid":"989147941","full_name":"yarlson/lnk","owner":"yarlson","description":"🔗 Git-native dotfiles management that doesn't suck.","archived":false,"fork":false,"pushed_at":"2026-01-12T08:51:42.000Z","size":224,"stargazers_count":633,"open_issues_count":9,"forks_count":11,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-01-19T22:35:48.625Z","etag":null,"topics":["configuration-management","devtools","dotfiles","dotfiles-manager"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yarlson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-23T16:22:43.000Z","updated_at":"2026-01-17T03:09:01.000Z","dependencies_parsed_at":"2025-08-27T07:08:50.466Z","dependency_job_id":"33451c87-76ec-43ec-8e37-d373e3773abf","html_url":"https://github.com/yarlson/lnk","commit_stats":null,"previous_names":["yarlson/lnk"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/yarlson/lnk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Flnk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Flnk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Flnk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Flnk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yarlson","download_url":"https://codeload.github.com/yarlson/lnk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yarlson%2Flnk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28633757,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T04:47:28.174Z","status":"ssl_error","status_checked_at":"2026-01-21T04:47:22.943Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration-management","devtools","dotfiles","dotfiles-manager"],"created_at":"2025-05-29T06:07:20.356Z","updated_at":"2026-01-21T13:25:26.967Z","avatar_url":"https://github.com/yarlson.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# Lnk\n\n**Git-native dotfiles management that doesn't suck.**\n\nLnk makes managing your dotfiles straightforward, no tedious setups, no complex configurations. Just tell Lnk what files you want tracked, and it'll automatically move them into a tidy Git repository under `~/.config/lnk`. It then creates clean, portable symlinks back to their original locations. Easy.\n\nWhy bother with Lnk instead of plain old Git or other dotfile managers? Unlike traditional methods, Lnk automates the boring parts: safely relocating files, handling host-specific setups, bulk operations for multiple files, recursive directory processing, and even running your custom bootstrap scripts automatically. This means fewer manual steps and less chance of accidentally overwriting something important.\n\nWith Lnk, your dotfiles setup stays organized and effortlessly portable, letting you spend more time doing real work, not wrestling with configuration files.\n\n```bash\nlnk init -r git@github.com:user/dotfiles.git    # Clones \u0026 runs bootstrap automatically\nlnk add ~/.vimrc ~/.bashrc ~/.gitconfig         # Multiple files at once\nlnk add --recursive ~/.config/nvim              # Process directory contents\nlnk add --dry-run ~/.tmux.conf                  # Preview changes first\nlnk add --host work ~/.ssh/config               # Host-specific config\nlnk push \"setup\"\n```\n\n## Install\n\n```bash\n# Quick install (recommended)\ncurl -sSL https://raw.githubusercontent.com/yarlson/lnk/main/install.sh | bash\n```\n\n```bash\n# Homebrew (macOS/Linux)\nbrew install lnk\n```\n\n```bash\n# Manual download\nwget https://github.com/yarlson/lnk/releases/latest/download/lnk-$(uname -s | tr '[:upper:]' '[:lower:]')-amd64\nchmod +x lnk-* \u0026\u0026 sudo mv lnk-* /usr/local/bin/lnk\n```\n\n```bash\n# From source\ngit clone https://github.com/yarlson/lnk.git \u0026\u0026 cd lnk \u0026\u0026 go build . \u0026\u0026 sudo mv lnk /usr/local/bin/\n```\n\n## Usage\n\n### Setup\n\n```bash\n# Fresh start\nlnk init\n\n# With existing repo (runs bootstrap automatically)\nlnk init -r git@github.com:user/dotfiles.git\n\n# Skip automatic bootstrap\nlnk init -r git@github.com:user/dotfiles.git --no-bootstrap\n\n# Force initialization (WARNING: overwrites existing managed files)\nlnk init -r git@github.com:user/dotfiles.git --force\n\n# Run bootstrap script manually\nlnk bootstrap\n```\n\n### Daily workflow\n\n```bash\n# Add multiple files at once (common config)\nlnk add ~/.vimrc ~/.bashrc ~/.gitconfig ~/.tmux.conf\n\n# Add directory contents individually\nlnk add --recursive ~/.config/nvim ~/.config/zsh\n\n# Preview changes before applying\nlnk add --dry-run ~/.config/git/config\nlnk add --dry-run --recursive ~/.config/kitty\n\n# Add host-specific files (supports bulk operations)\nlnk add --host laptop ~/.ssh/config ~/.aws/credentials\nlnk add --host work ~/.gitconfig ~/.ssh/config\n\n# List managed files\nlnk list                    # Common config only\nlnk list --host laptop      # Laptop-specific config\nlnk list --all              # All configurations\n\n# Check status\nlnk status\n\n# Sync changes\nlnk push \"updated vim config\"\nlnk pull                    # Pull common config\nlnk pull --host laptop      # Pull laptop-specific config\n```\n\n## How it works\n\n```\nCommon files:\nBefore: ~/.vimrc (file)\nAfter:  ~/.vimrc -\u003e ~/.config/lnk/.vimrc (symlink)\n\nHost-specific files:\nBefore: ~/.ssh/config (file)\nAfter:  ~/.ssh/config -\u003e ~/.config/lnk/laptop.lnk/.ssh/config (symlink)\n```\n\nYour files live in `~/.config/lnk` (a Git repo). Common files go in the root, host-specific files go in `\u003chost\u003e.lnk/` subdirectories. Lnk creates symlinks back to original locations. Edit files normally, use Git normally.\n\n## Safety Features\n\nLnk includes built-in safety checks to prevent accidental data loss:\n\n### Data Loss Prevention\n\n```bash\n# This will be blocked if you already have managed files\nlnk init -r git@github.com:user/dotfiles.git\n# ❌ Directory ~/.config/lnk already contains managed files\n#    💡 Use 'lnk pull' to update from remote instead of 'lnk init -r'\n\n# Use pull instead to safely update\nlnk pull\n\n# Or force if you understand the risks (shows warning only when needed)\nlnk init -r git@github.com:user/dotfiles.git --force\n# ⚠️  Using --force flag: This will overwrite existing managed files\n#    💡 Only use this if you understand the risks\n```\n\n### Smart Warnings\n\n- **Contextual alerts**: Warnings only appear when there are actually managed files to overwrite\n- **Clear guidance**: Error messages suggest the correct command to use\n- **Force override**: Advanced users can bypass safety checks when needed\n\n### Recovering from Accidental Deletion\n\nIf you accidentally delete a managed file without using `lnk rm`:\n\n```bash\n# File was deleted outside of lnk\nrm ~/.bashrc  # Oops! Should have used 'lnk rm'\n\n# lnk rm won't work because symlink is gone\nlnk rm ~/.bashrc\n# ❌ File or directory not found: ~/.bashrc\n\n# Use --force to clean up the orphaned tracking entry\nlnk rm --force ~/.bashrc\n# ✅ Force removed .bashrc from lnk\n```\n\n## Bootstrap Support\n\nLnk automatically runs bootstrap scripts when cloning dotfiles repositories, making it easy to set up your development environment. Just add a `bootstrap.sh` file to your dotfiles repo.\n\n### Examples\n\n**Simple bootstrap script:**\n\n```bash\n#!/bin/bash\n# bootstrap.sh\necho \"Setting up development environment...\"\n\n# Install Homebrew (macOS)\nif ! command -v brew \u0026\u003e /dev/null; then\n    /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\nfi\n\n# Install packages\nbrew install git vim tmux\n\necho \"✅ Setup complete!\"\n```\n\n**Usage:**\n\n```bash\n# Automatic bootstrap on clone\nlnk init -r git@github.com:you/dotfiles.git\n# → Clones repo and runs bootstrap script automatically\n\n# Skip bootstrap if needed\nlnk init -r git@github.com:you/dotfiles.git --no-bootstrap\n\n# Run bootstrap manually later\nlnk bootstrap\n```\n\n## Multihost Support\n\nLnk supports both **common configurations** (shared across all machines) and **host-specific configurations** (unique per machine).\n\n### File Organization\n\n```\n~/.config/lnk/\n├── .lnk                    # Tracks common files\n├── .lnk.laptop             # Tracks laptop-specific files\n├── .lnk.work               # Tracks work-specific files\n├── .vimrc                  # Common file\n├── .gitconfig              # Common file\n├── laptop.lnk/             # Laptop-specific storage\n│   ├── .ssh/\n│   │   └── config\n│   └── .tmux.conf\n└── work.lnk/               # Work-specific storage\n    ├── .ssh/\n    │   └── config\n    └── .gitconfig\n```\n\n### Usage Patterns\n\n```bash\n# Common config (shared everywhere) - supports multiple files\nlnk add ~/.vimrc ~/.bashrc ~/.gitconfig ~/.tmux.conf\n\n# Process directory contents individually\nlnk add --recursive ~/.config/nvim ~/.config/zsh\n\n# Preview operations before making changes\nlnk add --dry-run ~/.config/alacritty/alacritty.yml\nlnk add --dry-run --recursive ~/.config/i3\n\n# Host-specific config (unique per machine) - supports bulk operations\nlnk add --host $(hostname) ~/.ssh/config ~/.aws/credentials\nlnk add --host work ~/.gitconfig ~/.npmrc\n\n# List configurations\nlnk list                    # Common only\nlnk list --host work        # Work host only\nlnk list --all              # Everything\n\n# Pull configurations\nlnk pull                    # Common config\nlnk pull --host work        # Work-specific config\n```\n\n## Why not just Git?\n\nYou could `git init ~/.config/lnk` and manually symlink everything. Lnk just automates the tedious parts:\n\n- Moving files safely (with atomic operations)\n- Creating relative symlinks\n- Handling conflicts and rollback\n- Tracking what's managed\n- Processing multiple files efficiently\n- Recursive directory traversal\n- Preview mode for safety\n\n## Examples\n\n### First time setup\n\n```bash\n# Clone dotfiles and run bootstrap automatically\nlnk init -r git@github.com:you/dotfiles.git\n# → Downloads dependencies, installs packages, configures environment\n\n# Add common config (shared across all machines) - multiple files at once\nlnk add ~/.bashrc ~/.vimrc ~/.gitconfig ~/.tmux.conf\n\n# Add configuration directories individually\nlnk add --recursive ~/.config/nvim ~/.config/zsh\n\n# Preview before adding sensitive files\nlnk add --dry-run ~/.ssh/id_rsa.pub\nlnk add ~/.ssh/id_rsa.pub  # Add after verification\n\n# Add host-specific config (supports bulk operations)\nlnk add --host $(hostname) ~/.ssh/config ~/.aws/credentials\n\nlnk push \"initial setup\"\n```\n\n### On a new machine\n\n```bash\n# Bootstrap runs automatically\nlnk init -r git@github.com:you/dotfiles.git\n# → Sets up environment, installs dependencies\n\n# Pull common config\nlnk pull\n\n# Pull host-specific config (if it exists)\nlnk pull --host $(hostname)\n\n# Or run bootstrap manually if needed\nlnk bootstrap\n```\n\n### Daily edits\n\n```bash\nvim ~/.vimrc                    # edit normally\nlnk list                        # see common config\nlnk list --host $(hostname)     # see host-specific config\nlnk list --all                  # see everything\nlnk status                      # check what changed\nlnk push \"new plugins\"          # commit \u0026 push\n```\n\n### Multi-machine workflow\n\n```bash\n# On your laptop - use bulk operations for efficiency\nlnk add --host laptop ~/.ssh/config ~/.aws/credentials ~/.npmrc\nlnk add ~/.vimrc ~/.bashrc ~/.gitconfig    # Common config (multiple files)\nlnk push \"laptop configuration\"\n\n# On your work machine\nlnk pull                                   # Get common config\nlnk add --host work ~/.gitconfig ~/.ssh/config\nlnk add --recursive ~/.config/work-tools  # Work-specific tools\nlnk push \"work configuration\"\n\n# Back on laptop\nlnk pull                                   # Get updates (work config won't affect laptop)\n```\n\n## Commands\n\n- `lnk init [-r remote] [--no-bootstrap] [--force]` - Create repo (runs bootstrap automatically)\n- `lnk add [--host HOST] [--recursive] [--dry-run] \u003cfiles\u003e...` - Move files to repo, create symlinks\n- `lnk rm [--host HOST] [--force] \u003cfiles\u003e` - Move files back, remove symlinks\n- `lnk list [--host HOST] [--all]` - List files managed by lnk\n- `lnk status` - Git status + sync info\n- `lnk push [msg]` - Stage all, commit, push\n- `lnk pull [--host HOST]` - Pull + restore missing symlinks\n- `lnk bootstrap` - Run bootstrap script manually\n\n### Command Options\n\n- `--host HOST` - Manage files for specific host (default: common configuration)\n- `--recursive, -r` - Add directory contents individually instead of the directory as a whole\n- `--dry-run, -n` - Show what would be added without making changes\n- `--all` - Show all configurations (common + all hosts) when listing\n- `-r, --remote URL` - Clone from remote URL when initializing\n- `--no-bootstrap` - Skip automatic execution of bootstrap script after cloning\n- `--force` - Force initialization even if directory contains managed files (WARNING: overwrites existing content)\n- `--force, -f` (rm) - Remove from tracking even if symlink is missing (useful if you accidentally deleted a managed file)\n\n### Output Formatting\n\nLnk provides flexible output formatting options to suit different environments and preferences:\n\n#### Color Output\n\nControl when ANSI colors are used in output:\n\n```bash\n# Default: auto-detect based on TTY\nlnk init\n\n# Force colors regardless of environment\nlnk init --colors=always\n\n# Disable colors completely\nlnk init --colors=never\n\n# Environment variable support\nNO_COLOR=1 lnk init  # Disables colors (acts like --colors=never)\n```\n\n**Color modes:**\n\n- `auto` (default): Use colors only when stdout is a TTY\n- `always`: Force color output regardless of TTY\n- `never`: Disable color output regardless of TTY\n\nThe `NO_COLOR` environment variable acts like `--colors=never` when set, but explicit `--colors` flags take precedence.\n\n#### Emoji Output\n\nControl emoji usage in output messages:\n\n```bash\n# Default: emojis enabled\nlnk init\n\n# Disable emojis\nlnk init --no-emoji\n\n# Explicitly enable emojis\nlnk init --emoji\n```\n\n**Emoji flags:**\n\n- `--emoji` (default: true): Enable emoji in output\n- `--no-emoji`: Disable emoji in output\n\nThe `--emoji` and `--no-emoji` flags are mutually exclusive.\n\n#### Examples\n\n```bash\n# Clean output for scripts/pipes\nlnk init --colors=never --no-emoji\n\n# Force colorful output in non-TTY environments\nlnk init --colors=always\n\n# Disable colors but keep emojis\nlnk init --colors=never\n\n# Disable emojis but keep colors\nlnk init --no-emoji\n```\n\n### Add Command Examples\n\n```bash\n# Multiple files at once\nlnk add ~/.bashrc ~/.vimrc ~/.gitconfig\n\n# Recursive directory processing\nlnk add --recursive ~/.config/nvim\n\n# Preview changes first\nlnk add --dry-run ~/.ssh/config\nlnk add --dry-run --recursive ~/.config/kitty\n\n# Host-specific bulk operations\nlnk add --host work ~/.gitconfig ~/.ssh/config ~/.npmrc\n```\n\n## Technical bits\n\n- **Single binary** (~8MB, no deps)\n- **Relative symlinks** (portable)\n- **XDG compliant** (`~/.config/lnk`)\n- **Multihost support** (common + host-specific configs)\n- **Bootstrap support** (automatic environment setup)\n- **Bulk operations** (multiple files, atomic transactions)\n- **Recursive processing** (directory contents individually)\n- **Preview mode** (dry-run for safety)\n- **Data loss prevention** (safety checks with contextual warnings)\n- **Git-native** (standard Git repo, no special formats)\n\n## Alternatives\n\n| Tool    | Complexity | Why choose it                                                                             |\n| ------- | ---------- | ----------------------------------------------------------------------------------------- |\n| **lnk** | Minimal    | Just works, no config, Git-native, multihost, bootstrap, bulk ops, dry-run, safety checks |\n| chezmoi | High       | Templates, encryption, cross-platform                                                     |\n| yadm    | Medium     | Git power user, encryption                                                                |\n| dotbot  | Low        | YAML config, basic features                                                               |\n| stow    | Low        | Perl, symlink only                                                                        |\n\n## Contributing\n\n```bash\ngit clone https://github.com/yarlson/lnk.git\ncd lnk\nmake deps  # Install golangci-lint\nmake check # Runs fmt, vet, lint, test\n```\n\n**What we use:**\n\n- **Runtime deps**: Only `cobra` (CLI framework)\n- **Test deps**: `testify` for assertions\n- **Build pipeline**: Standard Makefile with quality checks\n\n**Before submitting:**\n\n```bash\nmake check  # Runs all quality checks + tests\n```\n\n**Adding features:**\n\n- Put integration tests in `test/integration_test.go`\n- Use conventional commits: `feat:`, `fix:`, `docs:`\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyarlson%2Flnk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyarlson%2Flnk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyarlson%2Flnk/lists"}