Dotsync
Warning
This gem is under active development. You can expect new changes that may not be backward-compatible.
What is Dotsync?
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: Push local dotfiles to your repository or pull from repository to local machine
- 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 - Customizable Output: Control verbosity and customize icons to match your preferences
- Auto-Updates: Get notified when new versions are available
Table of Contents
-
Requirements
-
Installation
-
Quick Start
-
Usage
- Executable Commands
- Configuration
- Safety Features
- Customizing Icons
- Automatic Update Checks
- Pro Tips
-
Common Use Cases
-
Troubleshooting
-
Development
-
Contributing
-
License
-
Code of Conduct
Requirements
- Ruby: MRI 3.2+
Installation
Add this line to your application's Gemfile:
gem "dotsync"And then execute:
$ bundle install
Or install it yourself as:
$ gem install dotsync
Quick Start
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:[[pull.mappings]] src = "$HOME/dotfiles/config" dest = "$HOME/.config"
-
Preview your changes (dry-run mode):
dotsync pull
This shows what would be changed without modifying any files.
-
Apply changes when you're ready:
dotsync pull --apply
Usage
Executable Commands
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.
Core Commands
-
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
Utility Commands
-
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.
Command Options
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
Examples
# 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 outputConfiguration
The configuration file uses a mappings structure to define the source and destination of your dotfiles. Here is an example:
[[pull.mappings]]
src = "$XDG_CONFIG_HOME_MIRROR"
dest = "$XDG_CONFIG_HOME"
ignore = ["nvim"]
[[pull.mappings]]
src = "$XDG_CONFIG_HOME_MIRROR/nvim"
dest = "$XDG_CONFIG_HOME/nvim"
# FEATURE: forces the deletion of destination folder
force = true
# FEATURE: use relative paths to "dest" to ignore files and folders
ignore = ["lazy-lock.json"]
[[pull.mappings]]
src = "$HOME_MIRROR/.zshenv"
dest = "$HOME"
[[push.mappings]]
src = "$HOME/.zshenv"
dest = "$HOME_MIRROR/.zshenv"
[[push.mappings]]
src = "$XDG_CONFIG_HOME/alacritty"
dest = "$DOTFILES_DIR/config/alacritty"
# FEATURE: transfer only relative paths of files and folders passed here
only = ["alacritty.toml", "rose-pine.toml"]
[[watch.mappings]]
src = "$HOME/.zshenv"
dest = "$HOME_MIRROR/.zshenv"
[[watch.mappings]]
src = "$XDG_CONFIG_HOME/alacritty"
dest = "$DOTFILES_DIR/config/alacritty"Tip
I use mirror environment variables for cleaner configuration
export XDG_CONFIG_HOME_MIRROR="$HOME/Code/dotfiles/xdg_config_home"Bidirectional Sync Mappings
For paths that need to be synchronized in both directions (push and pull), you can use the [[sync]] section instead of defining separate [[push.mappings]] and [[pull.mappings]] entries. This reduces configuration duplication significantly.
# Instead of defining separate push and pull mappings:
[[sync]]
local = "$XDG_CONFIG_HOME/nvim"
remote = "$XDG_CONFIG_HOME_MIRROR/nvim"
force = true
ignore = ["lazy-lock.json"]
[[sync]]
local = "$HOME/.zshenv"
remote = "$HOME_MIRROR/.zshenv"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(local is src, remote is dest) - For pull operations:
remoteβlocal(remote is src, local is dest) - All standard options (
force,ignore,only) are supported
This is especially useful when most of your mappings are symmetric mirrors of each other.
XDG Shorthand Mappings
For common XDG directory mappings, you can use shorthand syntax that automatically expands to the full environment variable paths:
# Instead of:
# local = "$XDG_CONFIG_HOME/nvim"
# remote = "$XDG_CONFIG_HOME_MIRROR/nvim"
# Use:
[[sync.xdg_config]]
path = "nvim"
force = true
ignore = ["lazy-lock.json"]
[[sync.xdg_data]]
path = "git"
force = true
[[sync.home]]
path = ".zshenv"Supported shorthands:
| Shorthand | Local | Remote |
|---|---|---|
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.home |
$HOME |
$HOME_MIRROR |
Options:
-
path(optional): Relative path within the XDG directory. If omitted, syncs the entire directory. -
force,ignore,only: All standard mapping options are supported.
Example: Sync entire XDG config directory
[[sync.xdg_config]]
only = ["nvim", "alacritty", "zsh"]Note
You can mix and match [[sync]], XDG shorthands, and traditional [[push.mappings]]/[[pull.mappings]] in the same configuration file. Use whatever style is most appropriate for each mapping.
force, only, and ignore Options in Mappings
Each mapping entry supports the following options:
force Option
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:
[[pull.mappings]]
src = "$XDG_CONFIG_HOME_MIRROR/nvim"
dest = "$XDG_CONFIG_HOME/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.
only Option
An array of relative paths (files or directories) 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 or individual files
- 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
[[push.mappings]]
src = "$XDG_CONFIG_HOME"
dest = "$DOTFILES_DIR/config"
only = ["nvim", "alacritty", "zsh"]This transfers only the nvim/, alacritty/, and zsh/ directories.
Example 2: Selecting specific files
[[push.mappings]]
src = "$XDG_CONFIG_HOME/alacritty"
dest = "$DOTFILES_DIR/config/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
[[push.mappings]]
src = "$HOME/.config"
dest = "$DOTFILES_DIR/config"
only = ["bundle/config", "ghc/ghci.conf", "cabal/config"]This transfers only specific configuration files from different subdirectories:
-
bundle/configfile from thebundle/directory -
ghc/ghci.conffile from theghc/directory -
cabal/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: Deeply nested paths
[[push.mappings]]
src = "$XDG_CONFIG_HOME"
dest = "$DOTFILES_DIR/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. -
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, only that specific file is managed.
ignore Option
An array of relative paths or patterns to exclude during transfer. This allows you to skip certain files or folders.
Example:
[[pull.mappings]]
src = "$XDG_CONFIG_HOME_MIRROR/nvim"
dest = "$XDG_CONFIG_HOME/nvim"
ignore = ["lazy-lock.json", "plugin/packer_compiled.lua"]Combining options:
[[push.mappings]]
src = "$XDG_CONFIG_HOME/nvim"
dest = "$DOTFILES_DIR/config/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.
Safety Features
Dotsync includes several safety mechanisms to prevent accidental data loss:
Confirmation Prompts
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.
Automatic Backups
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/Preview Mode (Dry-Run)
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
Enhanced Error Handling
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.
Symlink Support
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
Customizing Icons
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.
Available Icon Options
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 transferred -
ignore- Indicates files are being ignored during transfer -
invalid- Indicates the mapping is invalid (missing source/destination)
Difference Status Icons (shown in diff output):
-
diff_created- Shows newly created/added files -
diff_updated- Shows updated/modified files -
diff_removed- Shows removed/deleted files
Example Configuration
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 mappings section
[[pull.mappings]]
src = "$XDG_CONFIG_HOME_MIRROR"
dest = "$XDG_CONFIG_HOME"
ignore = ["cache"]Default Icons
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.
Automatic Update Checks
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.
Pro Tips
-
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
Common Use Cases
Here are some practical examples of how to use Dotsync for popular configuration files:
Syncing Neovim Configuration
[[pull.mappings]]
src = "$HOME/dotfiles/config/nvim"
dest = "$HOME/.config/nvim"
force = true
ignore = ["lazy-lock.json", ".luarc.json"]Syncing Terminal Emulator (Alacritty)
[[push.mappings]]
src = "$HOME/.config/alacritty"
dest = "$HOME/dotfiles/config/alacritty"
only = ["alacritty.toml", "themes"]Syncing Shell Configuration
[[pull.mappings]]
src = "$HOME/dotfiles/shell/.zshrc"
dest = "$HOME"
[[pull.mappings]]
src = "$HOME/dotfiles/shell/.zshenv"
dest = "$HOME"Syncing Multiple Config Directories
[[pull.mappings]]
src = "$HOME/dotfiles/config"
dest = "$HOME/.config"
ignore = ["nvim", "cache", "*.log"]Troubleshooting
Icons Not Displaying Correctly
Problem: 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 = "-"
Changes Not Being Applied
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.
Permission Denied Errors
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
Source or Destination Not Found
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
Restoring from Backups
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/Watch Command Not Detecting Changes
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
Confirmation Prompt Appearing
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 --quietUsing Multiple Config Files
Problem: 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.
Config File Not Found
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 setupDevelopment
- 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.
Releasing a new version
- Update the version number in
version.rb. - Run
bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the.gemfile to rubygems.org.
Contributing
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.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Dotsync projectβs codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.


