Small inotify daemon that forces files to have the same owner, group, and permissions as set in the configuration file.
  • Rust 93.9%
  • Shell 6.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-03 02:57:18 -05:00
.github Initial commit 2026-08-03 02:57:18 -05:00
config Initial commit 2026-08-03 02:57:18 -05:00
packaging/systemd Initial commit 2026-08-03 02:57:18 -05:00
src Initial commit 2026-08-03 02:57:18 -05:00
.gitignore Initial commit 2026-08-03 02:57:18 -05:00
Cargo.lock Initial commit 2026-08-03 02:57:18 -05:00
Cargo.toml Initial commit 2026-08-03 02:57:18 -05:00
PKGBUILD Initial commit 2026-08-03 02:57:18 -05:00
README.md Initial commit 2026-08-03 02:57:18 -05:00

RustyPerms

RustyPerms is a lightweight daemon that keeps filesystem permissions in sync with your policy. It watches the configured paths, ignores .zfs snapshot directories, and re-applies the expected mode bits and ownership when they drift.

Concepts

  • Monitor: notify::RecommendedWatcher polls the configured paths (recursively) for modify/create/attribute events.
  • Debounced reactor: events feed into a short-delay queue so we dont thrash while a storm of updates is processed.
  • Verify & enforce: each path is validated using std::fs::metadata (Unix-specific mode/owner metadata) and corrected via chmod/chown when needed.
  • Self-change filtering: modifications made by RustyPerms itself are ignored so the watcher doesnt loop forever.
  • .zfs guard: any path that contains a .zfs component is skipped to avoid touching snapshots.
  • Network filesystem guard: Linux builds also inspect the mount type and skip NFS/Samba exports so the daemon doesnt chase every remote metadata update.

Configuration

Copy config/rules.toml to the location you intend to use (the default path is config/rules.toml). Each [[rules]] entry should provide the path to monitor and the expected metadata:

[[rules]]
path = "/etc"
mode = 0755
owner = "root"
group = "wheel"

The mode value is optional; omit it to only police ownership. owner and group are also optional, but supplying them ensures the daemon will chown the target. The daemon resolves user/group names through the system database.

If you need different permissions for directories than files within a rules path, use dir_mode and file_mode. Each field is optional—if theyre unset the daemon falls back to mode for both file types—so you can still rely on the legacy mode setting for uniform enforcement.

All mode fields accept either integers (e.g., mode = 644) or strings with an octal prefix (mode = "0o644" or mode = "0777"), so your preferred notation is preserved while the parser still understands the value.

Similarly, owner/group understand both names (root, wheel) and numeric IDs (0, 1000) so you can pin ownership even on systems without matching name databases.

Running

cargo run --release -- --config config/rules.toml

The binary writes logs to stdout. Adjust RUST_LOG to trace or debug the flow.

Development

  • Format: cargo fmt
  • Check: cargo check
  • Build: cargo build --release

Packaging

  • Systemd service unit for RustyPerms lives in packaging/systemd/rustyperms.service; copy it into /usr/lib/systemd/system (or /etc/systemd/system for overrides) and enable it with systemctl enable --now rustyperms.service. The unit expects a configuration file at /etc/rustyperms/rules.toml, so install your policy there before starting the daemon.
  • An Arch Linux PKGBUILD sits at the repository root. Run makepkg in this directory to build the package, then install it with pacman -U. The build script compiles the binary, installs the sample config into /etc/rustyperms/rules.toml, and drops the service unit under /usr/lib/systemd/system/rustyperms.service so the daemon is ready to enable.

Notes

  • Because RustyPerms touches the filesystem, run it as a user or service account that has the appropriate privileges (typically root).
  • The watcher ignores any directory that contains a .zfs component, so snapshots do not trigger recursive enforcement.
  • You can customize the debounce window by setting the debounce_millis setting in config/rules.toml. The default is 250 ms.