Warning
This gem is under active development. You can expect new changes that may not be backward-compatible.
Dotsync is a powerful Ruby gem for managing and synchronizing your dotfiles across machines. Whether you're setting up a new development environment or keeping configurations in sync, Dotsync makes it effortless.
Key Features:
- Bidirectional Sync Mappings: Define once, sync both ways — eliminates config duplication with
[[sync.mappings]]syntax - XDG Shorthand DSL: Concise
[[sync.home]],[[sync.xdg_config]],[[sync.xdg_data]],[[sync.xdg_bin]]syntax for common directory patterns - Preview Mode: See what changes would be made before applying them (dry-run by default)
- Smart Filtering: Use
force,only, andignoreoptions to precisely control what gets synced - Automatic Backups: Pull operations create timestamped backups for easy recovery
- Live Watching: Continuously monitor and sync changes in real-time with
watchcommand - Config Includes: Compose configs from a shared base + machine-specific overlays with
include - Config Source: Point local config to your dotfiles repo with
source— changes are visible immediately without syncing - Post-Sync Hooks: Run commands automatically after files change (e.g., codesigning, chmod, service reload)
- Customizable Output: Control verbosity and customize icons to match your preferences
- Auto-Updates: Get notified when new versions are available
- Ruby: MRI 3.2+
Add this line to your application's Gemfile:
gem "dotsync"And then execute:
$ bundle install
Or install it yourself as:
$ gem install dotsync
Get started with Dotsync in just a few steps:
-
Install the gem:
gem install dotsync
-
Generate a default configuration:
dotsync setup
This creates
~/.config/dotsync.tomlwith example mappings. -
Edit the configuration (
~/.config/dotsync.toml) to define your dotfile mappings using bidirectional sync:# Sync your shell config [[sync.home]] path = ".zshenv" # Sync XDG config directories [[sync.xdg_config]] only = ["nvim", "alacritty", "zsh", "git"] force = true
-
Preview your changes (dry-run mode):
dotsync pull # Preview pulling from repo to local dotsync push # Preview pushing from local to repo
This shows what would be changed without modifying any files.
-
Apply changes when you're ready:
dotsync pull --apply # Apply repo → local dotsync push --apply # Apply local → repo
Dotsync provides the following commands to manage your dotfiles:
Important
By default, both push and pull commands run in preview mode (dry-run). They will show you what changes would be made without actually modifying any files. To apply changes, you must use the --apply flag.
-
Push: Transfer dotfiles from your local machine to the destination repository.
dotsync push [OPTIONS] dotsync push --apply [OPTIONS] # Apply changes -
Pull: Synchronize dotfiles from the repository to your local machine.
dotsync pull [OPTIONS] dotsync pull --apply [OPTIONS] # Apply changesDuring the
pulloperation,Dotsync::PullActioncreates a backup of the existing files on the destination. These backups are stored in a directory under the XDG path, with each backup organized by a timestamp. To prevent excessive storage usage, only the 10 most recent backups are retained. Older backups are automatically purged, ensuring efficient storage management. -
Watch: Continuously monitor and sync changes between your local machine and the repository.
dotsync watch [OPTIONS]
The watch command supports the same output control options as push and pull (e.g.,
--quiet,--no-legend,--no-mappings). -
Setup (alias: init): Generate a default configuration file at
~/.config/dotsync.tomlwith example mappings forpull,push, andwatch.dotsync setup dotsync init # Alias for setup
-
Status: Display current configuration and mappings without executing any actions.
dotsync status
This is useful for inspecting your configuration and verifying mappings are correct.
-
Diff: Show differences that would be made (alias for
pushin preview mode).dotsync diff
Convenient shorthand for previewing changes without typing
--dry-run.
All push and pull commands support the following options:
Action Control:
-a, --apply: Apply changes (without this, commands run in preview mode)--dry-run: Explicitly run in preview mode without applying changes (default behavior)-y, --yes: Skip confirmation prompt and auto-confirm changes-c, --config PATH: Specify a custom config file path (enables multiple config workflows)
Output Control:
-q, --quiet: Hide all non-essential output (only errors or final status)--no-legend: Hide all legends for config, mappings, and differences--no-config: Hide the config section in the output--no-mappings: Hide the mappings and their legend--no-diff-legend: Hide the differences legend only--no-diff: Hide the differences section itself--only-diff: Show only the differences section--only-config: Show only the config section--only-mappings: Show only the mappings section-v, --verbose: Force showing all available information
General:
--version: Display version number-h, --help: Show help message
# Setup and configuration
dotsync setup # Create initial config file
dotsync init # Same as setup (alias)
dotsync status # View current configuration
# Preview changes (dry-run mode)
dotsync push # Preview push changes
dotsync pull # Preview pull changes
dotsync diff # Quick preview (alias for push)
dotsync push --dry-run # Explicit dry-run flag
# Apply changes
dotsync push --apply # Apply changes with confirmation
dotsync pull --apply # Apply changes with confirmation
dotsync push -ay # Apply without confirmation (--apply + --yes)
dotsync pull --apply --yes # Apply without confirmation
# Custom configuration files
dotsync -c ~/work-dotfiles.toml push # Use work config
dotsync --config ~/.config/personal.toml pull # Use personal config
# Output control
dotsync pull --quiet # Minimal output
dotsync push --only-diff # Show only differences
dotsync pull --apply --yes -q # Silent apply for scripts
# Monitoring
dotsync watch # Watch with default output
dotsync watch --quiet # Watch with minimal outputDotsync uses TOML configuration files to define mappings between your local machine and your dotfiles repository. The recommended approach is bidirectional sync mappings, which eliminate duplication and keep your config clean.
Tip
Set up mirror environment variables for cleaner configuration:
# Add to your ~/.zshrc or ~/.bashrc
export DOTFILES_DIR="$HOME/Code/dotfiles"
export XDG_CONFIG_HOME_MIRROR="$DOTFILES_DIR/xdg_config_home"
export XDG_DATA_HOME_MIRROR="$DOTFILES_DIR/xdg_data_home"
export HOME_MIRROR="$DOTFILES_DIR/home"Use [[sync]] mappings to define paths that sync in both directions. This is the preferred approach as it eliminates duplication between push and pull configurations.
The most concise way to define mappings for standard XDG directories:
# Sync home directory files
[[sync.home]]
path = ".zshenv"
# Sync multiple configs from XDG_CONFIG_HOME
[[sync.xdg_config]]
only = ["alacritty", "git", "zsh", "starship.toml"]
force = true
# Sync specific config with custom options
[[sync.xdg_config]]
path = "nvim"
force = true
ignore = ["lazy-lock.json"]
# Sync XDG data directories
[[sync.xdg_data]]
path = "git"
force = trueSupported shorthands:
| Shorthand | Local | Remote |
|---|---|---|
sync.home |
$HOME |
$HOME_MIRROR |
sync.xdg_config |
$XDG_CONFIG_HOME |
$XDG_CONFIG_HOME_MIRROR |
sync.xdg_data |
$XDG_DATA_HOME |
$XDG_DATA_HOME_MIRROR |
sync.xdg_cache |
$XDG_CACHE_HOME |
$XDG_CACHE_HOME_MIRROR |
sync.xdg_bin |
$XDG_BIN_HOME |
$XDG_BIN_HOME_MIRROR |
Options:
path(optional): Relative path within the directory. If omitted, syncs the entire directory.force,ignore,only: All standard mapping options are supported.
For custom paths that don't follow XDG conventions, use explicit [[sync.mappings]] entries:
[[sync.mappings]]
local = "$XDG_CONFIG_HOME/nvim"
remote = "$XDG_CONFIG_HOME_MIRROR/nvim"
force = true
ignore = ["lazy-lock.json"]
[[sync.mappings]]
local = "$HOME/.zshenv"
remote = "$HOME_MIRROR/.zshenv"
# Sync config file to a different location in repo
[[sync.mappings]]
local = "$XDG_CONFIG_HOME/dotsync.toml"
remote = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.macbook.toml"How it works:
localis your local machine path (e.g.,~/.config/nvim)remoteis your dotfiles repository path (e.g.,~/dotfiles/config/nvim)- For push operations:
local→remote - For pull operations:
remote→local - All standard options (
force,ignore,only) are supported
Here's a real-world configuration using bidirectional sync:
## BIDIRECTIONAL SYNC CONFIGURATION
# Home directory files
[[sync.home]]
path = ".zshenv"
# Bulk sync multiple configs
[[sync.xdg_config]]
only = [
"alacritty",
"brewfile",
"git",
"lazygit",
"tmux",
"zellij",
"starship.toml"
]
force = true
# Zsh with ignored files
[[sync.xdg_config]]
path = "zsh"
ignore = [".zsh_sessions", ".zsh_history", ".zcompdump"]
# Neovim with force sync
[[sync.xdg_config]]
path = "nvim"
force = true
ignore = ["lazy-lock.json"]
# Git templates in data directory
[[sync.xdg_data]]
path = "git"
force = true
# This config file itself
[[sync.mappings]]
local = "$XDG_CONFIG_HOME/dotsync.toml"
remote = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.macbook.toml"For asymmetric sync scenarios where push and pull need different configurations, you can use separate [[push.mappings]] and [[pull.mappings]] sections:
# Pull from repo to local (repo → local)
[[pull.mappings]]
src = "$XDG_CONFIG_HOME_MIRROR/nvim"
dest = "$XDG_CONFIG_HOME/nvim"
force = true
ignore = ["lazy-lock.json"]
# Push from local to repo (local → repo)
[[push.mappings]]
src = "$XDG_CONFIG_HOME/alacritty"
dest = "$XDG_CONFIG_HOME_MIRROR/alacritty"
only = ["alacritty.toml", "themes"]
# Watch for live syncing
[[watch.mappings]]
src = "$XDG_CONFIG_HOME/nvim"
dest = "$XDG_CONFIG_HOME_MIRROR/nvim"Note
You can mix [[sync.mappings]], XDG shorthands ([[sync.home]], [[sync.xdg_config]], etc.), and [[push.mappings]]/[[pull.mappings]] in the same config file. Use bidirectional sync for symmetric mappings and unidirectional for special cases.
Each mapping entry supports the following options:
A boolean (true/false) value. When set to true, it forces deletion of files in the destination directory that don't exist in the source. This is particularly useful when you need to ensure the destination stays synchronized with the source.
Example:
[[sync.xdg_config]]
path = "nvim"
force = true
ignore = ["lazy-lock.json"]Warning
When using force = true with the only option, only files matching the only filter will be managed. Other files in the destination remain untouched.
An array of relative paths (files or directories) or glob patterns to selectively transfer from the source. This option provides precise control over which files get synchronized.
How it works:
- Paths are relative to the
srcdirectory - You can specify entire directories, individual files, or glob patterns (
*,?,[charset]) - Parent directories are automatically created as needed
- Other files in the source are ignored
- With
force = true, only files matching theonlyfilter are cleaned up in the destination
Example 1: Selecting specific directories
[[sync.xdg_config]]
only = ["nvim", "alacritty", "zsh"]This transfers only the nvim/, alacritty/, and zsh/ directories.
Example 2: Selecting specific files
[[sync.xdg_config]]
path = "alacritty"
only = ["alacritty.toml", "rose-pine.toml"]This transfers only two specific TOML files from the alacritty config directory.
Example 3: Selecting files inside nested directories
[[sync.xdg_config]]
only = ["bundle/config", "ghc/ghci.conf", "cabal/config"]This transfers only specific configuration files from different subdirectories:
bundle/configfile from thebundle/directoryghc/ghci.conffile from theghc/directorycabal/configfile from thecabal/directory
The parent directories (bundle/, ghc/, cabal/) are created automatically in the destination, but other files in those directories are not transferred.
Example 4: Glob patterns
[[sync.home]]
path = "Library/LaunchAgents"
only = ["local.*.plist"]This transfers only files matching the glob pattern — e.g., local.brew.upgrade.plist, local.ollama.plist — while ignoring system-generated plists like com.apple.*.plist.
Supported glob characters:
*— matches any sequence of characters (e.g.,local.*.plist)?— matches any single character (e.g.,config.?)[charset]— matches any character in the set (e.g.,log.[0-9])
Glob patterns can be mixed with exact paths in the same only array:
[[sync.home]]
path = "Library/LaunchAgents"
only = ["local.*.plist", "README.md"]Example 5: Deeply nested paths
[[sync.xdg_config]]
only = ["nvim/lua/plugins/init.lua", "nvim/lua/config/settings.lua"]This transfers only specific Lua files from deeply nested paths within the nvim configuration.
Important behaviors:
- File-specific paths: When specifying individual files (e.g.,
"bundle/config"), only that file is managed. Sibling files in the same directory are not affected, even withforce = true. - Directory paths: When specifying directories (e.g.,
"nvim"), all contents of that directory are managed, including subdirectories. - Glob patterns: When using patterns (e.g.,
"local.*.plist"), only files whose names match the pattern are managed. Non-matching files in the same directory are untouched. - Combining with
force: Withforce = trueand directory paths, files in the destination directory that don't exist in the source are removed. With file-specific paths or glob patterns, only matching files are managed.
An array of relative paths or patterns to exclude during transfer. This allows you to skip certain files or folders.
Example:
[[sync.xdg_config]]
path = "nvim"
ignore = ["lazy-lock.json", "plugin/packer_compiled.lua"]Combining options:
[[sync.xdg_config]]
path = "nvim"
only = ["lua", "init.lua"]
ignore = ["lua/plugin/packer_compiled.lua"]
force = trueThis configuration:
- Transfers only the
lua/directory andinit.luafile (only) - Excludes
lua/plugin/packer_compiled.luaeven though it's in thelua/directory (ignore) - Removes files in the destination that don't exist in the source (
force)
Note
When ignore and only both match a path, ignore takes precedence.
These options apply when the source is a directory and are relevant for both push and pull operations.
Hooks let you run commands automatically after a mapping's files are transferred. Hooks only execute when files actually changed, and only when using --apply.
| Hook | Description | Valid in |
|---|---|---|
post_sync |
Runs after sync in both directions | [[sync]] mappings |
post_push |
Runs only after push | [[sync]] and [[push]] mappings |
post_pull |
Runs only after pull | [[sync]] and [[pull]] mappings |
For sync mappings, hooks are resolved by direction:
- Push:
post_sync+post_pushcommands - Pull:
post_sync+post_pullcommands
Single command (shorthand mapping):
[[sync.xdg_bin]]
force = true
[sync.xdg_bin.hooks]
post_sync = "codesign -s - {files}"Multiple commands (explicit sync):
[[sync.mappings]]
local = "$XDG_CONFIG_HOME/scripts"
remote = "$XDG_CONFIG_HOME_MIRROR/scripts"
[sync.mappings.hooks]
post_sync = ["codesign -s - {files}", "chmod 700 {files}"]
post_pull = "launchctl kickstart -k gui/$(id -u)/com.example.service"Unidirectional mapping:
[[pull.mappings]]
src = "$DOTFILES_DIR/scripts"
dest = "$HOME/Scripts"
[pull.mappings.hooks]
post_pull = ["codesign -s - {files}", "chmod 700 {files}"]| Variable | Description |
|---|---|
{files} |
Shell-quoted paths of changed destination files |
{src} |
The mapping's source path |
{dest} |
The mapping's destination path |
# Codesign scripts after pulling (macOS Ventura+ requirement for LaunchAgents)
[[sync.xdg_bin]]
force = true
[sync.xdg_bin.hooks]
post_pull = "codesign -s - {files}"
# Reload a LaunchAgent after pulling config changes
[[pull.mappings]]
src = "$DOTFILES_DIR/LaunchAgents/com.example.plist"
dest = "$HOME/Library/LaunchAgents/com.example.plist"
[pull.mappings.hooks]
post_pull = "launchctl kickstart -k gui/$(id -u)/com.example"Note
In preview mode (without --apply), hooks are displayed as a preview showing what commands would run. Hook failures log errors but do not abort remaining hooks or mappings.
Use include to compose a config from a shared base file and a machine-specific overlay. This eliminates duplication when multiple machines share most of the same mappings.
# dotsync.mbp_personal.toml (overlay — only the delta)
include = "dotsync.base.toml"
# Machine-specific mappings appended to base
[[sync.xdg_config]]
path = "claude"
only = ["settings.json", "instructions", "commands"]
[[sync.mappings]]
local = "$XDG_CONFIG_HOME/opencode/opencode.jsonc"
remote = "$XDG_CONFIG_HOME_MIRROR/opencode/opencode.mbp_personal.jsonc"# dotsync.base.toml (shared across all machines)
[[sync.home]]
path = ".zshenv"
[[sync.xdg_config]]
only = ["alacritty", "brewfile", "git", "nvim", "zsh"]
force = true
[watch]
src = "~/.config"
paths = ["~/.config/nvim/", "~/.config/zsh/"]Merge semantics:
| Type | Behavior | Example |
|---|---|---|
Arrays ([[array-of-tables]]) |
Concatenate (base + overlay) | Base [[sync.home]] entries + overlay [[sync.home]] entries |
Hashes ([tables]) |
Recursive deep merge (overlay wins on leaves) | Overlay [watch].dest overrides base [watch].dest |
| Scalars | Overlay wins | Overlay force = false overrides base force = true |
Rules:
- The
includepath is resolved relative to the overlay file's directory - Chained includes (an included file that itself has
include) are not supported - The
includekey is consumed and does not appear in the merged config - Cache invalidation tracks both the overlay and included file's mtime/size
Use source to point your local config at the authoritative copy in your dotfiles repository. Instead of syncing config files back and forth, dotsync reads the config directly from the repo. Changes are visible immediately — no pull needed.
The problem it solves: When config files change in your dotfiles repo, you normally need to ds pull to update local copies, then run ds pull again so the new config takes effect. With source, dotsync reads from the repo directly, eliminating this two-pass problem.
Setup:
# ~/.config/dotsync.toml (one-time setup — never changes)
source = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.mbp_personal.toml"# ~/Code/dotfiles/xdg_config_home/dotsync/dotsync.mbp_personal.toml (the real config)
include = "dotsync.base.toml"
[[sync.xdg_config]]
path = "zsh"
ignore = [".zsh_sessions", ".zsh_history", ".zcompdump"]
[[sync.xdg_config]]
path = "nvim"
force = true
ignore = ["lazy-lock.json"]The local file is a thin pointer that never changes. The real config (and its include base) live in the repo and are read directly.
Rules:
sourcemust be the only key in the pointer file — no other config keys allowed- The source file can use
includeto compose with a base config (resolved relative to the source file) - Chained sources (a source file pointing to another source) are not supported
- Environment variables are expanded in the
sourcepath (e.g.,$XDG_CONFIG_HOME_MIRROR) - Cache invalidation tracks the pointer file, the source file, and any included file
Per-machine setup:
dotfiles/xdg_config_home/dotsync/
dotsync.base.toml # shared mappings, hooks, watch paths
dotsync.mbp_personal.toml # include + personal-only mappings
dotsync.mbp_work.toml # include + work-only mappings
dotsync.mac_mini.toml # include + mac-mini-only mappings
Each machine's ~/.config/dotsync.toml is a one-line pointer to its overlay. Edit the repo files and changes take effect immediately — no syncing, no two-pass.
Dotsync includes several safety mechanisms to prevent accidental data loss:
Before applying any changes with the --apply flag, Dotsync will:
- Show you all differences that will be applied
- Display the total count of files to be modified
- Ask for explicit confirmation:
About to modify X file(s). Continue? [y/N] - Only proceed if you type
yand press Enter
Example:
$ dotsync push --apply
# ... shows differences ...
About to modify 15 file(s).
Continue? [y/N] yBypassing Confirmation:
- Use the
--yesor-yflag to skip confirmation (useful for automation):dotsync push --apply --yes dotsync pull -ay # Short form - Use the
--quietflag (automatically skips prompt and suppresses output)
Note
No confirmation is shown if there are no differences to apply.
When using pull --apply, Dotsync automatically:
- Creates timestamped backups of existing files before overwriting them
- Stores backups in
~/.cache/dotsync/backups/YYYYMMDDHHMMSS/ - Retains only the 10 most recent backups (older ones are purged)
- Creates backups only when there are actual differences
To restore from a backup:
ls -la ~/.cache/dotsync/backups/
cp -r ~/.cache/dotsync/backups/20250110143022/* ~/.config/By default, all push and pull commands run in preview mode:
- Shows exactly what would change without modifying files
- Must explicitly use
--applyflag to make changes - Use
--dry-runflag for explicit clarity in scripts
Dotsync provides clear, actionable error messages for common issues:
-
Permission Errors:
Permission denied: /path/to/file Try: chmod +w <path> or check file permissions -
Disk Full Errors:
Disk full: No space left on device Free up disk space and try again -
Symlink Errors:
Symlink error: Target does not exist Check that symlink target exists and is accessible -
Type Conflicts:
Type conflict: Cannot overwrite directory with file Cannot overwrite directory with file or vice versa
Errors are reported per-mapping, allowing Dotsync to continue processing other mappings even if one fails.
Dotsync properly handles symbolic links:
- Preserves symlink targets (absolute and relative paths)
- Handles broken symlinks gracefully
- Detects type conflicts (e.g., file vs. directory vs. symlink)
- Provides clear error messages for symlink-related issues
Dotsync allows you to customize the icons displayed in the console output by adding an [icons] section to your configuration file (~/.config/dotsync.toml). This is useful if you prefer different icons or need compatibility with terminals that don't support Nerd Fonts.
You can customize the following icons in your configuration:
Mapping Status Icons (shown next to each mapping):
force- Indicates force deletion is enabled (clears destination before transfer)only- Indicates only specific files will be transferredignore- Indicates files are being ignored during transferinvalid- Indicates the mapping is invalid (missing source/destination)
Difference Status Icons (shown in diff output):
diff_created- Shows newly created/added filesdiff_updated- Shows updated/modified filesdiff_removed- Shows removed/deleted files
Here's a complete example showing all customizable icons using UTF-8 emojis (works without Nerd Fonts):
[icons]
# Mapping status icons
force = "⚡" # Force deletion enabled
only = "📋" # Only specific files transferred
ignore = "🚫" # Files ignored during transfer
invalid = "❌" # Invalid mapping
# Diff status icons
diff_created = "✨" # New files created
diff_updated = "📝" # Files modified
diff_removed = "🗑️ " # Files deleted
# Example sync mapping
[[sync.xdg_config]]
ignore = ["cache"]If you don't specify custom icons, Dotsync uses Nerd Font icons by default. These icons will only display correctly if you're using a terminal with a patched Nerd Font installed.
| Icon | Default (Nerd Font) | Nerd Font Code | Purpose |
|---|---|---|---|
force |
|
nf-md-lightning_bolt |
Force deletion enabled |
only |
|
nf-md-filter |
Only mode active |
ignore |
|
nf-md-cancel |
Ignoring files |
invalid |
|
nf-md-alert_octagram |
Invalid mapping |
diff_created |
|
nf-md-plus |
File created |
diff_updated |
|
nf-md-pencil |
File updated |
diff_removed |
|
nf-md-minus |
File removed |
Note
The icons in the "Default (Nerd Font)" column may not be visible unless you're viewing this with a Nerd Font. You can find these icons at nerdfonts.com by searching for the Nerd Font Code.
Tip
You can set any icon to an empty string ("") to hide it completely, or use any UTF-8 character or emoji. The dotsync setup command generates a configuration file with some example custom icons to get you started.
Dotsync automatically checks for new versions once per day and notifies you if an update is available. This check is non-intrusive and will not interrupt your workflow.
To disable automatic update checks:
- Set environment variable:
export DOTSYNC_NO_UPDATE_CHECK=1
The check runs after your command completes and uses a cached timestamp to avoid excessive API calls. The cache is stored in ~/.cache/dotsync/last_version_check following the XDG Base Directory specification.
-
Preview Before Applying: Always run commands without
--applyfirst to preview changes:dotsync pull # Preview changes dotsync diff # Quick preview (alias) dotsync pull --apply # Apply after reviewing
-
Check Configuration: Use the
statuscommand to inspect your configuration without executing any actions:dotsync status # View config and mappings -
Multiple Config Files: Use the
-cflag to maintain separate configurations for different workflows:# Work dotfiles dotsync -c ~/work-dotfiles.toml push --apply # Personal dotfiles dotsync -c ~/.config/personal.toml pull --apply # Server configs dotsync --config ~/server.toml push --apply
-
Automation and Scripting: Use
--yesflag to skip confirmation prompts:# In a script or CI/CD pipeline dotsync pull --apply --yes --quiet # Shorthand dotsync push -ayq
-
Using Environment Variables: Simplify your configuration with mirror environment variables:
# Add to your ~/.zshrc or ~/.bashrc export DOTFILES_DIR="$HOME/dotfiles" export XDG_CONFIG_HOME_MIRROR="$DOTFILES_DIR/config" export HOME_MIRROR="$DOTFILES_DIR/home"
-
Backup Location: Pull operations automatically backup files to
~/.cache/dotsync/backups/with timestamps. Only the 10 most recent backups are kept. -
Using rbenv: To ensure the gem uses the correct Ruby version managed by rbenv:
RBENV_VERSION=3.2.0 dotsync push
-
Global Installation: Install the gem using a globally available Ruby version to make the executable accessible anywhere:
gem install dotsync
-
Check Version: Quickly check which version you're running:
dotsync --version
-
Disable Update Checks: If you prefer not to see update notifications:
export DOTSYNC_NO_UPDATE_CHECK=1 -
Quiet Mode: For use in scripts or when you only want to see errors:
dotsync pull --apply --quiet
Here are practical examples using bidirectional sync for popular configuration files:
[[sync.xdg_config]]
path = "nvim"
force = true
ignore = ["lazy-lock.json", ".luarc.json"][[sync.xdg_config]]
path = "alacritty"
only = ["alacritty.toml", "themes"][[sync.home]]
path = ".zshenv"
[[sync.xdg_config]]
path = "zsh"
ignore = [".zsh_sessions", ".zsh_history", ".zcompdump"][[sync.xdg_config]]
only = ["nvim", "alacritty", "git", "zsh", "tmux", "starship.toml"]
force = trueUse include to share a base config across machines, with each machine adding only its delta:
dotfiles/xdg_config_home/dotsync/
dotsync.base.toml # shared mappings, hooks, watch paths
dotsync.mbp_personal.toml # include + personal-only mappings
dotsync.mbp_work.toml # include + work-only mappings
dotsync.mac_mini.toml # include + mac-mini-only mappings
# dotsync.mbp_personal.toml — slim overlay
include = "dotsync.base.toml"
[[sync.xdg_config]]
path = "claude"
only = ["settings.json", "instructions", "commands"]
[[sync.mappings]]
local = "$XDG_CONFIG_HOME/dotsync.toml"
remote = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.mbp_personal.toml"Each machine's overlay self-references so ds push keeps it in sync with the repo. Use -c to select a config:
dotsync -c ~/.config/dotsync/dotsync.macbook.toml push --applyProblem: Icons appear as boxes, question marks, or strange characters.
Solution:
- Install a Nerd Font and configure your terminal to use it
- Or customize icons in
~/.config/dotsync.tomlusing UTF-8 emojis or regular characters:[icons] force = "!" only = "*" ignore = "x" invalid = "?" diff_created = "+" diff_updated = "~" diff_removed = "-"
Problem: Running dotsync push or dotsync pull doesn't modify files.
Solution: Remember to use the --apply flag to apply changes. Without it, commands run in preview mode:
dotsync pull --apply
dotsync push --applyYou can also use the explicit --dry-run flag to make preview mode clear in scripts.
Problem: Getting permission errors when syncing files.
Solution:
- Ensure you have write permissions for destination directories
- Check file ownership:
ls -la ~/.config - For system directories, you may need to adjust your mappings to use user-writable locations
Problem: Error messages about missing source or destination paths.
Solution:
- Verify environment variables are set correctly (e.g.,
echo $XDG_CONFIG_HOME) - Use absolute paths in your configuration if environment variables aren't available
- Create destination directories before running pull:
mkdir -p ~/.config
Problem: Need to restore files after a pull operation.
Solution: Pull operations create automatic backups in ~/.cache/dotsync/backups/:
ls -la ~/.cache/dotsync/backups/
# Copy files from the timestamped backup directory
cp -r ~/.cache/dotsync/backups/YYYYMMDD_HHMMSS/* ~/.config/Problem: dotsync watch doesn't sync changes automatically.
Solution:
- Verify your watch mappings are configured correctly in
~/.config/dotsync.toml - Ensure the source directories exist and are accessible
- Try stopping and restarting the watch command
Problem: Being prompted to confirm changes when running in scripts or automation.
Solution: Use the --yes or -y flag to skip confirmation prompts:
dotsync push --apply --yes
dotsync pull -ay # ShorthandFor completely silent operation in scripts:
dotsync push --apply --yes --quietProblem: Need different dotfile configurations for work, personal, or different machines.
Solution: Use the --config or -c flag to specify custom config files:
dotsync -c ~/work-dotfiles.toml push
dotsync --config ~/.config/personal.toml pullYou can maintain separate config files for different environments and switch between them easily.
Problem: Error message about missing config file.
Solution: Create a config file using the setup command:
dotsync setup # Creates ~/.config/dotsync.toml
dotsync init # Alias for setupOr specify a custom config file path:
dotsync -c ~/my-config.toml setup- After checking out the repo, run
bin/setupto install dependencies. - Then, run
rake specto run the tests. - You can also run
bin/consolefor an interactive prompt that will allow you to experiment. - To install this gem onto your local machine, run
bundle exec rake install.
Automated (recommended):
# 1. Update version in lib/dotsync/version.rb
# 2. Commit all changes
# 3. Run the full release workflow:
rake release:publishThis generates a CHANGELOG entry from commits, opens it for review, commits, creates an annotated tag (with markdown stripped to plain text), and pushes everything.
Individual tasks:
rake release:changelog # Generate CHANGELOG entry from commits since last tag
rake release:tag # Create annotated tag from CHANGELOG (uses Dotsync::VERSION)
rake release:tag[0.3.0] # Create annotated tag for a specific versionPublishing the gem:
gem build dotsync.gemspec
gem push dotsync-X.Y.Z.gemThe release.yml GitHub Action automatically creates a GitHub Release when a version tag is pushed, extracting release notes from CHANGELOG.md.
Bug reports and pull requests are welcome on GitHub at https://github.com/dsaenztagarro/dotsync. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Dotsync project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.


