Personal dotfiles for Fedora Linux 44 running eight desktop rices across Hyprland, Niri and Umbriel — mostly Quickshell-family shells (end-4, Caelestia, iNiR, pleamar, Noctalia, minflair, Prism). Clean, minimalist frosted glassmorphism, tailored for productivity and gaming workflows.
caelestia · Hyprland + Caelestia Quickshell • vitreus · Hyprland + pleamar • noctalia · Umbriel + Noctalia v5 • iris · Niri + iNiR
| Component | Specification |
|---|---|
| OS | Fedora Linux 44 (Workstation Edition) |
| Kernel | Linux 7.2.x x86_64 |
| Compositors | Hyprland (6 rices), Niri, Umbriel (Wayland) |
| Shells | Quickshell-family: end4-pC, Caelestia, II, minflair, Prism, iNiR, pleamar, Noctalia v5 |
| Launcher | Rofi 2.0 (Custom Launchpad Grid) |
| Terminal | Kitty with Fish shell & Starship prompt |
| Laptop Hardware | ASUS (TigerLake Intel Iris Xe GT2) |
| Laptop Display | 14-inch 2.8K 90Hz OLED (eDP-1, 1.6x scale) |
| External Monitor | Xiaomi Mi Desktop Monitor 1080p @ 200Hz (HDMI-A-1, 1.0x scale) |
| Audio DAC | Intel Tiger Lake-LP Smart Sound Technology (SOF) |
| Mouse | Ajazz AJ139 V2 MC (PAW3311, WebHID driver) |
.
├── install.sh # Entry point: args, library loading, run order
├── lib/ # Installer internals (one concern per file)
│ ├── core.sh # logging, atomic linking, backups, manifest
│ ├── snapshot.sh # rollback snapshots
│ ├── backup.sh # backup retention
│ ├── common.sh # rice-independent configs + kitty wrapper
│ ├── rice.sh # dispatcher, foreign-link pruning, state folding
│ └── rices/ # one linker per rice
├── bin/ # dotfiles CLI, rice-switch, display switchers
├── tests/ # Regression tests (rollback, doctor, kitty, …)
├── docs/ # recovery guide
├── common/ # Core configs shared across all rices
│ ├── kitty/ # Terminal config + tracked fallback palette
│ ├── fish/ # Fish shell config & aliases
│ ├── starship/ # Starship minimalist prompt
│ ├── rofi/ # Launchpad & display switcher
│ ├── pipewire/ # Headphone DSP balance (+0.15 right channel)
│ ├── fastfetch/ # Custom ASCII logos & specs
│ ├── chrome/ # Wallpaper color sync host
│ ├── firefox/ # WhiteSur frosted glass theme
│ ├── swappy/ # Screenshot annotation config
│ ├── fontconfig/ # CJK & Nerd Font priorities
│ ├── sounds/ # macOS UI feedback sounds (opus)
│ └── systemd/ # Background watcher services & timers
└── rices/ # Isolated desktop rice environments
├── end4/ # Hyprland + Quickshell end4-pC (Material 3)
├── caelestia/ # Hyprland + Caelestia Quickshell
├── ii-p3drovfx/ # Hyprland + Quickshell II (dynamic VFX)
├── minflair/ # Hyprland + Quickshell minflair
├── prism-shell/ # Hyprland + Quickshell Prism
├── vitreus/ # Hyprland + pleamar (liquid glass)
├── iris/ # Niri + iNiR
└── noctalia/ # Umbriel + Noctalia v5
Each rice carries rice.conf metadata plus its compositor config and shell state.
The repo is the source of truth; ~/.config is a board of symlinks into it.
common/ holds what every rice shares, rices/<name>/ holds what differs, and
lib/rices/<name>.sh decides which of those files get linked for that rice.
rice-switch <rice> runs four steps in a fixed order:
| Step | What happens | On failure |
|---|---|---|
| 1. Link | install.sh --rice <rice> --no-set-current relinks configs |
abort, nothing written |
| 2. Verify | hyprland --verify-config / niri validate / umbriel config validate |
rollback snapshot restored |
| 3. Commit | write ~/.config/.current_rice, set the GDM session |
— |
| 4. Logout | quit the compositor; GDM starts the new session | — |
Step order is the safety property: a rice that cannot start never becomes the recorded active rice, and the symlinks are already back on the previous rice when the error is printed.
dotfiles switch <rice> # link → verify → set session → logout
dotfiles switch -n <rice> # dry run: show the full plan, change nothing
dotfiles install <rice> # relink only (no logout), e.g. Hyprland → Hyprland
dotfiles status # active rice, compositor, GDM session, manifest size
dotfiles doctor # verify every managed path against the manifest
dotfiles doctor --fix # relink the active rice when drift is found
dotfiles diff # repo changes vs generated state vs symlink drift
dotfiles save -m "msg" # commit only generated/theme state
dotfiles rollback # restore the last install snapshotEvery successful install writes ~/.local/state/dotfiles/manifest.tsv
(dest<TAB>src per path). That is what doctor compares against, and it is the
only check that catches a shell rewriting a symlink into a regular file with an
atomic rename — the config silently stops tracking the repo otherwise.
dotfiles doctor exits 1 on missing/dangling/drifted/replaced paths and 0 when
the map is intact (leftover links from other rices are reported as notes, not
failures). Safe as a pre-commit or CI gate.
| Kind | Examples | How it is committed |
|---|---|---|
| Hand-written config | rices/*/hypr/*.lua, hypridle.conf, common/** |
normal commits |
| Generated theme/UI state | rices/*/illogical-impulse/config.json (end-4 shell state), rices/*/hypr/hyprland/colors.lua (Matugen palette) |
dotfiles save -m "..." → chore(state): ... |
Keeping the two apart is why git diff stays readable: the state files churn on
every wallpaper change, hand-written config does not.
- Replaced real files go to
~/.config/dotfiles_backup_<timestamp>/with their original path preserved (.config/hypr/...), so two files with the same basename can never collide. The last 5 trees are kept. - Before touching anything, the pre-state of each path (symlink target, or
"real file") is written to
~/.local/state/dotfiles/rollback-<ts>-<pid>.tsv. On failure the installer restores that snapshot;install.sh --rollbackreplays the last one on demand. - Snapshots keep the previous manifest too, so a rollback also restores what
doctorcompares against.
Recovery runbook (black screen, wrong session, lost UI state):
docs/recovery.md.
From a terminal or the keybinding menu:
rice-switch # Open the Rofi/Fuzzel switcher (ACTIVE rice is marked)
rice-switch end4 # Switch to Hyprland Material 3
rice-switch iris # Switch to Niri + iNiR
rice-switch --dry-run vitreus # Show what a switch would link/verify, change nothing
rice-switch status # Show the active rice, compositor and GDM sessionTwo shells are cloned manually into ~/.config/quickshell/ and run from our own forks, so local patches survive upstream updates:
| Clone | Our fork | Upstream |
|---|---|---|
~/.config/quickshell/end4-pC |
Schnee111/end4-pC |
pctrade/end4-pC |
~/.config/quickshell/inir |
Schnee111/iNiR |
snowarch/iNiR |
What the iNiR fork carries (see git log in the clone):
fix(config)— every config resolver is pinned to~/.config/inir; the fallback to~/.config/illogical-impulse(end4's path) is gone, so the two shells no longer overwrite each other's state.fix(iris)—BackgroundEffectguards inIrisBlurRegion/IrisWaveBlur, and the explicit radius on the child regions dropped.fix(shell)—DefaultEnvpragmas changed toEnv(Quickshell 0.2.1 does not accept the former) plus a panel-family fallback from the config.
Updating upstream:
cd ~/.config/quickshell/inir
git fetch upstream && git merge upstream/main # resolve the patch conflicts, then git push- Create a directory under
rices/<rice-name>/. - Add
rice.confmetadata:NAME="rice-name" DISPLAY_NAME="🎨 Rice Name (Hyprland / Waybar)" COMPOSITOR="hyprland"
- Put the compositor and shell configuration inside it (e.g.
hypr/,waybar/,dunst/). - Run
rice-switch— the new profile is picked up automatically.
~/.config/kitty/kitty.conf is not symlinked into the repo; install.sh generates it per rice:
~/.config/kitty/kitty.conf # generated: include common/kitty/kitty.conf
~/.config/kitty/rice-theme.conf # symlink to the active rice's theme
The reason is that rice shells write include lines into kitty.conf at runtime — iNiR (generate_terminal_configs.py) appends include current-theme.conf at EOF, i.e. after background_opacity 0.50 + dynamic_background_opacity yes, so the theme's opaque background turns the glass off. With the file symlinked into the repo those writes land in a tracked file, dirtying the tree every iris session. The generated wrapper absorbs them and is rewritten on the next switch, so they disappear.
| Rice | Theme source |
|---|---|
end4, ii-p3drovfx |
~/.local/state/quickshell/user/generated/terminal/kitty-theme.conf |
iris |
~/.config/kitty/theme.conf |
| everything else | common/kitty/default-theme.conf (tracked fallback palette) |
Edit the base colours in common/kitty/kitty.conf and the fallback palette in common/kitty/default-theme.conf. The rice → theme mapping lives in kitty_theme_source() in lib/common.sh; tests/kitty-theme-ownership.sh asserts the invariants.
git clone https://github.com/Schnee111/dotfiles.git ~/Projects/dotfiles
cd ~/Projects/dotfiles./install.sh --dry-run./install.shWhat the installer guarantees:
- existing real files are moved, never deleted, into
~/.config/dotfiles_backup_<timestamp>/with their path preserved; - every symlink is replaced atomically (
ln -sf+mv -T), so no reader ever sees a half-written config; - each touched path is recorded first, and a failed run rolls itself back;
- the managed-path list is written to
~/.local/state/dotfiles/manifest.tsvfordotfiles doctor; - the last 5 backup trees are kept, older ones are pruned.
dotfiles doctorAfter installing on a new account, adjust the user-data paths stored in the
shell configs (wallpaper, download and recording folders in
rices/*/illogical-impulse/config.json and rices/iris/inir/config.json);
they still point at the original author's home directory.
The battery-limit and power-profile toggles need write access to ASUS sysfs
nodes. sudo ./setup_power_permissions.sh creates an asus-power group, adds
the invoking user to it (log out and back in afterwards) and installs a udev
rule, a tmpfiles entry and a boot service that make those nodes 0664 root:asus-power. Only group members can change them. It is a one-time,
ASUS-only step and the installer never runs it. If an older version of the
script installed /etc/polkit-1/rules.d/90-asus-power.rules or made the nodes
world-writable (0666), re-running the script removes the rule and tightens
the nodes.
The kernel default of 128 inotify instances per user is nearly used up by a
normal session here: every kitty window, fish shell, dev server and Electron
app takes one or more. When it runs out, new file watchers stop working without
an error, and kded6 shows "Inotify Instance Capacity Low".
sudo ./setup_inotify_limit.sh installs common/sysctl/90-inotify.conf into
/etc/sysctl.d/ and applies it, raising fs.inotify.max_user_instances to
1024. It only changes a per-user resource limit, not who can access what. It is
a one-time step, and the installer never runs it. To undo it, delete
/etc/sysctl.d/90-inotify.conf and reboot.
bash tests/run.sh # syntax, lint and regression tests
bash tests/shell-lint.sh # just `bash -n` + ShellCheck on every tracked script
bash tests/network/check-pins.sh # pinned third-party shells still fetchable (needs network)
./install.sh --dry-run --rice <rice> # what a link pass would dotests/shell-lint.sh checks every tracked .sh, bin/* and .githooks/* (vendored
scripts under rices/*/hypr/hyprland/scripts/ are skipped). Enable the pre-commit hook,
which just runs tests/run.sh, with git config core.hooksPath .githooks.
CI (.github/workflows/ci.yml) runs the same test suite (so ShellCheck and bash -n
included) and a dry-run link pass for every rice on each push.
GPL-3.0. The Hyprland modules under rices/end4/ and rices/ii-p3drovfx/ derive
from end-4/dots-hyprland, which is GPL-3.0, so the
repository as a whole is distributed under the same terms.
Vendored files keep their own licenses: for example the Apache-2.0
show-loaded-ollama-models.sh under rices/*/hypr/hyprland/scripts/ai/ carries its license
text next to it. The shells the installer fetches (Quickshell configurations, Vitreus,
end4-pC, ...) are separate projects under their own licenses and are not part of this
repository.
- end-4/dots-hyprland for the base framework.
- Quickshell for the reactive desktop widget environment.
- Material You / Matugen for automatic color palette generation.







