Skip to content

douglas/dotlayer

Repository files navigation

dotlayer

A convention-driven CLI wrapper around GNU Stow for managing dotfiles across multiple machines with layered overrides.

Why

Managing dotfiles across machines with different OSes, hardware profiles, and Linux distros means lots of conditional logic in install scripts. Dotlayer replaces that with a naming convention: directory names declare when they should be stowed.

config/                     → always stowed
config-linux/               → stowed on Linux
config-macos/               → stowed on macOS
config-omarchy/             → stowed when Omarchy is detected
config-omarchy-desktop/     → stowed on Omarchy + desktop profile
config-omarchy-laptop/      → stowed on Omarchy + laptop profile
config-mycompany/            → stowed when mycompany group is detected

No config file needed — just name your directories and dotlayer figures out the rest.

Install

gem install dotlayer

Or clone and run directly:

git clone https://github.com/douglas/dotlayer.git
export PATH="$HOME/src/dotlayer/exe:$PATH"

Requirements: Ruby >= 3.2, GNU Stow

Quick start

# See what dotlayer detects on your system
dotlayer status

# Stow all matching packages
dotlayer install

# Pull latest changes and re-stow
dotlayer update

# Check for broken symlinks and missing deps
dotlayer doctor

# Move existing config into a stow package
dotlayer adopt ~/.config/lazygit config

# Move config into the private repo
dotlayer adopt --private ~/.config/lazysql config

How it works

Dotlayer auto-detects four things about your system:

Detection Method Example
OS RbConfig::CONFIG["host_os"] linux, macos
Profile hostnamectl chassis or $DOTLAYER_PROFILE desktop, laptop
Distro Shell commands from config omarchy, fedora
Group Shell commands from config work, mycompany

It then scans your dotfiles repo for directories matching these tags and stows them in layer order:

  1. Base packagesstow, bin, git, zsh, config
  2. OS layerconfig-linux or config-macos
  3. Distro layerconfig-omarchy
  4. Distro + profileconfig-omarchy-desktop
  5. Group layerconfig-mycompany

Each layer can add files but never conflict with earlier layers (Stow constraint).

Configuration

Dotlayer works with zero configuration using sensible defaults. For customization, create a dotlayer.yml:

target: ~

repos:
  - path: ~/.public_dotfiles
  - path: ~/.private_dotfiles
    private: true

packages:
  - stow
  - bin
  - git
  - zsh
  - config

profiles:
  detect: hostnamectl chassis
  env: DOTLAYER_PROFILE

distros:
  omarchy:
    detect: test -d ~/.local/share/omarchy
  fedora:
    detect: . /etc/os-release && test "$ID" = "fedora"

groups:
  mycompany:
    detect: test -d ~/src/mycompany

system_files:
  - source: config-linux/etc/systemd/system-sleep/xremap-restart.sh
    dest: /etc/systemd/system-sleep/xremap-restart.sh
    mode: "0755"

hooks:
  after_system_files:
    - sudo udevadm control --reload-rules

Config file is discovered automatically from:

  • ~/.config/dotlayer/dotlayer.yml
  • ~/.public_dotfiles/dotlayer.yml
  • ~/.dotfiles/dotlayer.yml

Public and private dotfiles

Most people have two kinds of config: stuff you can share (shell aliases, editor themes, window manager rules) and stuff you can't (API keys, work credentials, employer-specific tooling). Dotlayer handles this with two repos:

~/.public_dotfiles/     ← shared on GitHub, safe for the world to see
~/.private_dotfiles/    ← private repo (or not pushed at all)

Why two repos?

A single dotfiles repo forces a choice: keep it private (lose the benefit of sharing) or keep it public (risk leaking secrets). Two repos solves this cleanly:

  • Public repo — your shell, editor, git, and desktop config. Push to GitHub, share with the community, clone on any new machine.
  • Private repo — work-specific config, API tokens, SSH configs, fonts with restrictive licenses, employer tooling. Keep in a private repo or don't push at all.

Both repos stow into the same target (~), so your home directory looks the same regardless of which repo a file came from.

Setting it up

Create both repos:

