Sync your dotfiles with an end-to-end encrypted Standard Notes account.
sn-dotfiles is a command-line tool to sync dotfiles with a Standard Notes account. It works by creating a tag called 'dotfiles' and then maps dotfile directories with tags and dotfiles as notes.
I wanted a simple way of securely storing, managing, and syncing my dotfiles across multiple machines. Standard Notes uses client-side encryption, so the contents of your dotfiles are encrypted before they leave your machine.
On macOS and linux, using homebrew:
brew install jonhadfield/tap/sn-dotfilesThat installs the sn-dotfiles binary and clears the macOS quarantine flag for
you.
Or in one line, which picks the right archive for your platform, verifies
its checksum against the published list, and installs to /usr/local/bin,
using sudo only if the install directory is not writable:
curl -fsSL https://raw.githubusercontent.com/jonhadfield/sn-dotfiles/main/install.sh | shSet BIN_DIR to install somewhere else, or VERSION to pin a release:
curl -fsSL https://raw.githubusercontent.com/jonhadfield/sn-dotfiles/main/install.sh | BIN_DIR="$HOME/.local/bin" sh
curl -fsSL https://raw.githubusercontent.com/jonhadfield/sn-dotfiles/main/install.sh | VERSION=0.2.0 shIf you would rather read it before running it, install.sh is in this repository.
Or download the latest release from the releases page and install the binary from the archive matching your platform:
tar -xzf sn-dotfiles_Darwin_universal.tar.gz sn-dotfiles
sudo install -m 755 ./sn-dotfiles /usr/local/bin/sn-dotfiles && rm ./sn-dotfilesThe darwin binaries are not signed. A browser download is quarantined, so Gatekeeper refuses to run it; homebrew and the install script both clear that flag, but for a browser download clear it yourself:
xattr -d com.apple.quarantine /usr/local/bin/sn-dotfilesA config file is required, so create one before the first run. This example tracks your git config and your neovim configuration:
mkdir -p ~/.config/sn-dotfiles
cat > ~/.config/sn-dotfiles/config.yaml <<'EOF'
include:
- '^\.gitconfig$'
- '^\.config/nvim/'
EOFThen authenticate, start tracking a file, and see where things stand:
sn-dotfiles session --add # stores a session in your keychain
sn-dotfiles add ~/.gitconfig # copies it into Standard Notes as a note
sn-dotfiles status # compares local files with the remote notes
sn-dotfiles sync --dry-run # shows what a sync would do
sn-dotfiles sync # does itBy default, your credentials will be requested every time, but you can store them using either environment variables or, on MacOS and Linux, store your session using the native Keychain application.
Using a session is different from storing credentials as you no longer need to authenticate. As a result, if using 2FA (Two Factor Authentication), you won't need to enter your token value each time.
sn-dotfiles session --add # session will be stored after successful authenticationTo encrypt your session when adding, pass a key, or . to hide its input:
sn-dotfiles session --add --session-key <key>Prefix any command with --use-session to automatically retrieve and use the session.
If your session is encrypted, you will be prompted for the session key. To specify the key on the command line:
sn-dotfiles --use-session --session-key <key> <command>Note: if using 2FA, the token value will be requested each time. Your password is your account's encryption password, so putting it in a shell profile leaves it in plain text on disk; prefer a session for interactive use.
export SN_EMAIL=<email address>
export SN_PASSWORD=<password>
export SN_SERVER=<https://myserver.example.com> # optional, if running personal server
export SN_USE_SESSION=true # same as passing --use-session
export SN_DEBUG=true # same as passing --debugA config file is required. By default it is read from $XDG_CONFIG_HOME/sn-dotfiles/config.yaml, falling back to ~/.config/sn-dotfiles/config.yaml; use --config <path> to read another file.
It lists regular expressions that decide which dotfiles are synced:
include: # required: sync paths matching at least one of these
- '^\.gitconfig$'
- '^\.config/fish/'
exclude: # optional: never sync paths matching any of these
- '\.swp$'Patterns are matched against each file's path relative to your home directory, using / as the separator, for example .config/fish/config.fish. To match everything in a folder, match its path as a prefix, e.g. ^\.config/fish/.
status,syncanddiffonly show and sync matching files. Notes in Standard Notes that don't match are left untouched.addskips files that don't match, so it never tracks something that would not be synced.removeandwipeare not filtered, so anything can still be removed.
To replace either list for a single run, pass the patterns before the command:
sn-dotfiles --include-regex '^\.config/nvim/' --exclude-regex '\.bak$' status| Command | Description |
|---|---|
status |
Compare tracked dotfiles with the remote notes |
sync |
Push newer local files, pull newer remote ones |
add |
Start tracking file(s) |
remove |
Stop tracking file(s), leaving the local files alone |
diff |
Show the differences between local files and remote notes |
session |
Add or remove a stored session |
wipe |
Remove every dotfiles note and tag from the account |
Global flags: --config, --home-dir, --server, --use-session, --session-key, --include-regex, --exclude-regex, --debug, --quiet, --no-stdout.
sn-dotfiles statusStatus compares each tracked dotfile with its remote note and reports one of five states, which tell you what a sync would then do:
| State | Meaning | What sync does |
|---|---|---|
identical |
Local file and note match | Nothing |
local newer |
The local file changed most recently | Pushes the local file |
remote newer |
The note changed most recently | Overwrites the local file |
local missing |
Tracked remotely, absent locally | Creates the local file |
untracked |
Present locally, not in Standard Notes | Nothing, until you add it |
sn-dotfiles add ~/.file1 ~/.dir1/file2Add will take a copy of the specified file(s) and convert the files to Notes and each path to a Tag. Files that don't match the configuration patterns are skipped. The above command would generate the following structure:
dotfiles <- tag
- .file1 <- note
- dir1 <- tag
- file2 <- note
add --all tracks the dotfiles in the top level of your home directory. It is not recursive, so ~/.config/... is not included.
sn-dotfiles sync --exclude ~/.file1Sync will compare any dotfiles currently tracked in Standard Notes with their local equivalents and:
- Update the filesystem dotfile if the remote was updated more recently
- Update the remote if the filesystem dotfile is newer
- Create any missing dotfiles and paths that exist remotely
The example command would sync the ~/.dir1 path and the file it contains, but ignore ~/.file1. Only files matching the configuration patterns are synced.
To see what a sync would do before letting it do anything, add --dry-run:
sn-dotfiles sync --dry-run.zshrc | would push
.vimrc | would pull
.gitconfig | unchanged
dry run: nothing was written (1 to push, 1 to pull)
It compares exactly as a real sync does, honouring --exclude, any paths you
name and the configuration patterns, then stops before writing anything.
sn-dotfiles remove ~/.dir1Remove will recursively (if path specified) remove the remote Notes for the specified filesystem path. In the above example, the Note file2 and the Tag dir1 will be deleted. Remove will never change files on the filesystem.
sn-dotfiles diff ~/.dir1Diff writes the local file and the remote note to temporary files and runs your system's diff command on them, so diff must be on your PATH. The temporary files are removed afterwards.
sn-dotfiles wipeWipe deletes every dotfiles note and tag in the account. It shows the account email and asks for confirmation first; --force skips the prompt. It does not touch your local files.
- Only files under your home directory whose path relative to it starts with a dot. Anything else is rejected with
is not a valid dotfile path. - Symlinks are an error, not a skip:
symlink not supported. The same goes for sockets, devices, named pipes and other irregular files. - Files over 10MB, rejected with
file too large. - Binary files, reported as
skipped: binary file. A note stores text, so a binary file would come back different to how it went in.addleaves it untracked, andsyncskips a tracked file that has since become binary, rather than overwriting the note with content it cannot store. A file counts as binary if its first 8000 bytes contain a NUL byte or are not valid UTF-8. - Anything not matching your include patterns, or matching an exclude pattern.
Two things worth knowing about what a pull does:
- File modes are not preserved. A file created by a pull gets your default permissions (usually 0644), not the mode it had on the machine it came from. Check anything sensitive, such as
~/.netrcor an ssh config, after pulling it onto a new machine. - Notes are text. Your dotfiles are stored as note content, so this is a tool for text configuration, not for keys, images or databases.
config file ... not found— every command exceptsessionneeds a config file. See Quick start.symlink not supported— sn-dotfiles will not follow a symlinked dotfile. Track the file it points at instead.the following notes and tags are overlapping— a note and a tag in your account describe the same path, for example a note.configand a tag.config. Rename or remove one of them in the Standard Notes app.- Stale or confusing local state — the cache lives at
~/.sn-dotfiles/sn-dotfiles-<hash>.db, one file per account. Deleting it forces a fresh download on the next run. - Anything unexplained —
--debugprints the API calls and the comparison decisions.
The bash completion tool is installed by default on most Linux distributions. On macOS, install it with homebrew:
brew install bash-completionThen add the following to ~/.bash_profile:
[ -f "$(brew --prefix)/etc/profile.d/bash_completion.sh" ] && . "$(brew --prefix)/etc/profile.d/bash_completion.sh"Install the completion script, naming it after the command so bash picks it up:
# macOS
cp autocomplete/bash_autocomplete "$(brew --prefix)/etc/bash_completion.d/sn-dotfiles"
# Linux
sudo cp autocomplete/bash_autocomplete /etc/bash_completion.d/sn-dotfilesThen sn-dotfiles <tab> completes commands and flags.
- Notes moved to trash using the Standard Notes app will still be managed by sn-dotfiles until they are permanently deleted