https://github.com/cebarks/dotm
a stow-like dotfile manager with composable roles, Tera templates, and host-specific overrides.
https://github.com/cebarks/dotm
Last synced: 5 months ago
JSON representation
a stow-like dotfile manager with composable roles, Tera templates, and host-specific overrides.
- Host: GitHub
- URL: https://github.com/cebarks/dotm
- Owner: cebarks
- License: agpl-3.0
- Created: 2026-02-17T01:10:24.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-02-17T06:57:45.000Z (5 months ago)
- Last Synced: 2026-02-17T07:58:57.865Z (5 months ago)
- Language: Rust
- Size: 103 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# dotm
A dotfile manager with composable roles, Tera templates, and host-specific overrides.
dotm organizes config files into **packages** (directories mirroring your home directory structure), groups them into **roles** (e.g. "desktop", "dev", "gaming"), and assigns roles to **hosts**. Deployment creates symlinks for plain files and copies for overrides/templates, so your dotfiles repo stays the single source of truth.
## Installation
```bash
cargo install dotm-rs
```
Or to install from the latest source:
```bash
cargo install --git https://github.com/cebarks/dotm
```
## Quick Start
```bash
# Initialize a dotm project
mkdir ~/dotfiles && cd ~/dotfiles
# Create the root config
cat > dotm.toml << 'EOF'
[dotm]
target = "~"
[packages.shell]
description = "Shell configuration"
[packages.editor]
description = "Editor configuration"
depends = ["shell"]
EOF
# Create a package
dotm init shell
cp ~/.bashrc packages/shell/.bashrc
# Create a role
mkdir roles
echo 'packages = ["shell", "editor"]' > roles/dev.toml
# Create a host config
mkdir hosts
cat > hosts/$(hostname).toml << EOF
hostname = "$(hostname)"
roles = ["dev"]
EOF
# Deploy (dry run first)
dotm deploy --dry-run
dotm deploy
```
## Core Concepts
### Packages
A package is a directory under `packages/` that mirrors the target directory structure (usually `~`). Files inside are symlinked to their corresponding locations during deployment.
```
packages/
├── shell/
│ ├── .bashrc
│ └── .bash_profile
└── editor/
└── .config/
└── nvim/
└── init.lua
```
Packages can declare dependencies and suggestions in `dotm.toml`:
```toml
[packages.editor]
description = "Editor configuration"
depends = ["shell"] # always pulled in
suggests = ["theme"] # informational only
target = "/" # override deploy target (default: ~)
```
### Roles
A role groups packages together and can define variables for template rendering. Role configs live in `roles/.toml`:
```toml
# roles/desktop.toml
packages = ["shell", "editor", "kde"]
[vars]
shell.prompt = "fancy"
display.resolution = "3840x2160"
```
### Hosts
A host config selects which roles to apply and can override variables. Host configs live in `hosts/.toml`:
```toml
# hosts/workstation.toml
hostname = "workstation"
roles = ["desktop", "gaming", "dev"]
[vars]
display.resolution = "3840x2160"
gpu.vendor = "amd"
```
Variable precedence: **host vars > role vars** (last role listed wins among roles).
## Directory Structure
```
~/dotfiles/
├── dotm.toml # root config: package declarations
├── hosts/
│ ├── workstation.toml # workstation
│ └── dev-server.toml # server
├── roles/
│ ├── desktop.toml
│ ├── dev.toml
│ └── gaming.toml
└── packages/
├── shell/
│ ├── .bashrc # plain file → symlinked
│ ├── .bashrc##host.dev-server # host override → copied
│ └── .bashrc##role.dev # role override → copied
├── editor/
│ └── .config/nvim/
│ └── init.lua
└── kde/
└── .config/
├── rc.conf
└── rc.conf.tera # template → rendered & copied
```
## File Overrides
Override files sit next to the base file with a `##` suffix:
| Pattern | Priority | Description |
|---------|----------|-------------|
| `file##host.` | 1 (highest) | Used only on the named host |
| `file##role.` | 2 | Used when the role is active |
| `file.tera` | 3 | Tera template, rendered with vars |
| `file` | 4 (lowest) | Base file, symlinked |
- Override and template files are **copied**, not symlinked
- Only the highest-priority matching variant is deployed
- Non-matching overrides are ignored entirely
## Templates
Files ending in `.tera` are rendered using [Tera](https://keats.github.io/tera/) (a Jinja2-like template engine). Variables come from role and host configs:
```
# .config/app.conf.tera
resolution={{ display.resolution }}
{% if gpu.vendor == "amd" %}
driver=amdgpu
{% else %}
driver=modesetting
{% endif %}
```
The `.tera` extension is stripped from the deployed filename.
## CLI Reference
```
dotm [OPTIONS]
Options:
-d, --dir Path to dotfiles directory [default: .]
-V, --version Print version
Commands:
deploy Deploy configs for the current host
undeploy Remove all managed symlinks and copies
status Show deployment status
check Validate configuration
init Initialize a new package
```
### deploy
```bash
dotm deploy # deploy for current hostname
dotm deploy --host dev-server # deploy for a specific host
dotm deploy --dry-run # show what would be done
dotm deploy --force # overwrite existing unmanaged files
```
### undeploy
```bash
dotm undeploy # remove all managed files
```
### status
```bash
dotm status # show managed files and their state
```
### check
```bash
dotm check # validate configuration
dotm check --warn-suggestions # also warn about unresolved suggests
```
### init
```bash
dotm init mypackage # create packages/mypackage/
```
## Comparison
| Feature | dotm | GNU stow | yadm | dotter |
|---------|------|----------|------|--------|
| Symlink-based | Yes | Yes | Yes | Yes |
| Role/profile system | Yes | No | No | Yes |
| Host-specific overrides | Yes | No | Alt files | Yes |
| Template rendering | Tera | No | Jinja2* | Handlebars |
| Dependency resolution | Yes | No | No | No |
| Per-package target dirs | Yes | Yes | No | No |
*yadm templates require a separate `yadm alt` step.
## Disclaimer
Claude Code (Opus 4.6) was used for parts of the development of this tool, including some implementation, testing and documentation.
## License
GNU AGPLv3