mkdir -p ~/.public_dotfiles ~/.private_dotfiles
cd ~/.public_dotfiles && git init
cd ~/.private_dotfiles && git init

Add the dotlayer config at ~/.config/dotlayer/dotlayer.yml:

repos:
  - path: ~/.public_dotfiles
  - path: ~/.private_dotfiles
    private: true
    packages:
      - config
      - fonts

The private: true flag tells dotlayer which repo to use when you run dotlayer adopt --private.

What goes where?

Public repo — uses the standard base packages (stow, bin, git, zsh, config) with full layering support:

~/.public_dotfiles/
  config/                       ← shared config (editor, terminal, etc.)
  config-linux/                 ← Linux-specific overrides
  config-omarchy/               ← Omarchy distro overrides
  config-omarchy-desktop/       ← Omarchy + desktop profile
  git/                          ← git config
  zsh/                          ← shell config
  bin/                          ← personal scripts
  stow/                         ← stow's own config

Private repo — can use per-repo packages for layered resolution, plus standalone directories that are all stowed automatically:

~/.private_dotfiles/
  config/                       ← private config overlays (SSH, API keys)
  config-mycompany/              ← work-specific config (group layer)
  fonts/                        ← licensed fonts
  fonts-linux/                  ← Linux-specific font config
  claude/                       ← standalone: Claude AI config
  work/                         ← standalone: work tooling
  scripts/                      ← standalone: work automation scripts

Directories that match the per-repo base packages (config, fonts) get full layer resolution. Everything else (claude, work, scripts) is stowed as-is in alphabetical order.

Adopting files into the right repo

# Public config — goes to ~/.public_dotfiles
dotlayer adopt ~/.config/lazygit config

# Private config — goes to ~/.private_dotfiles
dotlayer adopt --private ~/.config/lazysql config

Layer precedence across repos

Repos are processed in order. Public packages are stowed first, then private packages overlay on top. This means:

  1. ~/.public_dotfiles/config/ — base config (stowed first)
  2. ~/.public_dotfiles/config-linux/ — OS layer
  3. ~/.public_dotfiles/config-omarchy/ — distro layer
  4. ~/.private_dotfiles/config/ — private overlays (stowed after public)
  5. ~/.private_dotfiles/config-mycompany/ — work group layer

Each layer adds files — they never conflict because different layers use different file paths within the same directory structure.

Keeping private dotfiles private

Your private repo doesn't need to be pushed anywhere. It works fine as a local-only git repo for tracking changes. If you do want backup:

  • Push to a private GitHub/GitLab repo
  • Use dotlayer update to pull both repos at once — it runs git pull --rebase on each repo that has a .git directory

CLI reference

dotlayer [options] <command>

Commands:
  install     Detect system, stow all layers, install system files
  update      Pull repos, re-stow all layers
  status      Show detected OS, profile, distros, groups, and packages
  doctor      Check for broken symlinks, missing repos, missing stow
  adopt       Move config files into a stow package and restow
  version     Print version

Options:
  -c, --config PATH   Config file path
  -n, --dry-run       Show what would be done without making changes
  -p, --private       Use private repo (for adopt command)
  -v, --verbose       Verbose output

adopt

Moves existing config files or directories into a stow package, then restows so the originals become symlinks managed by stow.

# Move a single directory
dotlayer adopt ~/.config/lazygit config

# Move multiple paths at once
dotlayer adopt ~/.config/lazygit ~/.config/lazydocker config

# Move into the private repo
dotlayer adopt --private ~/.config/lazysql config

# Preview without moving anything
dotlayer adopt --dry-run ~/.config/lazygit config

The last argument is always the package name. Dotlayer finds the first repo containing that package, or falls back to the first repo for new packages.

Development

bundle exec rake test          # run tests
bundle exec standardrb         # lint
bundle exec rubycritic lib/    # code quality report

See Contributing for full setup, testing, and contribution guidelines.

Documentation

  • Architecture — system design, data flow, and class responsibilities
  • Contributing — setup, testing, and contribution guidelines

License

O-SaaSy

About

dotlayer — Layered Dotfiles Framework

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages