- Rust 100%
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| clippy.toml | ||
| LICENSE | ||
| mise.toml | ||
| README.md | ||
| rustfmt.toml | ||
stratum
Overlay-based mod manager for Linux games. Mods stack via FUSE + overlayfs over a read-only game directory. Collections and modsets are declarative TOML. Switch loadouts atomically; never touch the original game tree.
Handles Cyberpunk 2077 and Dyson Sphere Program.
Why
- Game dir stays read-only. Verify integrity, hot-patch, redownload — none of it disturbs your mods.
- Atomic loadout switching. Activate a different modset in one mount; no install / uninstall churn.
- Per-modset saves. Optional saveset bind-mount redirects the game's save dir per loadout.
- Deterministic state. Collections, modsets, savesets are TOML. Same config on two machines = same overlay.
- Hexagonal core. Ports + adapters. Providers, games, and materializer passes are independent modules.
Install
cargo install --path .
Or mise run deploy to install and write zsh completions in one step (assumes mise + chezmoi).
Runtime binaries on PATH:
| Binary | Provides |
|---|---|
fuse-overlayfs |
overlay filesystem in user space |
unshare, mount, mountpoint |
mount namespace setup |
fusermount3 (or fusermount) |
FUSE unmount |
Gentoo:
emerge sys-fs/fuse-overlayfs sys-apps/util-linux sys-fs/fuse:3
Other distros: install the equivalent packages for your system.
Configure
~/.config/stratum/config.toml:
catalog_root = "~/.config/stratum/catalog"
data_root = "~/.local/share/stratum"
[[store]]
name = "personal"
path = "~/mods/personal"
accepts = ["local"]
[[store]]
name = "archive"
path = "/mnt/games/stratum-archive"
accepts = ["nexus", "thunderstore"]
[game.cp2077]
dir = "~/games/cyberpunk2077"
[game.dsp]
dir = "~/games/dyson-sphere-program"
Mod files live in [[store]] blocks (one per concern; writes route by provider). Per-mod metadata lives single-store under catalog_root. [game.<abbrev>] dirs are fallbacks — when stratum runs as a Steam wrapper, STEAM_COMPAT_INSTALL_PATH wins.
Set NEXUS_API_KEY in your shell env for Nexus downloads. Never in TOML — config is dotfiles-trackable.
Store-config rules + migration recipes live in the config module docs (cargo doc --no-deps --open).
Use
Catalog names are game-prefixed (cp2077:base, dsp:main). Pass -g <abbrev> for commands that don't take a scoped name.
# Archive a downloaded mod manually
stratum -g cp2077 mod add local:my_mod:v1 --name "My Mod" --version "1.0" --path ./mod-files
# Or pull from Nexus / Thunderstore by URL (interactive file picker; FOMOD runs if present)
stratum mod get https://www.nexusmods.com/cyberpunk2077/mods/2987
# Group mods into a collection
stratum collection create cp2077:base
stratum collection add cp2077:base cp2077:nexus:2987:125853
# Build a modset from collections
stratum modset create cp2077:main
stratum modset add cp2077:main cp2077:base
# Saveset (optional) — per-modset save redirection
stratum saveset create cp2077:main
# Dry-run: show the overlay plan + env block
stratum run -n --modset cp2077:main
Steam launch
Primary use: invoke stratum as Steam's %command% wrapper. In Steam game properties → Launch Options:
/home/you/.cargo/bin/stratum run --modset cp2077:main -- %command%
STEAM_COMPAT_INSTALL_PATH and STEAM_COMPAT_APP_ID tell stratum which game dir + Proton prefix to target. Per-game env vars (WINEDLLOVERRIDES for hook DLLs, etc.) get applied automatically.
Manual launch
stratum run --modset cp2077:main -- ./start-game.sh
Dev
mise run check # fmt + clippy + build
mise run test:all # all tests, all features
Repo layout:
| Path | Role |
|---|---|
src/core/ |
pure logic, ports, models. No IO. |
src/adapters/ |
IO impls slotting into core ports |
src/cli/ |
clap dispatch, structural validation |
src/config.rs, src/deps.rs |
wiring |
tests/ |
integration, system, litmus tests |
API reference:
cargo doc --no-deps --open
Design notes live in module-level rustdoc (the crate root indexes them).
License
GPLv2. Code flows freely; improvements come back. See LICENSE.