{"id":51151057,"url":"https://github.com/abdulrahman1s/nix-config","last_synced_at":"2026-06-26T06:02:44.455Z","repository":{"id":364301530,"uuid":"1267139954","full_name":"abdulrahman1s/nix-config","owner":"abdulrahman1s","description":"My personal NixOS flake: a niri scrollable-tiling Wayland desktop with per-app bubblewrap sandboxing (NixPak), Limine dual-boot, CachyOS x86-64-v3 kernel, vicinae launcher, and agenix secrets.","archived":false,"fork":false,"pushed_at":"2026-06-21T14:46:22.000Z","size":11115,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-21T16:25:36.241Z","etag":null,"topics":["agenix","bubblewrap","cachyos","dotfiles","ghostty","limine","niri","nix","nix-flake","nixos","nixos-config","nixpak","nvidia","sandboxing","scrollable-tiling","vicinae","wayland","zsh"],"latest_commit_sha":null,"homepage":"","language":"Nix","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/abdulrahman1s.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"ko_fi":"abdulrahman1s"}},"created_at":"2026-06-12T08:59:42.000Z","updated_at":"2026-06-21T14:46:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/abdulrahman1s/nix-config","commit_stats":null,"previous_names":["abdulrahman1s/nix-config"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/abdulrahman1s/nix-config","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abdulrahman1s%2Fnix-config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abdulrahman1s%2Fnix-config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abdulrahman1s%2Fnix-config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abdulrahman1s%2Fnix-config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abdulrahman1s","download_url":"https://codeload.github.com/abdulrahman1s/nix-config/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abdulrahman1s%2Fnix-config/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34805073,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agenix","bubblewrap","cachyos","dotfiles","ghostty","limine","niri","nix","nix-flake","nixos","nixos-config","nixpak","nvidia","sandboxing","scrollable-tiling","vicinae","wayland","zsh"],"created_at":"2026-06-26T06:02:42.382Z","updated_at":"2026-06-26T06:02:44.429Z","avatar_url":"https://github.com/abdulrahman1s.png","language":"Nix","funding_links":["https://ko-fi.com/abdulrahman1s"],"categories":[],"sub_categories":[],"readme":"```\n _   _ _______   ______   _____\n| \\ | |_   _\\ \\ / / __ \\ / ____|\n|  \\| | | |  \\ V / |  | | (___\n| . ` | | |   \u003e \u003c| |  | |\\___ \\\n| |\\  |_| |_ / . \\ |__| |____) |\n|_| \\_|_____/_/ \\_\\____/|_____/\n\n        my personal NixOS config\n```\n\nThis is the NixOS configuration for my machine, `abdulrahman@nixos`. It's not a\nframework or a starter template - just the actual system I use every day, kept\nin a flake so I can rebuild it from scratch.\n\nA lot of it is ordinary NixOS. The parts I actually spent time on are the\n**niri desktop**, the **app sandboxing**, an **ephemeral root** that wipes back\nto blank on every boot, and some **boot / kernel / terminal tuning**, so that's\nmostly what these notes cover. If you run NixOS, there might be a couple of ideas\nworth borrowing.\n\n```text\nLimine ─▶ ly ─▶ niri ─▶ noctalia ─▶ apps, each in its own bubblewrap sandbox\n            │\n            ├─ helper scripts in config/niri/ wiring niri's IPC into behavior\n            ├─ vicinae launcher with Nix-built extensions\n            └─ a small LAN HTTP server I drive from iOS Shortcuts\n```\n\n## A look at it\n\nEverything is a dark [adw-gtk3](https://github.com/lassekongo83/adw-gtk3) theme\n(GTK 3 + 4), tweaked with `nwg-look`. Ghostty with the Pure prompt and fastfetch,\nand Nautilus:\n\n\u003cp\u003e\n  \u003cimg src=\"assets/screenshots/ghostty.png\" width=\"49%\" alt=\"Ghostty terminal running fastfetch\"\u003e\n  \u003cimg src=\"assets/screenshots/nautilus.png\" width=\"49%\" alt=\"Nautilus file manager\"\u003e\n\u003c/p\u003e\n\nThe vicinae launcher (`Mod+S`):\n\n\u003cimg src=\"assets/screenshots/vicinae.png\" width=\"72%\" alt=\"vicinae launcher\"\u003e\n\n## Boot: Limine, with Windows\n\n[`hardware-configuration.nix`](hardware-configuration.nix) uses\n[Limine](https://limine-bootloader.org/) instead of GRUB - EFI, a 4K graphical\nmenu in a Catppuccin Mocha palette, branding stripped, 3-second timeout.\n\nThe dual-boot entry is the bit I like. Rather than hardcoding a path to\n`bootmgfw.efi`, it uses Limine's `efi_boot_entry` protocol:\n\n```kdl\n/Windows\n    protocol: efi_boot_entry\n    entry: Windows Boot Manager\n```\n\nThat resolves the firmware's \"Windows Boot Manager\" EFI variable at boot time,\nso Windows keeps working even when an update moves its loader - there's no\nchainload path to maintain. The Windows NTFS volumes also `x-systemd.automount`\nunder `/mnt` (with `windows_names` + `nofail`), so I can browse them from NixOS\nwhen I need to.\n\n## Ephemeral root and home\n\nBoth the `root` and `home` btrfs subvolumes are rolled back to an empty snapshot\non every boot. Nothing survives a reboot unless it's on an explicit keep-list, so\nthe machine always comes up in the same clean state and any state that matters is\nsomething I consciously decided to keep.\n\n[`system/impermanence.nix`](system/impermanence.nix) is two pieces:\n\n- A `rollback` service that runs **in the initrd**, before the real root mounts.\n  It deletes `root` and `home` (and any nested subvolumes) and restores them from\n  pristine `*-blank` snapshots:\n\n  ```nix\n  for sub in root home; do\n    btrfs subvolume delete \"/btrfs-tmp/$sub\"\n    btrfs subvolume snapshot \"/btrfs-tmp/$sub-blank\" \"/btrfs-tmp/$sub\"\n  done\n  ```\n\n- [nix-community/impermanence](https://github.com/nix-community/impermanence)\n  bind-mounts the keep-list back out of a third subvolume, `/persist`, which is\n  never wiped. System state (`/var/lib/nixos` for stable uid/gid maps,\n  NetworkManager connections, Bluetooth pairings, Docker, the journal) and a\n  hand-picked set of `$HOME` paths (the flake repo itself, `~/.ssh`, the browser\n  profile, shell history, app logins) live there.\n\nTwo things this buys, both nice: the keep-list *is* the documentation of what\nstate the system actually depends on, and because `/persist` is mounted in the\ninitrd (`neededForBoot`) the agenix identity at `/persist/root/.ssh/id_ed25519`\nis readable early enough to decrypt secrets before any service wants them.\n\n## The niri desktop\n\n[niri](https://github.com/YaLTeR/niri) is a scrollable-tiling compositor -\nwindows live on an infinite horizontal strip instead of a grid. Out of the box\nit's pretty minimal, so most of what makes it usable for me lives in the helper\nscripts under [`config/niri/`](config/niri/), which mostly just wrap `niri msg`\nIPC into behavior I wanted.\n\nA few of the keybinds:\n\n| Keys | What it does |\n| --- | --- |\n| `Mod+X` / `Mod+Shift+X` | New terminal, focused on *that* window; Shift cycles existing ones |\n| `Mod+B` | Raise Brave - press again to jump back to where you were |\n| `Mod+M` / `Mod+Y` | YouTube Music as a small floating PWA / YouTube maximized, raise-or-launch |\n| `Mod+Tab` | Alt-tab, but only within the focused app's windows |\n| `Mod+F` | Maximize if tiled, fullscreen if floating |\n| `Mod+D` / `Mod+Shift+D` | Peek the floating window into a corner / swap the two corners |\n| `Mod+scroll` | Move between columns (wraps; also works over the floating layer) |\n| `Shift+scroll` / `Mod+Shift+scroll` | Switch workspace / move the column to the next one |\n| `Win+Space` | Toggle keyboard US ⇄ Arabic |\n\nEvery bind has a `hotkey-overlay-title`, so `Mod+Shift+/` shows a readable cheat\nsheet.\n\nThe scripts behind them:\n\n- **[`niri-launch-or-focus`](config/niri/niri-launch-or-focus)** - launch the\n  app if it's closed, raise it if it's open, cycle if there are several. When it\n  launches it polls niri's window list briefly so it focuses the *new* window\n  instead of racing the compositor. `--toggle-previous` makes the key bounce you\n  back to the previous window if the app's already focused.\n\n- **[`peek-floating-window`](config/niri/peek-floating-window)** - the one I'm\n  most happy with. It stashes floating windows into the bottom corners as small\n  slivers, two slots, LIFO restore. It re-peeks the window you just put away\n  before popping a different corner, can be driven from hot corners via\n  `waycorner`, swaps corners, and bumps the older slot when both are full. It\n  tags peeked windows with niri's urgency flag and [`rules.kdl`](config/niri/rules.kdl)\n  styles those (dimmed, rounded, no shadow), so urgency doubles as the \"is\n  peeked\" marker. It moves windows with relative deltas to work around a couple\n  of niri CLI quirks - the comments explain why.\n\n- **[`niri-dynamic-float`](modules/niri-dynamic-float.nix)** - niri only checks\n  `open-floating` when a window opens, so windows that change their title later\n  (Brave DevTools detaching, a Bitwarden popup) don't get caught. This is a\n  small systemd service that watches niri's event stream and floats them after\n  the fact. Rules are declared in Nix and baked into the\n  [Python](modules/niri-dynamic-float.py) at build time.\n\n- The smaller ones: [`cycle-same-app`](config/niri/cycle-same-app) (app-scoped\n  alt-tab), [`focus-column`](config/niri/focus-column) (runs `focus-tiling`\n  first so scroll doesn't no-op over a float),\n  [`maximize-window`](config/niri/maximize-window) (float vs. tile), and\n  [`niri-launch-or-focus-webapp`](config/niri/niri-launch-or-focus-webapp) for\n  treating Brave PWAs as real apps.\n\n[`rules.kdl`](config/niri/rules.kdl) also gives any window that's currently a\nscreencast target a red focus ring, so I don't share the wrong one into a call.\nWorkspaces are named (`main` for browsers, `dev` for editors/terminals) with\napps routed to them, animations are critically-damped springs, and there's a\n4K@240 + 1080p@165 dual-monitor setup.\n\n## Sandboxed apps\n\nThis is the most-edited part of the repo. GUI apps go through\n[`mkSandboxed`](sandboxed-apps/nixpak/default.nix), a wrapper over\n[NixPak](https://github.com/nixpak/nixpak)/bubblewrap. Default is deny: each app\ngets a private `/tmp`, a zeroed `/etc/machine-id`, TIOCSTI protection,\ndie-with-parent - and then only the paths, devices, and D-Bus names it actually\nneeds.\n\nThings worth noting:\n\n- **Offline unless asked.** bubblewrap defaults network to `true` upstream, so\n  this sets it `false` (with `mkDefault`) and the `network` preset opts back in.\n- **Presets** for `wayland`, `gpu`, `audio`, `usb`, `webcam`, `u2f`, `portals`,\n  `secrets`, etc. - each a tight set of binds. The `gpu` one only exposes the\n  `/sys` subtrees the drivers read, not all of `/sys`.\n- **Per-launch path binding** ([`path-binding.nix`](sandboxed-apps/nixpak/path-binding.nix)):\n  open a file with a sandboxed app and it binds just that path (or its parent,\n  for sidecar files), up to 16 paths per launch, and refuses to bind `/` or\n  `$HOME`. There are [tests](sandboxed-apps/test-pathbinding.nix) for it under\n  `nix flake check`.\n- **Chromium needs `--unshare-pid` dropped**, not its sandbox disabled -\n  `sharePid` post-processes the bwrap args to fix the \"profile in use\" lock\n  instead of reaching for `--no-sandbox`.\n\nBrave (a few channels), Discord, MPV, Minecraft, and the UMU/Proton launchers\nall use it. The browser is the tightest one - it sees its own profile and\n`~/Downloads`, not all of `$HOME`.\n\n## ly and vicinae\n\n[ly](https://github.com/fairyglade/ly) is the display manager - a small TUI\ngreeter on the framebuffer, no Xorg login stack. Both outputs are forced to a\ncommon 4K boot mode so it centers properly.\n\n[vicinae](https://github.com/vicinaehq/vicinae) is the launcher (`Mod+S`) -\nRaycast-style, also handling clipboard history (`Mod+V`) and an emoji picker\n(`Mod+E`). [`modules/vicinae.nix`](modules/vicinae.nix) builds its extensions\nthrough Nix (including a `mkRayCastExtension` that sparse-checks one extension\nout of Raycast's monorepo) and symlinks them into place with tmpfiles.\n\n## Kernel and tuning\n\nThe kernel is the\n[CachyOS one built for `x86-64-v3`](https://github.com/xddxdd/nix-cachyos-kernel)\n(`linuxPackages-cachyos-latest-x86_64-v3`), via a binary-cached overlay. I use\nit for the BORE scheduler and the desktop-latency config, and the `v3` build\nlets the compiler assume AVX2/FMA, which stock nixpkgs doesn't.\n\n[`system/optimization.nix`](system/optimization.nix) has the rest, each setting\ncommented with why it's there:\n\n- zram (zstd, 75% of RAM) instead of disk swap, so `vm.swappiness` is set high\n  (100) and swap readahead off - the opposite of disk-swap advice, on purpose.\n- `/tmp` on tmpfs, BBR + `fq` for networking, `vm.max_map_count` maxed for\n  Proton/Wine, raised inotify limits for IDEs and Docker.\n- `ananicy-cpp` with the CachyOS rules, `earlyoom` to avoid kernel OOM hangs,\n  and a niri-specific NVIDIA profile ([`graphics.nix`](system/graphics.nix))\n  that caps the free-buffer-pool reuse to lower compositor VRAM use.\n\nGC, store optimization, btrfs scrub, and fstrim are on timers.\n\n## Terminal\n\nThe shell ([`terminal/shell.nix`](terminal/shell.nix) +\n[`config/zsh/common.zsh`](config/zsh/common.zsh)) is zsh with vi-mode, fzf\n(`Ctrl-R`/`Ctrl-T`/`Alt-C`, `**\u003cTab\u003e`), fzf-tab, history-substring-search, and\nzoxide bound as `cd`. History is 100k, de-duplicated and shared, and\nleading-space commands aren't saved. Ghostty windows remember the last directory\nI was in.\n\nSome of the functions I use most:\n\n| Command | What it does |\n| --- | --- |\n| `copy` / `paste` | Clipboard. `copy \u003cfile\u003e` stages the file into `~/Downloads` with an ASCII-safe name so Brave's sandbox can read it and Chromium doesn't choke on odd codepoints |\n| `mp3` / `mp4` | yt-dlp wrappers with quality presets; saves the file and copies it to the clipboard |\n| `record` | `gpu-screen-recorder` with an fzf picker for container/codec/quality/fps/audio (or `-d` for defaults) |\n| `vram` | Per-process VRAM usage with a bar graph |\n| `tunnel \u003cport\u003e` | Cloudflare quick-tunnel, prints just the URL |\n| `upload` / `shorten` / `gist` | Share a file / shorten a URL / make a gist - each copies the result back |\n| `ex` | Extract just about any archive format |\n\nPlus the usual aliases: `rebuild`, `ls` → eza, `:q` to exit, `please` for sudo.\n\n## Remote control from my phone\n\n[`modules/remote-control.nix`](modules/remote-control.nix) runs a small Python\nHTTP server ([source](modules/remote-control-server.py)) bound only to the wired\nLAN address, behind a bearer token generated to `/etc/remote-control.token` on\nfirst boot. It drops to my user and finds the live wayland/niri sockets, so it\ncan reach into the running session. Endpoints: lock / unlock (unlock also wakes\nthe monitors), session status, read/write the clipboard, a screenshot, and\nshutdown / reboot. I wire these into iOS Shortcuts to lock the desktop, push a\nlink to its clipboard, or shut it down from my phone.\n\n## Secrets\n\n[agenix](https://github.com/ryantm/agenix). Encrypted `.age` files in\n[`secrets/`](secrets/) (safe to commit), recipient keys in\n[`secrets/secrets.nix`](secrets/secrets.nix). Each is decrypted at activation to\n`/run/agenix/\u003cname\u003e` and handed to services via systemd `LoadCredential`, not an\nenv var. Tracked: Cloudflare Tunnel token, JuiceFS credentials/encryption key,\nDokploy DB password/auth secret, NextDNS upstream.\n\n## Validation\n\nNone of these change the running system:\n\n```bash\nnix-instantiate --parse configuration.nix\nnix build .#nixosConfigurations.default.config.system.build.toplevel --no-link\nnix flake check\n\nnix build .#nixosConfigurations.default.config.system.build.toplevel --out-link /tmp/nixos-pending\nnvd diff /run/current-system /tmp/nixos-pending\n```\n\nSwitching is separate:\n\n```bash\nsudo nixos-rebuild switch --flake .#default\n```\n\n## Using it\n\nIt's one specific machine, so it won't drop in unchanged - you'd start at\n[`specialArgs.nix`](specialArgs.nix), then redo the hardware config, disk UUIDs,\nLAN address, GPU/motherboard modules, and the monitor names in the niri config.\nMostly it's here as something to read and pick from.\n\n## License\n\n[MIT](LICENSE). Third-party packages keep their own licenses.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabdulrahman1s%2Fnix-config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabdulrahman1s%2Fnix-config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabdulrahman1s%2Fnix-config/lists"}