Compare commits

..

No commits in common. "master" and "hyprland" have entirely different histories.

43 changed files with 631 additions and 994 deletions

View File

@ -49,88 +49,25 @@ Partition the disk:
parted --script /dev/nvme0n1 -- mklabel gpt mkpart esp fat32 1MiB 512MiB mkpart primary 512MiB 100% set 1 boot on
```
Create an encrypted pool. The `-O` flags set filesystem properties that every
dataset inherits, so they only need to be given once here:
Create an encrypted pool:
```bash
zpool create -f \
-o ashift=12 \
-o autotrim=on \
-O encryption=on -O keyformat=passphrase \
-O compression=zstd \
-O dnodesize=auto \
-O xattr=sa \
-O acltype=posixacl \
-O relatime=on \
-O redundant_metadata=most \
-O mountpoint=none \
rpool /dev/nvme0n1p2
zpool create -f -o ashift=12 -O encryption=on -O keyformat=passphrase -O mountpoint=none rpool /dev/nvme0n1p2
```
Create the datasets. Keeping `/nix` separate from `/` means the store can be
excluded from snapshot and rollback policy, which is what makes an
erase-your-darlings setup possible later:
Create a root partition:
```bash
zfs create -o mountpoint=legacy rpool/root
zfs create -o mountpoint=legacy rpool/nix
zfs create -o mountpoint=legacy rpool/home
zfs create -o mountpoint=legacy -o recordsize=1M rpool/Storage
zfs create -o mountpoint=none -o refreservation=2G rpool/reserved
```
Mount them:
```bash
mkdir -p /mnt
mount -t zfs rpool/root /mnt
mkdir -p /mnt/nix /mnt/home /mnt/Storage
mount -t zfs rpool/nix /mnt/nix
mount -t zfs rpool/home /mnt/home
mount -t zfs rpool/Storage /mnt/Storage
```
#### Why these properties
Some of these can only be chosen when the pool or dataset is created, so they
are worth getting right the first time.
Creation-only, no way to change later without recreating the dataset:
- `encryption` and `keyformat` enable native ZFS encryption. This cannot be
turned on afterwards.
- `dnodesize=auto` allows dnodes larger than 512 bytes. Combined with
`xattr=sa` this keeps extended attributes inline instead of spilling into
separate blocks, which matters for a Nix store holding a hundred thousand
small files. Existing files keep whatever size they were written with.
- `ashift=12` matches the 4K physical sector size of modern NVMe drives.
Changeable later, but only applied to newly written blocks:
- `compression=zstd` gives a better ratio than the `lz4` that plain
`compression=on` selects. The Nix store compresses particularly well.
- `acltype=posixacl` is needed for systemd-journald to grant journal access
through ACLs. With the default of `off` that mechanism silently does nothing.
- `redundant_metadata=most` reduces the number of duplicate metadata writes.
- `recordsize=1M` suits bulk file storage. Leave it at the 128K default for
general purpose datasets, and drop it to 16K for databases or VM images.
- `autotrim=on` issues TRIM continuously. The `zpool-trim.timer` unit that
NixOS enables covers this periodically instead, so either approach works.
The `rpool/reserved` dataset never gets mounted. Its `refreservation` holds
space back so that the pool can be brought below its full mark by destroying
that one dataset. ZFS performance degrades noticeably above roughly 80%
capacity.
On a single disk pool there is no redundancy, so ZFS can detect corruption but
never repair it. Setting `copies=2` on the datasets that hold irreplaceable
data stores a second copy of each block at the cost of double the space:
And home partition:
```bash
zfs set copies=2 rpool/home
zfs create -o mountpoint=legacy -o compression=on rpool/home
mkdir -p /mnt/home
mount -t zfs rpool/home /mnt/home
```
Snapshots are configured through `services.sanoid` in `hosts/Common/zfs.nix`.
ZFS dataset names are case sensitive, so an entry there has to match the
dataset exactly. A name that does not resolve leaves that dataset with no
snapshots at all while the service still reports success.
Format the boot partition:
```bash
mkfs.fat -F 32 -n BOOT /dev/nvme0n1p1
@ -175,7 +112,7 @@ sudo nixos-install --impure --flake git+https://git.thomasave.be/thomasave/Dotfi
Clean-up:
```bash
umount /mnt/{home,nix,Storage,boot}
umount /mnt/{home,boot}
umount /mnt
```

View File

