Centralized dotfile manager — deploy configs via symlink, copy, or SSH.
clink lets you store all your config files in one place. Define deployment destinations in a config.yaml file and clink will deploy each file to the right location via symlink, copy, or SSH upload — while automatically backing up whatever was there before.
Centralizing your dotfiles makes saving and syncing them effortless. For example, you can sync your config directory across devices with Dropbox or any other sync tool. After a fresh OS install, just download your config directory and run clink to restore everything in one shot.
flowchart LR
subgraph dotfiles["📁 Config directory (cloud-synced)"]
cfg["config.yaml"]
f1[".vimrc"]
f2[".zshrc"]
f3["v2ray/config.json"]
f4["dotfiles/.vimrc"]
end
clink(["⚙️ clink"])
subgraph local["🖥️ Local machine"]
t1["~/.vimrc\n(symlink)"]
t2["~/.zshrc\n(symlink)"]
t3["/etc/v2ray/config.json\n(copy)"]
end
subgraph remote["🌐 Remote server"]
t4["/root/.vimrc\n(ssh)"]
end
cfg -->|"defines rules"| clink
f1 --> clink
f2 --> clink
f3 --> clink
f4 --> clink
clink -->|"symlink"| t1
clink -->|"symlink"| t2
clink -->|"copy"| t3
clink -->|"SFTP upload"| t4
go install github.com/alexmaze/clink@latestGo to the Releases page, download the binary for your platform, and place it somewhere on your $PATH.
# Example: macOS arm64
curl -L https://github.com/alexmaze/clink/releases/latest/download/clink-darwin-arm64.tar.gz | tar xz
sudo mv clink-darwin-arm64 /usr/local/bin/clinkclink -c <config-dir>/config.yaml
# Run only specific rules (by index or name; repeatable)
clink -c <config-dir>/config.yaml -r 1
clink -c <config-dir>/config.yaml -r "vim config"
clink -c <config-dir>/config.yaml -r 1 -r "v2ray config"
# Restore from a previous backup
clink --restore
# Preview a restore without applying it
clink --restore -d
# Restore only a specific rule's files
clink --restore -r "vim config"
# Check whether all configured links are correctly established (read-only)
clink --check -c <config-dir>/config.yaml
# Check only specific rules
clink --check -c <config-dir>/config.yaml -r "vim config"- Specify config file locations via
config.yaml - Automatic backup of existing files before deployment
- Variable support — reference variables in rule path definitions
- Pre/post script hooks per rule and globally (e.g. install software)
- Multiple deployment modes:
symlink/copy/ssh(remote SFTP upload) - Run only a subset of rules with the
-rflag - Interactive restore from backup history (
--restore) - Health-check to verify all configured links are correctly established (
--check)
| Flag | Description |
|---|---|
-c, --config |
Path to config.yaml (default: ./config.yaml) |
-d, --dry-run |
Preview changes without applying them |
-r, --rule |
Run only matching rules (1-based index or name, case-insensitive; repeatable) |
--restore |
Interactively restore from a previous backup (compatible with -d and -r) |
--check |
Check whether all configured links are correctly established (read-only, compatible with -r) |
mode: symlink # Global default mode (optional; default: symlink; values: symlink / copy / ssh)
hooks: # Top-level hooks — run before/after all rules
pre: echo 'start'
post: echo 'all done'
ssh_servers: # SSH server definitions (used by ssh mode)
my-server:
host: 192.168.1.1
port: 22 # default: 22
user: root
key: ~/.ssh/id_rsa # use either key or password; omit both to be prompted at runtime
# password: secret
vars:
V2RAY: /etc/v2ray
rules:
- name: vim config
# omitting mode inherits the global default (symlink here)
hooks: # Rule-level hooks — run before/after this rule
pre: brew install vim
post: echo 'vim ready'
items: # List of files/directories to deploy
- src: .src/.vimrc # relative paths are resolved from the yaml file's directory
dest: /root/.vimrc # absolute paths are supported
- src: ./.vim/autoload # directories are supported; missing parent dirs are created
dest: ~/.vim/autoload # ~ expands to the current user's home directory
- name: v2ray config (copy mode)
mode: copy
items:
- src: ./v2ray/config.json
dest: ${V2RAY}/config.json
- name: remote server config (ssh mode)
mode: ssh
ssh: my-server # references a key in ssh_servers
items:
- src: ./dotfiles/.vimrc
dest: /root/.vimrc # remote path — no local path processing applied| Mode | Description |
|---|---|
symlink (default) |
Creates a symlink at the destination pointing to the source file |
copy |
Recursively copies the source file/directory to the destination (a real copy) |
ssh |
Uploads the source file to a remote server via SFTP; the existing remote file is downloaded to the local backup directory first |
modecan be set globally at the top level; individual rules can override it- SSH authentication prefers a key file (
key), then a password (password); if neither is set, you are prompted interactively at runtime
flowchart LR
src["📄 Source file\n(config directory)"]
src --> sym["symlink mode"]
src --> cp["copy mode"]
src --> ssh["ssh mode"]
sym -->|"creates a symlink\npointing to source"| d1["🖥️ Destination\n(symbolic link)"]
cp -->|"recursive copy\nreal duplicate"| d2["🖥️ Destination\n(independent copy)"]
ssh -->|"SFTP upload\nold file backed up first"| d3["🌐 Remote server\ndestination path"]
style sym fill:#dbeafe,stroke:#3b82f6
style cp fill:#dcfce7,stroke:#22c55e
style ssh fill:#fef9c3,stroke:#eab308
Each time you deploy, clink backs up the existing file at each destination to ~/.clink/<timestamp>/. A snapshot of the config.yaml used for that run is also saved, so a restore can reconstruct the exact rules, modes, and SSH settings.
Example backup directory layout:
~/.clink/
20260326_150405/
config.yaml ← config snapshot
root/.vimrc ← backed-up original (path mirrors the destination path)
root/.vim/autoload/...
20260325_100000/
config.yaml
...
sequenceDiagram
participant U as User
participant C as clink
participant D as Destination
participant B as ~/.clink/<timestamp>/
rect rgb(219,234,254)
note over U,B: Deploy
U->>C: clink -c config.yaml
C->>D: Read existing file
D-->>B: Back up original + config.yaml snapshot
C->>D: Write new config (symlink / copy / ssh)
end
rect rgb(220,252,231)
note over U,B: Restore
U->>C: clink --restore
C->>B: Scan all backup snapshots
B-->>U: List backups (newest first)
U->>C: Select a backup
C->>B: Read config.yaml snapshot
C-->>U: Show restore plan
U->>C: Confirm
C->>D: Restore files (copy / SFTP upload)
end
Run clink --restore to enter the interactive restore flow:
- Scans all backups under
~/.clink/and lists them in reverse-chronological order - You select a backup
clinkparses the config snapshot to determine each file's restore mode (symlink/copy → local copy; ssh → re-upload)- The restore plan is displayed; execution proceeds after your confirmation
Notes:
- Restore always uses copy mode (no symlinks pointing into the backup directory)
- For older backups without a
config.yamlsnapshot, all files are restored locally via copy mode - Files originally deployed via SSH are re-uploaded to the remote server via SFTP; passwords are prompted as needed
- Restore overwrites files at the destination without merging
- Use
-dto preview the restore plan without applying it - Use
-rto restore only the files belonging to a specific rule
Run clink --check -c <config-dir>/config.yaml to verify that all configured items are correctly deployed — without making any changes to the filesystem.
For each item --check reports one of three statuses:
| Symbol | Status | Meaning |
|---|---|---|
| ✔ | OK | Correctly established |
| ! | Wrong | Destination exists but is incorrect (e.g. symlink points to the wrong target, or the path is a regular file instead of a symlink) |
| ✘ | Missing | Destination does not exist |
Example output:
[1/2] vim config [symlink]
✔ ~/.vimrc → /dotfiles/config/.vimrc
✘ ~/.vim → not found
[2/2] remote config [ssh → root@192.168.1.1]
SSH connected to root@192.168.1.1
✔ /root/.bashrc → exists on remote
! /root/.vimrc → exists but is not a symlink
Summary: 2 ✔ ok, 1 ! wrong, 1 ✘ missing, 0 errors.
--check exits with code 0 if everything is OK, or 1 if any item is wrong, missing, or errored — making it easy to use in scripts or CI.
pre hook (global)
↓
[rule 1] pre hook → backup + deploy items → post hook
[rule 2] pre hook → backup + deploy items → post hook
↓
post hook (global)
Hook commands are executed via
sh -c. A non-zero exit code immediately aborts the entire run.