my over complex system configurations dotfiles.isabelroses.com/
nixos nix flake dotfiles linux

Compare changes

Choose any two refs to compare.

+3
.gitignore
··· 4 # Ignore nixos build outputs 5 result* 6 .direnv/
··· 4 # Ignore nixos build outputs 5 result* 6 .direnv/ 7 + 8 + # deploy script 9 + .deploy-summary
+1 -20
README.md
··· 10 11 <br /> 12 13 - ![Preview image](./docs/src/images/main.png) 14 - 15 - <details> 16 - <summary>more previews</summary> 17 - 18 - <p align="center"> 19 - light mode 20 - <img src="./docs/src/images/lightmode.png" width="800px" /> 21 - </p> 22 - 23 - <p align="center"> 24 - wezterm + chromium 25 - <img src="./docs/src/images/blur.png" width="800px" /> 26 - </p> 27 - 28 - <p align="center"> 29 - neovim 30 - <img src="./docs/src/images/nvim.png" width="800px" /> 31 - </p> 32 - </details> 33 34 ![image of my flakes topology](./docs/src/images/topology.png) 35
··· 10 11 <br /> 12 13 + ![Preview image](./docs/src/images/main.webp) 14 15 ![image of my flakes topology](./docs/src/images/topology.png) 16
+2 -5
docs/book.toml
··· 1 [book] 2 authors = ["isabel roses"] 3 language = "en" 4 - multilingual = false 5 src = "src" 6 title = "dotfiles" 7 ··· 13 default-theme = "latte" 14 preferred-dark-theme = "mocha" 15 git-repository-url = "https://github.com/isabelroses/dotfiles" 16 - git-repository-icon = "fa-code-fork" 17 cname = "dotfiles.isabelroses.com" 18 - additional-css = ["./theme/catppuccin.css", "./theme/catppuccin-alerts.css"] 19 - 20 - [preprocessor.alerts]
··· 1 [book] 2 authors = ["isabel roses"] 3 language = "en" 4 src = "src" 5 title = "dotfiles" 6 ··· 12 default-theme = "latte" 13 preferred-dark-theme = "mocha" 14 git-repository-url = "https://github.com/isabelroses/dotfiles" 15 + # git-repository-icon = "fa-code-fork" 16 cname = "dotfiles.isabelroses.com" 17 + additional-css = ["./theme/catppuccin.css"]
+8
docs/src/README.md
··· 8 <img alt="nixos-unstable" src="https://img.shields.io/badge/NixOS-unstable-blue.svg?style=for-the-badge&labelColor=303446&logo=NixOS&logoColor=white&color=91D7E3" /> 9 </div> 10 11 ### Foreword 12 13 This repository contains my **personal** configuration for my systems, so its really important that you know it's **personal** and not everything will fit your needs. ··· 22 - Sensible defaults, so you can get started quickly 23 - Docs kind of 24 - [Catppuccin](https://github.com/catppuccin/catppuccin) everywhere.
··· 8 <img alt="nixos-unstable" src="https://img.shields.io/badge/NixOS-unstable-blue.svg?style=for-the-badge&labelColor=303446&logo=NixOS&logoColor=white&color=91D7E3" /> 9 </div> 10 11 + <br /> 12 + 13 + ![Preview image](./images/main.webp) 14 + 15 + ![image of my flakes topology](./images/topology.png) 16 + 17 ### Foreword 18 19 This repository contains my **personal** configuration for my systems, so its really important that you know it's **personal** and not everything will fit your needs. ··· 28 - Sensible defaults, so you can get started quickly 29 - Docs kind of 30 - [Catppuccin](https://github.com/catppuccin/catppuccin) everywhere. 31 + 32 + [![Star History Chart](https://api.star-history.com/svg?repos=isabelroses/dotfiles&type=Date)](https://star-history.com/#isabelroses/dotfiles&Date)
-1
docs/src/SUMMARY.md
··· 35 # Misc 36 37 - [mirrors](misc/mirrors.md) 38 - - [previews](misc/previews.md)
··· 35 # Misc 36 37 - [mirrors](misc/mirrors.md)
+2 -1
docs/src/further-reading.md
··· 27 - [nixos-hardware](https://github.com/NixOS/nixos-hardware) 28 - [getchoo/flake](https://github.com/getchoo/flake) 29 - [uncenter/flake](https://github.com/uncenter/flake) 30 31 # People 32 33 Thanks to all these people otherwise this config and I would be lost. 34 35 - - [comfysage](https://github.com/comfysage), for making my day that bit brighter and occasionally putting me on your back 36 - [getchoo](https://github.com/getchoo), for making catppucin/nix and helping a ton 37 - [nullishamy](https://github.com/nullishamy), for tricking me into using NixOS 38 - [nekowinston](https://github.com/nekowinston), for fixing half my problems ··· 40 - [nyxkrage](https://github.com/nyxkrage), for being there to answer my questions 41 - [NotAShelf](https://github.com/notashelf), lots of valuable information 42 - [Minion3665](https://github.com/Minion3665), for making me write better docs 43 - [Thorn](https://git.avery.garden/thorn), picrewnix????
··· 27 - [nixos-hardware](https://github.com/NixOS/nixos-hardware) 28 - [getchoo/flake](https://github.com/getchoo/flake) 29 - [uncenter/flake](https://github.com/uncenter/flake) 30 + - [lilyinstarlight/foosteros](https://github.com/lilyinstarlight/foosteros) 31 32 # People 33 34 Thanks to all these people otherwise this config and I would be lost. 35 36 - [getchoo](https://github.com/getchoo), for making catppucin/nix and helping a ton 37 - [nullishamy](https://github.com/nullishamy), for tricking me into using NixOS 38 - [nekowinston](https://github.com/nekowinston), for fixing half my problems ··· 40 - [nyxkrage](https://github.com/nyxkrage), for being there to answer my questions 41 - [NotAShelf](https://github.com/notashelf), lots of valuable information 42 - [Minion3665](https://github.com/Minion3665), for making me write better docs 43 + - [comfysage](https://github.com/comfysage), for the help with neovim 44 - [Thorn](https://git.avery.garden/thorn), picrewnix????
docs/src/images/blur.png

This is a binary file and will not be displayed.

docs/src/images/blur.webp

This is a binary file and will not be displayed.

docs/src/images/lightmode.png

This is a binary file and will not be displayed.

docs/src/images/lightmode.webp

This is a binary file and will not be displayed.

docs/src/images/main.png

This is a binary file and will not be displayed.

docs/src/images/main.webp

This is a binary file and will not be displayed.

docs/src/images/nvim.png

This is a binary file and will not be displayed.

docs/src/images/nvim.webp

This is a binary file and will not be displayed.

-5
docs/src/misc/previews.md
··· 1 - ![Preview image](/images/main.png) 2 - ![images/lightmode.png](/images/lightmode.png) 3 - ![images/blur](/images/blur.png) 4 - ![images/nvim](/images/nvim.png) 5 - ![image of my flakes topology](/images/topology.png)
···
+51 -50
flake.lock
··· 24 ] 25 }, 26 "locked": { 27 - "lastModified": 1765990358, 28 - "narHash": "sha256-l8x0gU8mnYaGMl+gWrsSHKBJlZWD8KXJfHTkRlFiPI0=", 29 "owner": "catppuccin", 30 "repo": "nix", 31 - "rev": "de1b60ca45a578f59f7d84c8d338b346017b2161", 32 "type": "github" 33 }, 34 "original": { ··· 39 }, 40 "crane": { 41 "locked": { 42 - "lastModified": 1766774972, 43 - "narHash": "sha256-8qxEFpj4dVmIuPn9j9z6NTbU+hrcGjBOvaxTzre5HmM=", 44 "owner": "ipetkov", 45 "repo": "crane", 46 - "rev": "01bc1d404a51a0a07e9d8759cd50a7903e218c82", 47 "type": "github" 48 }, 49 "original": { ··· 59 ] 60 }, 61 "locked": { 62 - "lastModified": 1767028240, 63 - "narHash": "sha256-0/fLUqwJ4Z774muguUyn5t8AQ6wyxlNbHexpje+5hRo=", 64 - "owner": "nix-darwin", 65 "repo": "nix-darwin", 66 - "rev": "c31afa6e76da9bbc7c9295e39c7de9fca1071ea1", 67 "type": "github" 68 }, 69 "original": { 70 - "owner": "nix-darwin", 71 "repo": "nix-darwin", 72 "type": "github" 73 } ··· 90 "flake-compat": { 91 "flake": false, 92 "locked": { 93 - "lastModified": 1761588595, 94 - "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", 95 - "owner": "edolstra", 96 "repo": "flake-compat", 97 - "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", 98 "type": "github" 99 }, 100 "original": { 101 - "owner": "edolstra", 102 "repo": "flake-compat", 103 "type": "github" 104 } ··· 110 ] 111 }, 112 "locked": { 113 - "lastModified": 1765835352, 114 - "narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=", 115 "owner": "hercules-ci", 116 "repo": "flake-parts", 117 - "rev": "a34fae9c08a15ad73f295041fec82323541400a9", 118 "type": "github" 119 }, 120 "original": { ··· 173 ] 174 }, 175 "locked": { 176 - "lastModified": 1767104570, 177 - "narHash": "sha256-GKgwu5//R+cLdKysZjGqvUEEOGXXLdt93sNXeb2M/Lk=", 178 "owner": "nix-community", 179 "repo": "home-manager", 180 - "rev": "e4e78a2cbeaddd07ab7238971b16468cc1d14daf", 181 "type": "github" 182 }, 183 "original": { ··· 211 ] 212 }, 213 "locked": { 214 - "lastModified": 1767230498, 215 - "narHash": "sha256-eAENy8+m5KfcD/3HCDvF8e0BLlBSd5Se3snzGkaInoI=", 216 "owner": "isabelroses", 217 "repo": "izlix", 218 - "rev": "d57e291079ef70c0f77ad3200b5b7db586e26922", 219 "type": "github" 220 }, 221 "original": { ··· 232 ] 233 }, 234 "locked": { 235 - "lastModified": 1767151551, 236 - "narHash": "sha256-OevEstP+kG3nTWYi41n+PIuscIVBXDffzIQ2ew10uL8=", 237 "owner": "isabelroses", 238 "repo": "nvim", 239 - "rev": "d87c265a3ee73dfe6eddc63c0e3cda91249d3c31", 240 "type": "github" 241 }, 242 "original": { ··· 255 "rust-overlay": "rust-overlay" 256 }, 257 "locked": { 258 - "lastModified": 1767013031, 259 - "narHash": "sha256-p8ANXBakAtfX/aEhLbU6w0tuQe3nrBvLdHbKirJP7ug=", 260 "owner": "nix-community", 261 "repo": "lanzaboote", 262 - "rev": "c2a82339373daee8cbbcad5f51f22ae6b71069e0", 263 "type": "github" 264 }, 265 "original": { ··· 291 }, 292 "nixpkgs": { 293 "locked": { 294 - "lastModified": 1767151656, 295 - "narHash": "sha256-enBX1DxL9cttnrJ+rzOds/GXWqSj/vcB97+X9mgiJho=", 296 - "rev": "f665af0cdb70ed27e1bd8f9fdfecaf451260fc55", 297 "type": "tarball", 298 - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre920165.f665af0cdb70/nixexprs.tar.xz?lastModified=1767151656&rev=f665af0cdb70ed27e1bd8f9fdfecaf451260fc55" 299 }, 300 "original": { 301 "type": "tarball", ··· 312 ] 313 }, 314 "locked": { 315 - "lastModified": 1765911976, 316 - "narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=", 317 "owner": "cachix", 318 "repo": "pre-commit-hooks.nix", 319 - "rev": "b68b780b69702a090c8bb1b973bab13756cc7a27", 320 "type": "github" 321 }, 322 "original": { ··· 352 ] 353 }, 354 "locked": { 355 - "lastModified": 1766976750, 356 - "narHash": "sha256-w+o3AIBI56tzfMJRqRXg9tSXnpQRN5hAT15o2t9rxYw=", 357 "owner": "oxalica", 358 "repo": "rust-overlay", 359 - "rev": "9fe44e7f05b734a64a01f92fc51ad064fb0a884f", 360 "type": "github" 361 }, 362 "original": { ··· 395 ] 396 }, 397 "locked": { 398 - "lastModified": 1766896955, 399 - "narHash": "sha256-BbAUnNjaBmfR7Mvho9BN0RfvDi5fpP19wd/Hs6DMX8k=", 400 "owner": "Mic92", 401 "repo": "sops-nix", 402 - "rev": "861c32b27cce26c4bb828dfd21bd23df0dba7df2", 403 "type": "github" 404 }, 405 "original": { ··· 417 "systems": "systems" 418 }, 419 "locked": { 420 - "lastModified": 1767195736, 421 - "narHash": "sha256-0xvPSbhIGeJzsJXNTkgJ3PjwdVItKm85wzYKA9NmSzI=", 422 "owner": "Gerg-L", 423 "repo": "spicetify-nix", 424 - "rev": "465adc0ab6ff0c4b9b1db1c6e7fd7eeb553b3261", 425 "type": "github" 426 }, 427 "original": { ··· 452 ] 453 }, 454 "locked": { 455 - "lastModified": 1767229092, 456 - "narHash": "sha256-HFi+WvYK7mLVL6Cka2m9wKpy9lt2K5wPQ+oN/bmuc0Q=", 457 "owner": "tgirlcloud", 458 "repo": "pkgs", 459 - "rev": "1d1d8e13541eaf85492e758be2a032fd20412a33", 460 "type": "github" 461 }, 462 "original": {
··· 24 ] 25 }, 26 "locked": { 27 + "lastModified": 1767967164, 28 + "narHash": "sha256-Cx4VETh9dGoQYDtWhre7g66d7SAr+h1h6f+SSHxVrck=", 29 "owner": "catppuccin", 30 "repo": "nix", 31 + "rev": "e973584280e3b0e1d5b5a1a5e9948dc222c54af7", 32 "type": "github" 33 }, 34 "original": { ··· 39 }, 40 "crane": { 41 "locked": { 42 + "lastModified": 1767461147, 43 + "narHash": "sha256-TH/xTeq/RI+DOzo+c+4F431eVuBpYVwQwBxzURe7kcI=", 44 "owner": "ipetkov", 45 "repo": "crane", 46 + "rev": "7d59256814085fd9666a2ae3e774dc5ee216b630", 47 "type": "github" 48 }, 49 "original": { ··· 59 ] 60 }, 61 "locked": { 62 + "lastModified": 1767469770, 63 + "narHash": "sha256-Rv1kumaBqlqCvLjVYuzdA38btSyjSRvXZ8UtwalGcHo=", 64 + "owner": "isabelroses", 65 "repo": "nix-darwin", 66 + "rev": "1d46ab42afd10adf9751ab6cfba6b426bfb17e33", 67 "type": "github" 68 }, 69 "original": { 70 + "owner": "isabelroses", 71 + "ref": "darwin-rebuild", 72 "repo": "nix-darwin", 73 "type": "github" 74 } ··· 91 "flake-compat": { 92 "flake": false, 93 "locked": { 94 + "lastModified": 1767039857, 95 + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", 96 + "owner": "NixOS", 97 "repo": "flake-compat", 98 + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", 99 "type": "github" 100 }, 101 "original": { 102 + "owner": "NixOS", 103 "repo": "flake-compat", 104 "type": "github" 105 } ··· 111 ] 112 }, 113 "locked": { 114 + "lastModified": 1767609335, 115 + "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", 116 "owner": "hercules-ci", 117 "repo": "flake-parts", 118 + "rev": "250481aafeb741edfe23d29195671c19b36b6dca", 119 "type": "github" 120 }, 121 "original": { ··· 174 ] 175 }, 176 "locked": { 177 + "lastModified": 1768068402, 178 + "narHash": "sha256-bAXnnJZKJiF7Xr6eNW6+PhBf1lg2P1aFUO9+xgWkXfA=", 179 "owner": "nix-community", 180 "repo": "home-manager", 181 + "rev": "8bc5473b6bc2b6e1529a9c4040411e1199c43b4c", 182 "type": "github" 183 }, 184 "original": { ··· 212 ] 213 }, 214 "locked": { 215 + "lastModified": 1768094542, 216 + "narHash": "sha256-8+iFjse2gJSXmWa9wX1nmk1Fv2qcYqu3nvCM7wnm7Mc=", 217 "owner": "isabelroses", 218 "repo": "izlix", 219 + "rev": "32cf9e0aa7fb8dd024512ba7e7dccf599be8e320", 220 "type": "github" 221 }, 222 "original": { ··· 233 ] 234 }, 235 "locked": { 236 + "lastModified": 1767756498, 237 + "narHash": "sha256-a7SYG/6uUeeyfg1Xiq/sXpRb2RXs+kQ0GKrKMVlMw+4=", 238 "owner": "isabelroses", 239 "repo": "nvim", 240 + "rev": "725ef38a0ea9b5bd8218aad272bd7458d430b2be", 241 "type": "github" 242 }, 243 "original": { ··· 256 "rust-overlay": "rust-overlay" 257 }, 258 "locked": { 259 + "lastModified": 1767697030, 260 + "narHash": "sha256-0iVZ99H3kR5h6Lhw8kDDuUc5C/k6iismeWgCS1qWTQ4=", 261 "owner": "nix-community", 262 "repo": "lanzaboote", 263 + "rev": "657469e8f036334db768daaf7732b1174676054b", 264 "type": "github" 265 }, 266 "original": { ··· 292 }, 293 "nixpkgs": { 294 "locked": { 295 + "lastModified": 1768032153, 296 + "narHash": "sha256-zvxtwlM8ZlulmZKyYCQAPpkm5dngSEnnHjmjV7Teloc=", 297 + "rev": "3146c6aa9995e7351a398e17470e15305e6e18ff", 298 "type": "tarball", 299 + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre925418.3146c6aa9995/nixexprs.tar.xz?lastModified=1768032153&rev=3146c6aa9995e7351a398e17470e15305e6e18ff" 300 }, 301 "original": { 302 "type": "tarball", ··· 313 ] 314 }, 315 "locked": { 316 + "lastModified": 1767281941, 317 + "narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=", 318 "owner": "cachix", 319 "repo": "pre-commit-hooks.nix", 320 + "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa", 321 "type": "github" 322 }, 323 "original": { ··· 353 ] 354 }, 355 "locked": { 356 + "lastModified": 1767495280, 357 + "narHash": "sha256-hEEgtE/RSRigw8xscchGymf/t1nluZwTfru4QF6O1CQ=", 358 "owner": "oxalica", 359 "repo": "rust-overlay", 360 + "rev": "cb24c5cc207ba8e9a4ce245eedd2d37c3a988bc1", 361 "type": "github" 362 }, 363 "original": { ··· 396 ] 397 }, 398 "locked": { 399 + "lastModified": 1768034555, 400 + "narHash": "sha256-M0jmN1VSfOIsP9QGY8gPMB7dhHKpcqI7MfB3/T02HXg=", 401 "owner": "Mic92", 402 "repo": "sops-nix", 403 + "rev": "aca3a51e38a02dc6f69e11af1192362c974d4e54", 404 "type": "github" 405 }, 406 "original": { ··· 418 "systems": "systems" 419 }, 420 "locked": { 421 + "lastModified": 1767502559, 422 + "narHash": "sha256-om0IPjW850vhhIrNZ5tiXjsYuqyoI44IdE+I9AwZ96I=", 423 "owner": "Gerg-L", 424 "repo": "spicetify-nix", 425 + "rev": "806c1fdeb7af3e013215d14f5d9f06685fa6650f", 426 "type": "github" 427 }, 428 "original": { ··· 453 ] 454 }, 455 "locked": { 456 + "lastModified": 1768093180, 457 + "narHash": "sha256-b3yaLToFvZc9vEcg12qevVNzgP/nKmQqXvuX805M2l0=", 458 "owner": "tgirlcloud", 459 "repo": "pkgs", 460 + "rev": "fff1718059bcb18af327003018e012387dd3008b", 461 "type": "github" 462 }, 463 "original": {
+2 -1
flake.nix
··· 25 # improved support for darwin 26 darwin = { 27 type = "github"; 28 - owner = "nix-darwin"; 29 repo = "nix-darwin"; 30 inputs.nixpkgs.follows = "nixpkgs"; 31 }; 32
··· 25 # improved support for darwin 26 darwin = { 27 type = "github"; 28 + owner = "isabelroses"; 29 repo = "nix-darwin"; 30 + ref = "darwin-rebuild"; 31 inputs.nixpkgs.follows = "nixpkgs"; 32 }; 33
-5
home/isabel/cli/shell/shellAlias.nix
··· 4 mkdir = "mkdir -pv"; # always create pearent directory 5 df = "df -h"; # human readblity 6 rs = "systemctl reboot"; 7 - sysctl = "sudo systemctl"; 8 jctl = "journalctl -p 3 -xb"; # get error messages from journalctl 9 lg = "lazygit"; 10 11 zzzpl = "cd ~/.local/share/zzz ; git pull ; git push ; cd -"; 12 zzzbk = "cd ~/.local/share/zzz ; git add . ; git commit -m 'chore: sync changes' ; git push ; cd -"; 13 - 14 - # Remap docker to podman 15 - docker = "podman"; 16 - docker-compose = "podman-compose"; 17 }; 18 }
··· 4 mkdir = "mkdir -pv"; # always create pearent directory 5 df = "df -h"; # human readblity 6 rs = "systemctl reboot"; 7 jctl = "journalctl -p 3 -xb"; # get error messages from journalctl 8 lg = "lazygit"; 9 10 zzzpl = "cd ~/.local/share/zzz ; git pull ; git push ; cd -"; 11 zzzbk = "cd ~/.local/share/zzz ; git add . ; git commit -m 'chore: sync changes' ; git push ; cd -"; 12 }; 13 }
+8 -1
home/isabel/gui/chromium.nix
··· 1 { 2 lib, 3 pkgs, 4 ... 5 }: 6 let ··· 22 "jghecgabfgfdldnmbfkhmffcabddioke" # Volume Master 23 "ndcooeababalnlpkfedmmbbbgkljhpjf" # scriptcat 24 "ephjcajbkgplkjmelpglennepbpmdpjg" # ff2mpv 25 ]; 26 27 package = pkgs.chromium.override { ··· 187 }; 188 }; 189 190 home.file = mkIf pkgs.stdenv.hostPlatform.isDarwin { 191 "Library/Application Support/Chromium/NativeMessagingHosts/ff2mpv.json".source = 192 - "${pkgs.ff2mpv}/etc/chromium/native-messaging-hosts/ff2mpv.json"; 193 }; 194 }
··· 1 { 2 lib, 3 pkgs, 4 + config, 5 ... 6 }: 7 let ··· 23 "jghecgabfgfdldnmbfkhmffcabddioke" # Volume Master 24 "ndcooeababalnlpkfedmmbbbgkljhpjf" # scriptcat 25 "ephjcajbkgplkjmelpglennepbpmdpjg" # ff2mpv 26 + "kpmjjdhbcfebfjgdnpjagcndoelnidfj" # Control Panel for Twitter 27 ]; 28 29 package = pkgs.chromium.override { ··· 189 }; 190 }; 191 192 + xdg.configFile = mkIf (pkgs.stdenv.hostPlatform.isLinux && config.programs.chromium.enable) { 193 + "chromium/NativeMessagingHosts/ff2mpv.json".source = 194 + "${pkgs.ff2mpv-rust}/etc/chromium/native-messaging-hosts/ff2mpv.json"; 195 + }; 196 + 197 home.file = mkIf pkgs.stdenv.hostPlatform.isDarwin { 198 "Library/Application Support/Chromium/NativeMessagingHosts/ff2mpv.json".source = 199 + "${pkgs.ff2mpv-rust}/etc/chromium/native-messaging-hosts/ff2mpv.json"; 200 }; 201 }
+17 -31
home/isabel/gui/hyprland.nix
··· 215 }; 216 217 layerrule = [ 218 - "blur,vicinae" 219 - "ignorealpha 0, vicinae" 220 - "noanim, vicinae" 221 ]; 222 223 general = { ··· 225 gaps_out = 8; 226 gaps_workspaces = 0; 227 border_size = 2; 228 - no_border_on_floating = true; 229 230 "col.active_border" = "$pink"; 231 "col.inactive_border" = "$surface1"; ··· 294 disable_autoreload = true; # autoreload is unnecessary on nixos, because the config is readonly anyway 295 }; 296 297 - windowrulev2 = [ 298 - "float, title:^(nm-connection-editor)$" 299 - "float, title:^(Network)$" 300 - "float, title:^(xdg-desktop-portal-gtk)$" 301 - "float, class:gay.vaskel.soteria" 302 - "float, title:^(Picture-in-Picture)$" 303 - "float, class:^(download)$" 304 - 305 - "center(1), initialTitle:(Open Files)" 306 - "float, initialTitle:(Open Files)" 307 - "size 40% 60%, initialTitle:(Open Files)" 308 - 309 - "center(1), class:.blueman-manager-wrapped" 310 - "float, class:.blueman-manager-wrapped" 311 - "size 40% 60%, class:.blueman-manager-wrapped" 312 - 313 - "center(1), class:com.saivert.pwvucontrol" 314 - "float, class:com.saivert.pwvucontrol" 315 - "size 40% 60%, class:com.saivert.pwvucontrol" 316 317 - # we can't just use the tag because we want to capture the popup window 318 - "float, title:Bitwarden" 319 - "size 800 600, title:Bitwarden" 320 - # "no_screenshare on, tag:bitwarden" 321 322 - "workspace 6, class:discord" # move discord to workspace 6 323 - "workspace 7, class:spotify" # move spotify to workspace 7 324 325 # throw sharing indicators away 326 - "workspace special silent, title:^(Firefox โ€” Sharing Indicator)$" 327 - "workspace special silent, title:^(.*is sharing (your screen|a window)\.)$" 328 ]; 329 }; 330
··· 215 }; 216 217 layerrule = [ 218 + "blur on, ignore_alpha 0, match:namespace vicinae" 219 + "no_anim on, match:namespace vicinae" 220 ]; 221 222 general = { ··· 224 gaps_out = 8; 225 gaps_workspaces = 0; 226 border_size = 2; 227 228 "col.active_border" = "$pink"; 229 "col.inactive_border" = "$surface1"; ··· 292 disable_autoreload = true; # autoreload is unnecessary on nixos, because the config is readonly anyway 293 }; 294 295 + windowrule = [ 296 + "float on, match:title ^(nm-connection-editor)$" 297 + "float on, match:title ^(Network)$" 298 + "float on, match:title ^(xdg-desktop-portal-gtk)$" 299 + "float on, match:class gay.vaskel.soteria" 300 + "float on, match:title ^(Picture-in-Picture)$" 301 + "float on, match:class ^(download)$" 302 303 + "center on, float on, size (monitor_w*0.4) (monitor_h*0.6), match:initial_title (Open Files)" 304 + "center on, float on, size (monitor_w*0.4) (monitor_h*0.6), match:class .blueman-manager-wrapped" 305 + "center on, float on, size (monitor_w*0.4) (monitor_h*0.6), match:class com.saivert.pwvucontrol" 306 + "float on, size 800 600, match:title Bitwarden" 307 308 + "workspace 6, match:class discord" # move discord to workspace 6 309 + "workspace 7, match:class spotify" # move spotify to workspace 7 310 311 # throw sharing indicators away 312 + "workspace special silent, match:title ^(Firefox โ€” Sharing Indicator)$" 313 + "workspace special silent, match:title ^(.*is sharing (your screen|a window).)$" 314 ]; 315 }; 316
+11 -2
home/isabel/gui/media/listening.nix
··· 1 { 2 lib, 3 config, 4 inputs, 5 inputs', ··· 16 config = mkIf config.garden.profiles.media.watching.enable { 17 programs.spicetify = { 18 enable = true; 19 enabledExtensions = with spicePkgs.extensions; [ 20 shuffle 21 copyToClipboard ··· 25 volumePercentage 26 aiBandBlocker 27 ]; 28 - theme = spicePkgs.themes.catppuccin; 29 - colorScheme = "mocha"; 30 }; 31 }; 32 }
··· 1 { 2 lib, 3 + pkgs, 4 config, 5 inputs, 6 inputs', ··· 17 config = mkIf config.garden.profiles.media.watching.enable { 18 programs.spicetify = { 19 enable = true; 20 + 21 + spotifyPackage = 22 + if pkgs.stdenv.hostPlatform.isLinux then 23 + pkgs.spotify.override { ffmpeg_4 = pkgs.ffmpeg; } 24 + else 25 + pkgs.spotify; 26 + 27 + colorScheme = "mocha"; 28 + theme = spicePkgs.themes.catppuccin; 29 + 30 enabledExtensions = with spicePkgs.extensions; [ 31 shuffle 32 copyToClipboard ··· 36 volumePercentage 37 aiBandBlocker 38 ]; 39 }; 40 }; 41 }
+4 -1
home/isabel/gui/media/watching.nix
··· 32 33 scripts = 34 (with pkgs.mpvScripts; [ 35 - videoclip 36 sponsorblock 37 38 # modern ui 39 modernz ··· 111 stop-screensaver = "yes"; 112 cursor-autohide = 100; # auto hide cursor after 100ms 113 reset-on-next-file = "video-zoom,panscan,video-unscaled,video-rotate,video-align-x,video-align-y"; 114 }; 115 116 profiles = {
··· 32 33 scripts = 34 (with pkgs.mpvScripts; [ 35 sponsorblock 36 + 37 + # unify my clipboard 38 + (videoclip.override { wl-clipboard = pkgs.wl-clipboard-rs; }) 39 40 # modern ui 41 modernz ··· 113 stop-screensaver = "yes"; 114 cursor-autohide = 100; # auto hide cursor after 100ms 115 reset-on-next-file = "video-zoom,panscan,video-unscaled,video-rotate,video-align-x,video-align-y"; 116 + ytdl-raw-options = "cookies=~/documents/yt-dlp-cookies.txt"; 117 }; 118 119 profiles = {
+11
home/isabel/gui/quickshell/components/BarDivider.qml
···
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import "root:/data" 4 + 5 + Rectangle { 6 + Layout.alignment: Qt.AlignHCenter 7 + width: 20 8 + height: 1 9 + color: Settings.colors.foreground 10 + opacity: 0.2 11 + }
+37
home/isabel/gui/quickshell/components/Battery.qml
···
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import QtQuick.Controls 4 + import "root:/data" 5 + import "root:/components" 6 + import "root:/services" 7 + 8 + Item { 9 + id: root 10 + 11 + Layout.alignment: Qt.AlignCenter 12 + implicitWidth: 20 13 + implicitHeight: 20 14 + 15 + // Only show if battery is present 16 + visible: UPower.isPresent 17 + 18 + MyIcon { 19 + anchors.centerIn: parent 20 + icon: UPower.icon 21 + size: 18 22 + } 23 + 24 + // Tooltip showing percentage and status 25 + ToolTip { 26 + id: tooltip 27 + visible: mouseArea.containsMouse 28 + text: `${Math.round(UPower.percentage)}% - ${UPower.statusText}` 29 + delay: 500 30 + } 31 + 32 + MouseArea { 33 + id: mouseArea 34 + anchors.fill: parent 35 + hoverEnabled: true 36 + } 37 + }
+16 -5
home/isabel/gui/quickshell/components/Clock.qml
··· 2 import QtQuick.Layouts 3 import "root:/data" 4 5 - Text { 6 - id: clock 7 - font.pointSize: 13 8 - color: Settings.colors.foreground 9 Layout.alignment: Qt.AlignCenter 10 11 - text: Time.time 12 }
··· 2 import QtQuick.Layouts 3 import "root:/data" 4 5 + Item { 6 + id: root 7 + 8 Layout.alignment: Qt.AlignCenter 9 + implicitWidth: 30 10 + implicitHeight: 50 11 12 + Text { 13 + id: clockText 14 + anchors.centerIn: parent 15 + text: Time.time 16 + color: Settings.colors.foreground 17 + font { 18 + pixelSize: 14 19 + weight: Font.Medium 20 + } 21 + horizontalAlignment: Text.AlignHCenter 22 + } 23 }
+60
home/isabel/gui/quickshell/components/ExpandablePanel.qml
···
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import Quickshell 4 + import Quickshell.Widgets 5 + import "root:/data" 6 + 7 + Rectangle { 8 + id: root 9 + 10 + property string title: "" 11 + property string subtitle: "" 12 + property bool expanded: false 13 + property alias content: contentLoader.sourceComponent 14 + property int contentHeight: 150 15 + 16 + Layout.fillWidth: true 17 + Layout.preferredHeight: expanded ? contentLoader.item?.height + 48 : 0 18 + visible: expanded 19 + radius: 8 20 + color: Settings.colors.backgroundLighter 21 + clip: true 22 + 23 + Behavior on Layout.preferredHeight { 24 + NumberAnimation { duration: 150; easing.type: Easing.OutCubic } 25 + } 26 + 27 + ColumnLayout { 28 + anchors.fill: parent 29 + anchors.margins: 12 30 + spacing: 8 31 + 32 + RowLayout { 33 + Layout.fillWidth: true 34 + 35 + Text { 36 + text: root.title 37 + color: Settings.colors.foreground 38 + font { 39 + pixelSize: 13 40 + weight: Font.Medium 41 + } 42 + } 43 + 44 + Item { Layout.fillWidth: true } 45 + 46 + Text { 47 + text: root.subtitle 48 + color: Settings.colors.accent 49 + font.pixelSize: 11 50 + visible: root.subtitle !== "" 51 + } 52 + } 53 + 54 + Loader { 55 + id: contentLoader 56 + Layout.fillWidth: true 57 + Layout.preferredHeight: Math.min(item?.implicitHeight ?? 0, root.contentHeight) 58 + } 59 + } 60 + }
+29 -15
home/isabel/gui/quickshell/components/IconButton.qml
··· 2 import Quickshell 3 import Quickshell.Widgets 4 import "root:/data" 5 6 Item { 7 - id: iconButton 8 - width: 16 9 - height: 16 10 11 - property string icon: "" 12 - signal clicked 13 14 - MouseArea { 15 - anchors.fill: parent 16 - onClicked: iconButton.clicked() 17 - } 18 19 - Text { 20 - anchors.fill: parent 21 - text: iconButton.icon 22 - color: Settings.colors.foreground 23 - font.pixelSize: 18 24 - } 25 }
··· 2 import Quickshell 3 import Quickshell.Widgets 4 import "root:/data" 5 + import "root:/components" 6 7 Item { 8 + id: root 9 10 + property string icon: "" 11 + property int size: 18 12 + property bool invert: false 13 14 + signal clicked 15 16 + implicitWidth: size 17 + implicitHeight: size 18 + 19 + MyIcon { 20 + anchors.centerIn: parent 21 + icon: root.icon 22 + size: root.size 23 + invert: root.invert 24 + } 25 + 26 + MouseArea { 27 + id: mouseArea 28 + anchors.fill: parent 29 + hoverEnabled: true 30 + cursorShape: Qt.PointingHandCursor 31 + onClicked: root.clicked() 32 + } 33 + 34 + scale: mouseArea.pressed ? 0.9 : 1.0 35 + 36 + Behavior on scale { 37 + NumberAnimation { duration: 100 } 38 + } 39 }
+16 -17
home/isabel/gui/quickshell/components/Launcher.qml
··· 2 import QtQuick.Layouts 3 import Quickshell 4 import Quickshell.Io 5 - import Quickshell.Widgets 6 7 - IconImage { 8 - id: launcher 9 - source: Quickshell.iconPath("nix-snowflake") 10 11 - Layout.alignment: Qt.AlignCenter 12 13 - width: 16 14 - height: 16 15 16 - Process { 17 - id: launcherProcess 18 - command: ["vicinae", "toggle"] 19 - } 20 - 21 - MouseArea { 22 - anchors.fill: parent 23 - hoverEnabled: true 24 - onClicked: launcherProcess.running = true; 25 - } 26 }
··· 2 import QtQuick.Layouts 3 import Quickshell 4 import Quickshell.Io 5 + import "root:/data" 6 7 + Item { 8 + id: root 9 10 + Layout.alignment: Qt.AlignCenter 11 + implicitWidth: 24 12 + implicitHeight: 24 13 14 + IconButton { 15 + anchors.centerIn: parent 16 + icon: "nix-snowflake" 17 + size: 20 18 + onClicked: launcherProcess.running = true 19 + } 20 21 + Process { 22 + id: launcherProcess 23 + command: ["vicinae", "toggle"] 24 + } 25 }
+186
home/isabel/gui/quickshell/components/MediaPlayers.qml
···
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import Quickshell.Widgets 4 + import Quickshell.Services.Mpris 5 + import "root:/data" 6 + import "root:/services" 7 + 8 + ColumnLayout { 9 + id: root 10 + 11 + Layout.fillWidth: true 12 + spacing: 8 13 + visible: Media.players.length > 0 14 + 15 + RowLayout { 16 + Layout.fillWidth: true 17 + spacing: 8 18 + 19 + Text { 20 + text: "Now Playing" 21 + color: Settings.colors.foreground 22 + font { 23 + pixelSize: 14 24 + weight: Font.Bold 25 + } 26 + } 27 + 28 + Item { Layout.fillWidth: true } 29 + 30 + // Player selector (if multiple players) 31 + Row { 32 + spacing: 4 33 + visible: Media.players.length > 1 34 + 35 + Repeater { 36 + model: Media.players 37 + 38 + Rectangle { 39 + required property MprisPlayer modelData 40 + required property int index 41 + width: 8 42 + height: 8 43 + radius: 4 44 + color: Media.selectedPlayer === modelData ? Settings.colors.accent : Settings.colors.backgroundLightest 45 + 46 + MouseArea { 47 + anchors.fill: parent 48 + cursorShape: Qt.PointingHandCursor 49 + onClicked: Media.selectedPlayer = modelData 50 + } 51 + } 52 + } 53 + } 54 + } 55 + 56 + // Current Player 57 + ListView { 58 + id: mediaPlayerList 59 + Layout.fillWidth: true 60 + Layout.preferredHeight: contentHeight 61 + model: Media.players 62 + spacing: 8 63 + clip: true 64 + interactive: false 65 + 66 + delegate: Rectangle { 67 + required property MprisPlayer modelData 68 + required property int index 69 + width: ListView.view.width 70 + height: 90 71 + radius: 8 72 + color: Settings.colors.backgroundLighter 73 + opacity: Media.selectedPlayer === modelData ? 1.0 : 0.6 74 + 75 + MouseArea { 76 + anchors.fill: parent 77 + onClicked: Media.selectedPlayer = modelData 78 + } 79 + 80 + RowLayout { 81 + anchors { 82 + fill: parent 83 + margins: 10 84 + } 85 + spacing: 10 86 + 87 + // Album Art 88 + ClippingWrapperRectangle { 89 + radius: 6 90 + Layout.preferredWidth: 70 91 + Layout.preferredHeight: 70 92 + 93 + Image { 94 + anchors.fill: parent 95 + source: modelData.trackArtUrl 96 + fillMode: Image.PreserveAspectCrop 97 + } 98 + } 99 + 100 + // Track Info & Controls 101 + ColumnLayout { 102 + Layout.fillWidth: true 103 + Layout.fillHeight: true 104 + spacing: 2 105 + 106 + RowLayout { 107 + Layout.fillWidth: true 108 + spacing: 4 109 + 110 + Text { 111 + text: modelData.trackTitle ?? "Unknown" 112 + color: Settings.colors.foreground 113 + font { 114 + pixelSize: 13 115 + weight: Font.Medium 116 + } 117 + elide: Text.ElideRight 118 + Layout.fillWidth: true 119 + } 120 + 121 + // Player app name badge 122 + Rectangle { 123 + visible: Media.players.length > 1 124 + Layout.preferredHeight: 16 125 + Layout.preferredWidth: appNameText.width + 8 126 + radius: 4 127 + color: Settings.colors.backgroundLightest 128 + 129 + Text { 130 + id: appNameText 131 + anchors.centerIn: parent 132 + text: modelData.identity ?? "" 133 + color: Settings.colors.foreground 134 + opacity: 0.7 135 + font.pixelSize: 9 136 + } 137 + } 138 + } 139 + 140 + Text { 141 + text: modelData.trackArtist ?? "" 142 + color: Settings.colors.foreground 143 + opacity: 0.7 144 + font.pixelSize: 11 145 + elide: Text.ElideRight 146 + Layout.fillWidth: true 147 + } 148 + 149 + Item { Layout.fillHeight: true } 150 + 151 + // Playback Controls 152 + RowLayout { 153 + spacing: 14 154 + Layout.alignment: Qt.AlignLeft 155 + 156 + IconButton { 157 + icon: "media-skip-backward-symbolic" 158 + size: 14 159 + onClicked: modelData.previous() 160 + } 161 + 162 + IconButton { 163 + icon: modelData.playbackState === MprisPlaybackState.Playing 164 + ? "media-playback-pause-symbolic" 165 + : "media-playback-start-symbolic" 166 + size: 18 167 + onClicked: { 168 + if (modelData.playbackState === MprisPlaybackState.Playing) { 169 + modelData.pause(); 170 + } else { 171 + modelData.play(); 172 + } 173 + } 174 + } 175 + 176 + IconButton { 177 + icon: "media-skip-forward-symbolic" 178 + size: 14 179 + onClicked: modelData.next() 180 + } 181 + } 182 + } 183 + } 184 + } 185 + } 186 + }
+28
home/isabel/gui/quickshell/components/MyIcon.qml
···
··· 1 + import org.kde.kirigami 2 + import QtQuick 3 + import QtQuick.Effects 4 + import Quickshell 5 + import Quickshell.Widgets 6 + import "root:/data" 7 + 8 + Item { 9 + id: root 10 + 11 + property string icon: "" 12 + property int size: 18 13 + property bool invert: false 14 + 15 + implicitWidth: size 16 + implicitHeight: size 17 + 18 + Icon { 19 + id: iconSource 20 + anchors.fill: parent 21 + source: Quickshell.iconPath(root.icon) 22 + 23 + isMask: true 24 + // FIXME: 25 + // color: Settings.colors.foreground 26 + color: invert ? "#1e1e2e" : "#cdd6f4" 27 + } 28 + }
+15 -10
home/isabel/gui/quickshell/components/Network.qml
··· 1 - //@ pragma IconTheme Cosmic 2 - 3 import QtQuick.Layouts 4 - import Quickshell 5 - import Quickshell.Widgets 6 import "root:/services" 7 8 - IconImage { 9 - id: networkIcon 10 - source: Quickshell.iconPath(Networking.active.icon) 11 12 - width: 16 13 - height: 16 14 - Layout.alignment: Qt.AlignCenter 15 }
··· 1 + import QtQuick 2 import QtQuick.Layouts 3 + import QtQuick.Controls.Basic 4 + import "root:/data" 5 + import "root:/components" 6 import "root:/services" 7 8 + Item { 9 + id: root 10 + 11 + Layout.alignment: Qt.AlignCenter 12 + implicitWidth: 20 13 + implicitHeight: 20 14 15 + MyIcon { 16 + anchors.centerIn: parent 17 + icon: Networking.icon 18 + size: 18 19 + } 20 }
+11 -360
home/isabel/gui/quickshell/components/Noti.qml
··· 1 import QtQuick 2 import QtQuick.Layouts 3 import QtQuick.Controls 4 - import QtQuick.Controls.Basic 5 import Quickshell 6 import Quickshell.Widgets 7 - import Quickshell.Services.Notifications 8 - import Quickshell.Services.Mpris 9 import "root:/data" 10 import "root:/services" 11 - import "root:/components" 12 13 Item { 14 - id: noti 15 - 16 - property bool showIndicator: Notifications.list.length > 0 || Media.players.length > 0 17 18 - Layout.alignment: Qt.AlignCenter 19 20 - Text { 21 - text: "๏‘ถ" 22 - color: Settings.colors.foreground 23 - font.pixelSize: 16 24 25 - anchors.horizontalCenter: parent.horizontalCenter 26 27 - visible: noti.showIndicator 28 - 29 - MouseArea { 30 - anchors.fill: parent 31 - onClicked: notificationLoader.item.visible = !notificationLoader.item.visible 32 } 33 - } 34 - 35 - LazyLoader { 36 - id: notificationLoader 37 - 38 - loading: true 39 - 40 - PopupWindow { 41 - id: popup 42 - anchor.window: noti.QsWindow.window 43 - anchor.rect.x: parentWindow.width * 1.2 44 - 45 - visible: false 46 - 47 - color: "transparent" 48 - 49 - implicitWidth: 400 50 - implicitHeight: noti.QsWindow.window.height 51 - 52 - Rectangle { 53 - anchors.fill: parent 54 - radius: 10 55 - color: Settings.colors.background 56 - 57 - ColumnLayout { 58 - spacing: 10 59 - 60 - anchors { 61 - fill: parent 62 - topMargin: 15 63 - bottomMargin: 15 64 - leftMargin: 15 65 - rightMargin: 15 66 - } 67 - 68 - Rectangle { 69 - width: parent.width 70 - height: 30 71 - color: Settings.colors.background 72 - radius: 5 73 - 74 - Text { 75 - text: "Notifications" 76 - color: Settings.colors.foreground 77 - font.pixelSize: 20 78 - font.bold: true 79 - 80 - anchors.centerIn: parent 81 - } 82 - } 83 - 84 - Rectangle { 85 - width: parent.width 86 - height: 120 87 - radius: 5 88 - color: Settings.colors.backgroundLighter 89 - 90 - ColumnLayout { 91 - id: mprisRoot 92 - spacing: 7 93 - 94 - width: parent.width 95 - height: parent.height 96 - 97 - anchors { 98 - fill: parent 99 - leftMargin: 10 100 - rightMargin: 20 101 - topMargin: 10 102 - bottomMargin: 10 103 - } 104 - 105 - Row { 106 - spacing: 10 107 - 108 - Layout.preferredWidth: parent.width 109 - Layout.preferredHeight: 80 110 - 111 - ClippingWrapperRectangle { 112 - radius: 5 113 - width: parent.height 114 - height: parent.height 115 - 116 - Image { 117 - source: Media.selectedPlayer.trackArtUrl 118 - fillMode: Image.PreserveAspectFit 119 - width: parent.width 120 - height: parent.height 121 - } 122 - } 123 - 124 - ColumnLayout { 125 - spacing: 5 126 - 127 - height: 80 128 - width: parent.width 129 - 130 - Text { 131 - text: Media.selectedPlayer.trackTitle 132 - color: Settings.colors.foreground 133 - font.pixelSize: 20 134 - } 135 - 136 - Text { 137 - text: Media.selectedPlayer.trackArtist 138 - color: Settings.colors.foreground 139 - font.pixelSize: 14 140 - } 141 - 142 - Row { 143 - spacing: (parent.width / 3) - 10 144 - 145 - IconButton { 146 - // icon: "media-skip-backward-symbolic" 147 - icon: "๓ฐ’ซ" 148 - onClicked: Media.selectedPlayer.previous() 149 - } 150 - 151 - IconButton { 152 - // icon: "media-playback-start-symbolic" 153 - icon: "๓ฐŠ" 154 - onClicked: Media.selectedPlayer.play() 155 - visible: Media.selectedPlayer.playbackState == MprisPlaybackState.Paused 156 - } 157 - 158 - IconButton { 159 - // icon: "media-playback-pause-symbolic" 160 - icon: "๓ฐค" 161 - onClicked: Media.selectedPlayer.pause() 162 - visible: Media.selectedPlayer.playbackState == MprisPlaybackState.Playing 163 - } 164 - 165 - IconButton { 166 - // icon: "media-skip-forward-symbolic" 167 - icon: "๓ฐ’ฌ" 168 - onClicked: Media.selectedPlayer.next() 169 - } 170 - } 171 - } 172 - } 173 - 174 - RowLayout { 175 - id: progressBar 176 - spacing: 10 177 - 178 - Layout.preferredWidth: parent.width 179 - Layout.preferredHeight: parent.height 180 - 181 - Text { 182 - id: positionText 183 - text: Math.round(Media.selectedPlayer.position) + "s" 184 - color: Settings.colors.foreground 185 - } 186 - 187 - Slider { 188 - id: progress 189 - from: 0 190 - to: Media.selectedPlayer.length 191 - value: Media.selectedPlayer.position 192 - 193 - onMoved: Media.selectedPlayer.position = value 194 - 195 - implicitHeight: parent.height 196 - implicitWidth: parent.width - positionText.width - lengthText.width - 10 197 - 198 - background: Rectangle { 199 - id: sliderBackground 200 - color: Settings.colors.backgroundDarker 201 - x: progress.leftPadding 202 - y: progress.topPadding + progress.availableHeight / 2 - height / 2 203 - implicitWidth: parent.width 204 - implicitHeight: 4 205 - width: progress.availableWidth 206 - height: implicitHeight 207 - radius: 2 208 - 209 - Rectangle { 210 - implicitWidth: progress.visualPosition * parent.width 211 - implicitHeight: parent.height 212 - color: Settings.colors.accent 213 - radius: 2 214 - } 215 - } 216 - 217 - handle: Rectangle { 218 - x: progress.leftPadding + progress.visualPosition * progress.availableWidth 219 - y: progress.topPadding + progress.availableHeight / 2 - height / 2 220 - implicitWidth: 20 221 - implicitHeight: 20 222 - radius: 13 223 - color: Settings.colors.backgroundLightest 224 - border.color: Settings.colors.border 225 - } 226 - } 227 - 228 - Text { 229 - id: lengthText 230 - text: Math.round(Media.selectedPlayer.length) + "s" 231 - color: Settings.colors.foreground 232 - } 233 - 234 - FrameAnimation { 235 - running: Media.selectedPlayer.playbackState == MprisPlaybackState.Playing 236 - onTriggered: { 237 - progress.value = Media.selectedPlayer.position; 238 - positionText.text = Math.round(Media.selectedPlayer.position) + "s"; 239 - } 240 - } 241 - } 242 - } 243 - } 244 - 245 - ListView { 246 - id: notiList 247 - model: Notifications.list 248 - 249 - Layout.alignment: Qt.AlignCenter 250 - Layout.preferredWidth: parent.width 251 - Layout.preferredHeight: parent.height 252 - 253 - ScrollBar.vertical: ScrollBar {} 254 - 255 - spacing: 15 256 - 257 - delegate: Item { 258 - required property Notification modelData 259 - 260 - width: parent.width 261 - height: 80 262 - 263 - Rectangle { 264 - anchors.fill: parent 265 - color: Settings.colors.backgroundLighter 266 - radius: 5 267 - 268 - IconImage { 269 - anchors { 270 - left: parent.left 271 - leftMargin: 10 272 - verticalCenter: parent.verticalCenter 273 - } 274 - width: 48 275 - height: 48 276 - source: Quickshell.iconPath(modelData.appIcon) 277 - } 278 - 279 - ColumnLayout { 280 - anchors { 281 - left: parent.left 282 - leftMargin: 75 283 - top: parent.top 284 - topMargin: 10 285 - } 286 - 287 - spacing: 5 288 - 289 - Text { 290 - text: modelData.appName 291 - color: Settings.colors.foreground 292 - font { 293 - pixelSize: 18 294 - bold: true 295 - } 296 - } 297 - 298 - Text { 299 - text: modelData.body 300 - color: Settings.colors.foreground 301 - font.pixelSize: 13 302 - } 303 - } 304 - 305 - Text { 306 - text: "x" 307 - color: Settings.colors.error 308 - font.pixelSize: 16 309 - 310 - anchors { 311 - top: parent.top 312 - topMargin: 5 313 - right: parent.right 314 - rightMargin: 10 315 - } 316 - 317 - MouseArea { 318 - anchors.fill: parent 319 - onClicked: { 320 - modelData.dismiss(); 321 - if (Notifications.list.length <= 0) { 322 - popup.visible = false; 323 - } 324 - } 325 - } 326 - } 327 - 328 - Repeater { 329 - model: modelData.actions 330 - 331 - Item { 332 - required property NotificationAction actionData 333 - 334 - width: 100 335 - height: 30 336 - 337 - anchors { 338 - left: parent.left 339 - leftMargin: 5 340 - top: parent.top 341 - topMargin: 5 342 - } 343 - 344 - Rectangle { 345 - anchors.fill: parent 346 - color: Settings.colors.backgroundDarker 347 - radius: 5 348 - 349 - Text { 350 - text: actionData.text 351 - color: Settings.colors.foreground 352 - font.pixelSize: 12 353 - 354 - anchors { 355 - left: parent.left 356 - leftMargin: 10 357 - verticalCenter: parent.verticalCenter 358 - } 359 - } 360 - 361 - MouseArea { 362 - anchors.fill: parent 363 - onClicked: actionData.invoke() 364 - } 365 - } 366 - } 367 - } 368 - } 369 - } 370 - } 371 - } 372 - } 373 - } 374 - } 375 }
··· 1 import QtQuick 2 import QtQuick.Layouts 3 import QtQuick.Controls 4 import Quickshell 5 import Quickshell.Widgets 6 + import Quickshell.Services.Notifications as QsNotifications 7 import "root:/data" 8 import "root:/services" 9 10 Item { 11 + id: root 12 13 + property bool hasNotifications: Notifications.list.length > 0 || Notifications.dndEnabled 14 15 + visible: hasNotifications 16 17 + Layout.alignment: Qt.AlignCenter 18 + implicitWidth: 24 19 + implicitHeight: 24 20 21 + MyIcon { 22 + anchors.centerIn: parent 23 + icon: Notifications.dndEnabled ? "notifications-disabled-symbolic" : "preferences-system-notifications-symbolic" 24 + size: 18 25 } 26 }
+57
home/isabel/gui/quickshell/components/PanelListItem.qml
···
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import "root:/data" 4 + 5 + Rectangle { 6 + id: root 7 + 8 + property string icon: "" 9 + property string text: "" 10 + property string badge: "" 11 + property bool active: false 12 + property bool showBadge: badge !== "" 13 + 14 + signal clicked() 15 + 16 + width: parent?.width ?? 100 17 + height: 36 18 + radius: 6 19 + color: active ? Settings.colors.accent : "transparent" 20 + 21 + MouseArea { 22 + id: mouseArea 23 + anchors.fill: parent 24 + cursorShape: Qt.PointingHandCursor 25 + hoverEnabled: true 26 + onEntered: if (!root.active) parent.color = Settings.colors.backgroundLightest 27 + onExited: parent.color = root.active ? Settings.colors.accent : "transparent" 28 + onClicked: root.clicked() 29 + } 30 + 31 + RowLayout { 32 + anchors.fill: parent 33 + anchors.margins: 8 34 + spacing: 8 35 + 36 + MyIcon { 37 + icon: root.icon 38 + size: 16 39 + } 40 + 41 + Text { 42 + text: root.text 43 + color: root.active ? Settings.colors.background : Settings.colors.foreground 44 + font.pixelSize: 12 45 + elide: Text.ElideRight 46 + Layout.fillWidth: true 47 + } 48 + 49 + Text { 50 + visible: root.showBadge 51 + text: root.badge 52 + color: root.active ? Settings.colors.background : Settings.colors.foreground 53 + opacity: 0.7 54 + font.pixelSize: 11 55 + } 56 + } 57 + }
+311
home/isabel/gui/quickshell/components/QuickSettings.qml
···
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import QtQuick.Controls.Basic 4 + import Quickshell.Io 5 + import "root:/data" 6 + import "root:/services" 7 + 8 + ColumnLayout { 9 + id: root 10 + 11 + Layout.fillWidth: true 12 + spacing: 12 13 + 14 + Text { 15 + text: "Quick Settings" 16 + color: Settings.colors.foreground 17 + font { 18 + pixelSize: 14 19 + weight: Font.Bold 20 + } 21 + } 22 + 23 + // Network Panel (expandable) 24 + Rectangle { 25 + id: networkPanel 26 + Layout.fillWidth: true 27 + Layout.preferredHeight: visible ? Math.min(networkColumn.implicitHeight + 20, 350) : 0 28 + visible: false 29 + radius: 8 30 + color: Settings.colors.backgroundLighter 31 + clip: true 32 + 33 + Behavior on Layout.preferredHeight { 34 + NumberAnimation { duration: 150; easing.type: Easing.OutCubic } 35 + } 36 + 37 + ColumnLayout { 38 + id: networkColumn 39 + anchors.fill: parent 40 + anchors.margins: 12 41 + spacing: 8 42 + 43 + RowLayout { 44 + Layout.fillWidth: true 45 + 46 + Text { 47 + text: "Networks" 48 + color: Settings.colors.foreground 49 + font { 50 + pixelSize: 13 51 + weight: Font.Medium 52 + } 53 + } 54 + 55 + Item { Layout.fillWidth: true } 56 + 57 + IconButton { 58 + icon: "view-refresh-symbolic" 59 + size: 14 60 + onClicked: Networking.reload() 61 + } 62 + } 63 + 64 + ScrollView { 65 + Layout.fillWidth: true 66 + Layout.preferredHeight: Math.min(networkListView.contentHeight, 280) 67 + clip: true 68 + 69 + ListView { 70 + id: networkListView 71 + width: parent.width 72 + model: Networking.networks 73 + spacing: 4 74 + 75 + delegate: PanelListItem { 76 + required property var modelData 77 + width: ListView.view.width 78 + icon: modelData.icon 79 + text: modelData.ssid || "Unknown" 80 + badge: modelData.strength + "%" 81 + active: modelData.active 82 + onClicked: { 83 + if (!modelData.active) { 84 + Networking.connectToNetwork(modelData.ssid); 85 + } 86 + } 87 + } 88 + } 89 + } 90 + 91 + Text { 92 + text: Networking.networks?.count === 0 ? "No networks found" : "" 93 + color: Settings.colors.foreground 94 + opacity: 0.5 95 + font.pixelSize: 12 96 + visible: Networking.networks?.count === 0 97 + Layout.alignment: Qt.AlignHCenter 98 + } 99 + 100 + Item { Layout.fillHeight: true } 101 + } 102 + } 103 + 104 + // Bluetooth Panel (expandable) 105 + Rectangle { 106 + id: bluetoothPanel 107 + Layout.fillWidth: true 108 + Layout.preferredHeight: visible ? Math.min(bluetoothColumn.implicitHeight + 20, 350) : 0 109 + visible: false 110 + radius: 8 111 + color: Settings.colors.backgroundLighter 112 + clip: true 113 + 114 + Behavior on Layout.preferredHeight { 115 + NumberAnimation { duration: 150; easing.type: Easing.OutCubic } 116 + } 117 + 118 + ColumnLayout { 119 + id: bluetoothColumn 120 + anchors.fill: parent 121 + anchors.margins: 12 122 + spacing: 8 123 + 124 + RowLayout { 125 + Layout.fillWidth: true 126 + 127 + Text { 128 + text: "Bluetooth Devices" 129 + color: Settings.colors.foreground 130 + font { 131 + pixelSize: 13 132 + weight: Font.Medium 133 + } 134 + } 135 + 136 + Item { Layout.fillWidth: true } 137 + 138 + // Scan button 139 + IconButton { 140 + icon: Bluetooth.adapter?.discovering ? "process-stop-symbolic" : "view-refresh-symbolic" 141 + size: 14 142 + onClicked: { 143 + if (Bluetooth.adapter?.discovering) { 144 + Bluetooth.stopDiscovery(); 145 + } else { 146 + Bluetooth.startDiscovery(); 147 + } 148 + } 149 + } 150 + 151 + Text { 152 + text: Bluetooth.connected ? Bluetooth.connectedDevice?.name ?? "" : "" 153 + color: Settings.colors.accent 154 + font.pixelSize: 11 155 + visible: Bluetooth.connected 156 + } 157 + } 158 + 159 + ScrollView { 160 + Layout.fillWidth: true 161 + Layout.preferredHeight: Math.min(bluetoothListView.contentHeight, 280) 162 + clip: true 163 + 164 + ListView { 165 + id: bluetoothListView 166 + width: parent.width 167 + model: Bluetooth.devices 168 + spacing: 8 169 + clip: true 170 + 171 + delegate: PanelListItem { 172 + required property var modelData 173 + width: ListView.view.width 174 + icon: modelData.icon || "bluetooth-symbolic" 175 + text: modelData.name 176 + badge: modelData.connected ? "Connected" : (modelData.paired ? "Paired" : "") 177 + active: modelData.connected 178 + onClicked: { 179 + if (modelData.connected) { 180 + modelData.disconnect(); 181 + } else if (modelData.paired) { 182 + modelData.connect(); 183 + } else { 184 + // Pair and connect 185 + modelData.pair(); 186 + } 187 + } 188 + } 189 + } 190 + } 191 + 192 + Text { 193 + text: { 194 + if (!Bluetooth.adapter) return "No Bluetooth adapter"; 195 + if (Bluetooth.devices?.count === 0) return "No devices found"; 196 + return ""; 197 + } 198 + color: Settings.colors.foreground 199 + opacity: 0.5 200 + font.pixelSize: 12 201 + visible: !Bluetooth.adapter || Bluetooth.devices?.count === 0 202 + Layout.alignment: Qt.AlignHCenter 203 + } 204 + 205 + Item { Layout.fillHeight: true } 206 + } 207 + } 208 + 209 + // Toggle Buttons Row 210 + RowLayout { 211 + Layout.fillWidth: true 212 + spacing: 8 213 + 214 + // Netoworking Toggle 215 + QuickSettingButton { 216 + icon: Networking.icon 217 + label: "Networking" 218 + active: Networking.connected 219 + onClicked: { 220 + // Do nothing if ethernet is connected 221 + if (Networking.ethernetConnected) return; 222 + 223 + // otherwise lets actually do wifi stuff 224 + Networking.toggleWifi() 225 + } 226 + onPressAndHold: { 227 + if (Networking.ethernetConnected) return; 228 + 229 + networkPanel.visible = !networkPanel.visible 230 + } 231 + } 232 + 233 + // Bluetooth Toggle 234 + QuickSettingButton { 235 + icon: Bluetooth.icon 236 + label: "Bluetooth" 237 + active: Bluetooth.powered 238 + onClicked: Bluetooth.toggle() 239 + onPressAndHold: bluetoothPanel.visible = !bluetoothPanel.visible 240 + } 241 + 242 + // Do Not Disturb Toggle 243 + QuickSettingButton { 244 + icon: Notifications.dndEnabled ? "notifications-disabled-symbolic" : "preferences-system-notifications-symbolic" 245 + label: "DND" 246 + active: Notifications.dndEnabled 247 + onClicked: Notifications.dndEnabled = !Notifications.dndEnabled 248 + } 249 + } 250 + 251 + // Volume Slider 252 + StyledSlider { 253 + icon: Pipewire.sinkIcon 254 + value: Pipewire.sinkVolume 255 + iconClickable: true 256 + onIconClicked: Pipewire.toggleSinkMute() 257 + onMoved: (val) => Pipewire.setSinkVolume(val) 258 + } 259 + 260 + // Brightness Slider 261 + StyledSlider { 262 + visible: Brightness.available 263 + icon: "display-brightness-symbolic" 264 + value: Brightness.brightness 265 + from: 0.05 266 + accentColor: Settings.colors.warning 267 + onMoved: (val) => Brightness.setBrightness(val) 268 + } 269 + 270 + // Quick Setting Button Component 271 + component QuickSettingButton: Rectangle { 272 + id: qsButton 273 + Layout.fillWidth: true 274 + Layout.preferredHeight: 64 275 + radius: 8 276 + color: active ? Settings.colors.accent : Settings.colors.backgroundLighter 277 + 278 + property string icon: "" 279 + property string label: "" 280 + property bool active: false 281 + 282 + signal clicked() 283 + signal pressAndHold() 284 + 285 + ColumnLayout { 286 + anchors.centerIn: parent 287 + spacing: 6 288 + 289 + MyIcon { 290 + icon: qsButton.icon 291 + size: 20 292 + Layout.alignment: Qt.AlignHCenter 293 + invert: active 294 + } 295 + 296 + Text { 297 + text: qsButton.label 298 + color: qsButton.active ? Settings.colors.background : Settings.colors.foreground 299 + font.pixelSize: 11 300 + Layout.alignment: Qt.AlignHCenter 301 + } 302 + } 303 + 304 + MouseArea { 305 + anchors.fill: parent 306 + cursorShape: Qt.PointingHandCursor 307 + onClicked: qsButton.clicked() 308 + onPressAndHold: qsButton.pressAndHold() 309 + } 310 + } 311 + }
+73
home/isabel/gui/quickshell/components/StyledSlider.qml
···
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import QtQuick.Controls.Basic 4 + import "root:/data" 5 + import "root:/components" 6 + 7 + RowLayout { 8 + id: root 9 + 10 + property string icon: "" 11 + property real value: 0 12 + property real from: 0 13 + property real to: 1 14 + property color accentColor: Settings.colors.accent 15 + property bool showPercentage: true 16 + property bool iconClickable: false 17 + 18 + signal moved(real value) 19 + signal iconClicked() 20 + 21 + Layout.fillWidth: true 22 + spacing: 12 23 + 24 + IconButton { 25 + icon: root.icon 26 + size: 18 27 + onClicked: root.iconClicked() 28 + } 29 + 30 + Slider { 31 + id: slider 32 + Layout.fillWidth: true 33 + from: root.from 34 + to: root.to 35 + value: root.value 36 + 37 + onMoved: root.moved(value) 38 + 39 + background: Rectangle { 40 + x: slider.leftPadding 41 + y: slider.topPadding + slider.availableHeight / 2 - height / 2 42 + width: slider.availableWidth 43 + height: 4 44 + radius: 2 45 + color: Settings.colors.backgroundLightest 46 + 47 + Rectangle { 48 + width: slider.visualPosition * parent.width 49 + height: parent.height 50 + radius: 2 51 + color: root.accentColor 52 + } 53 + } 54 + 55 + handle: Rectangle { 56 + x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width) 57 + y: slider.topPadding + slider.availableHeight / 2 - height / 2 58 + width: 14 59 + height: 14 60 + radius: 7 61 + color: slider.pressed ? root.accentColor : Settings.colors.foreground 62 + } 63 + } 64 + 65 + Text { 66 + visible: root.showPercentage 67 + text: Math.round((slider.value - root.from) / (root.to - root.from) * 100) + "%" 68 + color: Settings.colors.foreground 69 + opacity: 0.7 70 + font.pixelSize: 12 71 + Layout.preferredWidth: 36 72 + } 73 + }
+180 -51
home/isabel/gui/quickshell/components/SysTray.qml
··· 1 import QtQuick 2 import QtQuick.Layouts 3 - import Quickshell 4 import QtQuick.Controls 5 import Quickshell.Widgets 6 import Quickshell.Services.SystemTray 7 import "root:/data" 8 9 ColumnLayout { 10 - id: systray 11 12 Layout.alignment: Qt.AlignCenter 13 14 Repeater { 15 model: SystemTray.items 16 17 delegate: Item { 18 - id: delagate 19 required property SystemTrayItem modelData 20 21 - width: 24 22 - height: 24 23 24 IconImage { 25 - source: modelData.icon 26 - width: 16 27 - height: 16 28 anchors.centerIn: parent 29 } 30 31 MouseArea { 32 anchors.fill: parent 33 hoverEnabled: true 34 35 - onClicked: popupLoader.item.visible = !popupLoader.item.visible 36 } 37 38 QsMenuOpener { ··· 42 43 LazyLoader { 44 id: popupLoader 45 - 46 loading: true 47 48 PopupWindow { 49 id: popup 50 - anchor.window: delagate.QsWindow.window 51 - anchor.rect.x: parentWindow.width * 1.15 52 - anchor.rect.y: parentWindow.height / 1.25 53 - 54 color: "transparent" 55 56 - implicitWidth: 200 57 - implicitHeight: 200 58 59 Rectangle { 60 anchors.fill: parent 61 color: Settings.colors.background 62 - radius: 5 63 - } 64 65 - ListView { 66 - model: menu.children 67 68 anchors { 69 - top: parent.top 70 - topMargin: 5 71 - bottom: parent.bottom 72 - bottomMargin: 5 73 } 74 75 - width: parent.width 76 - height: parent.height 77 - spacing: 5 78 79 - ScrollBar.vertical: ScrollBar {} 80 81 - delegate: Item { 82 - required property QsMenuHandle modelData 83 84 - width: parent.width 85 - height: 40 86 87 - Rectangle { 88 - anchors { 89 - fill: parent 90 - leftMargin: 5 91 - rightMargin: 5 92 } 93 94 - color: Settings.colors.backgroundLighter 95 - radius: 5 96 97 - Text { 98 - anchors.centerIn: parent 99 - text: modelData.text 100 - color: Settings.colors.foreground 101 - font.pointSize: 12 102 - } 103 104 - MouseArea { 105 - anchors.fill: parent 106 - hoverEnabled: true 107 108 - onClicked: { 109 - modelData.triggered(); 110 - popup.visible = false; 111 } 112 } 113 }
··· 1 import QtQuick 2 import QtQuick.Layouts 3 import QtQuick.Controls 4 + import Quickshell 5 import Quickshell.Widgets 6 import Quickshell.Services.SystemTray 7 import "root:/data" 8 9 ColumnLayout { 10 + id: root 11 12 Layout.alignment: Qt.AlignCenter 13 + spacing: 6 14 15 Repeater { 16 model: SystemTray.items 17 18 delegate: Item { 19 + id: trayItem 20 required property SystemTrayItem modelData 21 22 + Layout.alignment: Qt.AlignHCenter 23 + implicitWidth: 24 24 + implicitHeight: 24 25 26 IconImage { 27 + source: trayItem.modelData.icon 28 + implicitSize: 18 29 anchors.centerIn: parent 30 + mipmap: true 31 } 32 33 MouseArea { 34 anchors.fill: parent 35 hoverEnabled: true 36 + cursorShape: Qt.PointingHandCursor 37 + acceptedButtons: Qt.LeftButton | Qt.RightButton 38 + onClicked: (mouse) => { 39 + if (mouse.button === Qt.RightButton || modelData.onlyMenu) { 40 + popupLoader.item.visible = !popupLoader.item.visible; 41 + } else { 42 + modelData.activate(); 43 + } 44 + } 45 + } 46 47 + ToolTip { 48 + visible: trayMouseArea.containsMouse && modelData.tooltipTitle !== "" 49 + text: modelData.tooltipTitle 50 + delay: 500 51 + } 52 + 53 + MouseArea { 54 + id: trayMouseArea 55 + anchors.fill: parent 56 + hoverEnabled: true 57 + acceptedButtons: Qt.NoButton 58 } 59 60 QsMenuOpener { ··· 64 65 LazyLoader { 66 id: popupLoader 67 loading: true 68 69 PopupWindow { 70 id: popup 71 + anchor.window: trayItem.QsWindow.window 72 + anchor.rect.x: (trayItem.QsWindow.window?.width ?? 0) * 1.15 73 + anchor.rect.y: (trayItem.QsWindow.window?.height ?? 0) / 1.25 74 + visible: false 75 color: "transparent" 76 77 + implicitWidth: 220 78 + implicitHeight: menuColumn.implicitHeight + 16 79 80 Rectangle { 81 anchors.fill: parent 82 color: Settings.colors.background 83 + radius: 10 84 85 + // Shadow effect 86 + layer.enabled: true 87 + layer.effect: null 88 + } 89 90 + ColumnLayout { 91 + id: menuColumn 92 anchors { 93 + fill: parent 94 + margins: 8 95 } 96 + spacing: 2 97 98 + // Header with app name 99 + Text { 100 + text: modelData.title || modelData.id 101 + color: Settings.colors.foreground 102 + font { 103 + pixelSize: 12 104 + weight: Font.Bold 105 + } 106 + opacity: 0.7 107 + Layout.fillWidth: true 108 + Layout.leftMargin: 8 109 + Layout.bottomMargin: 4 110 + elide: Text.ElideRight 111 + visible: text !== "" 112 + } 113 114 + Rectangle { 115 + Layout.fillWidth: true 116 + Layout.preferredHeight: 1 117 + Layout.leftMargin: 4 118 + Layout.rightMargin: 4 119 + Layout.bottomMargin: 4 120 + color: Settings.colors.foreground 121 + opacity: 0.1 122 + visible: (modelData.title || modelData.id) !== "" 123 + } 124 + 125 + Repeater { 126 + model: menu.children 127 128 + delegate: Loader { 129 + id: menuItemLoader 130 + required property var modelData 131 132 + Layout.fillWidth: true 133 + sourceComponent: modelData.isSeparator ? separatorComponent : menuItemComponent 134 + 135 + Component { 136 + id: separatorComponent 137 + 138 + Rectangle { 139 + height: 9 140 + color: "transparent" 141 142 + Rectangle { 143 + anchors.centerIn: parent 144 + width: parent.width - 16 145 + height: 1 146 + color: Settings.colors.foreground 147 + opacity: 0.1 148 + } 149 + } 150 } 151 152 + Component { 153 + id: menuItemComponent 154 + 155 + Rectangle { 156 + id: menuItemRect 157 + height: 32 158 + radius: 6 159 + color: menuMouse.containsMouse && menuItemLoader.modelData.enabled 160 + ? Settings.colors.backgroundLighter 161 + : "transparent" 162 + opacity: menuItemLoader.modelData.enabled ? 1.0 : 0.5 163 + 164 + RowLayout { 165 + anchors { 166 + fill: parent 167 + leftMargin: 10 168 + rightMargin: 10 169 + } 170 + spacing: 8 171 + 172 + // Checkbox/Radio indicator 173 + Rectangle { 174 + visible: menuItemLoader.modelData.buttonType !== 0 // QsMenuButtonType.None 175 + Layout.preferredWidth: 16 176 + Layout.preferredHeight: 16 177 + radius: menuItemLoader.modelData.buttonType === 2 ? 8 : 3 // RadioButton = 2 178 + color: "transparent" 179 + border.width: 1.5 180 + border.color: menuItemLoader.modelData.checkState !== Qt.Unchecked 181 + ? Settings.colors.accent 182 + : Settings.colors.foreground 183 + opacity: menuItemLoader.modelData.checkState !== Qt.Unchecked ? 1.0 : 0.5 184 + 185 + Rectangle { 186 + anchors.centerIn: parent 187 + width: 8 188 + height: 8 189 + radius: menuItemLoader.modelData.buttonType === 2 ? 4 : 2 190 + color: Settings.colors.accent 191 + visible: menuItemLoader.modelData.checkState !== Qt.Unchecked 192 + } 193 + } 194 + 195 + // Icon 196 + Image { 197 + visible: menuItemLoader.modelData.icon !== "" && menuItemLoader.modelData.buttonType === 0 198 + source: menuItemLoader.modelData.icon 199 + Layout.preferredWidth: 16 200 + Layout.preferredHeight: 16 201 + sourceSize.width: 16 202 + sourceSize.height: 16 203 + smooth: true 204 + } 205 + 206 + // Text 207 + Text { 208 + text: menuItemLoader.modelData.text 209 + color: Settings.colors.foreground 210 + font.pixelSize: 13 211 + elide: Text.ElideRight 212 + Layout.fillWidth: true 213 + } 214 215 + // Submenu indicator 216 + MyIcon { 217 + visible: menuItemLoader.modelData.hasChildren 218 + icon: "go-next-symbolic" 219 + size: 12 220 + opacity: 0.6 221 + } 222 + } 223 224 + MouseArea { 225 + id: menuMouse 226 + anchors.fill: parent 227 + hoverEnabled: true 228 + cursorShape: menuItemLoader.modelData.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor 229 + onClicked: { 230 + if (!menuItemLoader.modelData.enabled) return; 231 232 + if (menuItemLoader.modelData.hasChildren) { 233 + // TODO: Handle submenu 234 + } else { 235 + menuItemLoader.modelData.triggered(); 236 + popup.visible = false; 237 + } 238 + } 239 + } 240 } 241 } 242 }
+14 -8
home/isabel/gui/quickshell/components/Volume.qml
··· 1 - import Quickshell 2 - import Quickshell.Widgets 3 import QtQuick.Layouts 4 5 - IconImage { 6 - id: volumeIcon 7 - source: Quickshell.iconPath("audio-volume-high-symbolic") 8 - width: 16 9 - height: 16 10 11 - Layout.alignment: Qt.AlignCenter 12 }
··· 1 + import QtQuick 2 import QtQuick.Layouts 3 + import "root:/data" 4 + import "root:/services" 5 6 + Item { 7 + id: root 8 9 + Layout.alignment: Qt.AlignCenter 10 + implicitWidth: 20 11 + implicitHeight: 20 12 + 13 + MyIcon { 14 + anchors.centerIn: parent 15 + icon: Pipewire.sinkIcon 16 + size: 18 17 + } 18 }
+53 -28
home/isabel/gui/quickshell/components/Workspaces.qml
··· 4 import QtQuick.Layouts 5 import Quickshell.Hyprland 6 import "root:/data" 7 - import "root:/services" 8 9 Item { 10 - id: workspaces 11 12 - Layout.alignment: Qt.AlignCenter 13 14 - width: 20 15 - height: 20 16 17 - ColumnLayout { 18 - spacing: 20 19 20 - anchors.horizontalCenter: parent.horizontalCenter 21 22 - Repeater { 23 - model: Hyprland.workspaces 24 25 - delegate: Item { 26 - id: workspace 27 - required property HyprlandWorkspace modelData 28 29 - implicitWidth: 10 30 - implicitHeight: 10 31 - 32 - MouseArea { 33 - anchors.fill: parent 34 - hoverEnabled: true 35 36 - onClicked: modelData.activate() 37 - } 38 39 - Text { 40 - font.pointSize: 13 41 - Layout.alignment: Qt.AlignCenter 42 43 - color: modelData.focused === modelData.id ? Settings.colors.accent : Settings.colors.foreground 44 - text: modelData.id 45 } 46 - } 47 } 48 - } 49 }
··· 4 import QtQuick.Layouts 5 import Quickshell.Hyprland 6 import "root:/data" 7 8 Item { 9 + id: root 10 11 + Layout.alignment: Qt.AlignCenter 12 + implicitWidth: 24 13 + implicitHeight: workspaceColumn.implicitHeight 14 15 + ColumnLayout { 16 + id: workspaceColumn 17 + anchors.horizontalCenter: parent.horizontalCenter 18 + spacing: 4 19 20 + Repeater { 21 + model: Hyprland.workspaces 22 23 + delegate: Item { 24 + id: workspaceItem 25 + required property HyprlandWorkspace modelData 26 27 + Layout.alignment: Qt.AlignHCenter 28 + implicitWidth: 24 29 + implicitHeight: 24 30 31 + Rectangle { 32 + anchors.centerIn: parent 33 + width: 22 34 + height: 22 35 + radius: 6 36 + color: workspaceItem.modelData.focused 37 + ? Settings.colors.accent 38 + : "transparent" 39 40 + Behavior on color { 41 + ColorAnimation { duration: 150 } 42 + } 43 44 + Text { 45 + anchors.centerIn: parent 46 + text: workspaceItem.modelData.id 47 + color: workspaceItem.modelData.focused 48 + ? Settings.colors.background 49 + : Settings.colors.foreground 50 + font { 51 + pixelSize: 12 52 + weight: Font.Medium 53 + } 54 + opacity: workspaceItem.modelData.focused ? 1.0 : 0.6 55 56 + Behavior on color { 57 + ColorAnimation { duration: 150 } 58 + } 59 + Behavior on opacity { 60 + NumberAnimation { duration: 150 } 61 + } 62 + } 63 + } 64 65 + MouseArea { 66 + anchors.fill: parent 67 + hoverEnabled: true 68 + cursorShape: Qt.PointingHandCursor 69 + onClicked: workspaceItem.modelData.activate() 70 + } 71 + } 72 } 73 } 74 }
+5
home/isabel/gui/quickshell/data/Settings.qml
··· 23 } 24 25 property string wallpaper: "/home/isabel/media/pictures/wallpapers/flowers.jpg"; 26 }
··· 23 } 24 25 property string wallpaper: "/home/isabel/media/pictures/wallpapers/flowers.jpg"; 26 + 27 + // Notification settings 28 + property list<string> notificationBlacklist: [ 29 + "Spotify" 30 + ] 31 }
+47 -25
home/isabel/gui/quickshell/modules/Bar.qml
··· 9 Scope { 10 id: root 11 12 Variants { 13 model: Quickshell.screens 14 15 PanelWindow { 16 property var modelData 17 screen: modelData 18 - implicitWidth: 40 19 color: "transparent" 20 21 anchors { ··· 25 } 26 27 margins { 28 - left: 8 29 - right: 8 30 - top: 8 31 - bottom: 8 32 } 33 34 Rectangle { 35 id: bar 36 anchors.fill: parent 37 - radius: 10 38 color: Settings.colors.background 39 40 ColumnLayout { 41 anchors { 42 - left: parent.left 43 top: parent.top 44 - right: parent.right 45 - topMargin: 15 46 } 47 - 48 - spacing: 15 49 50 Launcher {} 51 Workspaces {} 52 } 53 54 ColumnLayout { 55 anchors { 56 - left: parent.left 57 - right: parent.right 58 - top: parent.verticalCenter 59 } 60 - 61 - spacing: 20 62 63 Clock {} 64 - Noti {} 65 } 66 67 - ColumnLayout { 68 anchors { 69 - left: parent.left 70 bottom: parent.bottom 71 - right: parent.right 72 - bottomMargin: 15 73 } 74 75 - spacing: 15 76 77 - SysTray {} 78 - Volume {} 79 - Network {} 80 } 81 } 82 }
··· 9 Scope { 10 id: root 11 12 + Process { 13 + id: quickSettingsToggle 14 + command: ["qs", "ipc", "call", "quicksettings", "toggle"] 15 + } 16 + 17 Variants { 18 model: Quickshell.screens 19 20 PanelWindow { 21 property var modelData 22 screen: modelData 23 + implicitWidth: 48 24 color: "transparent" 25 26 anchors { ··· 30 } 31 32 margins { 33 + left: 10 34 + top: 10 35 + bottom: 10 36 } 37 38 Rectangle { 39 id: bar 40 anchors.fill: parent 41 + radius: 12 42 color: Settings.colors.background 43 44 + // Top section - Launcher & Workspaces 45 ColumnLayout { 46 anchors { 47 + horizontalCenter: parent.horizontalCenter 48 top: parent.top 49 + topMargin: 12 50 } 51 + spacing: 16 52 53 Launcher {} 54 + 55 + BarDivider {} 56 + 57 Workspaces {} 58 } 59 60 + // Center section - Clock 61 ColumnLayout { 62 anchors { 63 + horizontalCenter: parent.horizontalCenter 64 + verticalCenter: parent.verticalCenter 65 } 66 + spacing: 16 67 68 Clock {} 69 } 70 71 + // Bottom section - System tray, Notis, Volume, Network 72 + Item { 73 anchors { 74 + horizontalCenter: parent.horizontalCenter 75 bottom: parent.bottom 76 + bottomMargin: 12 77 } 78 + implicitWidth: bottomColumn.implicitWidth 79 + implicitHeight: bottomColumn.implicitHeight 80 81 + MouseArea { 82 + anchors.fill: parent 83 + hoverEnabled: true 84 + cursorShape: Qt.PointingHandCursor 85 + onClicked: quickSettingsToggle.running = true; 86 + } 87 88 + ColumnLayout { 89 + id: bottomColumn 90 + anchors.fill: parent 91 + spacing: 12 92 + 93 + SysTray {} 94 + 95 + BarDivider {} 96 + 97 + Noti { } 98 + Volume {} 99 + Network {} 100 + Battery {} 101 + } 102 } 103 } 104 }
+259
home/isabel/gui/quickshell/modules/Osd.qml
···
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import Quickshell 4 + import Quickshell.Widgets 5 + import Quickshell.Wayland 6 + import Quickshell.Services.Notifications as QsNotifications 7 + import "root:/data" 8 + import "root:/components" 9 + import "root:/services" 10 + 11 + Scope { 12 + id: root 13 + 14 + // OSD State 15 + property string osdType: "" 16 + property real progress: 0 17 + property string iconSource: "" 18 + property string osdText: "" 19 + property bool osdVisible: false 20 + 21 + // Notification State 22 + property var currentNotification: null 23 + property bool notificationVisible: false 24 + 25 + // Connect to Pipewire service for volume changes 26 + Connections { 27 + target: Pipewire 28 + function onSinkVolumeChanged() { showVolumeOsd(); } 29 + function onSinkMutedChanged() { showVolumeOsd(); } 30 + } 31 + 32 + function showVolumeOsd(): void { 33 + root.osdType = "volume"; 34 + root.progress = Pipewire.sinkVolume; 35 + root.iconSource = Pipewire.sinkIcon; 36 + root.osdText = Pipewire.sinkVolumeText; 37 + root.osdVisible = true; 38 + osdHideTimer.restart(); 39 + } 40 + 41 + function showNotification(notification: QsNotifications.Notification): void { 42 + root.currentNotification = notification; 43 + root.notificationVisible = true; 44 + notificationHideTimer.restart(); 45 + } 46 + 47 + Timer { 48 + id: osdHideTimer 49 + interval: 1500 50 + onTriggered: root.osdVisible = false 51 + } 52 + 53 + Timer { 54 + id: notificationHideTimer 55 + interval: 4000 56 + onTriggered: root.notificationVisible = false 57 + } 58 + 59 + // Connect to the shared notification service 60 + Connections { 61 + target: Notifications 62 + function onNewNotification(notification) { 63 + root.showNotification(notification); 64 + } 65 + } 66 + 67 + Variants { 68 + model: Quickshell.screens 69 + 70 + PanelWindow { 71 + id: osdWindow 72 + property var modelData 73 + screen: modelData 74 + 75 + visible: root.osdVisible 76 + color: "transparent" 77 + 78 + WlrLayershell.namespace: "osd" 79 + WlrLayershell.layer: WlrLayer.Overlay 80 + exclusionMode: ExclusionMode.Ignore 81 + 82 + anchors { 83 + bottom: true 84 + } 85 + 86 + implicitWidth: 320 87 + implicitHeight: 80 88 + 89 + margins { 90 + bottom: 80 91 + } 92 + 93 + Rectangle { 94 + id: osdContainer 95 + anchors.centerIn: parent 96 + width: 300 97 + height: 56 98 + radius: 14 99 + color: Settings.colors.background 100 + 101 + RowLayout { 102 + anchors { 103 + fill: parent 104 + leftMargin: 18 105 + rightMargin: 18 106 + } 107 + spacing: 14 108 + 109 + MyIcon { 110 + icon: root.iconSource 111 + size: 22 112 + Layout.alignment: Qt.AlignVCenter 113 + } 114 + 115 + Rectangle { 116 + Layout.fillWidth: true 117 + Layout.preferredHeight: 6 118 + Layout.alignment: Qt.AlignVCenter 119 + radius: 3 120 + color: Settings.colors.backgroundLighter 121 + 122 + Rectangle { 123 + width: parent.width * Math.min(root.progress, 1.0) 124 + height: parent.height 125 + radius: 3 126 + color: root.muted ? Settings.colors.error : Settings.colors.accent 127 + 128 + Behavior on width { 129 + SmoothedAnimation { velocity: 600 } 130 + } 131 + } 132 + } 133 + 134 + Text { 135 + text: root.osdText 136 + color: Settings.colors.foreground 137 + font { 138 + pixelSize: 13 139 + weight: Font.Medium 140 + } 141 + Layout.preferredWidth: 40 142 + Layout.alignment: Qt.AlignVCenter 143 + horizontalAlignment: Text.AlignRight 144 + } 145 + } 146 + } 147 + } 148 + } 149 + 150 + // Notification Popup 151 + Variants { 152 + model: Quickshell.screens 153 + 154 + PanelWindow { 155 + id: notificationWindow 156 + property var modelData 157 + screen: modelData 158 + 159 + visible: root.notificationVisible && root.currentNotification !== null 160 + color: "transparent" 161 + 162 + WlrLayershell.namespace: "notification" 163 + WlrLayershell.layer: WlrLayer.Overlay 164 + exclusionMode: ExclusionMode.Ignore 165 + 166 + anchors { 167 + top: true 168 + } 169 + 170 + implicitWidth: 420 171 + implicitHeight: 100 172 + 173 + margins { 174 + top: 16 175 + } 176 + 177 + Rectangle { 178 + id: notificationContainer 179 + anchors { 180 + fill: parent 181 + margins: 8 182 + } 183 + radius: 12 184 + color: Settings.colors.background 185 + 186 + RowLayout { 187 + anchors { 188 + fill: parent 189 + margins: 14 190 + } 191 + spacing: 12 192 + 193 + IconImage { 194 + source: Quickshell.iconPath(root.currentNotification?.appIcon ?? "application-x-executable") 195 + implicitSize: 40 196 + Layout.alignment: Qt.AlignTop 197 + } 198 + 199 + ColumnLayout { 200 + Layout.fillWidth: true 201 + Layout.fillHeight: true 202 + spacing: 4 203 + 204 + Text { 205 + text: root.currentNotification?.appName ?? "" 206 + color: Settings.colors.foreground 207 + font { 208 + pixelSize: 13 209 + weight: Font.Bold 210 + } 211 + elide: Text.ElideRight 212 + Layout.fillWidth: true 213 + } 214 + 215 + Text { 216 + text: root.currentNotification?.summary ?? "" 217 + color: Settings.colors.foreground 218 + font { 219 + pixelSize: 12 220 + weight: Font.Medium 221 + } 222 + elide: Text.ElideRight 223 + Layout.fillWidth: true 224 + visible: text !== "" 225 + } 226 + 227 + Text { 228 + text: root.currentNotification?.body ?? "" 229 + color: Settings.colors.foreground 230 + opacity: 0.8 231 + font.pixelSize: 11 232 + elide: Text.ElideRight 233 + wrapMode: Text.WordWrap 234 + maximumLineCount: 2 235 + Layout.fillWidth: true 236 + } 237 + } 238 + 239 + IconButton { 240 + icon: "window-close-symbolic" 241 + size: 14 242 + Layout.alignment: Qt.AlignTop 243 + onClicked: { 244 + root.currentNotification?.dismiss(); 245 + root.notificationVisible = false; 246 + } 247 + } 248 + } 249 + 250 + // Click to dismiss 251 + MouseArea { 252 + anchors.fill: parent 253 + z: -1 254 + onClicked: root.notificationVisible = false 255 + } 256 + } 257 + } 258 + } 259 + }
+185
home/isabel/gui/quickshell/modules/QuickSettingsPopup.qml
···
··· 1 + import QtQuick 2 + import QtQuick.Layouts 3 + import QtQuick.Controls 4 + import Quickshell 5 + import Quickshell.Widgets 6 + import Quickshell.Wayland 7 + import Quickshell.Io 8 + import Quickshell.Services.Notifications as QsNotifications 9 + import "root:/data" 10 + import "root:/components" 11 + import "root:/services" 12 + 13 + 14 + Scope { 15 + id: root 16 + 17 + IpcHandler { 18 + target: "quicksettings" 19 + 20 + function toggle(): void { 21 + if (loader.active) { 22 + loader.active = false; 23 + } else { 24 + loader.activeAsync = true; 25 + } 26 + } 27 + } 28 + 29 + LazyLoader { 30 + id: loader 31 + 32 + PanelWindow { 33 + id: popup 34 + color: "transparent" 35 + 36 + anchors.left: true 37 + margins.left: 70 38 + 39 + implicitWidth: screen.width * 0.21 40 + implicitHeight: screen.height * 0.98 41 + exclusionMode: ExclusionMode.Ignore 42 + WlrLayershell.layer: WlrLayer.Overlay 43 + 44 + Rectangle { 45 + anchors.fill: parent 46 + radius: 12 47 + color: Settings.colors.background 48 + 49 + ColumnLayout { 50 + spacing: 12 51 + anchors { 52 + fill: parent 53 + margins: 16 54 + } 55 + 56 + // Header 57 + RowLayout { 58 + Layout.fillWidth: true 59 + spacing: 8 60 + 61 + Text { 62 + text: "Notifications" 63 + color: Settings.colors.foreground 64 + font { 65 + pixelSize: 18 66 + weight: Font.Bold 67 + } 68 + } 69 + 70 + Item { Layout.fillWidth: true } 71 + 72 + IconButton { 73 + icon: "edit-clear-all-symbolic" 74 + size: 16 75 + onClicked: { 76 + for (const n of Notifications.list) { 77 + n.dismiss(); 78 + } 79 + popup.visible = false; 80 + } 81 + } 82 + } 83 + 84 + // Notifications List 85 + ListView { 86 + id: notiList 87 + model: Notifications.list 88 + Layout.fillWidth: true 89 + Layout.fillHeight: true 90 + spacing: 8 91 + clip: true 92 + 93 + ScrollBar.vertical: ScrollBar { 94 + policy: ScrollBar.AsNeeded 95 + } 96 + 97 + delegate: Rectangle { 98 + required property QsNotifications.Notification modelData 99 + width: ListView.view.width 100 + height: 72 101 + radius: 8 102 + color: Settings.colors.backgroundLighter 103 + 104 + RowLayout { 105 + anchors { 106 + fill: parent 107 + margins: 10 108 + } 109 + spacing: 10 110 + 111 + IconImage { 112 + source: Quickshell.iconPath(modelData?.appIcon ?? "application-x-executable") 113 + 114 + implicitSize: 40 115 + Layout.alignment: Qt.AlignVCenter 116 + } 117 + 118 + ColumnLayout { 119 + Layout.fillWidth: true 120 + Layout.fillHeight: true 121 + spacing: 2 122 + 123 + Text { 124 + text: modelData.appName 125 + color: Settings.colors.foreground 126 + font { 127 + pixelSize: 13 128 + weight: Font.Medium 129 + } 130 + elide: Text.ElideRight 131 + Layout.fillWidth: true 132 + } 133 + 134 + Text { 135 + text: modelData.body 136 + color: Settings.colors.foreground 137 + opacity: 0.8 138 + font.pixelSize: 12 139 + elide: Text.ElideRight 140 + maximumLineCount: 2 141 + wrapMode: Text.WordWrap 142 + Layout.fillWidth: true 143 + } 144 + } 145 + 146 + IconButton { 147 + icon: "window-close-symbolic" 148 + size: 14 149 + Layout.alignment: Qt.AlignTop 150 + onClicked: { 151 + modelData.dismiss(); 152 + if (Notifications.list.length <= 0) { 153 + popup.visible = false; 154 + } 155 + } 156 + } 157 + } 158 + } 159 + } 160 + 161 + // Separator before Media 162 + Rectangle { 163 + Layout.fillWidth: true 164 + Layout.preferredHeight: 1 165 + color: Settings.colors.backgroundLightest 166 + visible: Media.players.length > 0 167 + } 168 + 169 + // Media Players Section 170 + MediaPlayers {} 171 + 172 + // Separator 173 + Rectangle { 174 + Layout.fillWidth: true 175 + Layout.preferredHeight: 1 176 + color: Settings.colors.backgroundLightest 177 + } 178 + 179 + // Quick Settings Section 180 + QuickSettings {} 181 + } 182 + } 183 + } 184 + } 185 + }
+71
home/isabel/gui/quickshell/services/Bluetooth.qml
···
··· 1 + pragma Singleton 2 + 3 + import Quickshell 4 + import Quickshell.Bluetooth 5 + import QtQuick 6 + 7 + Singleton { 8 + id: root 9 + 10 + // Adapter state 11 + readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter 12 + readonly property bool powered: adapter?.enabled ?? false 13 + readonly property int state: adapter?.state ?? BluetoothAdapterState.Disabled 14 + 15 + // Device state 16 + readonly property var devices: adapter?.devices ?? [] 17 + readonly property BluetoothDevice connectedDevice: { 18 + if (!adapter) return null; 19 + for (let i = 0; i < devices.count; i++) { 20 + const device = devices.values[i]; 21 + if (device.connected) return device; 22 + } 23 + return null; 24 + } 25 + readonly property bool connected: connectedDevice !== null 26 + 27 + // Icon based on state 28 + readonly property string icon: { 29 + if (state === BluetoothAdapterState.Blocked) return "bluetooth-disabled-symbolic"; 30 + if (!powered) return "bluetooth-disabled-symbolic"; 31 + if (connected) return "bluetooth-active-symbolic"; 32 + return "bluetooth-symbolic"; 33 + } 34 + 35 + readonly property string statusText: { 36 + if (state === BluetoothAdapterState.Blocked) return "Blocked"; 37 + if (state === BluetoothAdapterState.Enabling) return "Enabling..."; 38 + if (state === BluetoothAdapterState.Disabling) return "Disabling..."; 39 + if (!powered) return "Bluetooth Off"; 40 + if (connected) return connectedDevice.name; 41 + return "Not Connected"; 42 + } 43 + 44 + reloadableId: "bluetooth" 45 + 46 + function toggle() { 47 + if (adapter) { 48 + adapter.enabled = !adapter.enabled; 49 + } 50 + } 51 + 52 + function connectDevice(device) { 53 + device.connect(); 54 + } 55 + 56 + function disconnectDevice(device) { 57 + device.disconnect(); 58 + } 59 + 60 + function startDiscovery() { 61 + if (adapter) { 62 + adapter.discovering = true; 63 + } 64 + } 65 + 66 + function stopDiscovery() { 67 + if (adapter) { 68 + adapter.discovering = false; 69 + } 70 + } 71 + }
+60
home/isabel/gui/quickshell/services/Brightness.qml
···
··· 1 + pragma Singleton 2 + 3 + import Quickshell 4 + import Quickshell.Io 5 + import QtQuick 6 + 7 + Singleton { 8 + id: root 9 + 10 + // Brightness state 11 + property real brightness: 1.0 12 + property bool available: false 13 + 14 + // Icon based on brightness level 15 + readonly property string icon: { 16 + if (brightness > 0.66) return "display-brightness-high-symbolic"; 17 + if (brightness > 0.33) return "display-brightness-medium-symbolic"; 18 + return "display-brightness-low-symbolic"; 19 + } 20 + 21 + // Brightness percentage string 22 + readonly property string brightnessText: Math.round(brightness * 100) + "%" 23 + 24 + function setBrightness(value: real): void { 25 + const clampedValue = Math.max(0.05, Math.min(1, value)); 26 + brightness = clampedValue; 27 + setBrightnessProc.command = ["brightnessctl", "set", Math.round(clampedValue * 100) + "%"]; 28 + setBrightnessProc.running = true; 29 + } 30 + 31 + function increaseBrightness(step = 0.05): void { 32 + setBrightness(brightness + step); 33 + } 34 + 35 + function decreaseBrightness(step = 0.05): void { 36 + setBrightness(brightness - step); 37 + } 38 + 39 + Component.onCompleted: getBrightnessProc.running = true 40 + 41 + Process { 42 + id: getBrightnessProc 43 + command: ["bash", "-c", "brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%'"] 44 + stdout: SplitParser { 45 + onRead: data => { 46 + const val = parseInt(data.trim()); 47 + if (!isNaN(val)) { 48 + root.brightness = val / 100; 49 + root.available = true; 50 + } 51 + } 52 + } 53 + } 54 + 55 + Process { 56 + id: setBrightnessProc 57 + } 58 + 59 + reloadableId: "brightness" 60 + }
+122 -25
home/isabel/gui/quickshell/services/Networking.qml
··· 1 - // stolen from https://github.com/caelestia-dots/shell/blob/cf1180df7ba9c8f1180ab3a902746d4f169c556f/services/Network.qml 2 - // thankies :D 3 4 pragma Singleton 5 ··· 10 Singleton { 11 id: root 12 13 readonly property list<AccessPoint> networks: [] 14 - readonly property AccessPoint active: networks.find(n => n.active) ?? null 15 16 reloadableId: "network" 17 18 Process { 19 running: true 20 command: ["nmcli", "m"] 21 stdout: SplitParser { 22 - onRead: getNetworks.running = true 23 } 24 } 25 26 Process { 27 id: getNetworks 28 - running: true 29 command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID", "d", "w"] 30 - environment: ({ 31 - LANG: "C", 32 - LC_ALL: "C" 33 - }) 34 stdout: StdioCollector { 35 onStreamFinished: { 36 const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED"; 37 const rep = new RegExp("\\\\:", "g"); 38 const rep2 = new RegExp(PLACEHOLDER, "g"); 39 40 - const networks = text.trim().split("\n").map(n => { 41 const net = n.replace(rep, PLACEHOLDER).split(":"); 42 return { 43 active: net[0] === "yes", 44 - strength: parseInt(net[1]), 45 - frequency: parseInt(net[2]), 46 - ssid: net[3], 47 bssid: net[4]?.replace(rep2, ":") ?? "" 48 }; 49 - }); 50 const rNetworks = root.networks; 51 52 - const destroyed = rNetworks.filter(rn => !networks.find(n => n.frequency === rn.frequency && n.ssid === rn.ssid && n.bssid === rn.bssid)); 53 - for (const network of destroyed) 54 - rNetworks.splice(rNetworks.indexOf(network), 1).forEach(n => n.destroy()); 55 56 for (const network of networks) { 57 - const match = rNetworks.find(n => n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid); 58 if (match) { 59 match.lastIpcObject = network; 60 } else { 61 - rNetworks.push(apComp.createObject(root, { 62 - lastIpcObject: network 63 - })); 64 } 65 } 66 } ··· 75 readonly property int frequency: lastIpcObject.frequency 76 readonly property bool active: lastIpcObject.active 77 readonly property string icon: { 78 - if (!active) return "network-wireless-signal-none-symbolic"; 79 - 80 if (strength >= 75) return "network-wireless-signal-excellent-symbolic"; 81 if (strength >= 50) return "network-wireless-signal-good-symbolic"; 82 if (strength >= 25) return "network-wireless-signal-ok-symbolic"; 83 - return "network-wireless-signal-weak-symbolic"; 84 } 85 } 86 87 Component { 88 id: apComp 89 - 90 AccessPoint {} 91 } 92 }
··· 1 + // Network service with WiFi and Ethernet support 2 3 pragma Singleton 4 ··· 9 Singleton { 10 id: root 11 12 + // WiFi 13 readonly property list<AccessPoint> networks: [] 14 + readonly property AccessPoint activeWifi: networks.find(n => n.active) ?? null 15 + readonly property bool wifiEnabled: internal.wifiEnabled 16 + 17 + // Ethernet 18 + readonly property bool ethernetConnected: internal.ethernetConnected 19 + readonly property string ethernetDevice: internal.ethernetDevice 20 + 21 + // Combined state 22 + readonly property bool connected: ethernetConnected || activeWifi !== null 23 + readonly property string connectionType: ethernetConnected ? "ethernet" : (activeWifi ? "wifi" : "none") 24 + 25 + readonly property string icon: { 26 + if (ethernetConnected) return "network-wired-symbolic"; 27 + if (activeWifi) return activeWifi.icon; 28 + if (!wifiEnabled) return "network-wireless-disabled-symbolic"; 29 + return "network-wireless-offline-symbolic"; 30 + } 31 + 32 + readonly property string statusText: { 33 + if (ethernetConnected) return ethernetDevice; 34 + if (activeWifi) return activeWifi.ssid; 35 + return "Disconnected"; 36 + } 37 38 reloadableId: "network" 39 40 + function toggleWifi() { 41 + toggleWifiProc.running = true; 42 + } 43 + 44 + function reload() { 45 + checkStatus.running = true; 46 + } 47 + 48 + function connectToNetwork(ssid) { 49 + connectProc.command = ["nmcli", "d", "wifi", "connect", ssid]; 50 + connectProc.running = true; 51 + } 52 + 53 + QtObject { 54 + id: internal 55 + property bool wifiEnabled: true 56 + property bool ethernetConnected: false 57 + property string ethernetDevice: "" 58 + } 59 + 60 + // Monitor for network changes 61 Process { 62 running: true 63 command: ["nmcli", "m"] 64 stdout: SplitParser { 65 + onRead: checkStatus.running = true 66 + } 67 + } 68 + 69 + // Check overall network status 70 + Process { 71 + id: checkStatus 72 + running: true 73 + command: ["bash", "-c", ` 74 + # Check WiFi status 75 + wifi_status=$(nmcli radio wifi 2>/dev/null) 76 + echo "WIFI:$wifi_status" 77 + 78 + # Check Ethernet 79 + eth_info=$(nmcli -t -f TYPE,STATE,DEVICE,CONNECTION device 2>/dev/null | grep '^ethernet:connected' | head -1) 80 + if [ -n "$eth_info" ]; then 81 + eth_name=$(echo "$eth_info" | cut -d: -f4) 82 + echo "ETH:connected:$eth_name" 83 + else 84 + echo "ETH:disconnected:" 85 + fi 86 + `] 87 + environment: ({ LANG: "C", LC_ALL: "C" }) 88 + stdout: SplitParser { 89 + onRead: data => { 90 + const line = data.trim(); 91 + if (line.startsWith("WIFI:")) { 92 + internal.wifiEnabled = line.includes("enabled"); 93 + } else if (line.startsWith("ETH:")) { 94 + const parts = line.split(":"); 95 + internal.ethernetConnected = parts[1] === "connected"; 96 + internal.ethernetDevice = parts[2] || ""; 97 + } 98 + } 99 + } 100 + onExited: { 101 + if (internal.wifiEnabled) { 102 + getNetworks.running = true; 103 + } 104 } 105 } 106 107 Process { 108 + id: toggleWifiProc 109 + command: ["nmcli", "radio", "wifi", internal.wifiEnabled ? "off" : "on"] 110 + onExited: checkStatus.running = true 111 + } 112 + 113 + Process { 114 + id: connectProc 115 + onExited: checkStatus.running = true 116 + } 117 + 118 + Process { 119 id: getNetworks 120 command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID", "d", "w"] 121 + environment: ({ LANG: "C", LC_ALL: "C" }) 122 stdout: StdioCollector { 123 onStreamFinished: { 124 const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED"; 125 const rep = new RegExp("\\\\:", "g"); 126 const rep2 = new RegExp(PLACEHOLDER, "g"); 127 128 + const networks = text.trim().split("\n").filter(l => l.length > 0).map(n => { 129 const net = n.replace(rep, PLACEHOLDER).split(":"); 130 return { 131 active: net[0] === "yes", 132 + strength: parseInt(net[1]) || 0, 133 + frequency: parseInt(net[2]) || 0, 134 + ssid: net[3] || "", 135 bssid: net[4]?.replace(rep2, ":") ?? "" 136 }; 137 + }).filter(n => n.ssid !== ""); 138 + 139 const rNetworks = root.networks; 140 141 + // Remove networks that no longer exist 142 + const destroyed = rNetworks.filter(rn => !networks.find(n => 143 + n.frequency === rn.frequency && n.ssid === rn.ssid && n.bssid === rn.bssid 144 + )); 145 + for (const network of destroyed) { 146 + const idx = rNetworks.indexOf(network); 147 + if (idx >= 0) { 148 + rNetworks.splice(idx, 1); 149 + network.destroy(); 150 + } 151 + } 152 153 + // Update or add networks 154 for (const network of networks) { 155 + const match = rNetworks.find(n => 156 + n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid 157 + ); 158 if (match) { 159 match.lastIpcObject = network; 160 } else { 161 + rNetworks.push(apComp.createObject(root, { lastIpcObject: network })); 162 } 163 } 164 } ··· 173 readonly property int frequency: lastIpcObject.frequency 174 readonly property bool active: lastIpcObject.active 175 readonly property string icon: { 176 if (strength >= 75) return "network-wireless-signal-excellent-symbolic"; 177 if (strength >= 50) return "network-wireless-signal-good-symbolic"; 178 if (strength >= 25) return "network-wireless-signal-ok-symbolic"; 179 + if (strength > 0) return "network-wireless-signal-weak-symbolic"; 180 + return "network-wireless-signal-none-symbolic"; 181 } 182 } 183 184 Component { 185 id: apComp 186 AccessPoint {} 187 } 188 } 189 +
+12 -1
home/isabel/gui/quickshell/services/Notifications.qml
··· 3 import Quickshell 4 import Quickshell.Io 5 import Quickshell.Services.Notifications 6 7 Singleton { 8 id: root 9 10 - property list<Notification> list: notifactionServer.trackedNotifications.values.filter(notification => notification.tracked) 11 12 NotificationServer { 13 id: notifactionServer 14 onNotification: (notification) => { 15 notification.tracked = true 16 } 17 18 actionsSupported: true ··· 28 } 29 } 30 }
··· 3 import Quickshell 4 import Quickshell.Io 5 import Quickshell.Services.Notifications 6 + import "root:/data" 7 8 Singleton { 9 id: root 10 11 + property bool dndEnabled: false 12 + property list<Notification> list: notifactionServer.trackedNotifications.values.filter( 13 + notification => notification.tracked && !root.dndEnabled && !Settings.notificationBlacklist.includes(notification.appName) 14 + ) 15 + 16 + signal newNotification(Notification notification) 17 18 NotificationServer { 19 id: notifactionServer 20 onNotification: (notification) => { 21 notification.tracked = true 22 + // Only emit signal if not in DND and not blacklisted 23 + if (!root.dndEnabled && !Settings.notificationBlacklist.includes(notification.appName)) { 24 + root.newNotification(notification) 25 + } 26 } 27 28 actionsSupported: true ··· 38 } 39 } 40 } 41 +
+90
home/isabel/gui/quickshell/services/Pipewire.qml
···
··· 1 + pragma Singleton 2 + 3 + import Quickshell 4 + import Quickshell.Services.Pipewire as QsPipewire 5 + import QtQuick 6 + 7 + Singleton { 8 + id: root 9 + 10 + // Track the default audio sink 11 + QsPipewire.PwObjectTracker { 12 + objects: [QsPipewire.Pipewire.defaultAudioSink, QsPipewire.Pipewire.defaultAudioSource] 13 + } 14 + 15 + // Default audio sink (speakers/headphones) 16 + readonly property QsPipewire.PwNode sink: QsPipewire.Pipewire.defaultAudioSink 17 + readonly property real sinkVolume: sink?.audio?.volume ?? 0 18 + readonly property bool sinkMuted: sink?.audio?.muted ?? false 19 + 20 + // Default audio source (microphone) 21 + readonly property QsPipewire.PwNode source: QsPipewire.Pipewire.defaultAudioSource 22 + readonly property real sourceVolume: source?.audio?.volume ?? 0 23 + readonly property bool sourceMuted: source?.audio?.muted ?? false 24 + 25 + // All nodes for detailed view 26 + readonly property var nodes: QsPipewire.Pipewire.nodes 27 + 28 + // Icon based on volume and mute state for sink 29 + readonly property string sinkIcon: { 30 + if (sinkMuted) return "audio-volume-muted-symbolic"; 31 + if (sinkVolume > 0.66) return "audio-volume-high-symbolic"; 32 + if (sinkVolume > 0.33) return "audio-volume-medium-symbolic"; 33 + if (sinkVolume > 0) return "audio-volume-low-symbolic"; 34 + return "audio-volume-muted-symbolic"; 35 + } 36 + 37 + // Icon based on volume and mute state for source 38 + readonly property string sourceIcon: { 39 + if (sourceMuted) return "microphone-sensitivity-muted-symbolic"; 40 + if (sourceVolume > 0.66) return "microphone-sensitivity-high-symbolic"; 41 + if (sourceVolume > 0.33) return "microphone-sensitivity-medium-symbolic"; 42 + if (sourceVolume > 0) return "microphone-sensitivity-low-symbolic"; 43 + return "microphone-sensitivity-muted-symbolic"; 44 + } 45 + 46 + // Volume percentage string 47 + readonly property string sinkVolumeText: Math.round(sinkVolume * 100) + "%" 48 + readonly property string sourceVolumeText: Math.round(sourceVolume * 100) + "%" 49 + 50 + // Functions to control sink (speakers) 51 + function setSinkVolume(volume: real): void { 52 + if (sink?.audio) { 53 + sink.audio.volume = Math.max(0, Math.min(1, volume)); 54 + } 55 + } 56 + 57 + function toggleSinkMute(): void { 58 + if (sink?.audio) { 59 + sink.audio.muted = !sink.audio.muted; 60 + } 61 + } 62 + 63 + function setSinkMuted(muted: bool): void { 64 + if (sink?.audio) { 65 + sink.audio.muted = muted; 66 + } 67 + } 68 + 69 + // Functions to control source (microphone) 70 + function setSourceVolume(volume: real): void { 71 + if (source?.audio) { 72 + source.audio.volume = Math.max(0, Math.min(1, volume)); 73 + } 74 + } 75 + 76 + function toggleSourceMute(): void { 77 + if (source?.audio) { 78 + source.audio.muted = !source.audio.muted; 79 + } 80 + } 81 + 82 + function setSourceMuted(muted: bool): void { 83 + if (source?.audio) { 84 + source.audio.muted = muted; 85 + } 86 + } 87 + 88 + reloadableId: "pipewire" 89 + } 90 +
+79
home/isabel/gui/quickshell/services/UPower.qml
···
··· 1 + pragma Singleton 2 + 3 + import Quickshell 4 + import Quickshell.Services.UPower as QsUPower 5 + import QtQuick 6 + 7 + Singleton { 8 + id: root 9 + 10 + // Display device represents the overall system battery 11 + readonly property QsUPower.UPowerDevice displayDevice: QsUPower.UPower.displayDevice 12 + 13 + // Battery state 14 + readonly property bool isPresent: displayDevice?.isPresent ?? false 15 + readonly property real percentage: displayDevice?.percentage ?? 0 16 + readonly property int state: displayDevice?.state ?? QsUPower.UPowerDeviceState.Unknown 17 + readonly property bool charging: state === QsUPower.UPowerDeviceState.Charging 18 + readonly property bool discharging: state === QsUPower.UPowerDeviceState.Discharging 19 + readonly property bool fullyCharged: state === QsUPower.UPowerDeviceState.FullyCharged 20 + 21 + // Time estimates (in seconds) 22 + readonly property real timeToEmpty: displayDevice?.timeToEmpty ?? 0 23 + readonly property real timeToFull: displayDevice?.timeToFull ?? 0 24 + 25 + // Energy info 26 + readonly property real energy: displayDevice?.energy ?? 0 27 + readonly property real energyFull: displayDevice?.energyFull ?? 0 28 + readonly property real energyRate: displayDevice?.energyRate ?? 0 29 + 30 + // Icon based on percentage and charging state 31 + readonly property string icon: { 32 + if (!isPresent) return "battery-missing-symbolic"; 33 + 34 + const level = percentage; 35 + let iconName = "battery"; 36 + 37 + if (level >= 90) iconName = "battery-full"; 38 + else if (level >= 60) iconName = "battery-good"; 39 + else if (level >= 30) iconName = "battery-medium"; 40 + else if (level >= 10) iconName = "battery-low"; 41 + else iconName = "battery-empty"; 42 + 43 + if (charging) iconName += "-charging"; 44 + 45 + return iconName + "-symbolic"; 46 + } 47 + 48 + // Status text 49 + readonly property string statusText: { 50 + if (!isPresent) return "No Battery"; 51 + if (fullyCharged) return "Fully Charged"; 52 + if (charging) { 53 + const time = formatTime(timeToFull); 54 + return time ? `Charging (${time})` : "Charging"; 55 + } 56 + if (discharging) { 57 + const time = formatTime(timeToEmpty); 58 + return time ? `${time} remaining` : "Discharging"; 59 + } 60 + return `${Math.round(percentage)}%`; 61 + } 62 + 63 + // Format time in seconds to human-readable string 64 + function formatTime(seconds: real): string { 65 + if (seconds <= 0) return ""; 66 + const hours = Math.floor(seconds / 3600); 67 + const minutes = Math.floor((seconds % 3600) / 60); 68 + if (hours > 0) { 69 + return `${hours}h ${minutes}m`; 70 + } 71 + return `${minutes}m`; 72 + } 73 + 74 + // All power devices (for detailed view) 75 + readonly property var devices: QsUPower.UPower.devices 76 + 77 + reloadableId: "upower" 78 + } 79 +
+6 -3
home/isabel/gui/quickshell/shell.qml
··· 1 - //@ pragma IconTheme Cosmic 2 3 import Quickshell 4 import "root:/modules" 5 6 Scope { 7 - Bar {} 8 - Desktop {} 9 }
··· 1 + //@ pragma IconTheme Adwaita 2 3 import Quickshell 4 import "root:/modules" 5 + import "root:/services" 6 7 Scope { 8 + Bar {} 9 + Desktop {} 10 + Osd {} 11 + QuickSettingsPopup {} 12 }
+2
home/isabel/gui/quickshell.nix
··· 6 paths = [ 7 pkgs.quickshell 8 pkgs.kdePackages.qtimageformats 9 ]; 10 meta.mainProgram = pkgs.quickshell.meta.mainProgram; 11 };
··· 6 paths = [ 7 pkgs.quickshell 8 pkgs.kdePackages.qtimageformats 9 + pkgs.adwaita-icon-theme 10 + pkgs.kdePackages.kirigami.unwrapped 11 ]; 12 meta.mainProgram = pkgs.quickshell.meta.mainProgram; 13 };
+6 -5
home/isabel/gui/vicinae/default.nix
··· 14 extensions = map mkMyVicinaeExt [ 15 { 16 extName = "nix"; 17 - npmDepsHash = "sha256-Zx+QPVWWppz6mvQKyu4c6ND8E4TeeK12assE2khE/sA="; 18 } 19 { 20 extName = "wifi-commander"; ··· 24 extName = "bluetooth"; 25 npmDepsHash = "sha256-cpyuJTc3a7oLibKUY2EhD33w8/35frfwIaGFKFezvts="; 26 } 27 - { 28 - extName = "mullvad"; 29 - npmDepsHash = "sha256-WbnZtsTUMDHh2BojAjHUrca8aBw+OGXMMgX79Ek8wQ0="; 30 - } 31 ]; 32 }; 33 }
··· 14 extensions = map mkMyVicinaeExt [ 15 { 16 extName = "nix"; 17 + npmDepsHash = "sha256-HPWNUznCWVPz39PlPEBR7GpgbC0DuIAvVBdB2GAs47A="; 18 } 19 { 20 extName = "wifi-commander"; ··· 24 extName = "bluetooth"; 25 npmDepsHash = "sha256-cpyuJTc3a7oLibKUY2EhD33w8/35frfwIaGFKFezvts="; 26 } 27 + # broken 28 + # { 29 + # extName = "mullvad"; 30 + # npmDepsHash = "sha256-WbnZtsTUMDHh2BojAjHUrca8aBw+OGXMMgX79Ek8wQ0="; 31 + # } 32 ]; 33 }; 34 }
+2 -2
home/isabel/gui/vicinae/extension.nix
··· 22 fetchFromGitHub { 23 owner = "vicinaehq"; 24 repo = "extensions"; 25 - rev = "ec7334e9bb636f4771580238bd3569b58dbce879"; 26 - hash = "sha256-C2b6upygLE6xUP/cTSKZfVjMXOXOOqpP5Xmgb9r2dhA="; 27 } 28 + "/extensions/${extName}" 29 );
··· 22 fetchFromGitHub { 23 owner = "vicinaehq"; 24 repo = "extensions"; 25 + rev = "62f81e63d0420d6a310092746a96d7c105f7a53e"; 26 + hash = "sha256-Tqd5BOxfCtVWY19Gl32Fq5xsV3sTepItub20OQYgPmU="; 27 } 28 + "/extensions/${extName}" 29 );
+1 -1
home/isabel/packages.nix
··· 54 # jellyfin-media-player 55 # insomnia # rest client 56 swappy # used for screenshot area selection 57 - wl-clipboard 58 wl-gammactl 59 # keep-sorted end 60 ;
··· 54 # jellyfin-media-player 55 # insomnia # rest client 56 swappy # used for screenshot area selection 57 + wl-clipboard-rs 58 wl-gammactl 59 # keep-sorted end 60 ;
+1 -1
home/isabel/system/ssh.nix
··· 36 37 "aphrodite".hostname = "95.111.208.153"; 38 39 - "athena".hostname = "192.168.86.3"; 40 41 "aur.archlinux.org" = { 42 user = "aur";
··· 36 37 "aphrodite".hostname = "95.111.208.153"; 38 39 + "athena".hostname = "192.168.1.135"; 40 41 "aur.archlinux.org" = { 42 user = "aur";
+63 -27
justfile
··· 3 # rebuild is also set as a var so you can add --set to change it if you need to 4 5 rebuild := if os() == "macos" { "sudo darwin-rebuild" } else { "nixos-rebuild" } 6 - system-args := if os() == "macos" { "" } else { "--sudo --no-reexec --log-format internal-json" } 7 - nom-cmd := if os() == "macos" { "nom" } else { "nom --json" } 8 9 [private] 10 default: ··· 13 # rebuild group 14 15 [group('rebuild')] 16 [private] 17 builder goal *args: 18 {{ rebuild }} {{ goal }} \ 19 --flake {{ flake }} \ 20 {{ system-args }} \ 21 {{ args }} \ 22 - |& {{ nom-cmd }} 23 24 [group('rebuild')] 25 deploy host *args: 26 - #!/usr/bin/env bash 27 - before=$(ssh {{ host }} 'readlink -f /run/current-system') 28 - before=$(echo "$before" | tail -n 1) 29 - just builder switch --target-host {{ host }} --use-substitutes {{ args }} 30 - [ $? -eq 0 ] && ssh {{ host }} 'lix-diff "$before" /run/current-system' 31 32 [group('rebuild')] 33 deploy-all: 34 - just deploy minerva 35 - just deploy aphrodite 36 - just deploy skadi 37 - just deploy hephaestus 38 - just deploy isis 39 40 # rebuild the boot 41 [group('rebuild')] 42 boot *args: (builder "boot" args) 43 44 # test what happens when you switch 45 [group('rebuild')] 46 test *args: (builder "test" args) 47 48 # switch the new system configuration 49 [group('rebuild')] 50 switch *args: 51 - #!/usr/bin/env bash 52 - before="$(readlink -f /run/current-system)" 53 - just builder switch {{ args }} 54 - [ $? -eq 0 ] && lix-diff "$before" /run/current-system 55 56 [group('rebuild')] 57 [macos] 58 provision host: 59 sudo nix run github:LnL7/nix-darwin -- switch --flake {{ flake }}#{{ host }} 60 sudo -i nix-env --uninstall lix # we need to remove the none declarative install of lix 61 62 # package group 63 - 64 # build the package, you must specify the package you want to build 65 - [group('package')] 66 - build pkg: 67 - nix build {{ flake }}#{{ pkg }} \ 68 - --log-format internal-json \ 69 - -v \ 70 - |& nom --json 71 72 # build the iso image, you must specify the image you want to build 73 [group('package')] 74 - iso image: (build "nixosConfigurations." + image + ".config.system.build.isoImage") 75 76 # build the tarball, you must specify the host you want to build 77 [group('package')] 78 tar host: 79 sudo nix run {{ flake }}#nixosConfigurations.{{ host }}.config.system.build.tarballBuilder 80 ··· 82 83 # check the flake for errors 84 [group('dev')] 85 check *args: 86 nix flake check --option allow-import-from-derivation false {{ args }} 87 88 [group('dev')] 89 repl-host host=`hostname`: 90 nix repl .#nixosConfigurations.{{ host }} 91 92 # update a set of given inputs 93 [group('dev')] 94 update *input: 95 nix flake update {{ input }} \ 96 --refresh \ ··· 100 101 # build & serve the docs locally 102 [group('dev')] 103 serve: 104 nix run {{ flake }}#docs.serve 105 106 # push to the mirrors 107 [group('dev')] 108 push-mirrors: 109 git push git@gitlab.com:isabelroses/dotfiles.git 110 git push --mirror ssh://git@codeberg.org/isabel/dotfiles.git ··· 112 113 # rotate all secrets 114 [group('dev')] 115 roate-secrets: 116 - find secrets/ -name "*.yaml" | xargs -I {} sops rotate -i {} 117 118 # update the secret keys 119 [group('dev')] 120 update-secrets: 121 - find secrets/ -name "*.yaml" | xargs -I {} sops updatekeys -y {} 122 123 # utils group 124 ··· 126 127 # verify the integrity of the nix store 128 [group('utils')] 129 verify *args: 130 nix-store --verify {{ args }} 131 132 # repairs the nix store from any breakages it may have 133 [group('utils')] 134 repair: (verify "--check-contents --repair") 135 136 # clean the nix store and optimise it 137 [group('utils')] 138 clean: 139 nix-collect-garbage --delete-older-than 3d 140 nix store optimise
··· 3 # rebuild is also set as a var so you can add --set to change it if you need to 4 5 rebuild := if os() == "macos" { "sudo darwin-rebuild" } else { "nixos-rebuild" } 6 + system-args := if os() == "macos" { "" } else { "--sudo --no-reexec" } 7 8 [private] 9 default: ··· 12 # rebuild group 13 14 [group('rebuild')] 15 + [no-exit-message] 16 [private] 17 builder goal *args: 18 + #!/usr/bin/env bash 19 + set -euo pipefail 20 {{ rebuild }} {{ goal }} \ 21 --flake {{ flake }} \ 22 + --log-format internal-json \ 23 {{ system-args }} \ 24 {{ args }} \ 25 + |& nom --json 26 27 [group('rebuild')] 28 + [no-exit-message] 29 deploy host *args: 30 + #!/usr/bin/env bash 31 + set -euo pipefail 32 + before=$(ssh -q {{ host }} 'readlink /run/current-system') 33 + just builder switch --target-host {{ host }} --use-substitutes {{ args }} 34 + 35 + if [[ -n "${DEPLOY_SUMMARY:-}" ]]; then 36 + { 37 + echo "===== {{ host }} =====" 38 + ssh -q {{ host }} lix diff "$before" 39 + echo 40 + } >> "$DEPLOY_SUMMARY" 41 + else 42 + ssh {{ host }} lix diff "$before" 43 + fi 44 45 [group('rebuild')] 46 + [no-exit-message] 47 deploy-all: 48 + #!/usr/bin/env bash 49 + set -euo pipefail 50 + export DEPLOY_SUMMARY=".deploy-summary" 51 + : > "$DEPLOY_SUMMARY" 52 + 53 + just deploy minerva 54 + just deploy athena 55 + just deploy aphrodite 56 + just deploy skadi 57 + just deploy hephaestus 58 + just deploy isis 59 + 60 + echo 61 + echo "===== DEPLOYMENT SUMMARY =====" 62 + cat "$DEPLOY_SUMMARY" 63 + rm "$DEPLOY_SUMMARY" 64 65 # rebuild the boot 66 [group('rebuild')] 67 + [no-exit-message] 68 boot *args: (builder "boot" args) 69 70 # test what happens when you switch 71 [group('rebuild')] 72 + [no-exit-message] 73 test *args: (builder "test" args) 74 75 # switch the new system configuration 76 [group('rebuild')] 77 + [no-exit-message] 78 switch *args: 79 + #!/usr/bin/env bash 80 + set -euo pipefail 81 + before=$(readlink /run/current-system) 82 + just builder switch {{ args }} 83 + lix diff "$before" 84 85 [group('rebuild')] 86 [macos] 87 + [no-exit-message] 88 provision host: 89 sudo nix run github:LnL7/nix-darwin -- switch --flake {{ flake }}#{{ host }} 90 sudo -i nix-env --uninstall lix # we need to remove the none declarative install of lix 91 92 # package group 93 # build the package, you must specify the package you want to build 94 95 # build the iso image, you must specify the image you want to build 96 [group('package')] 97 + [no-exit-message] 98 + iso image: 99 + nom build {{ flake }}#nixosConfigurations.{{ image }}.config.system.build.isoImage 100 101 # build the tarball, you must specify the host you want to build 102 [group('package')] 103 + [no-exit-message] 104 tar host: 105 sudo nix run {{ flake }}#nixosConfigurations.{{ host }}.config.system.build.tarballBuilder 106 ··· 108 109 # check the flake for errors 110 [group('dev')] 111 + [no-exit-message] 112 check *args: 113 nix flake check --option allow-import-from-derivation false {{ args }} 114 115 [group('dev')] 116 + [no-exit-message] 117 repl-host host=`hostname`: 118 nix repl .#nixosConfigurations.{{ host }} 119 120 # update a set of given inputs 121 [group('dev')] 122 + [no-exit-message] 123 update *input: 124 nix flake update {{ input }} \ 125 --refresh \ ··· 129 130 # build & serve the docs locally 131 [group('dev')] 132 + [no-exit-message] 133 serve: 134 nix run {{ flake }}#docs.serve 135 136 # push to the mirrors 137 [group('dev')] 138 + [no-exit-message] 139 push-mirrors: 140 git push git@gitlab.com:isabelroses/dotfiles.git 141 git push --mirror ssh://git@codeberg.org/isabel/dotfiles.git ··· 143 144 # rotate all secrets 145 [group('dev')] 146 + [no-exit-message] 147 roate-secrets: 148 + find secrets/ -name "*.yaml" | xargs -I {} sops rotate -i {} 149 150 # update the secret keys 151 [group('dev')] 152 + [no-exit-message] 153 update-secrets: 154 + find secrets/ -name "*.yaml" | xargs -I {} sops updatekeys -y {} 155 156 # utils group 157 ··· 159 160 # verify the integrity of the nix store 161 [group('utils')] 162 + [no-exit-message] 163 verify *args: 164 nix-store --verify {{ args }} 165 166 # repairs the nix store from any breakages it may have 167 [group('utils')] 168 + [no-exit-message] 169 repair: (verify "--check-contents --repair") 170 171 # clean the nix store and optimise it 172 [group('utils')] 173 + [no-exit-message] 174 clean: 175 nix-collect-garbage --delete-older-than 3d 176 nix store optimise
+2 -11
modules/flake/packages/docs/package.nix
··· 1 { 2 stdenvNoCC, 3 mdbook, 4 - mdbook-alerts, 5 fetchurl, 6 simple-http-server, 7 writeShellApplication, ··· 16 17 src = self + /docs; 18 19 - nativeBuildInputs = [ 20 - mdbook 21 - mdbook-alerts 22 - ]; 23 24 buildPhase = '' 25 runHook preBuild 26 27 mkdir -p theme 28 cp ${finalAttrs.passthru.catppuccin-mdbook} theme/catppuccin.css 29 - cp ${finalAttrs.passthru.catppuccin-alerts} theme/catppuccin-alerts.css 30 31 cp -r ${libdoc} src/lib 32 ··· 45 ''; 46 47 passthru = { 48 catppuccin-mdbook = fetchurl { 49 url = "https://github.com/catppuccin/mdBook/releases/download/v3.1.1/catppuccin.css"; 50 hash = "sha256-WSl6UaRfx2jwcDg/ZlDlRbB5zwBD7YIuHHPwFj5ldKM="; 51 - }; 52 - 53 - catppuccin-alerts = fetchurl { 54 - url = "https://github.com/catppuccin/mdBook/releases/download/v3.1.1/catppuccin-alerts.css"; 55 - hash = "sha256-TnOuFi/0LeIj9pwg9dgfyDpd1oZVCN18dvjp8uB+K78="; 56 }; 57 58 serve = writeShellApplication {
··· 1 { 2 stdenvNoCC, 3 mdbook, 4 fetchurl, 5 simple-http-server, 6 writeShellApplication, ··· 15 16 src = self + /docs; 17 18 + nativeBuildInputs = [ mdbook ]; 19 20 buildPhase = '' 21 runHook preBuild 22 23 mkdir -p theme 24 cp ${finalAttrs.passthru.catppuccin-mdbook} theme/catppuccin.css 25 26 cp -r ${libdoc} src/lib 27 ··· 40 ''; 41 42 passthru = { 43 + # TODO: update me when a new version is released 44 catppuccin-mdbook = fetchurl { 45 url = "https://github.com/catppuccin/mdBook/releases/download/v3.1.1/catppuccin.css"; 46 hash = "sha256-WSl6UaRfx2jwcDg/ZlDlRbB5zwBD7YIuHHPwFj5ldKM="; 47 }; 48 49 serve = writeShellApplication {
+1 -1
modules/nixos/boot/generic.nix
··· 52 53 extraModprobeConfig = mkOption { 54 type = str; 55 - default = ''options hid_apple fnmode=1''; 56 description = "Extra modprobe config that will be passed to system modprobe config."; 57 }; 58
··· 52 53 extraModprobeConfig = mkOption { 54 type = str; 55 + default = "options hid_apple fnmode=1"; 56 description = "Extra modprobe config that will be passed to system modprobe config."; 57 }; 58
+1 -1
modules/nixos/hardware/cpu/default.nix
··· 20 ] 21 ); 22 default = null; 23 - description = "The manufacturer of the primary system gpu"; 24 }; 25 }
··· 20 ] 21 ); 22 default = null; 23 + description = "The manufacturer of the primary system cpu"; 24 }; 25 }
+2
modules/nixos/hardware/gpu/nvidia.nix
··· 1 { 2 lib, 3 pkgs,
··· 1 + # A worthy read before attempting to change anything here: 2 + # <https://discourse.nixos.org/t/can-we-solve-the-nvidia-situation/73198> 3 { 4 lib, 5 pkgs,
+17 -15
modules/nixos/networking/firewall.nix
··· 1 { 2 lib, 3 - pkgs, 4 config, 5 ... 6 }: ··· 16 # inactive until opensnitch UI is opened 17 # services.opensnitch.enable = device.type != "server"; 18 19 - networking.firewall = { 20 - enable = true; 21 - package = pkgs.iptables; 22 23 - allowedTCPPorts = [ ]; 24 - allowedUDPPorts = [ ]; 25 26 - allowedTCPPortRanges = [ ]; 27 - allowedUDPPortRanges = [ ]; 28 29 - # allow servers to be pinnged, but not our clients 30 - allowPing = config.garden.profiles.server.enable; 31 32 - # make a much smaller and easier to read log 33 - logReversePathDrops = true; 34 - logRefusedConnections = false; 35 36 - # Don't filter DHCP packets, according to nixops-libvirtd 37 - checkReversePath = mkForce false; 38 }; 39 }; 40 }
··· 1 { 2 lib, 3 config, 4 ... 5 }: ··· 15 # inactive until opensnitch UI is opened 16 # services.opensnitch.enable = device.type != "server"; 17 18 + networking = { 19 + nftables.enable = true; 20 21 + firewall = { 22 + enable = true; 23 24 + allowedTCPPorts = [ ]; 25 + allowedUDPPorts = [ ]; 26 27 + allowedTCPPortRanges = [ ]; 28 + allowedUDPPortRanges = [ ]; 29 30 + # allow servers to be pinnged, but not our clients 31 + allowPing = config.garden.profiles.server.enable; 32 33 + # make a much smaller and easier to read log 34 + logReversePathDrops = true; 35 + logRefusedConnections = false; 36 + 37 + # Don't filter DHCP packets, according to nixops-libvirtd 38 + checkReversePath = mkForce false; 39 + }; 40 }; 41 }; 42 }
+2 -2
modules/nixos/networking/networkmanager.nix
··· 5 ... 6 }: 7 let 8 - inherit (lib) mkIf optionalAttrs; 9 in 10 { 11 garden.packages = optionalAttrs config.garden.profiles.graphical.enable { ··· 40 }; 41 42 # causes server to be unreachable over SSH 43 - ethernet.macAddress = mkIf (!config.garden.profiles.server.enable) "random"; 44 }; 45 }
··· 5 ... 6 }: 7 let 8 + inherit (lib) optionalAttrs; 9 in 10 { 11 garden.packages = optionalAttrs config.garden.profiles.graphical.enable { ··· 40 }; 41 42 # causes server to be unreachable over SSH 43 + # ethernet.macAddress = mkIf (!config.garden.profiles.server.enable) "random"; 44 }; 45 }
+1 -2
modules/nixos/networking/systemd.nix
··· 3 inherit (lib) mkForce concatMapAttrs genAttrs; 4 5 ethernetDevices = [ 6 - "wlp1s0f0u8" # wifi dongle 7 - "enp7s0" # ethernet interface on the motherboard 8 ]; 9 in 10 {
··· 3 inherit (lib) mkForce concatMapAttrs genAttrs; 4 5 ethernetDevices = [ 6 + "enp0s31f6" # ethernet interface on the motherboard 7 ]; 8 in 9 {
+4 -1
modules/nixos/networking/tailscale.nix
··· 20 in 21 { 22 options.garden.system.networking.tailscale = { 23 - enable = mkEnableOption "Tailscale VPN"; 24 25 defaultFlags = mkOption { 26 type = listOf str;
··· 20 in 21 { 22 options.garden.system.networking.tailscale = { 23 + enable = mkEnableOption "Tailscale VPN" // { 24 + default = true; 25 + defaultText = "true"; 26 + }; 27 28 defaultFlags = mkOption { 29 type = listOf str;
+49 -16
modules/nixos/nix.nix
··· 1 { 2 - nix = { 3 - # set the nix store to clean every Monday at 3am 4 - gc.dates = "Mon *-*-* 03:00"; 5 6 - # automatically optimize /nix/store by removing hard links 7 - optimise = { 8 - automatic = true; 9 - dates = [ "04:00" ]; 10 - }; 11 12 - # Make builds run with a low priority, keeping the system fast 13 - # daemonCPUSchedPolicy = "idle"; 14 - # daemonIOSchedClass = "idle"; 15 - # daemonIOSchedPriority = 7; 16 17 - # set the build dir to /var/tmp to avoid issues on tmpfs 18 - # https://github.com/NixOS/nixpkgs/issues/293114#issuecomment-2663470083 19 - settings.build-dir = "/var/tmp"; 20 - }; 21 }
··· 1 { 2 + lib, 3 + config, 4 + options, 5 + modulesPath, 6 + ... 7 + }: 8 + let 9 + lixModuleMerged = lib.pathExists "${modulesPath}/programs/lix.nix"; 10 + nixDaemonCfg = config.systemd.services.nix-daemon; 11 + in 12 + { 13 + config = lib.mkMerge [ 14 + { 15 + nix = { 16 + # set the nix store to clean every Monday at 3am 17 + gc.dates = "Mon *-*-* 03:00"; 18 + 19 + # automatically optimize /nix/store by removing hard links 20 + optimise = { 21 + automatic = true; 22 + dates = [ "04:00" ]; 23 + }; 24 25 + # Make builds run with a low priority, keeping the system fast 26 + # daemonCPUSchedPolicy = "idle"; 27 + # daemonIOSchedClass = "idle"; 28 + # daemonIOSchedPriority = 7; 29 + 30 + # set the build dir to /var/tmp to avoid issues on tmpfs 31 + # https://github.com/NixOS/nixpkgs/issues/293114#issuecomment-2663470083 32 + settings.build-dir = "/var/tmp"; 33 + }; 34 + } 35 36 + # https://github.com/NixOS/nixpkgs/pull/469067 37 + (lib.mkIf (!lixModuleMerged) { 38 + systemd.services."nix-daemon@" = { 39 + path = lib.subtractLists (options.systemd.services.type.getSubOptions "").path.value nixDaemonCfg.path; 40 + environment = lib.filterAttrs (n: _v: n != "PATH") nixDaemonCfg.environment; 41 + inherit (nixDaemonCfg) serviceConfig unitConfig; 42 + stopIfChanged = false; 43 + restartIfChanged = false; 44 + }; 45 46 + # stc can't restart socket units. it can only reload them, but reloading sockets is in invalid operation! 47 + systemd.services.lix-daemon-socket-permissions = { 48 + overrideStrategy = "asDropin"; 49 + inherit (nixDaemonCfg) restartTriggers; 50 + stopIfChanged = false; 51 + }; 52 + }) 53 + ]; 54 }
+1 -1
modules/nixos/services/akkoma/default.nix
··· 29 30 "favicon.png" = pkgs.fetchurl { 31 url = "https://gravatar.com/avatar/c487c810e09878b4bd90df713a7c9523?size=512"; 32 - hash = "sha256-psRjtuG+U8KJtWWfa3HAYjOQrjAYQAymDEc6HqhQmnk="; 33 }; 34 35 "emoji/blobs" = pkgs.blobs_gg;
··· 29 30 "favicon.png" = pkgs.fetchurl { 31 url = "https://gravatar.com/avatar/c487c810e09878b4bd90df713a7c9523?size=512"; 32 + hash = "sha256-LRDHPMkJmtkjA/Bd0fpMm5dLf4nhNSIXAAOG8u2ZSqs="; 33 }; 34 35 "emoji/blobs" = pkgs.blobs_gg;
+89
modules/nixos/services/arr.nix
···
··· 1 + { lib, config, ... }: 2 + let 3 + inherit (lib) 4 + mkEnableOption 5 + mkOption 6 + types 7 + genAttrs 8 + ; 9 + 10 + cfg = config.garden.services.arr; 11 + in 12 + { 13 + options.garden.services.arr = { 14 + enable = mkEnableOption "arr services"; 15 + 16 + mediaDir = mkOption { 17 + type = types.str; 18 + default = "/media"; 19 + description = "Directory for storing media files managed by arr services"; 20 + }; 21 + 22 + contentDir = mkOption { 23 + type = types.str; 24 + default = "${cfg.mediaDir}/content"; 25 + defaultText = "\${cfg.mediaDir}/content"; 26 + description = "Directory for storing application data for arr services"; 27 + }; 28 + 29 + mediaOwner = mkOption { 30 + type = types.str; 31 + default = "root"; 32 + description = "User that owns the media and content directories"; 33 + }; 34 + 35 + mediaGroup = mkOption { 36 + type = types.str; 37 + default = "media"; 38 + description = "Group that owns the media and content directories"; 39 + }; 40 + 41 + openFirewall = mkEnableOption "open the firewall for the arr services" // { 42 + default = true; 43 + defaultText = "true"; 44 + }; 45 + }; 46 + 47 + config = lib.mkIf cfg.enable { 48 + garden.services = { 49 + jellyfin.enable = true; 50 + sonarr.enable = true; 51 + radarr.enable = true; 52 + prowlarr.enable = true; 53 + qbittorrent.enable = true; 54 + }; 55 + 56 + users.groups.media = { }; 57 + 58 + systemd.tmpfiles.settings = { 59 + "media-content-dirs" = 60 + genAttrs 61 + [ 62 + "${cfg.mediaDir}/content" 63 + "${cfg.mediaDir}/content/tv" 64 + "${cfg.mediaDir}/content/movies" 65 + "${cfg.mediaDir}/content/home" 66 + ] 67 + (_: { 68 + d = { 69 + mode = "0775"; 70 + user = cfg.mediaOwner; 71 + group = cfg.mediaGroup; 72 + }; 73 + }); 74 + 75 + "media-downloads-dirs" = 76 + genAttrs 77 + [ 78 + "${cfg.mediaDir}/downloads" 79 + ] 80 + (_: { 81 + d = { 82 + mode = "0755"; 83 + user = "qbittorrent"; 84 + group = "media"; 85 + }; 86 + }); 87 + }; 88 + }; 89 + }
+5
modules/nixos/services/default.nix
··· 3 # keep-sorted start 4 ./akkoma 5 ./anubis.nix 6 ./attic.nix 7 ./atuin.nix 8 ./blahaj.nix ··· 23 ./piper.nix 24 ./port-collector.nix 25 ./postgresql.nix 26 ./redis.nix 27 ./uptime-kuma.nix 28 ./vaultwarden.nix 29 ./wakapi.nix
··· 3 # keep-sorted start 4 ./akkoma 5 ./anubis.nix 6 + ./arr.nix 7 ./attic.nix 8 ./atuin.nix 9 ./blahaj.nix ··· 24 ./piper.nix 25 ./port-collector.nix 26 ./postgresql.nix 27 + ./prowlarr.nix 28 + ./qbittorent.nix 29 + ./radarr.nix 30 ./redis.nix 31 + ./sonarr.nix 32 ./uptime-kuma.nix 33 ./vaultwarden.nix 34 ./wakapi.nix
+2
modules/nixos/services/jellyfin.nix
··· 26 jellyfin = { 27 enable = true; 28 dataDir = "/srv/storage/jellyfin"; 29 }; 30 31 cloudflared.tunnels.${config.networking.hostName} = {
··· 26 jellyfin = { 27 enable = true; 28 dataDir = "/srv/storage/jellyfin"; 29 + group = "media"; 30 + inherit (config.garden.services.arr) openFirewall; 31 }; 32 33 cloudflared.tunnels.${config.networking.hostName} = {
+26
modules/nixos/services/pds/default.nix
··· 44 enable = true; 45 pdsadmin.enable = true; 46 47 environmentFiles = [ config.sops.secrets.pds-env.path ]; 48 49 settings = { ··· 75 PDS_OAUTH_PROVIDER_ERROR_COLOR = "#F6598E"; 76 77 PDS_SERVICE_HANDLE_DOMAINS = ".tgirl.beauty"; 78 }; 79 }; 80
··· 44 enable = true; 45 pdsadmin.enable = true; 46 47 + package = pkgs.bluesky-pds.overrideAttrs ( 48 + finalAttrs: _: { 49 + src = pkgs.fetchFromGitHub { 50 + owner = "isabelroses"; 51 + repo = "pds-fork"; 52 + rev = "66c026acfcfd290ea962dd4d03379f0990d80071"; 53 + hash = "sha256-x+oh3YKcz2eWkAqg+jZKrh35UoGI6dLQXcEiGItTHKc="; 54 + }; 55 + 56 + pnpmDeps = pkgs.fetchPnpmDeps { 57 + inherit (finalAttrs) 58 + pname 59 + version 60 + src 61 + sourceRoot 62 + ; 63 + pnpm = pkgs.pnpm_9; 64 + fetcherVersion = 2; 65 + hash = "sha256-4qKWkINpUHzatiMa7ZNYp1NauU2641W0jHDjmRL9ipI="; 66 + }; 67 + } 68 + ); 69 + 70 environmentFiles = [ config.sops.secrets.pds-env.path ]; 71 72 settings = { ··· 98 PDS_OAUTH_PROVIDER_ERROR_COLOR = "#F6598E"; 99 100 PDS_SERVICE_HANDLE_DOMAINS = ".tgirl.beauty"; 101 + 102 + # custom session duration: 30 days 103 + PDS_OAUTH_AUTHENTICATION_MAX_AGE = "2592000000"; 104 }; 105 }; 106
+1 -1
modules/nixos/services/port-collector.nix
··· 5 ... 6 }: 7 let 8 - srvs = lib.mapAttrs (_: srv: srv.port) config.garden.services; 9 10 # from port = 3000; to servicesOnPort.3000 = [ "pds" "kittr" ]; 11 portsToServices = lib.foldl' (
··· 5 ... 6 }: 7 let 8 + srvs = lib.mapAttrs (_: srv: srv.port or 0) config.garden.services; 9 10 # from port = 3000; to servicesOnPort.3000 = [ "pds" "kittr" ]; 11 portsToServices = lib.foldl' (
+30
modules/nixos/services/prowlarr.nix
···
··· 1 + { 2 + lib, 3 + self, 4 + config, 5 + ... 6 + }: 7 + let 8 + inherit (lib) mkIf; 9 + inherit (self.lib) mkServiceOption; 10 + 11 + cfg = config.garden.services.prowlarr; 12 + in 13 + { 14 + options.garden.services.prowlarr = mkServiceOption "prowlarr" { 15 + port = 3022; 16 + }; 17 + 18 + config = mkIf config.garden.services.prowlarr.enable { 19 + services.prowlarr = { 20 + enable = true; 21 + inherit (config.garden.services.arr) openFirewall; 22 + settings.server.port = cfg.port; 23 + }; 24 + 25 + systemd.services.prowlarr.serviceConfig = { 26 + User = "prowlarr"; 27 + Group = "media"; 28 + }; 29 + }; 30 + }
+74
modules/nixos/services/qbittorent.nix
···
··· 1 + { 2 + lib, 3 + self, 4 + config, 5 + ... 6 + }: 7 + let 8 + inherit (lib) mkIf; 9 + inherit (self.lib) mkServiceOption mkSecret; 10 + 11 + cfg = config.garden.services.qbittorrent; 12 + inherit (config.garden.services) arr; 13 + in 14 + { 15 + # <https://github.com/NixOS/nixpkgs/pull/472934> 16 + imports = [ ./qui.nix ]; 17 + 18 + options.garden.services.qbittorrent = mkServiceOption "qbittorrent" { 19 + port = 3019; 20 + host = "0.0.0.0"; 21 + }; 22 + 23 + config = mkIf cfg.enable { 24 + networking.firewall.allowedTCPPorts = [ 25 + cfg.port 26 + config.services.qbittorrent.torrentingPort 27 + ]; 28 + 29 + sops.secrets.qui = mkSecret { 30 + file = "qui"; 31 + key = "qui"; 32 + }; 33 + 34 + services = { 35 + qui = { 36 + enable = true; 37 + secretFile = config.sops.secrets.qui.path; 38 + settings = { 39 + inherit (cfg) host port; 40 + checkForUpdates = false; 41 + }; 42 + }; 43 + 44 + qbittorrent = { 45 + enable = true; 46 + group = arr.mediaGroup; 47 + 48 + webuiPort = 4019; 49 + torrentingPort = 43125; 50 + 51 + serverConfig = { 52 + LegalNotice.Accepted = true; 53 + 54 + BitTorrent = { 55 + "Session\\BTProtocol" = "TCP"; 56 + "Session\\DHTEnabled" = false; 57 + "Session\\LSDEnabled" = false; 58 + "Session\\PeXEnabled" = false; 59 + "Session\\QueueingSystemEnabled" = false; 60 + "Session\\DefaultSavePath" = "/media/downloads"; 61 + }; 62 + 63 + Preferences = { 64 + "WebUI\\LocalHostAuth" = false; 65 + 66 + # generate with <https://codeberg.org/feathecutie/qbittorrent_password> 67 + "WebUI\\Password_PBKDF2" = 68 + "@ByteArray(2PRai2N/GL+Lt+VDdda0kw==:X4+iM6WwTPXExbBwJGcrHqxVsEN0cBxrhACiTMbEeQ6RjTdbfnJSB+CyTn3r1iJzEMMa0/XZzq2U1cG4O6AZZg==)"; 69 + }; 70 + }; 71 + }; 72 + }; 73 + }; 74 + }
+192
modules/nixos/services/qui.nix
···
··· 1 + # https://github.com/NixOS/nixpkgs/pull/472934 2 + { 3 + config, 4 + lib, 5 + pkgs, 6 + ... 7 + }: 8 + 9 + let 10 + inherit (lib) 11 + getExe 12 + maintainers 13 + mkEnableOption 14 + mkIf 15 + mkOption 16 + mkPackageOption 17 + ; 18 + inherit (lib.types) 19 + bool 20 + path 21 + port 22 + str 23 + submodule 24 + ; 25 + cfg = config.services.qui; 26 + 27 + stateDir = "/var/lib/qui"; 28 + configFormat = pkgs.formats.toml { }; 29 + configFile = configFormat.generate "qui.toml" cfg.settings; 30 + in 31 + { 32 + options = { 33 + services.qui = { 34 + enable = mkEnableOption "qui"; 35 + 36 + package = mkPackageOption pkgs "qui" { }; 37 + 38 + user = mkOption { 39 + type = str; 40 + default = "qui"; 41 + description = "User to run qui as."; 42 + }; 43 + 44 + group = mkOption { 45 + type = str; 46 + default = "qui"; 47 + example = "torrents"; 48 + description = "Group to run qui as."; 49 + }; 50 + 51 + openFirewall = mkOption { 52 + type = bool; 53 + default = false; 54 + description = "Whether or not to open ports in the firewall for qui."; 55 + }; 56 + 57 + secretFile = mkOption { 58 + type = path; 59 + example = "/run/secrets/qui-session.txt"; 60 + description = '' 61 + Path to a file that contains the session secret. The session secret 62 + can be generated with `openssl rand -hex 32`. 63 + ''; 64 + }; 65 + 66 + settings = mkOption { 67 + default = { }; 68 + example = { 69 + port = 7777; 70 + logLevel = "DEBUG"; 71 + metricsEnabled = true; 72 + }; 73 + type = submodule { 74 + freeformType = configFormat.type; 75 + options = { 76 + host = mkOption { 77 + type = str; 78 + default = "127.0.0.1"; 79 + description = "The host address qui listens on."; 80 + }; 81 + 82 + port = mkOption { 83 + type = port; 84 + default = 7476; 85 + description = "The port qui listens on."; 86 + }; 87 + }; 88 + }; 89 + description = '' 90 + qui configuration options. 91 + 92 + Refer to the [template config](https://github.com/autobrr/qui/blob/main/internal/config/config.go) 93 + in the source code for the available options. 94 + The documentation contains the available [environment variables](https://getqui.com/docs/configuration/environment/), 95 + this can be used to get an overview. 96 + ''; 97 + }; 98 + 99 + }; 100 + }; 101 + 102 + config = mkIf cfg.enable { 103 + assertions = [ 104 + { 105 + assertion = !(cfg.settings ? sessionSecret); 106 + message = '' 107 + Session secrets should not be passed via settings, as 108 + these are stored in the world-readable nix store. 109 + 110 + Use the secretFile option instead.''; 111 + } 112 + ]; 113 + 114 + systemd.services.qui = { 115 + description = "qui: alternative qBittorrent webUI"; 116 + after = [ "network-online.target" ]; 117 + wants = [ "network-online.target" ]; 118 + wantedBy = [ "multi-user.target" ]; 119 + 120 + serviceConfig = { 121 + Type = "simple"; 122 + User = cfg.user; 123 + Group = cfg.group; 124 + 125 + LoadCredential = "sessionSecret:${cfg.secretFile}"; 126 + Environment = [ "QUI__SESSION_SECRET_FILE=%d/sessionSecret" ]; 127 + StateDirectory = "qui"; 128 + 129 + ExecStartPre = '' 130 + ${pkgs.coreutils}/bin/install -m 600 '${configFile}' '%S/qui/config.toml' 131 + ''; 132 + ExecStart = "${getExe cfg.package} serve --config-dir %S/qui"; 133 + Restart = "on-failure"; 134 + 135 + # Based on qbittorrent and nemorosa hardening settings 136 + # Similar to what systemd hardening helper suggests 137 + CapabilityBoundingSet = ""; 138 + LockPersonality = true; 139 + MemoryDenyWriteExecute = true; 140 + NoNewPrivileges = true; 141 + PrivateDevices = true; 142 + PrivateNetwork = false; 143 + PrivateTmp = true; 144 + PrivateUsers = true; 145 + ProcSubset = "pid"; 146 + ProtectClock = true; 147 + ProtectControlGroups = true; 148 + ProtectHome = "yes"; 149 + ProtectHostname = true; 150 + ProtectKernelLogs = true; 151 + ProtectKernelModules = true; 152 + ProtectKernelTunables = true; 153 + ProtectProc = "invisible"; 154 + # This should allow for hardlinking to torrent client files 155 + ProtectSystem = "full"; 156 + RemoveIPC = true; 157 + RestrictAddressFamilies = [ 158 + "AF_INET" 159 + "AF_INET6" 160 + "AF_NETLINK" 161 + "AF_UNIX" 162 + ]; 163 + RestrictNamespaces = true; 164 + RestrictRealtime = true; 165 + RestrictSUIDSGID = true; 166 + SystemCallArchitectures = "native"; 167 + SystemCallFilter = [ "@system-service" ]; 168 + }; 169 + }; 170 + 171 + networking.firewall = mkIf cfg.openFirewall { 172 + allowedTCPPorts = [ cfg.settings.port ]; 173 + }; 174 + 175 + users = { 176 + users = mkIf (cfg.user == "qui") { 177 + qui = { 178 + inherit (cfg) group; 179 + description = "qui user"; 180 + isSystemUser = true; 181 + home = stateDir; 182 + }; 183 + }; 184 + 185 + groups = mkIf (cfg.group == "qui") { 186 + qui = { }; 187 + }; 188 + }; 189 + }; 190 + 191 + meta.maintainers = with maintainers; [ undefined-landmark ]; 192 + }
+27
modules/nixos/services/radarr.nix
···
··· 1 + { 2 + lib, 3 + self, 4 + config, 5 + ... 6 + }: 7 + let 8 + inherit (lib) mkIf; 9 + inherit (self.lib) mkServiceOption; 10 + 11 + cfg = config.garden.services.radarr; 12 + in 13 + { 14 + options.garden.services.radarr = mkServiceOption "radarr" { 15 + port = 3021; 16 + }; 17 + 18 + config = mkIf config.garden.services.radarr.enable { 19 + services.radarr = { 20 + inherit (cfg) enable; 21 + group = "media"; 22 + dataDir = "/srv/storage/ranarr"; 23 + inherit (config.garden.services.arr) openFirewall; 24 + settings.server.port = cfg.port; 25 + }; 26 + }; 27 + }
+27
modules/nixos/services/sonarr.nix
···
··· 1 + { 2 + lib, 3 + self, 4 + config, 5 + ... 6 + }: 7 + let 8 + inherit (lib) mkIf; 9 + inherit (self.lib) mkServiceOption; 10 + 11 + cfg = config.garden.services.sonarr; 12 + in 13 + { 14 + options.garden.services.sonarr = mkServiceOption "sonarr" { 15 + port = 3020; 16 + }; 17 + 18 + config = mkIf config.garden.services.sonarr.enable { 19 + services.sonarr = { 20 + inherit (cfg) enable; 21 + group = "media"; 22 + dataDir = "/srv/storage/sonarr"; 23 + inherit (config.garden.services.arr) openFirewall; 24 + settings.server.port = cfg.port; 25 + }; 26 + }; 27 + }
+2 -2
modules/nixos/system/scheduler.nix
··· 1 - { pkgs, config, ... }: 2 { 3 services.scx = { 4 - inherit (config.garden.profiles.workstation) enable; 5 scheduler = "scx_bpfland"; 6 package = pkgs.scx.rustscheds; 7 };
··· 1 + { pkgs, ... }: 2 { 3 services.scx = { 4 + # inherit (config.garden.profiles.workstation) enable; 5 scheduler = "scx_bpfland"; 6 package = pkgs.scx.rustscheds; 7 };
+3 -3
secrets/services/piper.yaml
··· 1 - env: ENC[AES256_GCM,data:scYfFEXl6esjrYaTLbFbeqQVnOw6h2ZzZVQ2EUsCxnYzaI+rhCTVqkrqXgowFAILWwQOiYM7B5ukgqTytiB5h6C2Bh/xa/VdMOPdY3EXqRed7NzZC96LZt3+dKDKKDbkw1wyddWvX/phQFk/cHq6BR5CQZfQ7nPXRr2ofZGoK/3m+GhouyJq2I4RHHRGxv0WXYniHkR9zgdc+myg1KsFphu9XaoR4WN/uaGACv59pguMJWRcBzr+wKPxdWtrzb4vTIVQiF8wpTSdWBmnwmKq29y/pBSNivDdhWB5N1/VHGFDRge5RzxXCg98Hv4fegebx4+YPiVlwoC2peGSi/q8EeNLFES+waywaKmbQ1r54AAFqGnXIIp16Ib5lCUa+VwY2jQl9c+QZOcNEJkVZ6jWmuoUguO1q2y4WbLvcaWksjRXJHs/NOsQoF4=,iv:b8O+3GksPP4hTuH57IxgAeYe6MAiYGO83nJKn7MVn6Q=,tag:V1dFrgABjL6SXN7af1DtgA==,type:str] 2 sops: 3 age: 4 - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQDiHbMSinj8twL9cTgPOfI6OMexrTZyHX27T8gnMj2 ··· 71 lm9UxzoNda1OtptX52t1sWVYkx1pwvlTbTmuNy/8uVufZVekdPBz+pywuJbSJlh2 72 bH8K6Q== 73 -----END AGE ENCRYPTED FILE----- 74 - lastmodified: "2025-11-14T16:08:46Z" 75 - mac: ENC[AES256_GCM,data:jd968uS+JcdIg1eDyo1ssHSVw/cxXVwuIDE6YdJrZCHpCf/XSHIWvlu6+L3LILixhV45eMdI81GKCMR3uBnXaXf6SOEPWZ2BJc9cT+p1seAPR1surmVA8d2GGKSWuGmi7mFW30kLsDJhRk1pybKpIHf/Jxdgx0cuh95DEoOtoPg=,iv:+XUddLha4hj68zCcnHiH5aEJU6buzKV+FNUO8ZXQofY=,tag:wlmooDkxQJyPD883cwRdSA==,type:str] 76 unencrypted_suffix: _unencrypted 77 version: 3.11.0
··· 1 + env: ENC[AES256_GCM,data:/bJSpMnpO/P5pen9RKdsRF47veCmkRstBFUwbdyu+fp3UDayUrL2u+/397XQQxBQYl266wJOQB7sjJ5lsOI/yRiM/89yv4zow7Qh/akPnsYyhxiLAjdIdJHFzh6tQMBZiHEHNF16edhejKMgIz0qOIvIQZu0yHX6ozvWfc7KKzYH3CUohbhIjgjqbJfzP/YMFNnqu6Aa4mlksprCeVPg/ZX3vf4z/DS9vIhmUbkc91064gjytMq3EFZvVt01aD3Wror/ApBCtCZ1Pt8zAbH/mrQd2Dsx9pOKueqYhTGcF6nsU5fxkKG62K0nK3xGOS2vOpl4k9m220k5BF1CbSKbWXFBafvtvorYbBBK+N4caBKI6DQz66/eA75D0DwA0KbBKadboqzRZOWFsvqQwIuiqnkzJottJZ2swGbyVjhLsAQEmZI/b6Ie0I0=,iv:Z1gnEHoJ8eBDWn1IorUn/pT+ghxrB7+nk9boHx7efWg=,tag:zNJRN/JeWP98TDxfFDdElw==,type:str] 2 sops: 3 age: 4 - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQDiHbMSinj8twL9cTgPOfI6OMexrTZyHX27T8gnMj2 ··· 71 lm9UxzoNda1OtptX52t1sWVYkx1pwvlTbTmuNy/8uVufZVekdPBz+pywuJbSJlh2 72 bH8K6Q== 73 -----END AGE ENCRYPTED FILE----- 74 + lastmodified: "2026-01-07T01:01:47Z" 75 + mac: ENC[AES256_GCM,data:ThIfvJcDdWqprQ0i66bTy29QOcPzWjIYCB8aYhdeEIPzBOjQDKgVh3YNXVaMC7/2tBJmC/IQbxvJ/pIPP/UICSxFvqS+Ta6wipRlTs2A23gaoaVXwj7nY8oMwUH4w04NbWuNFavQ/gnKYBEOmqDBsLrZ6+v5IPxfxx/fRapMKMM=,iv:OYGE7Ja0X6WNHswwYwQ8VsDSlb3U/sm47tsTnJ5wspc=,tag:5Pcfd0/eSL36rFNp8sH6qA==,type:str] 76 unencrypted_suffix: _unencrypted 77 version: 3.11.0
+77
secrets/services/qui.yaml
···
··· 1 + qui: ENC[AES256_GCM,data:SkU/OLmyCUPRg5DlGaiEtQyLVoilLUGXRQM+m+LbqQAmwBv7LhDghmNAZgZ2XWoc+6yMfC7UjfZSFfLXGN3xQw==,iv:Ez9tZX7Hzb4VHF4NisatdoNNr+1eq0nqWGqLnfpUp7U=,tag:f/w121+Ia/odoofgLNCXjw==,type:str] 2 + sops: 3 + age: 4 + - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQDiHbMSinj8twL9cTgPOfI6OMexrTZyHX27T8gnMj2 5 + enc: | 6 + -----BEGIN AGE ENCRYPTED FILE----- 7 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IDk1NDQzZyA3M1RP 8 + YkF0Sy9GelVkVHZPMkNMN3pVVitGMkxJYlkzOXZnMGhXUTFSb1VJCmVYWXRJNDBw 9 + UlpQbU55NnhFcEdMUzA4V0ZNS0VGL1p3c1d0MXhzcjRJUUkKLS0tIDZ6cFlhU1ha 10 + RWcwTlU2cUoxZkREZFBXZlgwM2pJeHE3ZWlzUS9LRklETVUKsXMTiH+E7+kXlNdZ 11 + YfIpYCQhDLIeLBjsoz6dcJcXKskv8Snyb8akTLXKpU2Xt7XTxus0gODbk9xEcaik 12 + 6TL9dw== 13 + -----END AGE ENCRYPTED FILE----- 14 + - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAomF8O3ZqZBpLRlAkS1+FwRllSMrREHtndw07trrfcA 15 + enc: | 16 + -----BEGIN AGE ENCRYPTED FILE----- 17 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IENXL2NhQSAvRDZN 18 + SWtzeHduQzMxQTIrSHNUdEpwMmJpRVRmSnMrRUhuRWhCbzQ5c1FzCklGMWdINng1 19 + SkhQMDBKemlKS1ZIK1dpZTkycXpCeVRNRVpZeGg5Rm9mOVEKLS0tIGNhNVpCMzV3 20 + OHcvQ3FOalo3QzRPT3FDbnF3a3JtYndkMDZzOERsL1E3aXcKVCElom5e6Wy/6/1s 21 + n0acAXybuuh0M3VOU37Vod5VDVFAhBNe5cRSheiMjsQz1kN5X1lcf6/XK8E8vntz 22 + 3LffQw== 23 + -----END AGE ENCRYPTED FILE----- 24 + - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKV3N2r1cX3kZMHxuJYaFsjWJRtik/2b/KZ5ru38IqAT 25 + enc: | 26 + -----BEGIN AGE ENCRYPTED FILE----- 27 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFh6aDdpUSA4cUkw 28 + UVVycHdyd3dUSHJCdHY5M2UrR1JvdmhleEp3WkMySDBYdk01LzN3Cks2VUJUb1FM 29 + U1FpQ012cS80WTZsNmNSckRINUR1b0Ivb0R2UXRUT1MwSEkKLS0tIHpKL1BaLy9B 30 + cmlZa3gxM1NrZ2h6aEQ1Z3FFRjU0UHNjRWVOSTVyTVhYcEEKVvHwiJ6U8E/pJmzb 31 + 25xhRR/XCAvXqL5NUb49egdQQpn106AR2D4Ph+ERoKw8XGmyIo4Oybv6RwbwLinz 32 + r31UIQ== 33 + -----END AGE ENCRYPTED FILE----- 34 + - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN2pZDnEwnOsrf9aPUVkBigeFwAirWPVGliSs5AgcEFr 35 + enc: | 36 + -----BEGIN AGE ENCRYPTED FILE----- 37 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFZTUGdBUSBsM0dQ 38 + SGhibGo1Wm96bWVDY21SOVc2S2p0dyswQittYWNhT3IycVlMcmlvCldScjU4T3JN 39 + MEVlWXZtQkRkN0pqNVRUVmFYOG9XcmUxaUtKZVVXVzJpK1UKLS0tIDRmc1ZhMWsx 40 + QTlGT1ZZOUtvRnNtOG5CUVI1cFU4Zkw0U25SemFGOE5Md00Kk2VoPff3AH3YEA2p 41 + 6WyT4QtaVbqdpTBJeuUpE20yG1SjeEsDAwEifnpsZ9rGgCum/lREWk18mJHw3GUk 42 + sm+zQg== 43 + -----END AGE ENCRYPTED FILE----- 44 + - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICOXg0Qq1bdnrYp+KL6tfLZsMf3KHFfpaP2GMjwmpug2 45 + enc: | 46 + -----BEGIN AGE ENCRYPTED FILE----- 47 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGRaVG9vQSBBdnkw 48 + M3V6ci80M3AyaGRTZU1iSkV3VlBXU3Z4QTNoWUhYaEI4cEtORVI4CjY3TTM2L3VR 49 + Ly9iN09yb3BxNHlYaG1wY1QxSDg0elBVeFNJTjMyRnRPSncKLS0tIDhOVW1xWlpR 50 + Y1U0YVAvQkpCYjFDOEZOSHJnbG1BUGVJTCtTSWZxUnlHQTAKCRGKSvk2nVgJ/r/C 51 + ULwpJscVGX7Pb8i/toimZ8D0X303Gu237BgTO9TQnD8yCTutUym04vOqWuppG1ra 52 + pYcTLg== 53 + -----END AGE ENCRYPTED FILE----- 54 + - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFecrTXoINyGQkeOLQetR5BC6s9VlJHlCbY02WY5FAnJ 55 + enc: | 56 + -----BEGIN AGE ENCRYPTED FILE----- 57 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IDgrRFVnUSBmbmtE 58 + V0l1eFg2ZDJhMW1CVE15b0dDR3BpNjlKYWYvbk5VcFBLM0QrZmxjClhsMkN3bHhv 59 + eXpMeldRTVIzNnp6ZEUxZnVuU29TUnJCQitPdHgwMzVMTmsKLS0tIFUyWVlIQXFq 60 + QngvUWZNMEdYZ01tekwzcERsblJwaExmVk5lMStCbUR6WHcKMzUdkSaFSUKZOhvN 61 + bPvciYd9h9Qq0dyh0KRK+T52T8U38izSdC5XSc4qWXmCbjac4FsLMgHPIjrORbnU 62 + uxBsNA== 63 + -----END AGE ENCRYPTED FILE----- 64 + - recipient: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIErl+4GkgJOoaUUOU9gblWJ6FpIls/adHx0YpOUXXxJA 65 + enc: | 66 + -----BEGIN AGE ENCRYPTED FILE----- 67 + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEptYzFtUSBQZWdY 68 + SzFxbHV4dUR5ak1kUExRNWwvM1o4dDd1U2Z4WlJuejcrdkRGaVFRCi81d09keU5N 69 + aTQzNWo0MDk3aTlYZlN0RWVDeERiOGRaazNFZTE3K3p3MlkKLS0tIFlWQ2hodzNW 70 + YU5yK2tIUVFoazRSbW5FRUVyR05JWkNteXova05MVkh1Z3MK3cdep/MFRjRABDIk 71 + rpjZna7G7eTZ8jG3L+x9GovzgXijUA7nsw7qY6Igzwq1+Mrdupgv7GbPt2+57+88 72 + fcmFIg== 73 + -----END AGE ENCRYPTED FILE----- 74 + lastmodified: "2026-01-11T15:37:29Z" 75 + mac: ENC[AES256_GCM,data:7wy+KnaJiEXFNYVSCZk4k4sK1gBt2gbjxn6yvPwEbbE9F0E6Cx1fIa5alQEXTpyWzyzb0brGbrHI9HunUjf/xYVfF/ebbI5luy7sj9oJgfPLsYaZ4hVhZ3NnH6VFdrkFUF565Y5uf9TVG3jp8iNYM5JesIyszugtasF5GAu27Yo=,iv:pOza+xfiexKeqUE1wC4/6cQTIBDKOf0ZbU1yj4kS/Sw=,tag:LSA5l9DKYMGD7ESuFAH8Rg==,type:str] 76 + unencrypted_suffix: _unencrypted 77 + version: 3.11.0
+1 -3
systems/amaterasu/default.nix
··· 25 keyboard = "us"; 26 }; 27 28 - services.xmrig.enable = true; 29 30 system = { 31 boot = { ··· 43 bluetooth.enable = true; 44 printing.enable = false; 45 emulation.enable = true; 46 - 47 - security.auditd.enable = true; 48 }; 49 }; 50 }
··· 25 keyboard = "us"; 26 }; 27 28 + # services.xmrig.enable = true; 29 30 system = { 31 boot = { ··· 43 bluetooth.enable = true; 44 printing.enable = false; 45 emulation.enable = true; 46 }; 47 }; 48 }
+10 -11
systems/athena/default.nix
··· 1 { 2 imports = [ ./hardware.nix ]; 3 ··· 19 }; 20 }; 21 22 - system = { 23 - boot = { 24 - loader = "systemd-boot"; 25 - secureBoot = false; 26 - loadRecommendedModules = true; 27 - enableKernelTweaks = true; 28 - initrd.enableTweaks = true; 29 - }; 30 - 31 - security.auditd.enable = true; 32 }; 33 34 services = { 35 cloudflared.enable = true; 36 immich.enable = true; 37 - jellyfin.enable = true; 38 borgbackup.enable = true; 39 }; 40 }; 41 }
··· 1 + { lib, ... }: 2 { 3 imports = [ ./hardware.nix ]; 4 ··· 20 }; 21 }; 22 23 + system.boot = { 24 + loader = "systemd-boot"; 25 + secureBoot = false; 26 + loadRecommendedModules = true; 27 + enableKernelTweaks = true; 28 + initrd.enableTweaks = true; 29 }; 30 31 services = { 32 cloudflared.enable = true; 33 immich.enable = true; 34 + arr.enable = true; 35 borgbackup.enable = true; 36 }; 37 }; 38 + 39 + services.smartd.enable = lib.mkForce false; 40 }
+5
systems/athena/hardware.nix
··· 9 device = "/dev/disk/by-uuid/2524-71ED"; 10 fsType = "vfat"; 11 }; 12 }; 13 14 swapDevices = [ { device = "/dev/disk/by-uuid/e45cd5a5-ec02-4933-9adb-5d968f270f54"; } ];
··· 9 device = "/dev/disk/by-uuid/2524-71ED"; 10 fsType = "vfat"; 11 }; 12 + 13 + "/media" = { 14 + device = "/dev/disk/by-uuid/c4f7c302-f492-47dd-8bfd-e3073c1923bd"; 15 + fsType = "ext4"; 16 + }; 17 }; 18 19 swapDevices = [ { device = "/dev/disk/by-uuid/e45cd5a5-ec02-4933-9adb-5d968f270f54"; } ];
+1 -1
systems/skadi/default.nix
··· 9 }; 10 11 device = { 12 - cpu = "amd"; 13 gpu = null; 14 }; 15
··· 9 }; 10 11 device = { 12 + cpu = null; 13 gpu = null; 14 }; 15
-1
systems/valkyrie/default.nix
··· 33 emulation.enable = true; 34 35 bluetooth.enable = false; 36 - security.auditd.enable = true; 37 }; 38 }; 39 }
··· 33 emulation.enable = true; 34 35 bluetooth.enable = false; 36 }; 37 }; 38 }