@ -10,11 +10,11 @@
]
},
"locked": {
"lastModified": 1775689345,
"narHash": "sha256-tM3s7CX+tgxlYW0Sk3nzVThg2MHn08foIuMxABupxIs=",
"lastModified": 1764289441,
"narHash": "sha256-ak+lgFiYE5PHByN1/BRkO5JP498hno6Ix24C1Qf/vec=",
"owner": "aylur",
"repo": "ags",
"rev": "bbee2f18939f1ec7ff720e717cf305e73635628f",
"rev": "e169694390548dfd38ff40f1ef2163d6c3ffe3ea",
"type": "github"
},
"original": {
@ -30,11 +30,11 @@
]
},
"locked": {
"lastModified": 1784907849,
"narHash": "sha256-wtypRfcfVvofMON6xemTObJCpPWLRyBoqLrvzgnLYlI=",
"lastModified": 1769774308,
"narHash": "sha256-8Ve6VdUpcYbl8bS5oyDwVnnNobyPxdPmUHGgSDyOazQ=",
"owner": "aylur",
"repo": "astal",
"rev": "9dac92f20e6c89b9373bbb238c49b1cb115724db",
"rev": "eb235f8813bdea2a4a38ac228f2efc4e2a8a90af",
"type": "github"
},
"original": {
@ -65,6 +65,27 @@
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"nur",
"nixpkgs"
]
},
"locked": {
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_3": {
"inputs": {
"nixpkgs-lib": [
"worktimer",
@ -111,11 +132,11 @@
]
},
"locked": {
"lastModified": 1785958398,
"narHash": "sha256-5r/JgsoNMTx+qIh83WkxpujEKBR7PF5ssnh5vYCuSAw=",
"lastModified": 1773093840,
"narHash": "sha256-u/96NoAyN8BSRuM3ZimGf7vyYgXa3pLx4MYWjokuoH4=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "a7c70cc290290f373f50cd820403833d250459ac",
"rev": "bb014746edb2a98d975abde4dd40fa240de4cf86",
"type": "github"
},
"original": {
@ -163,11 +184,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1785967620,
"narHash": "sha256-IItrdb7Puk05RqOBWZYFC5X6Wl1sJmCfh5MWVHw5iMM=",
"lastModified": 1772963539,
"narHash": "sha256-9jVDGZnvCckTGdYT53d/EfznygLskyLQXYwJLKMPsZs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b7c2ada94fe99c15b0dbcf4d11fd7850b957a436",
"rev": "9dcb002ca1690658be4a04645215baea8b95f31d",
"type": "github"
},
"original": {
@ -178,6 +199,38 @@
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1772963539,
"narHash": "sha256-9jVDGZnvCckTGdYT53d/EfznygLskyLQXYwJLKMPsZs=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9dcb002ca1690658be4a04645215baea8b95f31d",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1772542754,
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1730200266,
"narHash": "sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU=",
@ -193,6 +246,25 @@
"type": "github"
}
},
"nur": {
"inputs": {
"flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1773125003,
"narHash": "sha256-EF89LT3zxm8dM2pgoAohUcdQoTVVv1v02Jq+z6j8u+0=",
"owner": "nix-community",
"repo": "NUR",
"rev": "eb59c1e77102b9f836cdea9f12568f191a9aff57",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "NUR",
"type": "github"
}
},
"root": {
"inputs": {
"ags": "ags",
@ -201,13 +273,49 @@
"home-manager": "home-manager",
"nixos-06cb-009a-fingerprint-sensor": "nixos-06cb-009a-fingerprint-sensor",
"nixpkgs": "nixpkgs_2",
"nur": "nur",
"vicinae": "vicinae",
"worktimer": "worktimer"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"vicinae": {
"inputs": {
"nixpkgs": "nixpkgs_4",
"systems": "systems"
},
"locked": {
"lastModified": 1773099334,
"narHash": "sha256-ciiNQFZ2m2JOEDDg9ZZxOuxykl4Aw/2C1EQsOcq+F+E=",
"owner": "vicinaehq",
"repo": "vicinae",
"rev": "2a427670116461a4ce0207db2c089aee9f40428e",
"type": "github"
},
"original": {
"owner": "vicinaehq",
"repo": "vicinae",
"type": "github"
}
},
"worktimer": {
"inputs": {
"flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_3"
"flake-parts": "flake-parts_3",
"nixpkgs": "nixpkgs_5"
},
"locked": {
"lastModified": 1739738223,

View File

@ -5,7 +5,9 @@
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
nur.url = "github:nix-community/NUR";
fzgo.url = "git+https://git.thomasave.be/thomasave/fzgo.git";
vicinae.url = "github:vicinaehq/vicinae";
worktimer = {
type = "git";
url = "https://git.thomasave.be/thomasave/WorkTimer";
@ -30,25 +32,35 @@
outputs = {
nixpkgs,
home-manager,
nur,
nixos-06cb-009a-fingerprint-sensor,
...
} @ inputs: let
supportedSystems = ["x86_64-linux" "aarch64-linux"];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
system = builtins.currentSystem;
pkgs = nixpkgs.legacyPackages.${system};
commonModules = host: user: [
./hosts/${host}/hardware-configuration.nix
({pkgs, ...}: {
nixpkgs.config.allowUnfree = true;
{
nixpkgs.overlays = [
nur.overlays.default
(self: super: {utillinux = super.util-linux;})
];
}
{
networking.hostName = host;
time.timeZone = "Asia/Bangkok";
nix.settings = {
substituters = [
"https://nix-community.cachix.org"
"https://cuda-maintainers.cachix.org"
"https://vicinae.cachix.org"
];
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"cuda-maintainers.cachix.org-1:0dq3bujKpuEPMCX6U4WylrUDZ9JyUG0VpVZa7CNfq5E="
"vicinae.cachix.org-1:1kDrfienkGHPYbkpNj1mWTr7Fm1+zcenzgTizIcI3oc="
];
experimental-features = ["nix-command" "flakes"];
trusted-users = ["root" user];
@ -56,16 +68,9 @@
keep-derivations = true;
auto-optimise-store = true;
};
# Resolve the bare "nixpkgs" flakeref to the exact revision this system
# was built from. Dev shells that use it then reuse the store paths the
# system already has, rather than each locking its own nixos-unstable
# snapshot and pulling in a duplicate toolchain.
nix.registry.nixpkgs.flake = nixpkgs;
nix.nixPath = ["nixpkgs=${nixpkgs}"];
users.users.${user} = {
isNormalUser = true;
extraGroups = ["wheel" "video"]; # Enable 'sudo' for the user.
extraGroups = ["wheel" "video"]; # Enable sudo for the user.
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKAa3tMzSCRuprEACrBsKI0F/o73o6J9L1qR3TaZn/N8 user@Kell"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIByLwLAdJbmoDV5sx4hg5NbzKbOh1GmWEhDOUJ1GQBhK user@Riva"
@ -80,7 +85,7 @@
shell = pkgs.zsh;
};
system.stateVersion = "23.11";
})
}
home-manager.nixosModules.home-manager
{
home-manager = {
@ -118,10 +123,7 @@
};
mkHome = architecture: modulePath:
home-manager.lib.homeManagerConfiguration {
pkgs = import nixpkgs {
system = architecture;
config.allowUnfree = true;
};
pkgs = nixpkgs.legacyPackages.${architecture};
modules = [modulePath];
extraSpecialArgs = {inherit inputs;};
};
@ -134,7 +136,7 @@
"user@Tolnedra" = mkHome "x86_64-linux" ./home/Tolnedra.nix;
"server@Vault" = mkHome "x86_64-linux" ./home/Vault.nix;
"user@Docker" = mkHome "x86_64-linux" ./home/Docker.nix;
"server@mallorea" = mkHome "x86_64-linux" ./home/Mallorea.nix;
"server@Mallorea" = mkHome "x86_64-linux" ./home/Mallorea.nix;
};
# Generic formatter across architectures

View File

@ -15,30 +15,46 @@
};
in {
imports = [
(import ./utils/desktop.nix {
inherit inputs config pkgs;
scale = 1.2;
})
(import ./utils/desktop.nix {inherit inputs config pkgs;})
(import ./utils/fzgo_links.nix {inherit config pkgs fzgo_paths;})
];
programs.ssh.settings."*".identityFile = "/home/user/.secrets/SSH/Aloria/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "/home/user/.secrets/SSH/Aloria/id_ed25519";
home.packages = with pkgs; [
jetbrains.pycharm
rclone
opencode
# zed-editor
uv
google-cloud-sdk
awscli2
distrobox
gnome-disk-utility
moonlight-qt
vscode
texliveFull
gnome-power-manager
discord
smile
podman-compose
vesktop
sox
];
wayland.windowManager.hyprland.settings = {
monitor = [
"eDP-1,2880x1800@120.00Hz,480x0,1.2,vrr,1"
"HDMI-A-1,3840x2160@60.00Hz,0x-2160,1"
"DP-3,1920x1080,3840x-540,1"
"DP-1,1920x1080,3840x-540,1"
];
bind = [
", XF86PowerOff, exec, ${pkgs.rofi}/bin/rofi -show power-menu -modi power-menu:${
./hyprland/files/rofi-power-menu.sh
}"
];
general.gaps_out = 1;
};
programs.alacritty.settings.font.size = 13;
programs.kitty.font.size = 13;
systemd.user.services.vicinae.Service.Environment = [
"QT_SCALE_FACTOR=1.5"
];
}

View File

@ -15,20 +15,13 @@
};
in {
imports = [
(import ./utils/desktop.nix {
inherit inputs config pkgs;
scale = 1.0;
})
(import ./utils/desktop.nix {inherit inputs config pkgs;})
(import ./utils/fzgo_links.nix {inherit config pkgs fzgo_paths;})
];
programs.ssh.settings."*".identityFile = "/home/user/.secrets/SSH/Arendia/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "/home/user/.secrets/SSH/Arendia/id_ed25519";
home.packages = with pkgs; [
rclone
uv
claude-code
awscli2
distrobox
gnome-disk-utility
moonlight-qt
@ -36,15 +29,33 @@ in {
texliveFull
gnome-power-manager
# jetbrains.pycharm-professional
discord
smile
podman-compose
vesktop
];
services.hyprpaper = {
settings = {
wallpaper = [
"HDMI-A-1,${./hyprland/files/wallpaper.png}"
"eDP-1,${./hyprland/files/wallpaper.png}"
];
};
};
wayland.windowManager.hyprland.settings = {
monitor = [
"eDP-1,2560x1440@60.01,0x0,1"
"HDMI-A-1,1920x1080@60,0x-1080,1"
];
bind = [
", XF86PowerOff, exec, ${pkgs.rofi}/bin/rofi -show power-menu -modi power-menu:${
./hyprland/files/rofi-power-menu.sh
}"
];
general.gaps_out = 1;
};
programs.alacritty.settings.font.size = 13;
programs.kitty.font.size = 15;
programs.foot.settings.main.font = pkgs.lib.mkForce "DejaVu Sans Mono:size=15";
systemd.user.services.vicinae.Service.Environment = [
"QT_SCALE_FACTOR=1.2"
];
}

View File

@ -18,7 +18,7 @@ in {
(import ./utils/common.nix {inherit inputs config pkgs;})
./ssh
];
programs.ssh.settings."*".identityFile = "/home/ubuntu/.secrets/SSH/Arm/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "/home/ubuntu/.secrets/SSH/Arm/id_ed25519";
home.sessionVariables = {
NIX_PATH = "${config.xdg.stateHome}/nix/profiles/channels/";

View File

@ -8,7 +8,7 @@
(import ./utils/desktop.nix {inherit inputs config pkgs;})
];
programs.ssh.settings."*".identityFile = "/home/user/.secrets/SSH/Asturia/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "/home/user/.secrets/SSH/Asturia/id_ed25519";
home.packages = with pkgs; [
distrobox

View File

@ -21,7 +21,7 @@ in {
(import ./utils/desktop.nix {inherit inputs config pkgs;})
(import ./utils/fzgo_links.nix {inherit config pkgs fzgo_paths;})
];
programs.ssh.settings."*".identityFile = "/home/user/.secrets/SSH/Kell/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "/home/user/.secrets/SSH/Kell/id_ed25519";
dconf.settings = {
"org/virt-manager/virt-manager/connections" = {
@ -36,8 +36,18 @@ in {
moonlight-qt
vscode
jetbrains.rider
discord
];
services.hyprpaper = {
settings = {
wallpaper = [
"DP-1,${./hyprland/files/wallpaper.png}"
"DP-2,${./hyprland/files/wallpaper.png}"
"HDMI-A-1,${./hyprland/files/wallpaper.png}"
];
};
};
wayland.windowManager.hyprland.settings = {
monitor = [
"DP-2,preferred,0x550,1"

View File

@ -91,16 +91,11 @@ in {
(import ./utils/fzgo_links.nix {inherit config pkgs fzgo_paths;})
./ssh
];
programs.ssh.settings."*".identityFile = "${home_dir}/.secrets/SSH/Mallorea/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "${home_dir}/.secrets/SSH/Mallorea/id_ed25519";
home.sessionVariables = {
NIX_PATH = "${config.xdg.stateHome}/nix/profiles/channels/";
LANG = "en_US.UTF-8";
XDG_RUNTIME_DIR = "/run/user/$(id -u)";
};
home.packages = with pkgs; [
rclone
uv
claude-code
];
}

View File

@ -18,7 +18,7 @@ in {
(import ./utils/common.nix {inherit inputs config pkgs;})
./ssh
];
programs.ssh.settings."*".identityFile = "/home/user/.secrets/SSH/Pi/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "/home/user/.secrets/SSH/Pi/id_ed25519";
home.sessionVariables = {
NIX_PATH = "${config.xdg.stateHome}/nix/profiles/channels/";

View File

@ -12,14 +12,35 @@
dirs = [
"/home/user/.dotfiles"
"/home/server/.cache/fzgo/entries/d/|home|user|Workspace"
"/home/server/.cache/fzgo/entries/d/|home|server|Storage|Thomas"
"/home/server/.cache/fzgo/entries/d/|home|server|Storage|Shared"
];
};
in {
imports = [
(import ./utils/common.nix {inherit inputs config pkgs;})
(import ./utils/fzgo_links.nix {inherit config pkgs fzgo_paths;})
(import ./utils/desktop.nix {inherit inputs config pkgs;})
./ssh
];
programs.ssh.settings."*".identityFile = "/home/user/.secrets/SSH/Riva/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "/home/user/.secrets/SSH/Riva/id_ed25519";
home.packages = with pkgs; [
distrobox
jq
rclone
];
services.hyprpaper = {
settings = {
wallpaper = [
"Virtual-1,${./hyprland/files/wallpaper.png}"
];
};
};
wayland.windowManager.hyprland.settings = {
monitor = [
"Virtual-1,2560x1440,0x0,1"
];
input.kb_options = "compose:rctrl, caps:super, altwin:swap_alt_win";
};
}

View File

@ -21,7 +21,7 @@
./ssh
];
programs.ssh.settings."*".identityFile = "/home/user/.secrets/SSH/Tolnedra/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "/home/user/.secrets/SSH/Tolnedra/id_ed25519";
home.packages = with pkgs; [
wl-clipboard

View File

@ -69,7 +69,7 @@ in {
./ssh
];
programs.ssh.settings."*".identityFile = "/home/server/.secrets/SSH/Vault/id_ed25519";
programs.ssh.matchBlocks."*".identityFile = "/home/server/.secrets/SSH/Vault/id_ed25519";
home.sessionVariables = {
NIX_PATH = "${config.xdg.stateHome}/nix/profiles/channels/";

View File

@ -1,22 +1,14 @@
import { Astal, Gdk, Gtk } from "ags/gtk4";
import app from "ags/gtk4/app";
import {
createBinding,
createComputed,
createState,
For,
With,
Accessor,
} from "ags";
import { createBinding, createState, For, With, Accessor } from "ags";
import { createPoll } from "ags/time";
import { subprocess, execAsync } from "ags/process";
import Pango from "gi://Pango";
import Tray from "gi://AstalTray";
import { execAsync } from "ags/process";
import Hyprland from "gi://AstalHyprland";
import { getIconName } from "./utils";
import Wp from "gi://AstalWp";
import Battery from "gi://AstalBattery";
import GLib from "gi://GLib";
import Gio from "gi://Gio";
const battery = Battery.get_default();
const sensorsAvailable = await execAsync(["sensors"])
@ -24,36 +16,6 @@ const sensorsAvailable = await execAsync(["sensors"])
.catch(() => false);
const wirePlumber = Wp.get_default();
// --- NIRI IPC INTEGRATION ---
const [workspaces, setWorkspaces] = createState<any[]>([]);
const [windows, setWindows] = createState<any[]>([]);
async function updateNiriState() {
try {
const wsOut = await execAsync(["niri", "msg", "-j", "workspaces"]);
setWorkspaces(JSON.parse(wsOut) || []);
const winOut = await execAsync(["niri", "msg", "-j", "windows"]);
setWindows(JSON.parse(winOut) || []);
} catch (e) {
console.error("Failed to fetch Niri state:", e);
}
}
// Initialize state immediately
updateNiriState();
// Subscribe to Niri's native event stream
subprocess(
["niri", "msg", "-j", "event-stream"],
(line) => {
// A state change happened in Niri. Re-fetching guarantees perfect sync
updateNiriState();
},
(err) => console.error("Niri event stream error:", err)
);
// ----------------------------
function SysTray(): JSX.Element {
const tray = Tray.get_default();
let items = createBinding(tray, "items");
@ -90,18 +52,18 @@ function SysTray(): JSX.Element {
);
}
function Left({ connector }: { connector: string }): JSX.Element {
function Left(): JSX.Element {
return (
<box hexpand halign={Gtk.Align.START}>
<Clients connector={connector} />
<Clients />
</box>
);
}
function Center({ connector }: { connector: string }): JSX.Element {
function Center(): JSX.Element {
return (
<box>
<Workspaces connector={connector} />
<Workspaces />
</box>
);
}
@ -190,84 +152,34 @@ function Right() {
);
}
// Icon names to try for the current battery state, most specific first.
// Papirus does not ship every combination, so each entry needs a fallback:
// "-charged-symbolic" only exists at level 100, and "-charging-symbolic" only
// up to level 90 (above that the panel icon has to be named directly).
function batteryIconNames(): string[] {
const percentage = battery.percentage;
const thresholds = [...Array(11).keys()].map((i) => i * 10);
const level = thresholds.find((threshold) => threshold >= percentage * 100);
const plain = `battery-level-${level}-symbolic`;
// Being full is a state, not a 100% reading: with a charge limit set the
// battery stops well below 100 and sits there reporting FULLY_CHARGED.
if (battery.state === Battery.State.FULLY_CHARGED) {
return [`battery-level-${level}-charged-symbolic`, plain];
}
if (battery.charging) {
const padded = String(level).padStart(3, "0");
return [
`battery-level-${level}-charging-symbolic`,
`battery-${padded}-charging`,
plain,
];
}
return [plain];
}
// Papirus encodes the battery level as a 35%-opacity overlay inside its
// *-symbolic icons. GTK4's symbolic recoloring flattens that opacity, so every
// level renders as a solid, full battery. The Papirus *-symbolic icons are just
// symlinks to non-symbolic panel SVGs which keep the opacity when rendered
// normally — so we resolve each name to that underlying file and load it
// directly (as a non-symbolic file icon), which renders the correct level.
function batteryGicon(): Gio.Icon {
const theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!);
// lookup_icon() never fails: for an unknown name it hands back the
// "image-missing" paintable, so check availability before resolving.
const names = batteryIconNames();
const name = names.find((n) => theme.has_icon(n)) ?? names[names.length - 1];
const symbolic = theme
.lookup_icon(name, null, 16, 1, Gtk.TextDirection.NONE, 0)
.get_file();
if (!symbolic) return Gio.ThemedIcon.new(name);
try {
const target = symbolic
.query_info(
"standard::symlink-target",
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
null,
)
.get_symlink_target();
if (target) {
return new Gio.FileIcon({
file: symbolic.get_parent()!.resolve_relative_path(target),
});
}
} catch (e) {}
return new Gio.FileIcon({ file: symbolic });
}
function batteryTooltip(): string {
return `${Math.round(battery.percentage * 100)}%`;
}
function BatteryIcon(): JSX.Element {
if (battery.get_state() == 0) return <box />;
// AstalBattery does not emit notify::percentage/charging/state on this
// system, so a signal binding stays frozen at its initial value (e.g.
// showing "charged" forever). Poll the live values instead.
const gicon = createPoll<Gio.Icon>(batteryGicon(), 10000, batteryGicon);
const tooltip = createPoll<string>(batteryTooltip(), 10000, batteryTooltip);
let batteryPercentage = createBinding(battery, "percentage");
return (
<button
class="battery-item"
tooltipText={tooltip}
onClicked={() => execAsync(["gnome-power-statistics"])}
>
<box>
<image gicon={gicon} />
<With value={batteryPercentage}>
{(percentage) => {
const thresholds = [...Array(11).keys()].map((i) => i * 10);
const icon = thresholds.find(
(threshold) => threshold >= percentage * 100,
);
const charging_name =
battery.percentage >= 0.99 ? "charged" : "charging";
return (
<image
iconName={
battery.charging
? `battery-level-${icon}-${charging_name}-symbolic`
: `battery-level-${icon}-symbolic`
}
/>
);
}}
</With>
</box>
</button>
);
@ -282,126 +194,141 @@ function Icons() {
);
}
function SpeakerButton({ speaker }: { speaker: Wp.Endpoint }): JSX.Element {
const volume = createBinding(speaker, "volume");
const mute = createBinding(speaker, "mute");
function Volume(): JSX.Element {
if (!wirePlumber) return <box />;
const icon = volume.as((vol) => {
const percentage = vol * 100;
const level = [
const audio = wirePlumber.audio;
const icon = createBinding(audio.default_speaker, "volume").as((volume) => {
const vol = volume * 100;
const icon = [
[101, "overamplified"],
[67, "high"],
[34, "medium"],
[1, "low"],
[0, "muted"],
].find(([threshold]) => Number(threshold) <= percentage)?.[1];
return `audio-volume-${level}-symbolic`;
].find(([threshold]) => Number(threshold) <= vol)?.[1];
return `audio-volume-${icon}-symbolic`;
});
const label = createComputed(() =>
mute() ? "" : `${Math.floor(volume() * 100)}%`,
);
const css = mute.as((muted) =>
muted ? "margin-left: 0;" : "margin-left: 0.7em;",
);
const css = createBinding(audio.default_speaker, "mute").as((mute) => {
return mute ? "margin-left:0;" : "margin-left: 0.7em;";
});
let volume = createBinding(audio.default_speaker, "volume");
let mute = createBinding(audio.default_speaker, "mute");
return (
<button class="item blue" onClicked={() => (speaker.mute = !speaker.mute)}>
<button
class="item blue"
onClicked={() =>
(audio.default_speaker.mute = !audio.default_speaker.mute)
}
>
<box>
<image iconName={icon} />
<label label={label} css={css} />
<With value={volume}>
{(vol) => (
<box>
<With value={mute}>
{(muted) => {
return (
<label
label={muted ? "" : `${Math.floor(vol * 100)}%`}
css={css}
/>
);
}}
</With>
</box>
)}
</With>
</box>
</button>
);
}
function Volume(): JSX.Element {
if (!wirePlumber) return <box />;
// WirePlumber swaps out the whole endpoint object whenever the default
// sink changes, e.g. when a Bluetooth headset connects. Binding to the
// endpoint that happened to be default at startup leaves the widget stuck
// on that device forever, so track the property and rebuild on every swap.
const speaker = createBinding(wirePlumber.audio, "defaultSpeaker");
return (
<With value={speaker}>
{(endpoint: Wp.Endpoint | null) =>
endpoint ? <SpeakerButton speaker={endpoint} /> : <box />
}
</With>
);
}
function Workspaces({ connector }: { connector: string }): JSX.Element {
function Workspaces(): JSX.Element {
const hyprland = Hyprland.get_default();
let workspaces = createBinding(hyprland, "workspaces");
return (
<box class="workspaces">
{/* Generate a derived binding by passing a transformation function into the `workspaces` state */}
<For each={workspaces((wss: any[]) =>
wss.filter((ws) => ws.output === connector)
.filter((ws) => ws.is_active || windows().some((w: any) => w.workspace_id === ws.id))
.sort((a, b) => a.idx - b.idx)
)}>
{(ws: any) => (
<With value={workspaces}>
{(wss: Array<Hyprland.Workspace>) => (
<box>
<With value={createBinding(hyprland, "focusedMonitor")}>
{(fm: Hyprland.Monitor) => {
let filtered_wss = new Accessor(() =>
wss
.sort((a, b) => a.id - b.id)
.filter(
(ws) =>
ws &&
ws.get_monitor() &&
ws.get_monitor().get_id() === fm.get_id(),
),
);
return (
<box>
<For each={filtered_wss}>
{(ws: Hyprland.Workspace, _index) => (
<button
class={ws.is_active ? "focused" : ""}
onClicked={() => execAsync(["niri", "msg", "action", "focus-workspace", ws.idx.toString()])}
class={createBinding(hyprland, "focusedWorkspace").as(
(fw) => (ws === fw ? "focused" : ""),
)}
onClicked={() => ws.focus()}
>
{ws.name || ws.idx.toString()}
{`${ws.id}`.slice(-1)}
</button>
)}
</For>
</box>
);
}}
</With>
</box>
)}
</With>
</box>
);
}
const MAX_TOTAL_CHARS = 120;
const MIN_TITLE_CHARS = 10;
const OVERHEAD_PER_WINDOW = 5; // icon + padding + margin in character-width equivalents
function computeTitleBudgets(wins: any[]): any[] {
const totalLength = wins.reduce((sum, w) => sum + (w.title || "").length, 0);
const count = wins.length;
const totalWithOverhead = totalLength + count * OVERHEAD_PER_WINDOW;
let maxChars: number;
if (count === 0 || totalWithOverhead <= MAX_TOTAL_CHARS) {
maxChars = -1;
} else {
const charsAvailable = MAX_TOTAL_CHARS - count * OVERHEAD_PER_WINDOW;
maxChars = Math.max(MIN_TITLE_CHARS, Math.floor(charsAvailable / count));
function shorten(title: string) {
return title.length > 40 ? title.slice(0, 20) + "..." : title;
}
return wins.map(w => ({ ...w, maxChars }));
}
function Clients({ connector }: { connector: string }): JSX.Element {
function Clients(): JSX.Element {
const hyprland = Hyprland.get_default();
let clients = createBinding(hyprland, "clients");
return (
<box>
<With value={createBinding(hyprland, "focusedWorkspace")}>
{(fw: Hyprland.Workspace) => (
<box class="clients">
{/* Use With to react to workspace changes, so we know which WS is active */}
<With value={workspaces}>
{(wss: any[]) => {
let active_ws_for_monitor = wss.find((ws: any) => ws.is_active && ws.output === connector)?.id;
<With value={clients}>
{(cls: Array<Hyprland.Client>) => {
let filtered_clients = new Accessor(() =>
cls
.sort((a, b) => a.pid - b.pid)
.filter((cl) => !cl.title.includes("rofi"))
.filter(
(cl) => fw && cl.get_workspace().get_id() === fw.get_id(),
),
);
return (
<box>
{/* Generate a derived binding for windows, depending on the active workspace */}
<For each={windows((wins: any[]) =>
computeTitleBudgets(
wins.filter((w: any) => !w.title?.includes("rofi"))
.filter((w: any) => w.workspace_id === active_ws_for_monitor)
.sort((a: any, b: any) => {
const posA = a.layout?.pos_in_scrolling_layout?.[0] ?? 0;
const posB = b.layout?.pos_in_scrolling_layout?.[0] ?? 0;
return posA - posB;
})
)
)}>
{(win: any) => (
<box class={win.is_focused ? "focused" : "unfocused"}>
<image iconName={getIconName(win.app_id, win.title)} class="app-icon" />
<For each={filtered_clients}>
{(cl: Hyprland.Client, _index) => (
<box
class={createBinding(hyprland, "focusedClient").as(
(a) =>
a && a.address === cl.address
? "focused"
: "unfocused",
)}
>
<image iconName={getIconName(cl)} class="app-icon" />
<label
label={win.title || ""}
maxWidthChars={win.maxChars}
ellipsize={Pango.EllipsizeMode.END}
label={createBinding(cl, "title").as((title) =>
shorten(title),
)}
/>
</box>
)}
@ -411,13 +338,14 @@ function Clients({ connector }: { connector: string }): JSX.Element {
}}
</With>
</box>
)}
</With>
</box>
);
}
export default function Bar(gdkmonitor: Gdk.Monitor, scaleFactor: number = 1) {
console.log("Creating Bar on monitor:", gdkmonitor.get_connector());
const connector = gdkmonitor.get_connector() || "";
console.log("Creating Bar on monitor:", gdkmonitor);
return (
<window
visible
@ -431,11 +359,11 @@ export default function Bar(gdkmonitor: Gdk.Monitor, scaleFactor: number = 1) {
}
application={app}
class="Bar"
name={`top-bar-${connector}`}
name="top-bar"
>
<centerbox class="window-box">
<Left $type="start" connector={connector} />
<Center $type="center" connector={connector} />
<Left $type="start" />
<Center $type="center" />
<Right $type="end" />
</centerbox>
</window>

View File

@ -1,34 +1,60 @@
import app from "ags/gtk4/app";
import { Gdk } from "ags/gtk4";
import style from "./style.scss";
import Bar from "./Bar";
import Hyprland from "gi://AstalHyprland";
import NotificationPopups from "./notifications/NotificationPopups";
const PREFERRED_MONITOR = "eDP-1";
const hyprland = Hyprland.get_default();
function pick_monitor(): Gdk.Monitor | undefined {
const monitors = app.get_monitors();
return (
monitors.find((m: Gdk.Monitor) => m.get_connector() === PREFERRED_MONITOR) ??
monitors[0]
function find_main_monitor(): Hyprland.Monitor {
let monitors = hyprland.get_monitors();
for (let j = 0; j < monitors.length; j++) {
for (const monitor of ["eDP", "DP", "HDMI-A"]) {
for (let i = 0; i < monitors.length; i++) {
console.log(
"Checking monitor:",
monitors[i].get_name(),
"against",
monitor + "-" + j,
);
if (monitors[i].get_name() == monitor + "-" + j) {
return monitors[i];
}
}
}
}
return monitors[0];
}
function register_windows(monitor: Gdk.Monitor) {
let scale = (monitor.get_geometry().width >= 3000) ? 1.2 : 1;
Bar(monitor, scale);
function register_windows(monitor: Hyprland.Monitor) {
let gtkMonitor = app.get_monitors()[0];
let scale = monitor.get_width() >= 3000 ? 1.2 : 1;
Bar(gtkMonitor, scale);
NotificationPopups();
}
function switch_to_best_monitor() {
let mainMonitor = find_main_monitor();
for (var wd of app.get_windows()) {
wd.destroy();
}
register_windows(mainMonitor);
}
hyprland.connect("monitor-added", (_, _monitor: Hyprland.Monitor) => {
switch_to_best_monitor();
});
hyprland.connect("monitor-removed", () => {
switch_to_best_monitor();
});
console.log("Trying to find the best monitor");
app.start({
css: style,
iconTheme: "Papirus",
main() {
const monitor = pick_monitor();
if (!monitor) {
console.error("No monitors available, not creating the bar.");
return;
}
register_windows(monitor);
switch_to_best_monitor();
},
});

View File

@ -1,4 +1,5 @@
import Apps from "gi://AstalApps";
import AstalHyprland from "gi://AstalHyprland";
const app_icons = new Apps.Apps().list.reduce(
(acc, app) => {
@ -15,45 +16,57 @@ const app_icons = new Apps.Apps().list.reduce(
{ classOrNames: {} as Record<string, string>, executables: {} as Record<string, string> },
);
export function getIconName(app_id: string | null | undefined, title: string | null | undefined) {
if (!app_id && !title) return "";
export function getIconName(client: AstalHyprland.Client | null | undefined) {
if (!client) return "";
// Our foot terminals carry a unique per-window app-id ("footclient-<pid>") so the
// "duplicate window" keybind can tell them apart; strip that suffix back to the
// base app-id ("footclient") so the icon still resolves.
const norm = app_id ? app_id.replace(/-\d+$/, "") : app_id;
// try fields matching Niri outputs
// try a bunch of fields (snake_case and camelCase variants)
const possibleKeys = [
app_id,
norm,
title
// common client properties (snake_case)
(client as any).wm_class,
(client as any).initial_class,
(client as any).executable,
// camelCase variants (some bindings expose these)
(client as any).class,
(client as any).initialClass,
(client as any).initialTitle,
(client as any).title,
(client as any).name,
].filter(Boolean) as string[];
// 1) direct exact match
for (const k of possibleKeys) {
const icon = app_icons.classOrNames[k] ?? app_icons.executables[k];
if (icon) {
// cache the mapping
const cacheKey = app_id ?? title ?? k;
// cache the mapping for this client's canonical class (prefer wm_class or class)
const cacheKey = (client as any).wm_class ?? (client as any).class ?? (client as any).name ?? k;
app_icons.classOrNames[cacheKey] = icon;
return icon;
}
}
// 2) fuzzy match: see if any stored key includes any of the client strings (title, name, etc.)
for (const s of possibleKeys) {
const searchStrings = [
(client as any).title,
(client as any).initialTitle,
(client as any).initial_class,
(client as any).initialClass,
(client as any).wm_class,
(client as any).class,
(client as any).name,
].filter(Boolean) as string[];
for (const s of searchStrings) {
const matchKey = Object.keys(app_icons.classOrNames).find((key) => key.includes(s));
if (matchKey) {
const icon = app_icons.classOrNames[matchKey];
const cacheKey = app_id ?? title ?? s;
const cacheKey = (client as any).wm_class ?? (client as any).class ?? (client as any).name ?? s;
app_icons.classOrNames[cacheKey] = icon;
return icon;
}
}
// nothing found — cache empty string so we don't repeat work
const cacheKey = app_id ?? title ?? "";
const cacheKey = (client as any).wm_class ?? (client as any).class ?? (client as any).name ?? "";
if (cacheKey) app_icons.classOrNames[cacheKey] = "";
return "";
}

View File

@ -21,7 +21,7 @@
pkgs.accountsservice
battery
# hyprland
hyprland
tray
powerprofiles
wireplumber

View File

@ -1,8 +1,4 @@
{
pkgs,
config,
...
}: let
{pkgs, ...}: let
lock-false = {
Value = false;
Status = "locked";
@ -10,7 +6,6 @@
in {
programs.firefox = {
enable = true;
configPath = "${config.xdg.configHome}/mozilla/firefox";
package = pkgs.wrapFirefox pkgs.firefox-unwrapped {
pkcs11Modules = [
pkgs.eid-mw
@ -66,18 +61,22 @@ in {
default = "ddg";
order = ["ddg" "google"];
};
# extensions.packages = with pkgs.nur.repos.rycee.firefox-addons; [
# ublock-origin
# bitwarden
# display-_anchors
# facebook-container
# multi-account-containers
# istilldontcareaboutcookies
# return-youtube-dislikes
# sponsorblock
# unpaywall
# duckduckgo-privacy-essentials
# ];
extensions.packages = with pkgs.nur.repos.rycee.firefox-addons; [
ublock-origin
bitwarden
privacy-badger
beyond-20
display-_anchors
facebook-container
multi-account-containers
istilldontcareaboutcookies
return-youtube-dislikes
sponsorblock
unpaywall
vimium
google-container
duckduckgo-privacy-essentials
];
};
};
};

View File

@ -2,14 +2,6 @@
programs.git = {
enable = true;
lfs.enable = true;
# 1. Update the signing configuration
signing = {
format = "ssh";
key = "/home/user/.secrets/SSH/Aloria/id_ed25519";
signByDefault = true;
};
settings = {
alias = {
s = "status";
@ -19,14 +11,10 @@
name = "Thomas Avé";
email = "email@thomasave.be";
};
# 2. Tell Git where your allowed_signers file is located
gpg.ssh.allowedSignersFile = "~/.ssh/allowed_signers";
credentials.helper = "store";
pull.rebase = false;
push.autoSetupRemote = true;
branch.autoSetupMerge = "always";
push.autoSetupRemove = true;
branch.autoSetupMerge = true;
init.defaultBranch = "master";
diff.noprefix = true;
color.ui = "auto";
@ -36,24 +24,11 @@
};
rerere.enabled = true;
};
ignores = [
"*.direnv/"
"*.nix/"
"uv.lock"
".dmypy.json"
".envrc"
];
};
programs.delta = {
enable = true;
options = {
diff-so-fancy = true;
file-style = "omit";
# hunk-header-style = "omit";
};
};
programs.lazygit = {
enable = true;
settings = {
@ -61,18 +36,6 @@
editPreset = "nvim";
autoStageResolvedConflicts = false;
};
git.overrideGpg = true;
git.diffRenderers = [
{
colorArg = "always";
command = "delta --dark --color-only --paging=never";
}
];
};
};
# 3. Declaratively create the allowed_signers file (Assuming Home Manager)
home.file.".ssh/allowed_signers".text = ''
email@thomasave.be namespaces="git" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBxMq4kubz4wWr4S8xU3GRkPcn6XRS3y7IP+qylN5QAp user@Aloria
'';
}

View File

@ -4,11 +4,9 @@
pname = "Helium";
version = "0.4.7.1";
src = pkgs.fetchurl {
url = "https://github.com/imputnet/helium-linux/releases/download/0.11.7.1/helium-0.11.7.1-x86_64.AppImage";
sha256 = "sha256-qzc135IP5F2btxtOMUGMz+0azJhYL9KI0lcPG2KjcxU=";
url = "https://github.com/imputnet/helium-linux/releases/download/0.9.4.1/helium-0.9.4.1-x86_64.AppImage";
sha256 = "sha256-N5gdWuxOrIudJx/4nYo4/SKSxakpTFvL4zzByv6Cnug=";
};
extraPkgs = pkgs: [pkgs.tzdata];
profile = "export TZ=Asia/Bangkok";
})
];
xdg.desktopEntries = {

View File

@ -147,6 +147,7 @@ in {
"rounding 0, match:float 0, match:workspace f[1]"
];
# UPDATED: New Layer Rule Syntax (0.53.0)
# Syntax: effect value, match:namespace regex
layerrule = [
"no_anim on, match:namespace ags_bar_0"

View File

@ -3,40 +3,9 @@
pkgs,
...
}: {
home.file."${config.xdg.configHome}/mpv/scripts".source = ./files/scripts;
home.file."${config.xdg.configHome}/mpv/fonts".source = ./files/fonts;
programs.mpv = {
enable = true;
config = {
osc = false;
save-position-on-quit = true;
sub-auto = "fuzzy";
sub-font-size = 30;
profile = "gpu-hq";
scale = "ewa_lanczossharp";
cscale = "ewa_lanczossharp";
video-sync = "display-resample";
interpolation = true;
tscale = "oversample";
vo = "gpu";
hwdec = "auto";
force-window = true;
keep-open = true;
};
bindings = {
"UP" = "add volume +5";
"DOWN" = "add volume -5";
"Ctrl+Shift+r" = "add sub-scale +0.05";
"Ctrl+r" = "add sub-scale -0.05";
"Ctrl+Alt+r" = "set sub-scale 1";
};
profiles.Idle = {
profile-cond = ''p["idle-active"]'';
profile-restore = "copy-equal";
title = "' '";
keepaspect = false;
background = "none";
};
home.file."${config.xdg.configHome}/mpv" = {
source = ./files;
recursive = true;
};
programs.mpv = {enable = true;};
}

20
home/mpv/files/mpv.conf Normal file
View File

@ -0,0 +1,20 @@
osc=no
save-position-on-quit=yes
sub-auto=fuzzy
profile=gpu-hq
scale=ewa_lanczossharp
cscale=ewa_lanczossharp
video-sync=display-resample
interpolation
tscale=oversample
vo=gpu
hwdec=auto
force-window=yes
keep-open=yes
[Idle]
profile-cond=p["idle-active"]
profile-restore=copy-equal
title=' '
keepaspect=no
background=none

View File

@ -1,284 +0,0 @@
{
pkgs,
scale ? null,
...
}: let
nautilus = "${pkgs.nautilus}/bin/nautilus";
playerctl = "${pkgs.playerctl}/bin/playerctl";
wpctl = "${pkgs.wireplumber}/bin/wpctl";
brightnessctl = "${pkgs.brightnessctl}/bin/brightnessctl";
# Spawn a foot terminal that the "duplicate window" keybind can later trace back to
# its shell. foot's --server mode makes every window report the *server's* PID to the
# compositor, so niri can't distinguish windows by PID. Instead we give each window a
# unique app-id ("footclient-<pid>") and export the same value as FOOT_APPID into its
# shell; -E hands footclient's environment (incl. FOOT_APPID) to the server-spawned
# shell. launch_terminal then finds the focused window's shell by matching FOOT_APPID.
spawn_terminal = pkgs.writeShellScriptBin "spawn_terminal" ''
ID="footclient-$$"
export FOOT_APPID="$ID"
exec ${pkgs.foot}/bin/footclient -E --app-id="$ID" "$@"
'';
launch_terminal = pkgs.writeShellScriptBin "launch_terminal" ''
function find_leaf_pid(){
local PID
local RET
local NAME
PID=$1
for CHILD_PID in $(pgrep -P "$PID"); do
RET=$(find_leaf_pid "$CHILD_PID")
if [ "$RET" != "" ]; then
PID="$RET"
fi
done
NAME=$(ps -p "$PID" -o comm=)
if [ "$NAME" == "zsh" ]; then
echo "$PID"
elif [ "$NAME" == "ssh" ]; then
echo "$PID"
fi
}
# foot runs in --server mode, so niri reports the shared server PID for every foot
# window and cannot tell them apart. Resolve the focused window to its own shell by
# matching the FOOT_APPID marker that spawn_terminal exports into each window: scan
# the server's direct children for the one whose environment carries our app-id.
FOCUSED=$(niri msg --json focused-window)
SERVER_PID=$(echo "$FOCUSED" | ${pkgs.jq}/bin/jq -r '.pid // empty')
APP_ID=$(echo "$FOCUSED" | ${pkgs.jq}/bin/jq -r '.app_id // empty')
ROOT_PID=""
if [ -n "$APP_ID" ] && [ -n "$SERVER_PID" ]; then
for CHILD in $(pgrep -P "$SERVER_PID"); do
if grep -qzF "FOOT_APPID=$APP_ID" "/proc/$CHILD/environ" 2>/dev/null; then
ROOT_PID="$CHILD"
break
fi
done
fi
# Focused window isn't one of our tagged foot terminals: just open a plain terminal.
if [ -z "$ROOT_PID" ]; then
exec ${spawn_terminal}/bin/spawn_terminal
fi
LEAF_PID=$(find_leaf_pid "$ROOT_PID")
if [ "$(ps -p "$LEAF_PID" -o comm=)" == "ssh" ]; then
SSH_COMMAND=$(ps -p "$LEAF_PID" -o args --no-headers)
if [[ "$SSH_COMMAND" == *"waypipe"* ]]; then
CLIENT=$(echo "$SSH_COMMAND" | awk '{ print $5 }')
PREVIOUS_SESSION_ID=$(grep -z "SSH_SESSION_ID" "/proc/$LEAF_PID/environ" | xargs -0 -n 1 | grep -oP "[0-9]*")
SSH_SESSION_ID=$RANDOM
${spawn_terminal}/bin/spawn_terminal zsh -c "SSH_SESSION_ID=$SSH_SESSION_ID waypipe ssh -t \"$CLIENT\" env SSH_SESSION_ID=\"$SSH_SESSION_ID\" PREVIOUS_SESSION_ID=\"$PREVIOUS_SESSION_ID\" \"zsh --login\""
else
${spawn_terminal}/bin/spawn_terminal zsh -c "ssh -t \"$(echo "$SSH_COMMAND" | awk '{ print $2 }')\""
fi
else # Not an ssh session: open a local terminal in the same directory.
CWD=$(readlink -e /proc/"$LEAF_PID"/cwd)
if [ "$CWD" != "" ]; then
${spawn_terminal}/bin/spawn_terminal --working-directory "$CWD"
else
${spawn_terminal}/bin/spawn_terminal
fi
fi
'';
in {
# Since native Home Manager doesn't have a programs.niri module yet,
# we generate the KDL configuration file directly.
home.packages = with pkgs; [
wl-clipboard
];
programs.hyprlock = {
enable = true;
settings = {
input-field = [
{
size = "200, 50";
position = "0, -80";
monitor = "";
dots_center = true;
fade_on_empty = true;
font_color = "rgb(202, 211, 245)";
inner_color = "rgb(91, 96, 120)";
outer_color = "rgb(24, 25, 38)";
outline_thickness = 3;
placeholder_text = "<span foreground=\"##cad3f5\">Password...</span>";
shadow_passes = 2;
}
];
general = {
no_fade_in = false;
};
background = [
{
path = "screenshot";
blur_passes = 3;
blur_size = 8;
}
];
};
};
xdg.configFile."niri/config.kdl".text = ''
spawn-at-startup "${pkgs.swaybg}/bin/swaybg" "-i" "${../hyprland/files/wallpaper.png}" "-m" "fill"
spawn-at-startup "foot" "--server"
spawn-at-startup "ags" "run"
spawn-at-startup "${pkgs.mate-polkit}/bin/polkit-mate"
prefer-no-csd
hotkey-overlay {
skip-at-startup
}
gestures {
hot-corners {
off
}
}
layout {
gaps 5
border {
off
}
focus-ring {
width 4
active-color "rgba(0, 47, 95, 238)"
inactive-color "rgba(255, 255, 255, 0)"
}
}
input {
mouse {
accel-speed 0.5
}
touchpad {
tap
}
keyboard {
xkb {
options "compose:rctrl"
}
}
focus-follows-mouse max-scroll-amount="10%"
warp-mouse-to-focus
}
output "HDMI-A-1" {
scale 1.0
position x=0 y=-2160
}
${
if scale != null
then ''
output "eDP-1" {
scale ${toString scale}
position x=0 y=0
}
''
else ""
} window-rule {
match app-id="vicinae"
open-floating true
}
binds {
// General Binds
Mod+Return { spawn "${launch_terminal}/bin/launch_terminal"; }
Mod+Shift+Return { spawn "${spawn_terminal}/bin/spawn_terminal"; }
Mod+Q { close-window; }
Mod+A { spawn "${nautilus}"; }
Mod+B { spawn "Helium"; }
Mod+M { maximize-column; }
Mod+F {maximize-window-to-edges;}
Mod+Tab { toggle-overview; }
// Vicinae Binds
Mod+C { spawn "vicinae" "vicinae://launch/clipboard/history"; }
Mod+N { spawn "vicinae" "vicinae://launch/wm/switch-windows"; }
Mod+Escape { spawn "vicinae" "open" "-q" "Power Management "; }
Mod+Space { spawn "vicinae" "toggle"; }
Mod+Comma { consume-or-expel-window-left; }
Mod+Period { consume-or-expel-window-right; }
Mod+Alt+H { set-column-width "-5%"; }
Mod+Alt+L { set-column-width "+5%"; }
Mod+Alt+K { set-window-height "-5%"; }
Mod+Alt+J { set-window-height "+5%"; }
// Movement
Mod+H { focus-column-or-monitor-left; }
Mod+J { focus-window-or-monitor-down; }
Mod+K { focus-window-or-monitor-up; }
Mod+L { focus-column-or-monitor-right; }
Mod+Shift+H { move-column-left-or-to-monitor-left; }
Mod+Shift+L { move-column-right-or-to-monitor-right; }
Mod+Shift+J { move-window-down-or-to-workspace-down; }
Mod+Shift+K { move-window-up-or-to-workspace-up; }
// Move window to the other monitor
Mod+Ctrl+K { move-window-to-monitor-up; }
Mod+Ctrl+J { move-window-to-monitor-down; }
// Workspaces
Mod+Shift+U { move-column-to-workspace-down; }
Mod+Shift+I { move-column-to-workspace-up; }
Mod+I { focus-workspace-up; }
Mod+U { focus-workspace-down; }
Mod+1 { focus-workspace 1; }
Mod+2 { focus-workspace 2; }
Mod+3 { focus-workspace 3; }
Mod+4 { focus-workspace 4; }
Mod+5 { focus-workspace 5; }
Mod+6 { focus-workspace 6; }
Mod+7 { focus-workspace 7; }
Mod+8 { focus-workspace 8; }
Mod+9 { focus-workspace 9; }
// Move to workspace
Mod+Shift+1 { move-column-to-workspace 1; }
Mod+Shift+2 { move-column-to-workspace 2; }
Mod+Shift+3 { move-column-to-workspace 3; }
Mod+Shift+4 { move-column-to-workspace 4; }
Mod+Shift+5 { move-column-to-workspace 5; }
Mod+Shift+6 { move-column-to-workspace 6; }
Mod+Shift+7 { move-column-to-workspace 7; }
Mod+Shift+8 { move-column-to-workspace 8; }
Mod+Shift+9 { move-column-to-workspace 9; }
Print { screenshot; }
Ctrl+Alt+L {spawn "${pkgs.hyprlock}/bin/hyprlock"; }
XF86MonBrightnessUp { spawn "${brightnessctl}" "set" "+10%"; }
XF86MonBrightnessDown { spawn "${brightnessctl}" "set" "10%-"; }
// Volume
XF86AudioRaiseVolume { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "10%+" "--limit" "1.0"; }
XF86AudioLowerVolume { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "10%-"; }
XF86AudioMute { spawn "${wpctl}" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; }
XF86AudioMicMute { spawn "${wpctl}" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"; }
// Ctrl + horizontal mouse scroll controls volume
// (unmodified WheelScroll binds capture the whole wheel in niri and break normal scrolling)
Ctrl+Alt+WheelScrollRight cooldown-ms=10 { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "1%+" "--limit" "1.0"; }
Ctrl+Alt+WheelScrollLeft cooldown-ms=10 { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "1%-"; }
// Mod + horizontal scroll moves between windows (columns) left/right
Mod+WheelScrollRight cooldown-ms=200 { focus-column-right; }
Mod+WheelScrollLeft cooldown-ms=200 { focus-column-left; }
// Mod + vertical scroll moves between workspaces up/down
Mod+WheelScrollDown cooldown-ms=100 { focus-workspace-down; }
Mod+WheelScrollUp cooldown-ms=100 { focus-workspace-up; }
// Media
XF86AudioNext { spawn "${playerctl}" "next"; }
XF86AudioPrev { spawn "${playerctl}" "previous"; }
Mod+XF86AudioMute { spawn "${playerctl}" "play-pause"; }
Mod+XF86AudioRaiseVolume { spawn "${playerctl}" "next"; }
Mod+XF86AudioLowerVolume { spawn "${playerctl}" "previous"; }
}
'';
}

View File

@ -2,19 +2,9 @@
config,
pkgs,
...
}: let
basedpyrightShim = pkgs.symlinkJoin {
name = "basedpyright-with-pyright-shim";
paths = [pkgs.basedpyright];
postBuild = ''
ln -s $out/bin/basedpyright-langserver $out/bin/pyright-langserver
ln -s $out/bin/basedpyright $out/bin/pyright
'';
};
in {
}: {
home.packages = with pkgs; [
# codeium
basedpyrightShim
nixd
alejandra
clang-tools

View File

@ -5,13 +5,12 @@ end
return {
{
'saghen/blink.compat',
version = '*',
lazy = true,
opts = {},
},
{
'saghen/blink.cmp',
branch = 'v1',
version = "1.*",
dependencies = {
'rafamadriz/friendly-snippets',
'honza/vim-snippets',
@ -23,8 +22,8 @@ return {
},
build = 'nix run --extra-experimental-features flakes --extra-experimental-features nix-command .#build-plugin',
keys = {
{ "<leader>n", function() vim.diagnostic.jump({ count = 1 }) end },
{ "<leader>p", function() vim.diagnostic.jump({ count = -1 }) end },
{ "<leader>n", vim.diagnostic.goto_next },
{ "<leader>p", vim.diagnostic.goto_prev },
{ "<leader>f", vim.lsp.buf.code_action },
{ "gd", vim.lsp.buf.definition },
{ "gi", vim.lsp.buf.implementation },

View File

@ -160,7 +160,7 @@ borg create -s --progress \
borg prune -d 365 --save-space 10.4.0.1:Storage/Thomas/Borg/Cloud
borg compact 10.4.0.1:Storage/Thomas/Borg/Cloud
# ssh vault /home/server/Storage/Thomas/Borg/Sync.sh
ssh vault /home/server/Storage/Thomas/Borg/Sync.sh
##########################################
# Lock the Vault #

View File

@ -7,7 +7,7 @@
programs.ssh = {
enable = true;
enableDefaultConfig = false;
settings = {
matchBlocks = {
"*" = {
host = "*";
};
@ -25,6 +25,48 @@
user = "server";
forwardAgent = true;
};
Kell = {
host = "Kell";
hostname = "kell.thomasave.be";
port = 22;
user = "user";
forwardAgent = true;
};
kell = {
host = "kell";
hostname = "kell.thomasave.be";
port = 22;
user = "user";
forwardAgent = true;
};
Riva = {
host = "Riva";
hostname = "riva.thomasave.be";
port = 22;
user = "user";
forwardAgent = true;
};
riva = {
host = "riva";
hostname = "riva.thomasave.be";
port = 22;
user = "user";
forwardAgent = true;
};
Arch = {
host = "Arch";
hostname = "arch.thomasave.be";
port = 22;
user = "user";
forwardAgent = true;
};
arch = {
host = "arch";
hostname = "arch.thomasave.be";
port = 22;
user = "user";
forwardAgent = true;
};
Vault = {
host = "Vault";
hostname = "etienne.thomasave.be";

View File

@ -20,20 +20,14 @@
targets.genericLinux.enable = true;
programs.home-manager.enable = true;
home.packages = with pkgs; [
inputs.worktimer.packages.${pkgs.stdenv.hostPlatform.system}.default
htop
ghostscript
waypipe
traceroute
tree
inputs.worktimer.packages.${pkgs.stdenv.hostPlatform.system}.default
usbutils
gocryptfs
sshfs
ncdu
jq
rclone
claude-code
uv
];
home.sessionVariables = {
XDG_CONFIG_HOME = "${config.xdg.configHome}";

View File

@ -2,14 +2,12 @@
inputs,
config,
pkgs,
scale ? null,
...
}: {
imports = [
(import ./common.nix {inherit inputs config pkgs;})
(import ./python.nix {inherit inputs config pkgs;})
# (import ../hyprland {inherit inputs pkgs;})
(import ../niri {inherit inputs config pkgs scale;})
(import ../hyprland {inherit inputs pkgs;})
(import ../ags {inherit inputs pkgs;})
../rofi
../vicinae
@ -87,7 +85,6 @@
};
home.pointerCursor = {
enable = true;
gtk.enable = true;
name = "Adwaita";
size = 28;
@ -121,5 +118,4 @@
};
fonts.fontconfig.enable = true;
gtk.gtk4.theme = config.gtk.theme; # Silence warning
}

View File

@ -1,50 +1,16 @@
{...}: {
programs.vicinae = {
{inputs, ...}:
# Add inputs here
{
# Import the module definition here
imports = [inputs.vicinae.homeManagerModules.default];
services.vicinae = {
enable = true;
systemd = {
enable = true;
autoStart = true;
};
settings = {
font = {
normal = {
family = "Ubuntu Nerd Font";
size = 8;
};
};
theme = {
dark = {
name = "ayu-dark";
};
};
providers = {
clipboard = {
entrypoints = {
history = {
preferences = {
defaultAction = "copy";
};
};
};
};
core = {
entrypoints = {
"search-emojis" = {
preferences = {
defaultAction = "copy";
};
};
};
};
};
launcher_window = {
opacity = 0.95;
client_side_decorations = {
enabled = true;
border_width = 0;
rounding = 10;
shadow_size = 0;
};
environment = {
QT_SCALE_FACTOR = "1.5";
};
};
};

View File

@ -17,7 +17,6 @@
pkgs.moreutils
inputs.fzgo.packages.${pkgs.stdenv.hostPlatform.system}.default
];
programs.zsh.dotDir = "${config.xdg.configHome}/zsh";
home.sessionVariables = {
BROWSER = "Helium";
PDF_VIEWER = "evince";
@ -78,10 +77,6 @@
source ${./files/keys.zsh}
eval "$(direnv hook zsh)"
export LD_LIBRARY_PATH=${pkgs.sqlite.out}/lib:$LD_LIBRARY_PATH
# Fix zsh-vi-mode bracketed paste: the trailing ~ of \e[201~ leaks
# into vicmd and toggles case of the last pasted character.
bindkey -M viins '^[[200~' .bracketed-paste
bindkey -M vicmd '^[[200~' .bracketed-paste
}
function o() {
${pkgs.handlr-regex}/bin/handlr open $@ < /dev/null &> /dev/null & disown

View File

@ -89,8 +89,8 @@
# xplr # xplr shell (https://github.com/sayanarijit/xplr)
# vim_shell # vim shell indicator (:sh)
# midnight_commander # midnight commander shell (https://midnight-commander.org/)
# nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
# vi_mode # vi mode (you don't need this if you've enabled prompt_char)
nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
vi_mode # vi mode (you don't need this if you've enabled prompt_char)
# vpn_ip # virtual private network indicator
# load # CPU load
# disk_usage # disk usage

View File

@ -1,74 +1,20 @@
if [ -n "$REMOTE_PATH" ]; then
# Fresh `s <host>` connection: the initial working directory is handed over in
# REMOTE_PATH (an env var is the only channel that survives waypipe's command
# transport intact — shell grouping like `cd …; exec …` does not). cd into it,
# then unset so it does NOT leak to child shells: a lingering exported
# REMOTE_PATH is what made every new zellij pane jump back to this same path
# instead of inheriting the pane's own cwd.
[ -d "$REMOTE_PATH" ] && cd "$REMOTE_PATH"
unset REMOTE_PATH
if [ "$SSH_SESSION_ID" != "" ]; then
mkdir -p /tmp/ssh_sessions
echo $$ > /tmp/ssh_sessions/"$SSH_SESSION_ID"
fi
if [ -n "$PREVIOUS_SESSION_ID" ]; then
# Spawned as a new window/terminal for an existing remote session (by the
# niri/hyprland keybind). Inherit that session's working directory: find a
# live shell whose SSH_SESSION_ID (carried in its environment) matches and
# cd to its cwd. Then drop the hint so child shells don't keep jumping back.
_best_pid=0
_prev_cwd=""
for _environ in /proc/[0-9]*/environ; do
_pid=${_environ%/environ}
_pid=${_pid#/proc/}
# cat (not a < redirect) so unreadable/ptrace-protected environ files
# fail quietly into the pipe instead of raising a shell error.
if cat "$_environ" 2>/dev/null | tr '\0' '\n' | grep -Fxq "SSH_SESSION_ID=$PREVIOUS_SESSION_ID"; then
[ "$(cat /proc/$_pid/comm 2>/dev/null)" = zsh ] || continue
# SSH_SESSION_ID is exported, so transient `zsh -c …` subshells (prompt
# hooks, zellij helpers) inherit it too — but they aren't real session
# panes and often sit at /. Skip anything invoked in command mode; only
# interactive login/pane shells should count.
cat /proc/$_pid/cmdline 2>/dev/null | tr '\0' '\n' | grep -Fxq -- -c && continue
_c=$(readlink -e /proc/$_pid/cwd 2>/dev/null) || continue
# A login shell still sitting at / is a helper forked during startup
# (e.g. p10k) that never ran the REMOTE_PATH cd, not a shell the user
# works in. Real panes have always cd'd somewhere; skip root.
[ "$_c" = / ] && continue
if [ -n "$_c" ] && [ "$_pid" -gt "$_best_pid" ]; then
_best_pid=$_pid
_prev_cwd=$_c
if [ "$REMOTE_PATH" != "" ]; then
if [ -d "$REMOTE_PATH" ]; then
cd "$REMOTE_PATH"
fi
fi
done
[ -n "$_prev_cwd" ] && cd "$_prev_cwd"
unset _best_pid _prev_cwd _environ _pid _c PREVIOUS_SESSION_ID
elif [ "$PREVIOUS_SESSION_ID" != "" ]; then
cd "$(readlink -e /proc/$(cat /tmp/ssh_sessions/$PREVIOUS_SESSION_ID)/cwd)"
fi
function run_waypipe() {
if [ -z "$1" ]; then
echo "usage: run_waypipe <host>" >&2
return 1
fi
local SSH_SESSION_ID=$RANDOM REMOTE_PATH
# Map the current path relative to $HOME so it resolves to the remote user's
# home even when the username differs (the remote shell expands $HOME while
# parsing the command below).
if [[ "$PWD" == "$HOME" || "$PWD" == "$HOME"/* ]]; then
REMOTE_PATH="\$HOME${PWD#$HOME}"
else
REMOTE_PATH="$PWD"
fi
# Hand off the initial cwd and session token as a plain `env VAR=val … zsh`
# argv. This must stay free of shell grouping/metacharacters: waypipe rejoins
# and re-splits the command on whitespace and runs the leading word as a
# program, so `env` (a real binary) works but `cd …; exec …` would not.
#
# SSH_SESSION_ID is placed in TWO environments on purpose:
# - the leading prefix exports it into the LOCAL waypipe/ssh process, where
# the niri/hyprland "duplicate window" keybind reads it out of
# /proc/<ssh-pid>/environ to learn which session to clone;
# - the `env SSH_SESSION_ID=…` sets it on the REMOTE zsh, where the scan
# block above locates this session's shells by matching it.
SSH_SESSION_ID=$SSH_SESSION_ID waypipe -n ssh -t "$1" env REMOTE_PATH=\"$REMOTE_PATH\" SSH_SESSION_ID=$SSH_SESSION_ID zsh --login
SSH_SESSION_ID=$RANDOM
REMOTE_PATH=$(echo $(pwd) | sed "s|/home/[a-z]*|\$HOME|g")
SSH_SESSION_ID=$SSH_SESSION_ID waypipe -n ssh -t $1 env REMOTE_PATH=\"$REMOTE_PATH\" SSH_SESSION_ID=$SSH_SESSION_ID "zsh --login"
}
alias s='run_waypipe'

View File

@ -5,12 +5,12 @@
config,
...
}: let
# serverIP = "10.0.0.1";
serverIP = "10.0.0.1";
in {
imports = [
(import ../Common/default.nix {inherit inputs pkgs config;})
(import ../Common/desktop.nix {inherit inputs pkgs config;})
# (import ../Common/nfs.nix {inherit pkgs serverIP;})
(import ../Common/nfs.nix {inherit pkgs serverIP;})
(import ../Common/zfs.nix {inherit inputs pkgs config;})
];
@ -24,17 +24,7 @@ in {
services.logind.settings.Login = {
HandlePowerKey = "suspend";
};
# 'throttled' raises PL1/PL2 to 44W, but this machine is a Core Ultra 7 155U
# with a 15W spec sustained power. Holding it at 44W drives the chassis sensor
# (acpitz) past its 109C critical trip, which makes the kernel do a hardware
# protection shutdown. Leave the firmware's own power limits alone.
services.throttled.enable = false;
# thermald refuses to run on ThinkPads exposing thinkpad_acpi/dytc_lapmode,
# because the platform's DPTF firmware owns thermal management there. It exits
# immediately at boot, so enabling it only produces noise in the journal.
services.thermald.enable = lib.mkForce false;
services.throttled.enable = true;
services.thinkfan.enable = true;
services.thinkfan.settings = {
sensors = [
@ -70,8 +60,7 @@ in {
[2 50 60]
[3 56 68]
[5 64 78]
[7 76 90]
["level disengaged" 85 32767]
[7 76 95]
];
};
@ -139,7 +128,7 @@ in {
peers = [
{
publicKey = "/9ppjm3yeD0duDvxrqgcHscHmftXko+0s2RbivNEy2c=";
allowedIPs = ["0.0.0.0/0"];
allowedIPs = ["0.0.0.0/0" "::/0"];
endpoint = "external.thomasave.be:13231";
persistentKeepalive = 25;
}
@ -149,7 +138,7 @@ in {
address = ["10.0.0.5/24"];
privateKeyFile = "/home/user/.secrets/Wireguard/Aloria.key";
listenPort = 51820;
autostart = false;
autostart = true;
postUp = "resolvectl dns OPNsense 10.0.0.1; resolvectl domain OPNsense ~thomasave.be;";
dns = ["10.0.0.1"];
peers = [
@ -161,6 +150,21 @@ in {
}
];
};
GCP = {
address = ["10.5.0.5/24"];
privateKeyFile = "/home/user/.secrets/Wireguard/GCP.key";
listenPort = 51820;
autostart = false;
postUp = "resolvectl dns GCP 1.1.1.1;resolvectl dns wlan0 1.1.1.1;";
peers = [
{
publicKey = "NEBNE4Czf2MkZF2X5aVhmofENH1uXjDpvXjIMJvfMFA=";
allowedIPs = ["0.0.0.0/0"];
endpoint = "35.201.231.151:443";
persistentKeepalive = 25;
}
];
};
};
systemd.services."wg-quick-OPNsense" = {
serviceConfig = {
@ -169,19 +173,6 @@ in {
};
unitConfig.StartLimitIntervalSec = 0;
};
services.tailscale = {
enable = true;
extraUpFlags = [
"--login-server=https://headscale.thomasave.be"
"--accept-routes"
];
authKeyFile = "/home/user/.secrets/Tailscale/Aloria/authkey";
};
programs.winbox = {
enable = true;
openFirewall = true;
};
services.usbmuxd.enable = true;
environment.systemPackages = with pkgs; [
libcamera
@ -194,22 +185,5 @@ in {
"net.ipv6.conf.all.disable_ipv6" = 1;
"net.ipv6.conf.default.disable_ipv6" = 1;
"net.ipv6.conf.lo.disable_ipv6" = 1;
"net.ipv4.ip_forward" = 1;
"net.ipv4.conf.all.forwarding" = 1;
};
systemd.services.tailscale-ethtool-tweaks = {
description = "Apply ethtool tweaks for Tailscale subnet routing performance";
wantedBy = ["multi-user.target"];
after = ["network-online.target"];
wants = ["network-online.target"];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "tailscale-ethtool-tweaks" ''
NETDEV=$(${pkgs.iproute2}/bin/ip -o route get 1.1.1.1 | ${pkgs.coreutils}/bin/cut -f 5 -d " ")
${pkgs.ethtool}/bin/ethtool -K "$NETDEV" rx-udp-gro-forwarding on rx-gro-list off
'';
};
};
}

View File

@ -53,8 +53,10 @@
hardware.trackpoint.enable = lib.mkDefault true;
hardware.trackpoint.emulateWheel = lib.mkDefault config.hardware.trackpoint.enable;
hardware.trackpoint.device = "TPPS/2 Synaptics TrackPoint";
boot.kernelPackages = pkgs.linuxPackages;
boot.zfs.forceImportRoot = false;
nixpkgs.config.packageOverrides = pkgs: {
zfs = pkgs.zfs_unstable;
};
boot.kernelPackages = pkgs.linuxPackages_latest;
hardware.firmware = with pkgs; [
ipu6-camera-bins
ivsc-firmware

View File

@ -11,6 +11,7 @@ in {
imports = [
(import ../Common/default.nix {inherit inputs pkgs config;})
(import ../Common/desktop.nix {inherit inputs pkgs config;})
(import ../Common/nfs.nix {inherit pkgs serverIP;})
(import ../Common/zfs.nix {inherit inputs pkgs config;})
];

View File

@ -12,7 +12,6 @@
boot.initrd.kernelModules = [];
boot.kernelModules = ["kvm-intel"];
boot.extraModulePackages = [];
boot.zfs.forceImportRoot = false;
fileSystems."/" = {
device = "rpool/root";

View File

@ -1,4 +1,8 @@
{pkgs, ...}: {
{
inputs,
pkgs,
...
}: {
services.udev.packages = [pkgs.yubikey-personalization];
programs.appimage.enable = true;
programs.appimage.binfmt = true;
@ -11,26 +15,15 @@
vulkan-extension-layer
];
};
environment.systemPackages = with pkgs; [
wiremix
gnome-disk-utility
moonlight-qt
gnome-power-manager
smile
];
# programs.hyprland = {
# enable = true;
# withUWSM = true;
# };
programs.niri.enable = true;
programs.hyprland = {
enable = true;
withUWSM = true;
};
services.greetd = {
enable = pkgs.lib.mkDefault true;
settings = rec {
initial_session = {
# command = "uwsm start hyprland-uwsm.desktop";
command = "niri-session";
command = "uwsm start hyprland-uwsm.desktop";
user = "user";
};
default_session = initial_session;

View File

@ -17,7 +17,7 @@
autoprune = true;
autosnap = true;
};
"rpool/Storage" = {
"rpool/storage" = {
frequently = 8;
yearly = 0;
monthly = 0;

View File

@ -12,6 +12,7 @@ in {
# (import ../Common/nvidia.nix { inherit inputs pkgs config; })
(import ../Common/desktop.nix {inherit inputs pkgs config;})
(import ../Common/nfs.nix {inherit pkgs serverIP;})
(import ../Common/android.nix {inherit pkgs;})
];
# Kernel
@ -39,6 +40,7 @@ in {
nixpkgs.config.rocmSupport = true;
services.ollama = {
enable = true;
acceleration = "rocm";
};
# nixpkgs.config.cudaSupport = true;
services.logind.settings.Login = {

View File

@ -6,9 +6,19 @@
}: {
imports = [
(import ../Common/default.nix {inherit inputs config pkgs;})
(import ../Common/nvidia.nix {inherit pkgs config;})
# (import ../Common/desktop.nix { inherit inputs pkgs config; })
];
boot.kernelPackages = pkgs.linuxPackages_latest;
# services.sunshine = {
# enable = true;
# autoStart = false;
# capSysAdmin = true;
# openFirewall = true;
# };
boot.kernelParams = ["mitigations=off"];
zramSwap.enable = true;
@ -36,19 +46,14 @@
};
fileSystems."/home/user/Workspace" = {
device = "/home/server/Workspace";
fsType = "none";
options = ["bind"];
};
services.resolved = {
enable = false;
};
networking.nameservers = ["192.168.1.2"];
services.tailscale = {
programs.steam = {
enable = true;
extraUpFlags = [
"--login-server=https://headscale.thomasave.be"
"--accept-routes"
];
authKeyFile = "/home/user/.secrets/Tailscale/Riva/authkey";
gamescopeSession.enable = true;
};
}