Rustow is a modern Rust implementation of GNU Stow, a symlink farm manager. It helps you organize and manage configuration files (dotfiles) and software packages by creating symbolic links from a target directory to files stored in separate package directories.
- Full GNU Stow Compatibility: Supports all major GNU Stow features and command-line options
- Safe Operation: Two-phase execution (scan and action) prevents filesystem corruption
- Dotfiles Management: Special
--dotfilessupport for managing hidden configuration files - Advanced Conflict Resolution: Comprehensive conflict detection with
--overrideand--deferoptions - Flexible Ignore Patterns: Support for local, global, and built-in ignore patterns
- Tree Folding: Intelligent directory structure optimization
- GNU stowrc compatibility:
~/.stowrcand./.stowrcare merged with CLI options (CLI takes precedence) - Dry Run Mode: Preview operations before execution with
--simulate - Verbose Logging: Detailed output with configurable verbosity levels
- File Adoption: Migrate existing files into Stow packages with
--adopt - Cross-Platform: Supports Linux, macOS, and Windows release packaging (platform-specific artifacts are generated)
# Stow a package (create symlinks)
rustow mypackage
# Stow multiple packages
rustow package1 package2 package3
# Unstow a package (remove symlinks)
rustow -D mypackage
# Restow a package (unstow then stow)
rustow -R mypackage
# Dry run (preview operations)
rustow -n mypackage
# Verbose output
rustow -v mypackageRustow expects a specific directory structure:
/path/to/stow/ # Stow directory
βββ package1/ # Package directory
β βββ bin/
β β βββ program1
β βββ lib/
β βββ libfile.so
βββ package2/
β βββ etc/
β βββ config.conf
βββ dotfiles/ # Dotfiles package
βββ dot-bashrc # Becomes .bashrc when --dotfiles is used
βββ dot-vimrc # Becomes .vimrc when --dotfiles is used
βββ dot-config/ # Becomes .config/ when --dotfiles is used
βββ git/
βββ config
When you run rustow package1 from /path/to/stow/, it creates symlinks in the target directory (by default, the parent directory /path/to/):
/path/to/ # Target directory
βββ bin/
β βββ program1 -> stow/package1/bin/program1
βββ lib/
βββ libfile.so -> stow/package1/lib/libfile.so
rustow PACKAGE...- Stow packages (create symlinks)-S, --stow- Explicitly request stow mode (default action)-D, --delete- Unstow packages (remove symlinks)-R, --restow- Restow packages (unstow then stow)
Notes:
- Running stow repeatedly is idempotent for managed links; existing rustow-managed items are skipped.
- A target directory that already exists (including with unmanaged files) is not treated as a hard conflict. Rustow creates/keeps the directory and continues with child entries.
-t DIR, --target=DIR- Set target directory (default: parent of stow dir)-d DIR, --dir=DIR- Set stow directory (default: current directory)- If
--diris not specified, Rustow also acceptsSTOW_DIRas fallback.
--dotfiles- Enable dot- prefix processing for dotfiles--adopt- Move conflicting files into stow directory--no-folding- Disable tree folding optimization-p, --compat- Use GNU Stow compatible search mode for package symlinks (--compat)
--ignore=REGEXP- Ignore files matching regex in addition to built-in/local/global ignore lists
--override=REGEXP- Force override files matching pattern--defer=REGEXP- Skip files matching pattern
-n, --simulate- Dry run mode (show what would be done)-v, --verbose[=LEVEL]- Increase verbosity--help- Show command help-V, --version- Show binary version
Rustow also reads configuration from resource files:
~/.stowrc./.stowrc(current directory)
Options in resource files are merged with CLI arguments (CLI args have higher priority), and resource values support environment variable and ~ expansion.
~/.stowrc(if readable regular file) is loaded first../.stowrc(if readable regular file) is loaded after.- When both contain the same option, later values override earlier ones.
--in.stowrcstops option parsing for the remaining tokens in that file.
# Set up dotfiles with the --dotfiles option
cd ~/dotfiles
rustow --dotfiles --target=~ vim bash git
# This creates:
# ~/.vimrc -> ~/dotfiles/vim/dot-vimrc
# ~/.bashrc -> ~/dotfiles/bash/dot-bashrc
# ~/.config/git/config -> ~/dotfiles/git/dot-config/git/config# Install packages to /usr/local
cd /usr/local/stow
sudo rustow --target=/usr/local myprogram
# Dry run to see what would happen
rustow -n --target=/usr/local myprogram# Override existing files
rustow --override="\.bashrc" mybash
# Defer conflicting files
rustow --defer="\.vimrc" myvim
# Adopt existing files into stow package
rustow --adopt mypackage# Existing unmanaged directory is preserved, and nested items are still merged
cd /path/to/stow
rustow --no-folding package-with-mixed-dir
# Package items that are symlinks are tracked as managed links, so re-running stow is a no-op for them
rustow --no-folding package-with-symlink-itemsRustow supports three levels of ignore patterns:
- Package-local (
.stow-local-ignorein package directory) - Global (
~/.stow-global-ignorein home directory) - Built-in defaults
By default, Rustow ignores:
- Version control directories (
.git,.svn,CVS, etc.) - Editor backup files (
*~,#*#,.#*) - Documentation files (
README*,LICENSE*,COPYING) - Ignore files themselves (
.gitignore,.stow-local-ignore)
Create .stow-local-ignore in a package directory:
# Ignore all .org files in package root
^/.*\.org$
# Ignore temp directories anywhere
temp
# Ignore specific files
build.log
Rustow is built with a modular architecture:
- CLI Module: Command-line argument parsing with
clap - Config Module: Configuration management and validation
- Stow Module: Core stow/unstow logic with two-phase execution
- FS Utils Module: File system operations abstraction
- Ignore Module: Pattern matching for ignore functionality
- Dotfiles Module: Dot-prefix processing for dotfiles
- Error Module: Comprehensive error handling with
thiserror
# Run all tests
cargo test
# Run integration tests
cargo test --test integration_tests
# Run with verbose output
cargo test -- --nocapture
# Run specific test module
cargo test ignore::tests# Format code
cargo fmt
# Check formatting
cargo fmt -- --check
# Run linter
cargo clippy
# Run linter with strict warnings
cargo clippy -- -D warnings
# Security audit (requires cargo-audit)
cargo audit
# Check for problematic dependencies (requires cargo-deny)
cargo deny checkThis repository has an automated GitHub Actions release workflow that builds release binaries and uploads them to GitHub Releases.
-
Merge changes to
main. -
Create a tag on the target commit:
git tag -a vX.Y.Z -m "Release vX.Y.Z" -
Push the tag:
git push origin vX.Y.Z
-
The
Releaseworkflow runs automatically and uploads:rustow-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gzrustow-vX.Y.Z-x86_64-unknown-linux-musl.tar.gzrustow-vX.Y.Z-x86_64-apple-darwin.tar.gzrustow-vX.Y.Z-aarch64-apple-darwin.tar.gzrustow-vX.Y.Z-x86_64-pc-windows-msvc.tar.gzrustow-vX.Y.Z-sha256sums.txt
If you need to rerun release packaging, use workflow_dispatch in GitHub Actions and specify an existing tag name (for example vX.Y.Z).
- Fork the repository
- Create a feature branch
- Write tests for your feature
- Implement the feature
- Ensure all tests pass
- Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
- GNU Stow project for the original implementation and inspiration
- The Rust community for excellent crates and tooling
- Contributors who help improve this project