Merge branch 'main' of codeberg.org:muon/home

This commit is contained in:
muon 2026-03-19 09:50:23 +00:00
commit 87f09972b0
47 changed files with 2603 additions and 136 deletions

View file

@ -3,6 +3,7 @@ keys:
- &muho age1v4s4hg7u3vjjkarvrk7v6ev7w3wja2r5xm7f4t06culw3fuq7qns8sfju7 - &muho age1v4s4hg7u3vjjkarvrk7v6ev7w3wja2r5xm7f4t06culw3fuq7qns8sfju7
- &mups age1n7qz2w3hkf7fcdv92kxw9k6uef487na2tlc87486rcjwj8lyfuws5q46gn - &mups age1n7qz2w3hkf7fcdv92kxw9k6uef487na2tlc87486rcjwj8lyfuws5q46gn
- &murk age1mgjhkqy9x27gv2t2xvq46dxcajkr9c8zes7rr3dj0ac7md2j6vas43dftp - &murk age1mgjhkqy9x27gv2t2xvq46dxcajkr9c8zes7rr3dj0ac7md2j6vas43dftp
- &musk age1m97a3eptxwpdd7h5kkqe9gkmhg6rquc64qjmlsfqfhfqv8q72crqrylhgc
creation_rules: creation_rules:
- path_regex: modules/nixos/sops/secrets.ya?ml$ - path_regex: modules/nixos/sops/secrets.ya?ml$
@ -12,6 +13,7 @@ creation_rules:
- *muho - *muho
- *mups - *mups
- *murk - *murk
- *musk
- path_regex: modules/home/sops/secrets.ya?ml$ - path_regex: modules/home/sops/secrets.ya?ml$
key_groups: key_groups:
@ -20,3 +22,4 @@ creation_rules:
- *muho - *muho
- *mups - *mups
- *murk - *murk
- *musk

102
flake.lock generated
View file

@ -139,11 +139,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1760948891, "lastModified": 1769996383,
"narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=", "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04", "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -233,11 +233,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1768598210, "lastModified": 1773367248,
"narHash": "sha256-kkgA32s/f4jaa4UG+2f8C225Qvclxnqs76mf8zvTVPg=", "narHash": "sha256-FFMc1uAwy2GYasd0rdNDVxKyAgzuoJH2M+GglBQbqf0=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "c47b2cc64a629f8e075de52e4742de688f930dc6", "rev": "be0c641a6a5564caa33982faa1fe2c60d92131c7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -254,11 +254,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1747978958, "lastModified": 1768598210,
"narHash": "sha256-pQQnbxWpY3IiZqgelXHIe/OAE/Yv4NSQq7fch7M6nXQ=", "narHash": "sha256-kkgA32s/f4jaa4UG+2f8C225Qvclxnqs76mf8zvTVPg=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "7419250703fd5eb50e99bdfb07a86671939103ea", "rev": "c47b2cc64a629f8e075de52e4742de688f930dc6",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -275,11 +275,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1767104570, "lastModified": 1772330611,
"narHash": "sha256-GKgwu5//R+cLdKysZjGqvUEEOGXXLdt93sNXeb2M/Lk=", "narHash": "sha256-UZjPc/d5XRxvjDbk4veAO4XFdvx6BUum2l40V688Xq8=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "e4e78a2cbeaddd07ab7238971b16468cc1d14daf", "rev": "58fd7ff0eec2cda43e705c4c0585729ec471d400",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -294,11 +294,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1767822991, "lastModified": 1769548169,
"narHash": "sha256-iyrn9AcPZCoyxX4OT8eMkBsjG7SRUQXXS/V1JzxS7rA=", "narHash": "sha256-03+JxvzmfwRu+5JafM0DLbxgHttOQZkUtDWBmeUkN8Y=",
"owner": "nix-community", "owner": "nix-community",
"repo": "impermanence", "repo": "impermanence",
"rev": "82e5bc4508cab9e8d5a136626276eb5bbce5e9c5", "rev": "7b1d382faf603b6d264f58627330f9faa5cba149",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -309,11 +309,11 @@
}, },
"mnw": { "mnw": {
"locked": { "locked": {
"lastModified": 1758834834, "lastModified": 1770419553,
"narHash": "sha256-Y7IvY4F8vajZyp3WGf+KaiIVwondEkMFkt92Cr9NZmg=", "narHash": "sha256-b1XqsH7AtVf2dXmq2iyRr2NC1yG7skY7Z6N2MpWHlK4=",
"owner": "Gerg-L", "owner": "Gerg-L",
"repo": "mnw", "repo": "mnw",
"rev": "cfbc7d1cc832e318d0863a5fc91d940a96034001", "rev": "2aaffa8030d0b262176146adbb6b0e6374ce2957",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -330,15 +330,16 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1765720983, "lastModified": 1768214250,
"narHash": "sha256-tWtukpABmux6EC/FuCJEgA1kmRjcRPtED44N+GGPq+4=", "narHash": "sha256-hnBZDQWUxJV3KbtvyGW5BKLO/fAwydrxm5WHCWMQTbw=",
"owner": "feel-co", "owner": "feel-co",
"repo": "ndg", "repo": "ndg",
"rev": "f399ace8bb8e1f705dd8942b24d207aa4d75c936", "rev": "a6bd3c1ce2668d096e4fdaaa03ad7f03ba1fbca8",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "feel-co", "owner": "feel-co",
"ref": "refs/tags/v2.6.0",
"repo": "ndg", "repo": "ndg",
"type": "github" "type": "github"
} }
@ -350,11 +351,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1768475717, "lastModified": 1771150922,
"narHash": "sha256-185VOlWF4K9gzwr7M56ArjqDt6beN/5TxCYLEyVPOcs=", "narHash": "sha256-+oQJun4CFDlOQRocbZpqQDj7agoy56/4ZjT1oUR7NOs=",
"owner": "thiagokokada", "owner": "thiagokokada",
"repo": "nix-alien", "repo": "nix-alien",
"rev": "a579610c67dc946f39c2a64656699eb29eb2ffb5", "rev": "96045e886ba0dd45b27590e7c0c6e77bbb54033d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -387,11 +388,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1765267181, "lastModified": 1771130777,
"narHash": "sha256-d3NBA9zEtBu2JFMnTBqWj7Tmi7R5OikoU2ycrdhQEws=", "narHash": "sha256-UIKOwG0D9XVIJfNWg6+gENAvQP+7LO46eO0Jpe+ItJ0=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nix-index-database", "repo": "nix-index-database",
"rev": "82befcf7dc77c909b0f2a09f5da910ec95c5b78f", "rev": "efec7aaad8d43f8e5194df46a007456093c40f88",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -422,11 +423,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1748026106, "lastModified": 1768564909,
"narHash": "sha256-6m1Y3/4pVw1RWTsrkAK2VMYSzG4MMIj7sqUy7o8th1o=", "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "063f43f2dbdef86376cc29ad646c45c46e93234c", "rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -438,11 +439,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1768305791, "lastModified": 1771008912,
"narHash": "sha256-AIdl6WAn9aymeaH/NvBj0H9qM+XuAuYbGMZaP0zcXAQ=", "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "1412caf7bf9e660f2f962917c14b1ea1c3bc695e", "rev": "a82ccc39b39b621151d6732718e3e250109076fa",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -470,11 +471,11 @@
}, },
"nixpkgs_4": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1768564909, "lastModified": 1773282481,
"narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=", "narHash": "sha256-b/GV2ysM8mKHhinse2wz+uP37epUrSE+sAKXy/xvBY4=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f", "rev": "fe416aaedd397cacb33a610b33d60ff2b431b127",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -537,16 +538,15 @@
"systems": "systems_2" "systems": "systems_2"
}, },
"locked": { "locked": {
"lastModified": 1767628834, "lastModified": 1773343795,
"narHash": "sha256-qiPFYDicHq4/ji0/9QxVM8hhjspsJrYcMR/S3zKlfjQ=", "narHash": "sha256-0+HEuOytpwyPt7i1jj6v2QJ+NXXisCYnL2XNwPBltvg=",
"owner": "thamenato", "owner": "NotAShelf",
"repo": "nvf", "repo": "nvf",
"rev": "7161c8d857cf7c641433cc750a1a3666f82a3ff0", "rev": "83b44eaf50b96bd5d06b1a56a3a51f1b2362db52",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "thamenato", "owner": "NotAShelf",
"ref": "fix-nvim-treesitter",
"repo": "nvf", "repo": "nvf",
"type": "github" "type": "github"
} }
@ -573,11 +573,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1768481291, "lastModified": 1773096132,
"narHash": "sha256-NjKtkJraCZEnLHAJxLTI+BfdU//9coAz9p5TqveZwPU=", "narHash": "sha256-M3zEnq9OElB7zqc+mjgPlByPm1O5t2fbUrH3t/Hm5Ag=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "e085e303dfcce21adcb5fec535d65aacb066f101", "rev": "d1ff3b1034d5bab5d7d8086a7803c5a5968cd784",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -626,11 +626,11 @@
"tinted-zed": "tinted-zed" "tinted-zed": "tinted-zed"
}, },
"locked": { "locked": {
"lastModified": 1768603455, "lastModified": 1772296853,
"narHash": "sha256-ih6dYNhX1oSg0emfSAvf3iRcgsJtMmS6RUaoCX8kNoU=", "narHash": "sha256-pAtzPsgHRKw/2Kv8HgAjSJg450FDldHPWsP3AKG/Xj0=",
"owner": "danth", "owner": "danth",
"repo": "stylix", "repo": "stylix",
"rev": "590e5c68c4d5e8c766420473c0185d75113f653b", "rev": "c4b8e80a1020e09a1f081ad0f98ce804a6e85acf",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -794,11 +794,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1768638486, "lastModified": 1773290887,
"narHash": "sha256-+LC0wOiliUXbIj6zT2hCoOQ0zn33BD2NxGoy0QqP3Eo=", "narHash": "sha256-L1yMYmFffHfZNP+hKJGRBmrFKkn/VDhu7jEbVftBQuM=",
"owner": "0xc000022070", "owner": "0xc000022070",
"repo": "zen-browser-flake", "repo": "zen-browser-flake",
"rev": "76bbc35c59419b8b0616fb779ce5600e85edab11", "rev": "9346698c4562819f61b4e5097151ec0b17729fab",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -14,8 +14,7 @@
nix-alien.url = "github:thiagokokada/nix-alien"; nix-alien.url = "github:thiagokokada/nix-alien";
nvf = { nvf = {
# url = "github:NotAShelf/nvf"; url = "github:NotAShelf/nvf";
url = "github:thamenato/nvf/fix-nvim-treesitter";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
@ -47,35 +46,32 @@
}; };
utils = import ./utils.nix {inherit inputs system sources;}; utils = import ./utils.nix {inherit inputs system sources;};
# Discover hosts: all subdirectories of hosts/
hosts = builtins.attrNames (nixpkgs.lib.filterAttrs
(_: type: type == "directory")
(builtins.readDir ./hosts));
nixosConfigs = builtins.listToAttrs (map (host: {
name = host;
value = utils.mkHost ./hosts/${host}/configuration.nix;
}) hosts);
in { in {
nixosConfigurations = { nixosConfigurations = nixosConfigs;
# desktop
muon = utils.mkHost ./hosts/muon/configuration.nix;
# laptop
muop = utils.mkHost ./hosts/muop/configuration.nix;
# server
muho = utils.mkHost ./hosts/muho/configuration.nix;
# vps
mups = utils.mkHost ./hosts/mups/configuration.nix;
# vm
muvm = utils.mkHost ./hosts/muvm/configuration.nix;
# work
murk = utils.mkHost ./hosts/murk/configuration.nix;
# lenovo
muvo = utils.mkHost ./hosts/muvo/configuration.nix;
# installer
muin = utils.mkHost ./hosts/muin/configuration.nix;
};
homeManagerModules.default = ./modules/home; homeManagerModules.default = ./modules/home;
# Standalone HM configurations — one per host.
# osConfig is injected so all modules using it continue to work.
# Use: home-manager switch --flake '.#muon@<host>'
homeConfigurations = builtins.listToAttrs (map (host: {
name = "muon@${host}";
value = utils.mkHome {
hostConfig = nixosConfigs.${host};
homeFile = ./hosts/${host}/home.nix;
};
}) hosts);
colmena = { colmena = {
meta = { meta = {
nixpkgs = import inputs.nixpkgs {inherit system;}; nixpkgs = import inputs.nixpkgs {inherit system;};

View file

@ -50,6 +50,7 @@ in {
mods.server.ntfy.enable = true; mods.server.ntfy.enable = true;
mods.server.lemmy.enable = true; mods.server.lemmy.enable = true;
mods.server.audio.enable = true; mods.server.audio.enable = true;
mods.server.murmur.enable = true;
mods.server.atuin.enable = true; mods.server.atuin.enable = true;
mods.server.seedbox.enable = true; mods.server.seedbox.enable = true;

View file

@ -10,6 +10,8 @@ in {
mods.xdg.enable = true; mods.xdg.enable = true;
mods.social.enable = false; mods.social.enable = false;
mods.i3.enable = false; mods.i3.enable = false;
mods.terminal.wezterm.enable = true;
mods.terminal.nushell.enable = true;
mods.terminal.zsh.enable = true; mods.terminal.zsh.enable = true;
mods.terminal.emulator.enable = false; mods.terminal.emulator.enable = false;
mods.terminal.development.enable = true; mods.terminal.development.enable = true;

View file

@ -24,8 +24,16 @@ in {
gnumeric gnumeric
opensnitch opensnitch
opensnitch-ui opensnitch-ui
mumble
]; ];
nixpkgs.config.permittedInsecurePackages = [
"libsoup-2.74.3"
];
users.users.muon.extraGroups = ["docker"];
virtualisation.docker.enable = true;
# System # System
mods.user.name = "muon"; mods.user.name = "muon";
networking.hostName = cfg.user.name; networking.hostName = cfg.user.name;
@ -61,6 +69,20 @@ in {
services.xserver.windowManager.i3.enable = true; services.xserver.windowManager.i3.enable = true;
services.actual.enable = true; services.actual.enable = true;
# Reverse proxy: *.word.local -> localhost:3030
services.nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts."~^(?<subdomain>.+)\\.word\\.local$" = {
serverName = "~^(?<subdomain>.+)\\.word\\.local$";
locations."/" = {
proxyPass = "http://127.0.0.1:3030";
proxyWebsockets = true;
};
};
};
networking.firewall.allowedTCPPorts = [ 80 ];
virtualisation.virtualbox.host.enable = true; virtualisation.virtualbox.host.enable = true;
users.extraGroups.vboxusers.members = ["user-with-access-to-virtualbox"]; users.extraGroups.vboxusers.members = ["user-with-access-to-virtualbox"];

View file

@ -10,15 +10,21 @@ in {
mods.xdg.enable = true; mods.xdg.enable = true;
mods.social.enable = true; mods.social.enable = true;
mods.i3.enable = true; mods.i3.enable = true;
mods.terminal.wezterm.enable = true;
mods.terminal.nushell.enable = true;
mods.terminal.zsh.enable = true; mods.terminal.zsh.enable = true;
mods.terminal.emulator.enable = true; mods.terminal.emulator.enable = true;
mods.terminal.development.enable = true; mods.terminal.development.enable = true;
mods.terminal.tools.enable = true; mods.terminal.tools.enable = true;
mods.terminal.hr.enable = true;
mods.terminal.gh.enable = true;
mods.desktop.development.enable = true; mods.desktop.development.enable = true;
mods.desktop.productivity.enable = true; mods.desktop.productivity.enable = true;
mods.desktop.media.enable = true; mods.desktop.media.enable = true;
mods.zen.enable = true; mods.zen.enable = true;
mods.obsidian.enable = true; mods.obsidian.enable = true;
mods.opencode.enable = true;
mods.octo.enable = true;
# Hardware preferences # Hardware preferences
@ -64,6 +70,16 @@ in {
input.sensitivity = -0.4; input.sensitivity = -0.4;
}; };
# SSH: auto-add work key to gpg-agent on first use
programs.ssh = {
enable = true;
matchBlocks."*" = {
identityFile = "~/.ssh/work_ed25519";
addKeysToAgent = "yes";
};
matchBlocks."muho".identityFile = "~/.ssh/id_ed25519";
};
# Version of first install # Version of first install
home.stateVersion = "23.05"; home.stateVersion = "23.05";
} }

View file

@ -19,11 +19,15 @@ in {
mods.terminal.emulator.enable = true; mods.terminal.emulator.enable = true;
mods.terminal.development.enable = true; mods.terminal.development.enable = true;
mods.terminal.tools.enable = true; mods.terminal.tools.enable = true;
mods.terminal.gh.enable = true;
mods.terminal.hr.enable = true;
mods.desktop.development.enable = true; mods.desktop.development.enable = true;
mods.desktop.productivity.enable = false; mods.desktop.productivity.enable = false;
mods.zen.enable = true; mods.zen.enable = true;
mods.obsidian.enable = true; mods.obsidian.enable = true;
mods.theme.slideshow.enable = true; mods.theme.slideshow.enable = true;
mods.octo.enable = true;
mods.theme.slideshow = true;
home.packages = with pkgs; home.packages = with pkgs;
[ [
@ -43,7 +47,6 @@ in {
go go
rainfrog rainfrog
tealdeer tealdeer
gh
(callPackage ./packages/mender-cli.nix {}) (callPackage ./packages/mender-cli.nix {})
] ]
# Non-free </3 # Non-free </3

View file

@ -0,0 +1,117 @@
{
config,
lib,
pkgs,
inputs,
system,
sources,
modulesPath,
...
}: let
cfg = config.mods;
keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKEio+Y5wBVD1wILaH2R3wV10FvVjiqy/4gGBWHOITTB muon@muon"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKevYmkH7xvYoquBjnYZ7PJiVqf+GOh9fxAJBN6wZGBB gin4@hi.is"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILmAOd9VbhyJeibt6Vrb101MNTk5W8+rh94Djv/C+pyu muon@muho"
];
in {
# Hardware
imports = [
./hardware-configuration.nix
"${
builtins.fetchTarball {
url = "https://github.com/nix-community/disko/archive/refs/tags/v1.12.0.tar.gz";
sha256 = "0wbx518d2x54yn4xh98cgm65wvj0gpy6nia6ra7ns4j63hx14fkq";
}
}/module.nix"
./disk-config.nix
# (inputs.nixpkgs
# + "/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix")
];
environment.systemPackages = with inputs.nix-alien.packages.${system}; [
nix-alien
pkgs.libratbag
pkgs.piper
pkgs.libpq
pkgs.qmk
pkgs.jq
pkgs.wireguard-tools
pkgs.opencode
];
boot.binfmt = {
emulatedSystems = ["aarch64-linux"];
preferStaticEmulators = true; # Make it work with Docker
};
# System
mods.user.name = "muon";
networking.hostName = "musk";
networking.hostId = "a2309091";
mods.home.file = ./home.nix;
nix.settings.trusted-users = ["root" "muon"];
users.users.muon.extraGroups = ["docker"];
# Modules
mods.desktop.enable = true;
mods.boot.enable = true;
mods.theme.enable = true;
mods.theme.scheme = "catppuccin-macchiato";
mods.theme.wallpaper = ./wallpaper.png;
services.xserver.windowManager.i3.enable = true;
# mods.desktop.wayland.enable = true;
mods.impermanence.enable = true;
virtualisation.docker.enable = true;
users.users.muon.openssh.authorizedKeys.keys = keys;
users.users.root.openssh.authorizedKeys.keys = keys;
# Persist
environment.persistence."/persist" = {
directories = ["/etc/NetworkManager" "/var/lib/NetworkManager"];
};
# Hardware preferences
# environment.variables = {
# WINIT_HIDPI_FACTOR = "1";
# WINIT_X11_SCALE_FACTOR = "1";
# };
## Monitors
mods.monitors = {
primary = {
name = "DP-1";
config = {
enable = true;
mode = "2560x1440";
position = "0x0";
rate = "60.00";
dpi = 72;
};
};
right = {
name = "HDMI-1";
config = {
enable = true;
mode = "2560x1440";
position = "2560x0";
rate = "60.00";
dpi = 72;
};
};
};
## Mouse
services.libinput.mouse.accelProfile = "flat";
## Keyboard
hardware.keyboard.qmk.enable = true;
# Version of first install
system.stateVersion = "23.05";
}

View file

@ -0,0 +1,72 @@
{
disko.devices = {
disk = {
main = {
type = "disk";
device = "/dev/sda";
content = {
type = "gpt";
partitions = {
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
luks = {
size = "100%";
content = {
type = "luks";
name = "crypted";
# disable settings.keyFile if you want to use interactive password entry
#passwordFile = "/tmp/secret.key"; # Interactive
# settings = {
# allowDiscards = true;
# keyFile = "/tmp/secret.key";
# };
# additionalKeyFiles = [ "/tmp/additionalSecret.key" ];
content = {
type = "btrfs";
extraArgs = [ "-f" ];
subvolumes = {
"/root" = {
mountpoint = "/";
mountOptions = [ "compress=zstd" "noatime" ];
};
"/home" = {
mountpoint = "/home";
mountOptions = [ "compress=zstd" "noatime" ];
};
"/nix" = {
mountpoint = "/nix";
mountOptions = [ "compress=zstd" "noatime" ];
};
"/persist" = {
mountpoint = "/persist";
mountOptions = [ "compress=zstd" "noatime" ];
};
"/log" = {
mountpoint = "/var/log";
mountOptions = [ "compress=zstd" "noatime" ];
};
"/swap" = {
mountpoint = "/swap";
swap.swapfile.size = "4G";
};
};
};
};
};
};
};
};
};
};
fileSystems."/persist".neededForBoot = true;
fileSystems."/var/log".neededForBoot = true;
}

View file

@ -0,0 +1,18 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

101
hosts/musk/home.nix Normal file
View file

@ -0,0 +1,101 @@
{
pkgs,
lib,
config,
osConfig,
inputs,
...
}: let
cfg = osConfig.mods;
color = config.lib.stylix.colors.withHashtag;
in {
# Modules
mods.xdg.enable = true;
mods.i3.enable = true;
# mods.hyprland.enable = true;
mods.terminal.zsh.enable = true;
mods.terminal.nushell.enable = true;
mods.terminal.emulator.enable = true;
mods.terminal.wezterm.enable = true;
mods.terminal.development.enable = true;
mods.terminal.tools.enable = true;
mods.terminal.gh.enable = true;
mods.terminal.hr.enable = true;
mods.desktop.development.enable = true;
mods.desktop.productivity.enable = false;
mods.zen.enable = true;
mods.octo.enable = true;
home.packages = with pkgs;
[
thunderbird
pulseaudio
pavucontrol
alsa-utils
rustdesk-flutter
# tools
docker
fish
devenv
dbeaver-bin
ruff
just
go
rainfrog
tealdeer
(callPackage ./packages/mender-cli.nix {})
]
# Non-free </3
++ [google-cloud-sdk google-cloud-sql-proxy];
services.flameshot = {
enable = true;
settings = {
General = {
disabledTrayIcon = true;
showStartupLaunchMessage = false;
startupLaunch = true;
uiColor = color.base01;
contrastUiColor = color.base00;
};
};
};
# Hardware preferences
## Monitors
xsession.windowManager.i3.config.workspaceOutputAssign = [
{
workspace = "1";
output = "${cfg.monitors.primary.name}";
}
{
workspace = "2";
output = "${cfg.monitors.right.name}";
}
];
services.autorandr.enable = true;
programs.autorandr = {
enable = true;
hooks.postswitch = {
"notify-i3" = "${pkgs.i3}/bin/i3-msg restart";
"set-wallpaper" = ''
${lib.getExe pkgs.feh} --bg-fill --nofehbg ${./wallpaper.png}
'';
};
profiles.default = {
fingerprint = {
"${cfg.monitors.right.name}" = "00ffffffffffff0030aef465010101011e1e0103803c22782a31d5a65453a0240a5054bfcf00d1c0d100b300a9c09500818081c08100e973006aa0a034504220680055502100001a565e00a0a0a029503020350055502100001a000000fd00304b0f6e1e000a202020202020000000fc00513237712d31300a202020202001ff020329f04b10050403021f1413121101230907078301000067030c001000183c681a00000101304b00023a801871382d40582c450055502100001e662156aa51001e30468f330055502100001eab22a0a050841a303020360055502100001a7c2e90a0601a1e403020360055502100001a000000000000000000000000000026";
"${cfg.monitors.primary.name}" = "00ffffffffffff0005e30427b11a0000321f0104a53c22783be445a554529e260d5054bfef00d1c0b30095008180814081c001010101565e00a0a0a029503020350055502100001e000000ff005141424d434841303036383333000000fc00513237563447350a2020202020000000fd00304b72721e010a2020202020200163020318f14b0103051404131f120211902309070783010000a073006aa0a029500820350055502100001a2a4480a0703827403020350055502100001a023a801871382d40582c450055502100001ef03c00d051a0355060883a0055502100001c000000000000000000000000000000000000000000000000000000000000005f";
};
config = {
"${cfg.monitors.primary.name}" = cfg.monitors.primary.config;
"${cfg.monitors.right.name}" = cfg.monitors.right.config;
};
};
};
# Version of first install
home.stateVersion = "23.05";
}

View file

@ -0,0 +1,54 @@
{
lib,
stdenv,
buildGoModule,
fetchFromGitHub,
makeWrapper,
installShellFiles,
xz,
go,
}:
buildGoModule rec {
pname = "mender-cli";
version = "1.12.0";
src = fetchFromGitHub {
owner = "mendersoftware";
repo = "mender-cli";
rev = version;
sha256 = "sha256-Pf87wTHXcFlnYsgx7ieiIJ9PWJFPUkFJYTkKJKmMFEQ=";
};
vendorHash = "sha256-MqyBa+wsbuXqtM4DL/QGBUWuEYlG8BRxIXq7O1LJUyM=";
nativeBuildInputs = [
makeWrapper
installShellFiles
];
buildInputs = [
xz
];
allowGoReference = true;
postFixup = ''
wrapProgram "$out/bin/mender-cli" \
--prefix PATH : ${go}/bin
'';
postInstall = lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) ''
installShellCompletion --cmd mender-cli \
--bash <($out/bin/mender-cli completion bash) \
--fish <($out/bin/mender-cli completion fish) \
--zsh <($out/bin/mender-cli completion zsh) \
'';
meta = {
description = "Mender CLI tool to simplify integration between the Mender server and cloud services like continuous integration (CI)/build automation";
mainProgram = "mender-cli";
homepage = "https://github.com/mendersoftware/mender-cli/";
changelog = "https://github.com/mendersoftware/mender-cli/releases/tag/${version}";
license = lib.licenses.asl20;
};
}

BIN
hosts/musk/wallpaper.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 MiB

View file

@ -5,28 +5,91 @@
osConfig, osConfig,
... ...
}: let }: let
fsss = with pkgs; # Bootstrap script that runs as the first program inside a new wezterm window.
writeShellApplication { # It sets up the three tabs synchronously before the shell prompt appears,
name = "fsss"; # so by the time the window is visible the layout is already complete.
runtimeInputs = [flameshot curl xsel]; # Receives the project path as $1.
wezterm-zmenu-init = pkgs.writeShellApplication {
name = "wezterm-zmenu-init";
runtimeInputs = [pkgs.wezterm pkgs.zsh];
text = '' text = ''
flameshot gui -r -s > /tmp/ss.png;if [ ! -s /tmp/ss.png ]; then ZPATH=$1
exit 1 WIN=$(wezterm cli list --format json 2>/dev/null \
fi | tr ',' '\n' \
AUTH=$(cat ${config.sops.secrets.zipline-auth.path}) | awk -v pane="$WEZTERM_PANE" '
curl -H "authorization: $AUTH" https://share.muon.host/api/upload -F file=@/tmp/ss.png -H "Content-Type: multipart/form-data" -H "Format: date" -H "Image-Compression-Percent: 90" -H "No-JSON: true" | tr -d '\n' | xsel -ib; /window_id/ { gsub(/.*: */,""); w=int($0) }
/pane_id/ { gsub(/.*: */,""); if (int($0)==pane) { print w; exit } }')
P1=$(wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" -- direnv exec . nvim)
wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" > /dev/null
wezterm cli spawn --window-id "$WIN" --cwd "$ZPATH" -- lazygit > /dev/null
wezterm cli activate-tab --tab-index 0 --pane-id "$P1" 2>/dev/null
''; '';
}; };
zmenu = with pkgs; zmenu = with pkgs;
writeShellApplication { writeShellApplication {
name = "zmenu"; name = "zmenu";
runtimeInputs = [zellij zoxide wmctrl i3 rofi alacritty zsh]; runtimeInputs =
text = '' [zoxide wmctrl i3 rofi zsh]
++ lib.optionals config.mods.terminal.wezterm.enable [wezterm]
++ lib.optionals (!config.mods.terminal.wezterm.enable) [zellij alacritty];
text =
if config.mods.terminal.wezterm.enable
then ''
ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu)
[[ -z "$ZPATH" ]] && exit
ZSESH=$(echo "$ZPATH" | tr / -)
# Per-workspace socket registry so we can focus existing workspaces.
SOCKDIR="$XDG_RUNTIME_DIR/zmenu-wez"
mkdir -p "$SOCKDIR"
SOCKFILE="$SOCKDIR/$ZSESH"
SOCK=""
if [[ -f "$SOCKFILE" ]]; then
SOCK=$(cat "$SOCKFILE")
# Extract the PID from the socket path (gui-sock-<pid>) and check
# if the process is still alive. Dead sockets linger on disk.
SOCK_PID=''${SOCK##*-}
if ! kill -0 "$SOCK_PID" 2>/dev/null; then
rm -f "$SOCKFILE"
SOCK=""
fi
fi
if [[ -n "$SOCK" ]]; then
# Workspace open: raise its window by matching the wezterm PID in
# wmctrl's process list, then activate a pane to ensure focus.
SOCK_PID=''${SOCK##*-}
WIN_HEX=$(wmctrl -lp | awk -v pid="$SOCK_PID" '$3==pid {print $1; exit}')
wmctrl -i -a "$WIN_HEX"
PANE=$(WEZTERM_UNIX_SOCKET="$SOCK" wezterm cli list --format json 2>/dev/null \
| tr ',' '\n' \
| awk '/pane_id/{gsub(/.*: */,""); print int($0); exit}')
WEZTERM_UNIX_SOCKET="$SOCK" wezterm cli activate-pane --pane-id "$PANE" 2>/dev/null
else
# Not open: the init script runs as the first program inside the new
# window. It sets up all tabs synchronously before the shell prompt
# appears, so the layout is complete before anything is visible.
wezterm start --always-new-process \
--workspace "$ZSESH" --cwd "$HOME/$ZPATH" \
-- ${lib.getExe wezterm-zmenu-init} "$HOME/$ZPATH" &
WEZ_PID=$!
# Record the socket for future focus calls.
for _ in $(seq 50); do
sleep 0.1
[[ -S /run/user/$UID/wezterm/gui-sock-$WEZ_PID ]] && break
done
echo "/run/user/$UID/wezterm/gui-sock-$WEZ_PID" > "$SOCKFILE"
fi
''
else ''
ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu) ZPATH=$(zoxide query -l | sed -e "s|$HOME/||g" | rofi -dmenu)
[[ -z "$ZPATH" ]] && exit [[ -z "$ZPATH" ]] && exit
ZSESH=$(echo "$ZPATH" | tr / -) ZSESH=$(echo "$ZPATH" | tr / -)
ZWIND=$(wmctrl -l | grep "$ZSESH$" || echo "") ZWIND=$(wmctrl -l | grep "$ZSESH$" || echo "")
cd "$ZPATH" cd "$HOME/$ZPATH"
if [[ -z "$ZWIND" ]]; then if [[ -z "$ZWIND" ]]; then
alacritty -T "$ZSESH" -e zsh -c "zellij -s $ZSESH -n dev || zellij a $ZSESH" alacritty -T "$ZSESH" -e zsh -c "zellij -s $ZSESH -n dev || zellij a $ZSESH"
else else
@ -106,7 +169,10 @@ in
enable = true; enable = true;
config = { config = {
modifier = modifier; modifier = modifier;
terminal = "alacritty"; terminal =
if config.mods.terminal.wezterm.enable
then "wezterm"
else "alacritty";
menu = "rofi -show drun"; menu = "rofi -show drun";
window = { window = {
@ -155,8 +221,10 @@ in
// { // {
"XF86AudioRaiseVolume" = "exec --no-startup-id pactl set-sink-volume 0 +2%"; "XF86AudioRaiseVolume" = "exec --no-startup-id pactl set-sink-volume 0 +2%";
"XF86AudioLowerVolume" = "exec --no-startup-id pactl set-sink-volume 0 -2%"; "XF86AudioLowerVolume" = "exec --no-startup-id pactl set-sink-volume 0 -2%";
"Print" = "exec ${getExe fsss}"; "Print" = "exec flameshot gui -c -s";
"${modifier}+z" = "exec ${getExe zmenu}"; "${modifier}+z" = "exec ${getExe zmenu}";
"${modifier}+p" = "exec clipmenu";
"${modifier}+b" = "exec ${getExe pkgs.rofi-rbw-x11}";
"${modifier}+y" = "sticky toggle"; "${modifier}+y" = "sticky toggle";
"${modifier}+g" = "floating toggle"; "${modifier}+g" = "floating toggle";
}); });

View file

@ -0,0 +1,54 @@
{
fetchFromGitLab,
gtk3,
libsoup_2_4,
json-glib,
leveldb,
rtaudio,
libopus,
libsodium,
libsecret,
pkg-config,
stdenv,
lib,
}: let
version = "0.1.0";
in
stdenv.mkDerivation {
inherit version;
pname = "LibreDiscord";
src = fetchFromGitLab {
owner = "Zipdox";
repo = "LibreDiscord";
rev = "a8130fab059e69437f30e320374fe6f5d21398f8";
hash = "sha256-yr2pxW0e2ruMnDzkQMv2BQrOcN18m8zdzovnD4Dxr3M=";
};
nativeBuildInputs = [
pkg-config
];
buildInputs = [
gtk3
libsoup_2_4
json-glib
leveldb
rtaudio
libopus
libsodium
libsecret
];
installPhase = ''
mkdir -p $out/bin
cp build/librediscord $out/bin
'';
meta = {
description = "Voice client for Discord written in C using GTK3 and GLib";
homepage = "https://gitlab.com/Zipdox/LibreDiscord";
license = lib.licenses.gpl3Only;
};
}

View file

@ -48,12 +48,12 @@ in
programs.zsh.sessionVariables.BROWSER = "librewolf"; programs.zsh.sessionVariables.BROWSER = "librewolf";
services.flameshot = { services.flameshot = {
enable = false; enable = true;
settings = { settings = {
General = { General = {
disabledTrayIcon = true; disabledTrayIcon = true;
showStartupLaunchMessage = false; showStartupLaunchMessage = false;
startupLaunch = false; startupLaunch = true;
uiColor = color.base01; uiColor = color.base01;
contrastUiColor = color.base00; contrastUiColor = color.base00;

View file

@ -17,10 +17,19 @@ in {
# Communication # Communication
# kotatogram-desktop # kotatogram-desktop
signal-desktop signal-desktop
abaddon
vesktop-nogain vesktop-nogain
# (callPackage ./packages/librediscord.nix {})
jami
# Video # Video
freetube freetube
# Security
gcr
]; ];
services.gnome-keyring.enable = true;
services.dunst.enable = true;
}; };
} }

View file

@ -17,5 +17,7 @@ in
secrets.atuin-auth = {}; secrets.atuin-auth = {};
secrets.hr-password = {}; secrets.hr-password = {};
secrets.sops-key = {}; secrets.sops-key = {};
secrets.google-db-test = {};
secrets.google-db-prod = {};
}; };
} }

View file

@ -2,6 +2,8 @@ zipline-auth: ENC[AES256_GCM,data:RkJI6GuH7RzdcSlKn32gMGojjB6rkdDcnNUvsi/BTfJk2s
atuin-auth: ENC[AES256_GCM,data:LDkiXWIwxor8Ro383gonJCyqu+nyxS7DrI2J8uo4Cqu2X61rBUlnpNR6YirUZS/lYAnWYJhZM7sR0G7ZNh9EgQ==,iv:UEs2KW8ImMnaQrSLrIGbVXEq86QiVPAPNIXBZpa3jFI=,tag:N0rhnPbasFzkoI3CJ9CV+Q==,type:str] atuin-auth: ENC[AES256_GCM,data:LDkiXWIwxor8Ro383gonJCyqu+nyxS7DrI2J8uo4Cqu2X61rBUlnpNR6YirUZS/lYAnWYJhZM7sR0G7ZNh9EgQ==,iv:UEs2KW8ImMnaQrSLrIGbVXEq86QiVPAPNIXBZpa3jFI=,tag:N0rhnPbasFzkoI3CJ9CV+Q==,type:str]
hr-password: ENC[AES256_GCM,data:QZuzAnTJ2KgPnffHvdCWrJEM5d/FXxhX3dA1,iv:FgDw6aXDY0jCpJiYc9WOobR96TXNtnvN7neJu8drxMM=,tag:YT82wryVy3V+41w0YbMOrA==,type:str] hr-password: ENC[AES256_GCM,data:QZuzAnTJ2KgPnffHvdCWrJEM5d/FXxhX3dA1,iv:FgDw6aXDY0jCpJiYc9WOobR96TXNtnvN7neJu8drxMM=,tag:YT82wryVy3V+41w0YbMOrA==,type:str]
sops-key: ENC[AES256_GCM,data:msX0EJqJauteOBICUsLcVgqNxqGcqvD+Xi/B2EhUX2OAoyBH5oDae8XWlQCi2RdOm4NtnrSTnG8FRQXfkXO+tne0VEfYTCjeVtU=,iv:qxpvofr56Ey17xcPpju/mQgiz+0cOYED5caAHs3myXw=,tag:oDFXh0rlc0tmV2IUJ1ezBQ==,type:str] sops-key: ENC[AES256_GCM,data:msX0EJqJauteOBICUsLcVgqNxqGcqvD+Xi/B2EhUX2OAoyBH5oDae8XWlQCi2RdOm4NtnrSTnG8FRQXfkXO+tne0VEfYTCjeVtU=,iv:qxpvofr56Ey17xcPpju/mQgiz+0cOYED5caAHs3myXw=,tag:oDFXh0rlc0tmV2IUJ1ezBQ==,type:str]
google-db-test: ENC[AES256_GCM,data:ZMm/BF/k+XnZZkHMDSV/fk3ds0LAOHAmag==,iv:tmfJ7ju5yAO6Oco3jXYNyqzJr7cgshyd/SkjfYnEl6U=,tag:jMD2N6TsgbRwefhJ/XYhtg==,type:str]
google-db-prod: ENC[AES256_GCM,data:fIPL9XKk9sAmpVsQBubSVbh3DlEEKadG9g==,iv:R34zkCIUDlk5/wg8eU8RZIanGayL+nX+7ZhyVmbcQC0=,tag:lu24b28742O46fjUaw2UBA==,type:str]
sops: sops:
age: age:
- recipient: age1m97a3eptxwpdd7h5kkqe9gkmhg6rquc64qjmlsfqfhfqv8q72crqrylhgc - recipient: age1m97a3eptxwpdd7h5kkqe9gkmhg6rquc64qjmlsfqfhfqv8q72crqrylhgc
@ -40,7 +42,7 @@ sops:
a0V1N2VjUDE4Z3R5MGxMQVNmOVp0bVUK9cppJW33tKFOSvbIn/2Dga8k7/McaTpK a0V1N2VjUDE4Z3R5MGxMQVNmOVp0bVUK9cppJW33tKFOSvbIn/2Dga8k7/McaTpK
m7M+83guMzNoOlpJ/WYU1BaePcM974AgjVR0WD/v+xGBvGKubKHqtw== m7M+83guMzNoOlpJ/WYU1BaePcM974AgjVR0WD/v+xGBvGKubKHqtw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-08-04T07:58:56Z" lastmodified: "2026-01-21T14:37:21Z"
mac: ENC[AES256_GCM,data:aJw3KK4GMj5/Q06v1C5rdSerdO21cNxpTIJYoxmfhBKudzD7lSL6l+d47kWoB0U4J5jtbs9obWz2MH3CvyPBapjJaSFnYEXk1JuGihf8GK3QrqLAt+dmF2ZD1FBLpQELripueneyHkzT32180hpXGnppNlgOuATlIMSPosvlpVI=,iv:SpGAyTqqbpuxcLkMq7VnLQUoR6oW0ERgnyPaqVHpaN8=,tag:OSNGT8/5E+PRhoR8dIyaSA==,type:str] mac: ENC[AES256_GCM,data:bxr3U1Ig0qjuOcxHeOlOrXO0xtZs0vKTuXn8GE1dJGCFDjVgakbIwiW6+2WNYUbIpipCAwdecgb0jBngwt3zKGS4PMzapUXxl7RoCr5DWCh6kSD4CCUH4v8guuy0k8SMQXDO3CdbUd/5/asIPfxlvEESCQL54X2OJlt5xpE7PsU=,iv:m/lrHHFYXFKCVEOK462II8bcFvw7k4rKEuMOHHmzT/8=,tag:jgEQso2bAShLJERsOHhrKw==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.10.2 version: 3.11.0

View file

@ -6,12 +6,17 @@
imports = [ imports = [
./shell.nix ./shell.nix
./emulator.nix ./emulator.nix
./wezterm.nix
./nushell.nix
./development.nix ./development.nix
./tools.nix ./tools.nix
./yazi.nix ./yazi.nix
./hr
./helix ./helix
./nvim ./nvim
./zellij ./zellij
./opencode
./gh.nix
]; ];
config = lib.mkIf osConfig.mods.desktop.enable { config = lib.mkIf osConfig.mods.desktop.enable {

View file

@ -23,9 +23,12 @@ in {
enable = true; enable = true;
nix-direnv.enable = true; nix-direnv.enable = true;
enableZshIntegration = lib.mkIf config.mods.terminal.zsh.enable true; enableZshIntegration = lib.mkIf config.mods.terminal.zsh.enable true;
enableNushellIntegration = lib.mkIf config.mods.terminal.nushell.enable true;
}; };
home.sessionVariables.EDITOR = "nvim"; home.sessionVariables.EDITOR = "nvim";
programs.zsh.sessionVariables.EDITOR = "nvim"; programs.zsh.sessionVariables = lib.mkIf config.mods.terminal.zsh.enable {
EDITOR = "nvim";
};
}; };
} }

View file

@ -0,0 +1,30 @@
{
pkgs,
lib,
config,
...
}: {
options.mods.terminal.gh.enable = lib.mkEnableOption "enables gh and gh-dash";
config = lib.mkIf config.mods.terminal.gh.enable {
programs.gh = {
enable = true;
extensions = [pkgs.gh-dash];
};
programs.gh-dash = {
enable = true;
settings = {
keybindings = {
prs = [
{
key = "C";
command = "nvim -c 'Octo pr edit {{.PrNumber}}'";
description = "Open PR in Octo.nvim";
}
];
};
};
};
};
}

View file

@ -62,7 +62,7 @@
{ {
name = "nix"; name = "nix";
auto-format = true; auto-format = true;
formatter.command = "${pkgs.nixfmt-classic}/bin/nixfmt"; formatter.command = "${pkgs.nixfmt}/bin/nixfmt";
} }
{ {
name = "rust"; name = "rust";

View file

@ -0,0 +1,65 @@
{
pkgs,
lib,
config,
...
}: let
cfg = config.mods.terminal;
test-port = "5436";
prod-port = "5437";
in {
options.mods.terminal.hr.enable = lib.mkEnableOption "Hefring (Work Tooling)";
config = lib.mkIf cfg.hr.enable {
programs.nushell = lib.mkIf cfg.nushell.enable {
extraConfig = ''
$env.PROJECT_ID = if ($env | get -o PROJECT_ID | is-empty) { "mk2-test" } else { $env.PROJECT_ID }
'' + builtins.readFile ./hr.nu;
};
programs.starship.settings.custom.project_id = {
command = "if $env.PROJECT_ID =~ 'prod' { $'(ansi yellow_bold) (ansi blue_bold)($env.PROJECT_ID)(ansi reset)' } else { $'(ansi blue_bold)($env.PROJECT_ID)(ansi reset)' }";
when = "not ($env | get -o PROJECT_ID | is-empty)";
shell = ["nu" "--no-config-file" "-c"];
format = "on $output ";
};
programs.zsh.initContent =
''
export MK2_TEST_SQL_INSTANCE_USER=gijs
export MK2_TEST_SQL_INSTANCE_PASSWORD="$(cat ${config.sops.secrets.google-db-test.path})"
export MK2_TEST_SQL_INSTANCE_PORT=${test-port}
export MK2_TEST_SQL_INSTANCE_HOST=localhost
export MK2_PROD_SQL_INSTANCE_USER=gijs
export MK2_PROD_SQL_INSTANCE_PASSWORD="$(cat ${config.sops.secrets.google-db-prod.path})"
export MK2_PROD_SQL_INSTANCE_HOST=localhost
export MK2_PROD_SQL_INSTANCE_PORT=${prod-port}
''
+ builtins.readFile ./hr.sh;
systemd.user.services = let
proxy-service = name: port: {
"google-db-proxy-${name}" = {
Unit = {
Description = "Google Cloud SQL Proxy (${name})";
After = ["network.target"];
};
Service = {
Type = "simple";
Environment = [
"GOOGLE_APPLICATION_CREDENTIALS=${config.home.homeDirectory}/.config/gcloud/application_default_credentials.json"
];
ExecStart = "${pkgs.google-cloud-sql-proxy}/bin/cloud-sql-proxy mk2-${name}:europe-west1:mk2-${name}-sql-instance -p ${port}";
Restart = "always";
};
Install = {
WantedBy = ["default.target"];
};
};
};
in
proxy-service "test" test-port
// proxy-service "prod" prod-port;
};
}

View file

@ -0,0 +1,474 @@
# HR - Hefring work tooling for nushell
#
# This module provides commands for working with Hefring Cloud Run services and Cloud Functions.
#
# Key features:
# - Shell-style argument passing: -key value supports dot notation for nested structures
# Example: hr call service -items.0.type "trip" -items.0.id "123"
#
# - Nushell record syntax with --data flag for complex payloads:
# Example: hr call service --data {company_id: "abc", items: [{type: "trip"}]}
#
# - Automatic type detection: numbers, booleans, and JSON are parsed automatically
# Example: -count "42" → number, -active "true" → boolean
#
# - Pretty display: Payloads are shown as Nushell tables before sending
def _hr_usage [] {
print "Usage: hr <command>"
print "Commands:"
print " switch Switch PROJECT_ID between mk2-test and mk2-prod"
print " call Call a Cloud Run service route"
print " cf Call a Cloud Function"
print " init py Initialize a python devenv environment (git-ignored)"
print " init go Initialize a go devenv environment (git-ignored)"
print " init rs Initialize a rust devenv environment (git-ignored)"
print " init cpp Initialize a C++ devenv environment (git-ignored)"
print " freeze Freeze dependencies to requirements.txt"
}
def _hr_init_devenv [] {
if (".gitignore" | path exists) {
cp .gitignore .gitignore.bak
}
if (which devenv | is-empty) {
error make { msg: "Error: devenv not found in path." }
}
devenv init
print "Direnv allowed"
if (".gitignore.bak" | path exists) {
mv .gitignore.bak .gitignore
} else if (".gitignore" | path exists) {
rm .gitignore
}
}
def _hr_add_ignores [files: list<string>] {
let git_dir = (do { git rev-parse --git-dir } | complete)
if $git_dir.exit_code != 0 {
print "Warning: Not a git repository. Skipping git ignore setup."
return
}
let exclude_file = (git rev-parse --git-path info/exclude)
mkdir (($exclude_file) | path dirname)
for file in $files {
let already_exists = (
if ($exclude_file | path exists) {
open $exclude_file | lines | any { |line| $line == $file }
} else {
false
}
)
if not $already_exists {
$"($file)\n" | save --append $exclude_file
print $"Added ($file) to local git exclude \(($exclude_file)\)"
}
}
}
def _hr_py_files [] {
"
{pkgs, ...}: {
packages = [ pkgs.google-cloud-sdk pkgs.libpq ];
languages.python = {
enable = true;
venv.enable = true;
uv = {
enable = true;
sync = {
enable = true;
allExtras = true;
};
};
};
# We use the named index \"google\" defined in uv.toml
env.UV_INDEX_GOOGLE_USERNAME = \"oauth2accesstoken\";
env.PROJECT_ID = \"mk2-test\";
enterShell = ''
export PATH=\"$DEVENV_STATE/venv/bin:$PATH\"
if ! gcloud auth print-access-token >/dev/null 2>&1; then
echo \"⚠️ gcloud not authenticated. Run 'gcloud auth login' to access Google Artifact Registry.\"
else
export UV_INDEX_GOOGLE_PASSWORD=$(gcloud auth print-access-token)
fi
'';
}
" | save -f devenv.nix
"
[[index]]
name = \"google\"
url = \"https://europe-west1-python.pkg.dev/mk2-prod/python-packages/simple/\"
" | save -f uv.toml
}
def _hr_rs_files [] {
"
{pkgs, ...}: {
languages.rust = {
enable = true;
channel = \"stable\";
};
}
" | save -f devenv.nix
"
inputs:
rust-overlay:
url: github:oxalica/rust-overlay
inputs:
nixpkgs:
follows: nixpkgs
" | save -f devenv.yaml
}
def _hr_cpp_files [] {
"
{ pkgs, ... }:
let
# Use glibc-compatible static openssl to match system libs
staticOpenSSL = pkgs.openssl.override { static = true; };
# Shim to satisfy CMake looking for \"ssl.a\"
compatOpenSSL = pkgs.runCommand \"openssl-compat\" {} ''
mkdir -p $out/lib
ln -s ${staticOpenSSL.out}/lib/libssl.a $out/lib/ssl.a
ln -s ${staticOpenSSL.out}/lib/libcrypto.a $out/lib/crypto.a
'';
in {
packages = [
pkgs.cmake
pkgs.clang-tools
pkgs.pkg-config
pkgs.mosquitto
staticOpenSSL
compatOpenSSL
];
# Explicitly add lib paths so linker finds -lssl AND ssl.a
env.LIBRARY_PATH = \"${staticOpenSSL.out}/lib:${compatOpenSSL}/lib\";
env.CPATH = \"${staticOpenSSL.dev}/include\";
languages.cplusplus.enable = true;
}
" | save -f devenv.nix
}
def _hr_go_files [] {
"
{pkgs, ...}: {
languages.go = {
enable = true;
};
}
" | save -f devenv.nix
}
def _hr_init_base [name: string, write_files: closure, ignores: list<string>] {
print $"Initializing ($name) devenv..."
# 1. Init devenv
_hr_init_devenv
# 2. Write language-specific files
do $write_files
# 3. Add to local git exclude
let base_ignores = [
".devenv*"
".direnv"
"devenv.nix"
"devenv.yaml"
"devenv.lock"
".envrc"
]
_hr_add_ignores ($base_ignores ++ $ignores)
direnv allow
}
def _hr_setpath [data: any, path: list, value: any] {
# Recursively build nested structure, similar to jq's setpath
if ($path | is-empty) {
$value
} else if ($path | length) == 1 {
$data | upsert ($path | first) $value
} else {
let key = ($path | first)
let rest = ($path | skip 1)
let next_key = ($rest | first)
# Check if key exists in data and get intermediate value
let key_type = ($key | describe)
let data_type = ($data | describe)
let is_list = ($data_type | str starts-with "list") or ($data_type | str starts-with "table")
# Determine if key exists and get intermediate structure
let intermediate = if $key_type == "int" and $is_list {
# Array index
if $key < ($data | length) {
$data | get $key
} else {
# Index doesn't exist, create structure based on next key
if ($next_key | describe) == "int" { [] } else { {} }
}
} else if $key_type != "int" {
# Object key
if $key in $data {
$data | get $key
} else {
# Key doesn't exist, create structure based on next key
if ($next_key | describe) == "int" { [] } else { {} }
}
} else {
# Data is not a list but key is int - create array
if ($next_key | describe) == "int" { [] } else { {} }
}
# Recursively set the rest of the path
let updated_intermediate = (_hr_setpath $intermediate $rest $value)
# Update data with the new intermediate value
$data | upsert $key $updated_intermediate
}
}
def _hr_add_json_field [json: string, key: string, value: string] {
# Determine if value should be parsed as raw JSON or treated as a string
let is_bool = ($value == "true" or $value == "false")
let is_number = ($value =~ '^-?(0|[1-9][0-9]*)(\.[0-9]+)?$')
let is_json_container = ($value =~ '^\[' or $value =~ '^\{')
# Parse the value into the appropriate type
let parsed_value = if $is_bool or $is_number {
$value | from json
} else if $is_json_container {
let parsed = (do { $value | from json } | complete)
if $parsed.exit_code == 0 {
$value | from json
} else {
print $"Warning: Value for '($key)' looks like JSON but is invalid. Treating as string." --stderr
$value
}
} else {
$value
}
# Convert key path (e.g., "items.0.type") to a list of path segments
# Numbers are converted to integers for array indexing
let path_segments = ($key | split row "." | each { |segment|
if ($segment =~ '^[0-9]+$') {
$segment | into int
} else {
$segment
}
})
let data = ($json | from json)
_hr_setpath $data $path_segments $parsed_value | to json
}
def _hr_parse_flags [args: list<string>] {
mut json_str = "{}"
mut i = 0
while $i < ($args | length) {
let arg = ($args | get $i)
if ($arg | str starts-with "-") {
let key = ($arg | str replace --regex '^-+' '')
let next_i = $i + 1
if $next_i >= ($args | length) {
error make { msg: $"Error: Missing value for option ($key)" }
}
let next = ($args | get $next_i)
if ($next | str starts-with "-") {
error make { msg: $"Error: Missing value for option ($key)" }
}
$json_str = (_hr_add_json_field $json_str $key $next)
$i = $i + 2
} else {
error make { msg: $"Error: Unexpected argument '($arg)'" }
}
}
$json_str | from json
}
def _hr_call [args: list<string>] {
if ($args | is-empty) {
print "Usage: hr call <route-name>[/path] [OPTIONS]"
print ""
print "Options:"
print " -key value Set JSON field (supports dot notation, e.g., -items.0.type \"trip\")"
print " --data <record> Pass structured data as a Nushell record"
return
}
let route_arg = ($args | first)
let rest = ($args | skip 1)
let parts = if ($route_arg | str contains "/") {
let service = ($route_arg | split row "/" | first)
let path = "/" + ($route_arg | split row "/" | skip 1 | str join "/")
{ service: $service, path: $path }
} else {
{ service: $route_arg, path: "" }
}
let project_number = if $env.PROJECT_ID == "mk2-prod" {
"1013087376822"
} else {
"322048751601"
}
# Check if --data flag is present
let data_idx = ($rest | enumerate | where item == "--data" | get index.0? | default (-1))
let payload_record = if $data_idx >= 0 {
# Use --data record
if ($data_idx + 1) >= ($rest | length) {
error make { msg: "Error: --data requires a record argument" }
}
let data_str = ($rest | get ($data_idx + 1))
# Try to parse as Nushell code to get a record
try {
nu -c $data_str
} catch {
error make { msg: $"Error: --data argument must be a valid Nushell record, got: ($data_str)" }
}
} else {
# Use -key value pairs
_hr_parse_flags $rest
}
let payload = ($payload_record | to json -r)
let url = $"https://($parts.service)-($project_number).europe-west1.run.app($parts.path)"
print $"Calling ($url)..."
print ($payload | from json)
let token = (gcloud auth print-identity-token)
^curl -s -S -L $url -H $"Authorization: Bearer ($token)" -H "Content-Type: application/json" -d $payload
}
def _hr_cf [args: list<string>] {
if ($args | is-empty) {
print "Usage: hr cf <function-name> [OPTIONS]"
print ""
print "Options:"
print " -key value Set JSON field (supports dot notation, e.g., -items.0.type \"trip\")"
print " --data <record> Pass structured data as a Nushell record"
return
}
let function_name = ($args | first)
let rest = ($args | skip 1)
# Check if --data flag is present
let data_idx = ($rest | enumerate | where item == "--data" | get index.0? | default (-1))
let payload_record = if $data_idx >= 0 {
# Use --data record
if ($data_idx + 1) >= ($rest | length) {
error make { msg: "Error: --data requires a record argument" }
}
let data_str = ($rest | get ($data_idx + 1))
# Try to parse as Nushell code to get a record
try {
nu -c $data_str
} catch {
error make { msg: $"Error: --data argument must be a valid Nushell record, got: ($data_str)" }
}
} else {
# Use -key value pairs
_hr_parse_flags $rest
}
let payload = ($payload_record | to json -r)
let url = $"https://europe-west1-($env.PROJECT_ID).cloudfunctions.net/($function_name)"
print $"Calling ($url)..."
print ($payload | from json)
let token = (gcloud auth print-identity-token)
^curl -s -S -L $url -H $"Authorization: Bearer ($token)" -H "Content-Type: application/json" -d $payload
}
# HR - Hefring work tooling
export def --env --wrapped hr [...args: string] {
if ($args | is-empty) {
_hr_usage
return
}
let cmd = ($args | first)
let rest = ($args | skip 1)
match $cmd {
"switch" => {
if ($rest | is-empty) {
if $env.PROJECT_ID == "mk2-test" {
$env.PROJECT_ID = "mk2-prod"
print "Switched PROJECT_ID to mk2-prod"
} else {
$env.PROJECT_ID = "mk2-test"
print "Switched PROJECT_ID to mk2-test"
}
} else {
match ($rest | first) {
"test" => {
$env.PROJECT_ID = "mk2-test"
print "Set PROJECT_ID to mk2-test"
}
"prod" => {
$env.PROJECT_ID = "mk2-prod"
print "Set PROJECT_ID to mk2-prod"
}
_ => {
print "Usage: hr switch [test|prod]"
}
}
}
}
"init" => {
if ($rest | is-empty) {
_hr_usage
return
}
match ($rest | first) {
"py" => { _hr_init_base "Python" { _hr_py_files } ["uv.lock" "uv.toml"] }
"rs" => { _hr_init_base "Rust" { _hr_rs_files } [] }
"go" => { _hr_init_base "Go" { _hr_go_files } [] }
"cpp" => {
_hr_init_base "C++" { _hr_cpp_files } []
mkdir build
cd build
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Release ..
make -j (sys cpu | length)
cp compile_commands.json ..
}
_ => { _hr_usage }
}
}
"freeze" => {
let extra_index_url = "https://europe-west1-python.pkg.dev/mk2-prod/python-packages/simple/"
uv pip install keyrings.google-artifactregistry-auth==1.1.2 keyring
uv pip install --no-cache -e ".[test]" --extra-index-url $extra_index_url --keyring-provider subprocess
$"--extra-index-url ($extra_index_url)\n" | save -f requirements.txt
uv pip freeze --exclude-editable | save --append requirements.txt
}
"call" => {
_hr_call $rest
}
"cf" => {
_hr_cf $rest
}
_ => { _hr_usage }
}
}

View file

@ -0,0 +1,414 @@
# Set default PROJECT_ID if not already set
if [[ -z "$PROJECT_ID" ]]; then
export PROJECT_ID="mk2-test"
fi
_hr_usage() {
echo "Usage: hr <command>"
echo "Commands:"
echo " switch Switch PROJECT_ID between mk2-test and mk2-prod"
echo " call Call a Cloud Run service route"
echo " cf Call a Cloud Function"
echo " init py Initialize a python devenv environment (git-ignored)"
echo " init go Initialize a go devenv environment (git-ignored)"
echo " freeze Freeze dependencies to requirements.txt"
}
_hr_init_devenv() {
if [ -f .gitignore ]; then
cp .gitignore .gitignore.bak
fi
if command -v devenv >/dev/null; then
devenv init
echo "Direnv allowed"
else
echo "Error: devenv not found in path."
return 1
fi
if [ -f .gitignore.bak ]; then
mv .gitignore.bak .gitignore
elif [ -f .gitignore ]; then
rm .gitignore
fi
}
_hr_add_ignores() {
if git rev-parse --git-dir >/dev/null 2>&1; then
EXCLUDE_FILE=$(git rev-parse --git-path info/exclude)
mkdir -p "$(dirname "$EXCLUDE_FILE")"
for file in "$@"; do
if ! grep -Fxq "$file" "$EXCLUDE_FILE" 2>/dev/null; then
echo "$file" >>"$EXCLUDE_FILE"
echo "Added $file to local git exclude ($EXCLUDE_FILE)"
fi
done
else
echo "Warning: Not a git repository. Skipping git ignore setup."
fi
}
_hr_py_files() {
cat <<EOF >devenv.nix
{pkgs, ...}: {
packages = [ pkgs.google-cloud-sdk pkgs.libpq ];
languages.python = {
enable = true;
venv.enable = true;
uv = {
enable = true;
sync = {
enable = true;
allExtras = true;
};
};
};
# We use the named index "google" defined in uv.toml
env.UV_INDEX_GOOGLE_USERNAME = "oauth2accesstoken";
env.PROJECT_ID = "mk2-test";
enterShell = ''
export PATH="\$DEVENV_STATE/venv/bin:\$PATH"
if ! gcloud auth print-access-token >/dev/null 2>&1; then
echo "⚠️ gcloud not authenticated. Run 'gcloud auth login' to access Google Artifact Registry."
else
export UV_INDEX_GOOGLE_PASSWORD=\$(gcloud auth print-access-token)
fi
'';
}
EOF
cat <<EOF >uv.toml
[[index]]
name = "google"
url = "https://europe-west1-python.pkg.dev/mk2-prod/python-packages/simple/"
EOF
}
_hr_rs_files() {
cat <<EOF >devenv.nix
{pkgs, ...}: {
languages.rust = {
enable = true;
channel = "stable";
};
}
EOF
cat <<EOF >devenv.yaml
inputs:
rust-overlay:
url: github:oxalica/rust-overlay
inputs:
nixpkgs:
follows: nixpkgs
EOF
}
_hr_cpp_files() {
cat <<EOF >devenv.nix
{ pkgs, ... }:
let
# Use glibc-compatible static openssl to match system libs
staticOpenSSL = pkgs.openssl.override { static = true; };
# Shim to satisfy CMake looking for "ssl.a"
compatOpenSSL = pkgs.runCommand "openssl-compat" {} ''
mkdir -p \$out/lib
ln -s \${staticOpenSSL.out}/lib/libssl.a \$out/lib/ssl.a
ln -s \${staticOpenSSL.out}/lib/libcrypto.a \$out/lib/crypto.a
'';
in {
packages = [
pkgs.cmake
pkgs.clang-tools
pkgs.pkg-config
pkgs.mosquitto
staticOpenSSL
compatOpenSSL
];
# Explicitly add lib paths so linker finds -lssl AND ssl.a
env.LIBRARY_PATH = "\${staticOpenSSL.out}/lib:\${compatOpenSSL}/lib";
env.CPATH = "\${staticOpenSSL.dev}/include";
languages.cplusplus.enable = true;
}
EOF
}
_hr_go_files() {
cat <<EOF >devenv.nix
{pkgs, ...}: {
languages.go = {
enable = true;
};
}
EOF
}
_hr_init_base() {
local name="$1"
local func="$2"
shift 2
local ignores=("$@")
echo "Initializing $name devenv..."
# 1. Init devenv
_hr_init_devenv
# 2. Replace devenv.nix
"$func"
# 3. Add to local git exclude
IGNORES=(
".devenv*"
".direnv"
"devenv.nix"
"devenv.yaml"
"devenv.lock"
".envrc"
"${ignores[@]}"
)
_hr_add_ignores "${IGNORES[@]}"
direnv allow
}
_hr_init_py() {
IGNORES=(
"uv.lock"
"uv.toml"
)
_hr_init_base "Python" _hr_py_files "${IGNORES[@]}"
}
_hr_init_rs() {
_hr_init_base "Rust" _hr_rs_files
}
_hr_init_cpp() {
_hr_init_base "C++" _hr_cpp_files
mkdir -p build &&
cd build &&
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Release .. &&
make -j$(nproc) &&
cp compile_commands.json ..
}
_hr_init_go() {
_hr_init_base "Go" _hr_go_files
}
_hr_freeze() {
local extra_index_url="https://europe-west1-python.pkg.dev/mk2-prod/python-packages/simple/"
# Install the auth plugin and keyring CLI
uv pip install keyrings.google-artifactregistry-auth==1.1.2 keyring
# Install project dependencies using the subprocess keyring provider
uv pip install --no-cache -e ".[test]" --extra-index-url "${extra_index_url}" --keyring-provider subprocess
# Generate requirements.txt
echo "--extra-index-url ${extra_index_url}" >requirements.txt
uv pip freeze --exclude-editable >>requirements.txt
}
_hr_add_json_field() {
local json="$1"
local key="$2"
local value="$3"
local jq_opt="--arg"
# Check if explicit boolean
if [[ "$value" == "true" || "$value" == "false" ]]; then
jq_opt="--argjson"
# Check if number (integer or float, no leading zeros unless just 0)
elif [[ "$value" =~ ^-?(0|[1-9][0-9]*)(\.[0-9]+)?$ ]]; then
jq_opt="--argjson"
# Check if object or array
elif [[ "$value" == "["* || "$value" == "{"* ]]; then
if echo "$value" | jq empty >/dev/null 2>&1; then
jq_opt="--argjson"
else
# Warn to stderr, but proceed as string
echo "Warning: Value for '$key' looks like JSON but is invalid. Treating as string." >&2
fi
fi
# Apply the value at the path defined by the key (dot-notation supported)
# paths like items.0.id are converted to ["items", 0, "id"]
echo "$json" | jq --arg k "$key" $jq_opt v "$value" \
'setpath($k | split(".") | map(if test("^[0-9]+$") then tonumber else . end); $v)'
}
_hr_call() {
local route_arg="$1"
shift
if [[ -z "$route_arg" ]]; then
echo "Usage: hr call <route-name>[/path] <options>"
return 1
fi
local service_name
local url_path
if [[ "$route_arg" == */* ]]; then
service_name="${route_arg%%/*}"
url_path="/${route_arg#*/}"
else
service_name="$route_arg"
url_path=""
fi
local project_number
if [[ "$PROJECT_ID" == "mk2-prod" ]]; then
project_number="1013087376822"
else
project_number="322048751601"
fi
if ! command -v jq >/dev/null; then
echo "Error: jq is required but not installed."
return 1
fi
local json_payload="{}"
while [[ $# -gt 0 ]]; do
if [[ "$1" == -* ]]; then
local key="${1#-}"
if [[ -z "$2" || "$2" == -* ]]; then
echo "Error: Missing value for option $key"
return 1
fi
local value="$2"
json_payload=$(_hr_add_json_field "$json_payload" "$key" "$value")
shift 2
else
echo "Error: Unexpected argument '$1'"
return 1
fi
done
local url="https://${service_name}-${project_number}.europe-west1.run.app${url_path}"
echo "Calling $url..."
echo "$json_payload"
curl "$url" \
-H "Authorization: bearer $(gcloud auth print-identity-token)" \
-H "Content-Type: application/json" \
-d "$json_payload"
}
_hr_cf() {
local function_name="$1"
shift
if [[ -z "$function_name" ]]; then
echo "Usage: hr cf <function-name> <options>"
return 1
fi
if ! command -v jq >/dev/null; then
echo "Error: jq is required but not installed."
return 1
fi
local json_payload="{}"
while [[ $# -gt 0 ]]; do
if [[ "$1" == -* ]]; then
local key="${1#-}"
if [[ -z "$2" || "$2" == -* ]]; then
echo "Error: Missing value for option $key"
return 1
fi
local value="$2"
json_payload=$(_hr_add_json_field "$json_payload" "$key" "$value")
shift 2
else
echo "Error: Unexpected argument '$1'"
return 1
fi
done
local url="https://europe-west1-${PROJECT_ID}.cloudfunctions.net/${function_name}"
echo "Calling $url..."
echo "$json_payload"
curl "$url" \
-H "Authorization: bearer $(gcloud auth print-identity-token)" \
-H "Content-Type: application/json" \
-d "$json_payload"
}
hr() {
if [[ $# -eq 0 ]]; then
_hr_usage
return 1
fi
local command="$1"
shift
if [[ "$command" == "switch" ]]; then
if [[ -z "$1" ]]; then
# Toggle between test and prod
if [[ "$PROJECT_ID" == "mk2-test" ]]; then
export PROJECT_ID="mk2-prod"
echo "Switched PROJECT_ID to mk2-prod"
else
export PROJECT_ID="mk2-test"
echo "Switched PROJECT_ID to mk2-test"
fi
elif [[ "$1" == "test" ]]; then
export PROJECT_ID="mk2-test"
echo "Set PROJECT_ID to mk2-test"
elif [[ "$1" == "prod" ]]; then
export PROJECT_ID="mk2-prod"
echo "Set PROJECT_ID to mk2-prod"
else
echo "Usage: hr switch [test|prod]"
return 1
fi
return 0
fi
# Run original logic in a subshell to preserve set -e behavior without affecting current shell
(
set -e
# Restore arguments for processing
set -- "$command" "$@"
if [ "$1" = "init" ] && [ "$2" = "py" ]; then
_hr_init_py
elif [ "$1" = "init" ] && [ "$2" = "rs" ]; then
_hr_init_rs
elif [ "$1" = "init" ] && [ "$2" = "go" ]; then
_hr_init_go
elif [ "$1" = "init" ] && [ "$2" = "cpp" ]; then
_hr_init_cpp
elif [ "$1" = "freeze" ]; then
_hr_freeze
elif [ "$1" = "call" ]; then
shift
_hr_call "$@"
elif [ "$1" = "cf" ]; then
shift
_hr_cf "$@"
else
_hr_usage
exit 1
fi
)
}

View file

@ -0,0 +1,79 @@
{
pkgs,
lib,
config,
...
}: let
cfg = config.mods.terminal;
aliases = {
la = "ls -la";
".." = "cd ..";
"..." = "cd ../..";
"...." = "cd ../../..";
"....." = "cd ../../../..";
"......" = "cd ../../../../..";
};
in {
options.mods.terminal.nushell.enable = lib.mkEnableOption "enables nushell";
config = lib.mkIf cfg.nushell.enable {
programs.nushell = {
enable = true;
shellAliases = aliases;
# vi mode + sensible defaults via flat assignments (avoids clobbering other modules)
settings = {
show_banner = false;
edit_mode = "vi";
cursor_shape = {
vi_insert = "line";
vi_normal = "block";
};
history = {
max_size = 2097152;
sync_on_enter = true;
file_format = "sqlite";
isolation = false;
};
completions = {
case_sensitive = false;
quick = true;
partial = true;
algorithm = "fuzzy";
};
table.mode = "rounded";
};
# Append the / keybinding after all integrations (including atuin) are sourced,
# so _atuin_search_cmd is defined when this runs.
extraConfig = lib.mkAfter ''
$env.config = (
$env.config | upsert keybindings (
$env.config.keybindings | append {
name: atuin_search_vi_normal
modifier: none
keycode: char_/
mode: vi_normal
event: { send: executehostcommand cmd: (_atuin_search_cmd) }
}
)
)
'';
# Carry over zsh session variables
extraEnv = lib.optionalString config.mods.terminal.development.enable ''
$env.EDITOR = "nvim"
'';
};
# Starship prompt (same as zsh)
programs.starship.enable = true;
# direnv nushell integration
programs.direnv = lib.mkIf config.mods.terminal.development.enable {
enableNushellIntegration = true;
};
};
}

View file

@ -23,6 +23,7 @@ in {
imports = [ imports = [
inputs.nvf.homeManagerModules.default inputs.nvf.homeManagerModules.default
./obsidian.nix ./obsidian.nix
./octo.nix
]; ];
config = lib.mkIf config.mods.nvim.enable { config = lib.mkIf config.mods.nvim.enable {
@ -60,6 +61,13 @@ in {
action = "<cmd>lua vim.lsp.buf.definition()<CR>"; action = "<cmd>lua vim.lsp.buf.definition()<CR>";
desc = "Go to Definition"; desc = "Go to Definition";
} }
{
key = "<C-k>";
mode = ["n"];
action = "<cmd>FzfLua combine pickers=keymaps;commands<CR>";
silent = true;
desc = "FzfLua keymaps and commands";
}
]; ];
languages = { languages = {
@ -93,6 +101,7 @@ in {
statusline.lualine.enable = true; statusline.lualine.enable = true;
telescope.enable = true; telescope.enable = true;
fzf-lua.enable = true;
autocomplete.nvim-cmp.enable = true; autocomplete.nvim-cmp.enable = true;
autopairs.nvim-autopairs.enable = true; autopairs.nvim-autopairs.enable = true;
tabline.nvimBufferline.enable = true; tabline.nvimBufferline.enable = true;
@ -103,6 +112,7 @@ in {
clipboard = { clipboard = {
enable = true; enable = true;
providers.xclip.enable = true; providers.xclip.enable = true;
registers = "unnamed,unnamedplus";
}; };
mini = { mini = {

View file

@ -0,0 +1,54 @@
{
pkgs,
lib,
config,
...
}: {
options.mods.octo.enable =
lib.mkEnableOption "enables octo";
config = lib.mkIf config.mods.octo.enable {
programs.nvf.settings.vim.lazy.plugins."octo.nvim" = {
package = pkgs.vimPlugins.octo-nvim;
setupModule = "octo";
setupOpts = {
picker = "telescope";
enable_builtin = true;
};
cmd = ["Octo"];
keys = [
{
key = "<leader>oi";
mode = "n";
action = "<CMD>Octo issue list<CR>";
desc = "List GitHub Issues";
}
{
key = "<leader>op";
mode = "n";
action = "<CMD>Octo pr list<CR>";
desc = "List GitHub PullRequests";
}
{
key = "<leader>od";
mode = "n";
action = "<CMD>Octo discussion list<CR>";
desc = "List GitHub Discussions";
}
{
key = "<leader>on";
mode = "n";
action = "<CMD>Octo notification list<CR>";
desc = "List GitHub Notifications";
}
{
key = "<leader>os";
mode = "n";
action = "function() require('octo.utils').create_base_search_command { include_current_repo = true } end";
lua = true;
desc = "Search GitHub";
}
];
};
};
}

View file

@ -0,0 +1,38 @@
{
lib,
fetchFromGitHub,
rustPlatform,
openssl,
pkg-config,
git,
}:
rustPlatform.buildRustPackage (finalAttrs: {
pname = "dcg";
version = "v0.4.0";
src = fetchFromGitHub {
owner = "Dicklesworthstone";
repo = "destructive_command_guard";
tag = finalAttrs.version;
hash = "sha256-tkjHhSMoLRV56AwUa0DkoDMoEj6gUZx/ih0VTC9C+4o=";
};
cargoHash = "sha256-G6cOjl5tLdjBg7A+Itnk/t6tLzoU7gKYOTYlZm3HSlA=";
doCheck = false;
nativeBuildInputs = [
pkg-config
openssl
openssl.dev
git
];
PKG_CONFIG_PATH = "${openssl.dev}/lib/pkgconfig";
meta = {
description = "A high-performance hook for AI coding agents that blocks destructive commands before they execute, protecting your work from accidental deletion";
homepage = "https://github.com/Dicklesworthstone/destructive_command_guard";
license = lib.licenses.mit;
maintainers = [];
};
})

View file

@ -0,0 +1,38 @@
{
lib,
config,
pkgs,
...
}: {
options.mods.opencode.enable = lib.mkEnableOption "enables opencode";
# imports = [
# ./package.nix
# ./dcg.nix
# ];
config = lib.mkIf config.mods.opencode.enable {
home.packages = with pkgs; [
(callPackage ./dcg.nix {})
];
programs.opencode = {
enable = true;
settings = {
"plugin" = ["opencode-gemini-auth"];
"permission" = {
"bash" = {
"*" = "ask";
"rm *" = "deny";
"rmdir *" = "deny";
"unlink *" = "deny";
"*rm *" = "ask";
"*rmdir *" = "ask";
"*unlink *" = "ask";
};
};
};
};
};
}

View file

@ -23,6 +23,7 @@ in {
programs.zsh = lib.mkIf cfg.zsh.enable { programs.zsh = lib.mkIf cfg.zsh.enable {
enable = true; enable = true;
dotDir = "${config.xdg.configHome}/zsh";
enableCompletion = true; enableCompletion = true;
autosuggestion.enable = true; autosuggestion.enable = true;
syntaxHighlighting.enable = true; syntaxHighlighting.enable = true;

View file

@ -42,6 +42,7 @@ in
atuin = { atuin = {
enable = true; enable = true;
enableZshIntegration = true; enableZshIntegration = true;
enableNushellIntegration = true;
flags = ["--disable-up-arrow"]; flags = ["--disable-up-arrow"];
settings = { settings = {
sync_frequency = "5m"; sync_frequency = "5m";

View file

@ -0,0 +1,599 @@
{
pkgs,
lib,
config,
...
}: let
cfg = config.mods.terminal;
color = config.lib.stylix.colors.withHashtag;
# Shell to use inside wezterm
shell =
if cfg.nushell.enable
then lib.getExe pkgs.nushell
else if cfg.zsh.enable
then lib.getExe pkgs.zsh
else null;
# ── Lua codegen helpers ──────────────────────────────────────────────────────
luaKey = {
key,
mods ? null,
action,
indent ? " ",
}: let
modsStr = lib.optionalString (mods != null) ", mods = \"${mods}\"";
in "${indent}{ key = \"${key}\"${modsStr}, action = ${action} },\n";
# vi directions: key, arrow alias, wezterm direction name
viDirs = [
{
key = "h";
arrow = "LeftArrow";
dir = "Left";
}
{
key = "j";
arrow = "DownArrow";
dir = "Down";
}
{
key = "k";
arrow = "UpArrow";
dir = "Up";
}
{
key = "l";
arrow = "RightArrow";
dir = "Right";
}
];
# hjkl + arrow equivalents; mkAction :: dir -> lua-action-string
viDirKeys = {
mods ? null,
indent ? " ",
mkAction,
}:
lib.concatMapStrings (d:
luaKey {
inherit mods indent;
key = d.key;
action = mkAction d.dir;
}
+ luaKey {
inherit mods indent;
key = d.arrow;
action = mkAction d.dir;
})
viDirs;
# 1-9 tab-jump bindings
tabJumpKeys = {
mods ? null,
indent ? " ",
}:
lib.concatStrings (builtins.genList (i:
luaKey {
inherit mods indent;
key = toString (i + 1);
action = "act.ActivateTab(${toString i})";
})
9);
# hjkl + arrows for AdjustPaneSize at a given step
resizeDirKeys = {
step,
indent ? " ",
}:
lib.concatMapStrings (d:
luaKey {
inherit indent;
key = d.key;
action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";
}
+ luaKey {
inherit indent;
key = d.arrow;
action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";
})
viDirs;
# Uppercase HJKL fine-tune resize
resizeShiftKeys = {
step,
indent ? " ",
}:
lib.concatMapStrings (d:
luaKey {
inherit indent;
key = lib.toUpper d.key;
action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";
})
viDirs;
# Wrap a lua action string so it also pops the key table (return to locked)
andPop = action: "act.Multiple({ ${action}, act.PopKeyTable })";
# Standard exit bindings, given the mode's own pop key
exitKeys = {
selfKey,
indent ? " ",
}:
lib.concatStrings [
(luaKey {
inherit indent;
key = selfKey;
action = "act.PopKeyTable";
})
(luaKey {
inherit indent;
key = "Escape";
action = "act.PopKeyTable";
})
(luaKey {
inherit indent;
key = "Enter";
action = "act.PopKeyTable";
})
(luaKey {
inherit indent;
key = "Space";
mods = "ALT";
action = "act.PopKeyTable";
})
];
# ── Status bar ───────────────────────────────────────────────────────────────
# Per-mode accent colour (base16) and hint string shown in the status bar
# Hints use nerd font glyphs: separators, icons, key labels
modes = {
locked = {
fg = color.base03;
hint = "󱁐:󰌌 hl:󰹳 ^ud:󰹹 1-9:󰓩 n:󰤻 s:󱇳 w:󰅙 t:󰓩 p:󱃔";
};
normal = {
fg = color.base0D;
hint = "t:󰐕 x:󰅙 r:󰩨 c:󰆏 s:󰍉 esc:󱊷";
};
resize = {
fg = color.base0A;
hint = "hjkl:󰁌 HJKL:󰁎 esc:󱊷";
};
copy_mode = {
fg = color.base0E;
hint = "hjkl:󰹳 v:󰒆 V:󰒇 ^v:󰒈 y:󰆏 /:󰍉 n/p:󰁹 esc:󱊷";
};
search_mode = {
fg = color.base08;
hint = "type:󰈙 ^n/^p:󰁹 ^r:󰑓 /esc:󰆐";
};
};
# Lua table literal mapping mode name -> { fg, hint }
modeTableEntries = lib.concatStringsSep "\n " (lib.mapAttrsToList (name: m: ''
["${name}"] = { fg = "${m.fg}", hint = "${m.hint}" },
'')
modes);
in {
options.mods.terminal.wezterm.enable = lib.mkEnableOption "enables wezterm";
config = lib.mkIf cfg.wezterm.enable {
programs.wezterm = {
enable = true;
extraConfig = ''
local wezterm = require("wezterm")
local act = wezterm.action
-- Helpers
-- move_focus_or_tab: mirrors Zellij's MoveFocusOrTab.
-- Move focus in direction; if at the edge, switch to the adjacent tab.
local function move_focus_or_tab(direction)
return wezterm.action_callback(function(window, pane)
local neighbour = pane:tab():get_pane_direction(direction)
if neighbour ~= nil then
window:perform_action(act.ActivatePaneDirection(direction), pane)
else
if direction == "Left" then
window:perform_action(act.ActivateTabRelative(-1), pane)
elseif direction == "Right" then
window:perform_action(act.ActivateTabRelative(1), pane)
end
end
end)
end
-- scroll_or_passthrough: Ctrl+key scrolls at the shell prompt but passes
-- the key through when a program has taken the alt screen (vim, less, fzf).
local function scroll_or_passthrough(key, pages)
return wezterm.action_callback(function(window, pane)
if pane:is_alt_screen_active() then
window:perform_action(act.SendKey({ key = key, mods = "CTRL" }), pane)
else
window:perform_action(act.ScrollByPage(pages), pane)
end
end)
end
-- tab_is_zoomed: true if any pane in the current tab is zoomed.
-- Used by move_vertical to decide whether to carry zoom forward.
local function tab_is_zoomed(pane)
for _, info in ipairs(pane:tab():panes_with_info()) do
if info.is_zoomed then return true end
end
return false
end
-- move_vertical: move focus Up/Down.
-- If any pane in the tab is currently zoomed (zoom-mode is "on" for this
-- tab), zoom the destination pane too stacked-panel behaviour.
-- Otherwise just move focus plainly.
local function move_vertical(direction)
return wezterm.action_callback(function(window, pane)
local neighbour = pane:tab():get_pane_direction(direction)
if neighbour == nil then return end
if tab_is_zoomed(pane) then
-- unzoom_on_switch_pane unzooms the current pane when we move;
-- then SetPaneZoomState(true) zooms the newly active pane.
window:perform_action(
act.Multiple({
act.ActivatePaneDirection(direction),
act.SetPaneZoomState(true),
}),
pane
)
else
window:perform_action(act.ActivatePaneDirection(direction), pane)
end
end)
end
-- Status bar
-- Per-mode accent colour and hint text, generated from Nix/stylix palette
local mode_info = {
${modeTableEntries}
}
local bg_bar = "${color.base01}"
local fg_text = "${color.base05}"
local fg_dim = "${color.base03}"
local bg_main = "${color.base00}"
-- Render hints with the key in the mode accent colour and the icon dimmed.
-- Each hint token is "key:icon"; we colour them individually.
local function render_hints(hint, accent)
local cells = {}
for token in hint:gmatch("%S+") do
local key, icon = token:match("^(.-):(.)$")
if key and icon then
table.insert(cells, { Background = { Color = bg_bar } })
table.insert(cells, { Foreground = { Color = accent } })
table.insert(cells, { Text = key })
table.insert(cells, { Foreground = { Color = fg_text } })
table.insert(cells, { Text = ":" .. icon .. " " })
else
table.insert(cells, { Background = { Color = bg_bar } })
table.insert(cells, { Foreground = { Color = fg_dim } })
table.insert(cells, { Text = token .. " " })
end
end
return cells
end
wezterm.on("format-window-title", function(tab, _pane, _tabs, _panes, _config)
local ok, win = pcall(function() return wezterm.mux.get_window(tab.window_id) end)
if ok and win then
local ws = win:get_workspace()
if ws ~= "default" then
return ws .. " " .. tab.active_pane.title
end
end
return tab.active_pane.title
end)
-- format-tab-title: show "rank/total title" when the active tab is in
-- stacked (zoom) mode; otherwise show plain " title ".
-- Returning a cell table gives us explicit padding and colours;
-- returning a plain string loses the retro tab bar's built-in spacing.
local tab_bg_active = "${color.base0D}"
local tab_fg_active = "${color.base00}"
local tab_bg_inactive = "${color.base01}"
local tab_fg_inactive = "${color.base03}"
wezterm.on("format-tab-title", function(tab, _tabs, panes, _config, _hover, _max_width)
local title = (tab.tab_title and tab.tab_title ~= "") and tab.tab_title or tab.active_pane.title
local bg = tab.is_active and tab_bg_active or tab_bg_inactive
local fg = tab.is_active and tab_fg_active or tab_fg_inactive
-- Only compute stack position for the active tab.
-- The `panes` event parameter only contains the active pane snapshot
-- and lacks `top`, so we use wezterm.mux.get_tab():panes_with_info()
-- which returns full PaneInformation including top/is_zoomed/is_active.
if tab.is_active then
local mux_tab = wezterm.mux.get_tab(tab.tab_id)
if mux_tab then
local infos = mux_tab:panes_with_info()
table.sort(infos, function(a, b) return a.top < b.top end)
local any_zoomed = false
local active_rank, total = 0, 0
for _, p in ipairs(infos) do
total = total + 1
if p.is_active then active_rank = total end
if p.is_zoomed then any_zoomed = true end
end
if any_zoomed and total > 1 and active_rank > 0 then
title = active_rank .. "/" .. total .. " " .. title
end
end
end
return {
{ Background = { Color = bg } },
{ Foreground = { Color = fg } },
{ Text = " " .. title .. " " },
}
end)
wezterm.on("update-status", function(window, _pane)
local kt = window:active_key_table()
local mode = kt or "locked"
local info = mode_info[mode] or mode_info["locked"]
window:set_left_status("")
-- Right: coloured hints then mode badge
local cells = { { Text = " " } }
for _, c in ipairs(render_hints(info.hint, info.fg)) do
table.insert(cells, c)
end
table.insert(cells, "ResetAttributes")
table.insert(cells, { Background = { Color = info.fg } })
table.insert(cells, { Foreground = { Color = bg_bar } })
table.insert(cells, { Text = "" })
table.insert(cells, { Foreground = { Color = bg_main } })
table.insert(cells, { Attribute = { Intensity = "Bold" } })
table.insert(cells, { Text = " " .. mode:upper() .. " " })
table.insert(cells, "ResetAttributes")
window:set_right_status(wezterm.format(cells))
end)
-- Appearance
local config = wezterm.config_builder()
-- Stylix sets the color scheme via its wezterm integration;
-- this is a fallback in case it is not active.
config.color_scheme = "Synthwave (Gogh)"
config.font = wezterm.font("CommitMono Nerd Font")
config.font_size = 12.0
config.window_padding = { left = 6, right = 6, top = 6, bottom = 6 }
config.default_cursor_style = "BlinkingBar"
config.hide_tab_bar_if_only_one_tab = false
config.use_fancy_tab_bar = false
config.tab_max_width = 24
config.colors = {
tab_bar = {
background = "${color.base01}",
active_tab = { bg_color = "${color.base0D}", fg_color = "${color.base00}", intensity = "Bold" },
inactive_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" },
inactive_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" },
new_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" },
new_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" },
},
}
config.tab_bar_at_bottom = false
-- SSH Domains
config.ssh_domains = {
{
name = "muho",
remote_address = "muho",
username = "muon",
default_prog = { "nu" },
},
}
-- Shell
${lib.optionalString (shell != null) ''
config.default_prog = { "${shell}" }
''}
-- Scrollback
config.scrollback_lines = 10000
-- Stacked panes
-- When switching away from a zoomed pane, unzoom it automatically.
-- Paired with stack_focus (Alt+j/k) this gives Zellij-style stacked panels.
config.unzoom_on_switch_pane = true
-- Key bindings
--
-- Default mode = "locked" (all keys pass through to the shell).
-- Alt+Space enters "normal" mode.
-- From normal: tnew tab xclose tab p/nsplit rresize mode
-- Esc / Enter / Alt+Space back to locked from any mode.
config.disable_default_key_bindings = true
config.keys = {
-- Alt+h/l: move focus or switch tab at edge (Zellij MoveFocusOrTab)
-- Alt+j/k: initial bindings (overridden below by stacked-focus variants)
${viDirKeys {
mods = "ALT";
indent = " ";
mkAction = d:
if d == "Left" || d == "Right"
then "move_focus_or_tab(\"${d}\")"
else "act.ActivatePaneDirection(\"${d}\")";
}}
-- Alt+1-9: jump to tab by number
${tabJumpKeys {
mods = "ALT";
indent = " ";
}}
-- Alt+n: new pane (split right)
{ key = "n", mods = "ALT", action = act.SplitHorizontal({ domain = "CurrentPaneDomain" }) },
-- Alt+s: split below, then zoom the new pane so stacked mode starts.
-- Two separate perform_action calls: the split runs first and focuses
-- the new pane, then the second call zooms window:active_pane() which
-- is now the new pane (not the original `pane` argument).
{ key = "s", mods = "ALT", action = wezterm.action_callback(function(window, pane)
window:perform_action(act.SplitVertical({ domain = "CurrentPaneDomain" }), pane)
window:perform_action(act.SetPaneZoomState(true), window:active_pane())
end)
},
-- Alt+j/k: move_vertical plain focus move normally, stacked zoom
-- when any pane in the tab is already zoomed (mirrors Alt+f state).
{ key = "j", mods = "ALT", action = move_vertical("Down") },
{ key = "k", mods = "ALT", action = move_vertical("Up") },
{ key = "DownArrow", mods = "ALT", action = move_vertical("Down") },
{ key = "UpArrow", mods = "ALT", action = move_vertical("Up") },
-- Alt+f: toggle zoom
{ key = "f", mods = "ALT", action = act.TogglePaneZoomState },
-- Alt+[/]: rotate panes
{ key = "[", mods = "ALT", action = act.RotatePanes("CounterClockwise") },
{ key = "]", mods = "ALT", action = act.RotatePanes("Clockwise") },
-- Alt++/-/=: font size
{ key = "+", mods = "ALT", action = act.IncreaseFontSize },
{ key = "-", mods = "ALT", action = act.DecreaseFontSize },
{ key = "=", mods = "ALT", action = act.ResetFontSize },
-- Alt+i/o: reorder tabs
{ key = "i", mods = "ALT", action = act.MoveTabRelative(-1) },
{ key = "o", mods = "ALT", action = act.MoveTabRelative(1) },
-- Alt+w: close current pane; if tab was in stacked (zoom) mode, re-zoom
-- the new active pane so zoom mode persists across close.
-- confirm=false because the callback must run synchronously after close;
-- a confirm dialog would make the zoom fire before the user answers.
-- Guard: only re-zoom when there will still be panes left after close.
{ key = "w", mods = "ALT", action = wezterm.action_callback(function(window, pane)
local panes = pane:tab():panes()
local was_zoomed = tab_is_zoomed(pane)
window:perform_action(act.CloseCurrentPane({ confirm = false }), pane)
if was_zoomed and #panes > 1 then
window:perform_action(act.SetPaneZoomState(true), window:active_pane())
end
end)
},
-- Alt+p: command palette
{ key = "p", mods = "ALT", action = act.ActivateCommandPalette },
-- Alt+t: tab navigator (searchable tab list)
{ key = "t", mods = "ALT", action = act.ShowTabNavigator },
-- Alt+q: quit
{ key = "q", mods = "ALT", action = act.QuitApplication },
-- Ctrl+U/D: scroll half page when at the shell prompt; pass through
-- to the running program when it is using the alt screen (vim, less,
-- fzf, etc. switch to the alt screen and need the raw key).
{ key = "u", mods = "CTRL", action = scroll_or_passthrough("u", -0.5) },
{ key = "d", mods = "CTRL", action = scroll_or_passthrough("d", 0.5) },
-- Copy / paste
{ key = "c", mods = "CTRL|SHIFT", action = act.CopyTo("Clipboard") },
{ key = "v", mods = "CTRL|SHIFT", action = act.PasteFrom("Clipboard") },
-- Enter normal mode
{ key = "Space", mods = "ALT", action = act.ActivateKeyTable({ name = "normal", one_shot = false }) },
}
config.key_tables = {
-- NORMAL
normal = {
{ key = "t", action = ${andPop "act.SpawnTab(\"CurrentPaneDomain\")"} },
{ key = "x", action = ${andPop "act.CloseCurrentTab({ confirm = true })"} },
{ key = "r", action = act.ActivateKeyTable({ name = "resize", one_shot = false }) },
{ key = "c", action = ${andPop "act.ActivateCopyMode"} },
{ key = "s", action = ${andPop "act.Search({ CaseSensitiveString = \"\" })"} },
${exitKeys {selfKey = "Escape";}} },
-- RESIZE
resize = {
${resizeDirKeys {step = 5;}}${resizeShiftKeys {step = 1;}}
{ key = "+", action = act.AdjustPaneSize({ "Right", 5 }) },
{ key = "-", action = act.AdjustPaneSize({ "Left", 5 }) },
{ key = "=", action = act.AdjustPaneSize({ "Right", 5 }) },
${exitKeys {selfKey = "r";}} },
-- COPY MODE
copy_mode = {
-- movement
{ key = "h", action = act.CopyMode("MoveLeft") },
{ key = "j", action = act.CopyMode("MoveDown") },
{ key = "k", action = act.CopyMode("MoveUp") },
{ key = "l", action = act.CopyMode("MoveRight") },
{ key = "LeftArrow", action = act.CopyMode("MoveLeft") },
{ key = "DownArrow", action = act.CopyMode("MoveDown") },
{ key = "UpArrow", action = act.CopyMode("MoveUp") },
{ key = "RightArrow", action = act.CopyMode("MoveRight") },
{ key = "w", action = act.CopyMode("MoveForwardWord") },
{ key = "b", action = act.CopyMode("MoveBackwardWord") },
{ key = "e", action = act.CopyMode("MoveForwardWordEnd") },
{ key = "0", action = act.CopyMode("MoveToStartOfLine") },
{ key = "^", action = act.CopyMode("MoveToStartOfLineContent") },
{ key = "$", action = act.CopyMode("MoveToEndOfLineContent") },
{ key = "g", action = act.CopyMode("MoveToScrollbackTop") },
{ key = "G", action = act.CopyMode("MoveToScrollbackBottom") },
{ key = "Enter", action = act.CopyMode("MoveToStartOfNextLine") },
{ key = "u", mods = "CTRL", action = act.CopyMode({ MoveByPage = -0.5 }) },
{ key = "d", mods = "CTRL", action = act.CopyMode({ MoveByPage = 0.5 }) },
{ key = "b", mods = "CTRL", action = act.CopyMode("PageUp") },
{ key = "f", mods = "CTRL", action = act.CopyMode("PageDown") },
{ key = "PageUp", action = act.CopyMode("PageUp") },
{ key = "PageDown", action = act.CopyMode("PageDown") },
-- selection
{ key = "v", action = act.CopyMode({ SetSelectionMode = "Cell" }) },
{ key = "V", action = act.CopyMode({ SetSelectionMode = "Line" }) },
{ key = "v", mods = "CTRL", action = act.CopyMode({ SetSelectionMode = "Block" }) },
{ key = "o", action = act.CopyMode("MoveToSelectionOtherEnd") },
{ key = "O", action = act.CopyMode("MoveToSelectionOtherEndHoriz") },
-- yank and exit
{ key = "y", action = act.Multiple({
act.CopyTo("ClipboardAndPrimarySelection"),
act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }),
})
},
-- search within copy mode
{ key = "/", action = act.Search({ CaseSensitiveString = "" }) },
{ key = "n", action = act.CopyMode("NextMatch") },
{ key = "p", action = act.CopyMode("PriorMatch") },
-- exit
{ key = "q", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) },
{ key = "Escape", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) },
{ key = "c", mods = "CTRL", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) },
},
-- SEARCH MODE
search_mode = {
-- navigate matches
{ key = "Enter", action = act.CopyMode("AcceptPattern") },
{ key = "n", mods = "CTRL", action = act.CopyMode("NextMatch") },
{ key = "p", mods = "CTRL", action = act.CopyMode("PriorMatch") },
{ key = "PageUp", action = act.CopyMode("PriorMatchPage") },
{ key = "PageDown", action = act.CopyMode("NextMatchPage") },
{ key = "UpArrow", action = act.CopyMode("PriorMatch") },
{ key = "DownArrow", action = act.CopyMode("NextMatch") },
-- cycle match type (case-sensitive / insensitive / regex)
{ key = "r", mods = "CTRL", action = act.CopyMode("CycleMatchType") },
-- clear search input
{ key = "u", mods = "CTRL", action = act.CopyMode("ClearPattern") },
-- Escape: dismiss search bar, return to copy mode at current match
{ key = "Escape", action = act.CopyMode("AcceptPattern") },
},
}
return config
'';
};
};
}

View file

@ -5,6 +5,7 @@ in with lib; {
config = mkIf cfg.tools.enable { config = mkIf cfg.tools.enable {
programs.yazi = { programs.yazi = {
enable = true; enable = true;
shellWrapperName = "y";
settings = { settings = {
manager = { manager = {
ratio = [ 1 4 3 ]; ratio = [ 1 4 3 ];

View file

@ -100,6 +100,15 @@ in
10.0.0.2 muon 10.0.0.2 muon
10.0.0.3 muho 10.0.0.3 muho
10.0.0.4 muop 10.0.0.4 muop
127.0.0.1 word.local
'';
# Wildcard DNS: *.word.local -> 127.0.0.1 via NetworkManager's built-in dnsmasq
networking.networkmanager.dns = "dnsmasq";
# Force resolv.conf to use local dnsmasq so wildcard DNS is actually queried
networking.resolvconf.useLocalResolver = true;
environment.etc."NetworkManager/dnsmasq.d/word-local.conf".text = ''
address=/.word.local/127.0.0.1
''; '';
# gateway = # gateway =

View file

@ -51,6 +51,7 @@
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
inputs.nix-alien.packages.${system}.nix-alien inputs.nix-alien.packages.${system}.nix-alien
inputs.home-manager.packages.${system}.home-manager
colmena colmena
]; ];
programs.nix-ld.enable = true; programs.nix-ld.enable = true;

View file

@ -22,5 +22,6 @@ in
xdg.portal.extraPortals = [ xdg.portal.extraPortals = [
pkgs.xdg-desktop-portal pkgs.xdg-desktop-portal
]; ];
xdg.portal.config.common.default = "*";
}; };
} }

View file

@ -1,4 +1,8 @@
{ pkgs, lib, ... }: { {
pkgs,
lib,
...
}: {
imports = [ imports = [
./containers ./containers
./gaming ./gaming
@ -25,5 +29,6 @@
./lemmy.nix ./lemmy.nix
./audio.nix ./audio.nix
./atuin.nix ./atuin.nix
./murmur.nix
]; ];
} }

View file

@ -0,0 +1,25 @@
{
pkgs,
lib,
config,
...
}: let
cfg = config.mods.server.murmur;
in
with lib; {
options.mods.server = {
murmur = {
enable = mkEnableOption {
default = false;
description = "enables murmur server";
};
};
};
config = mkIf cfg.enable {
services.murmur = {
enable = true;
openFirewall = true;
};
};
}

View file

@ -1,5 +1,9 @@
{ pkgs, lib, config, ... }: {
let pkgs,
lib,
config,
...
}: let
inherit (lib) types mkOption mkEnableOption; inherit (lib) types mkOption mkEnableOption;
cfg = config.mods.server.search; cfg = config.mods.server.search;
port = config.mods.server.nginx.ports.search; port = config.mods.server.nginx.ports.search;
@ -24,18 +28,19 @@ in {
hostnames.remove = ["(.*.)?facebook.com$"]; hostnames.remove = ["(.*.)?facebook.com$"];
hostnames.replace = { hostnames.replace = {
# Self-hosted # Self-hosted
"(.*.)?reddit.com$" = "reddit.muon.host"; # "(.*.)?reddit.com$" = "reddit.muon.host";
# "(.*.)?youtube.com$" = "videos.muon.host"; # TODO not working # "(.*.)?youtube.com$" = "videos.muon.host"; # TODO not working
# External # External
"(.*.)?youtube.com$" = "invidious.nerdvpn.de"; "(.*.)?reddit.com$" = "old.reddit.com";
"(.*.)?imdb.com$" = "libremdb.iket.me"; # "(.*.)?youtube.com$" = "invidious.nerdvpn.de";
"(.*.)?imgur.com$" = "rimgo.privacyredirect.com"; # "(.*.)?imdb.com$" = "libremdb.iket.me";
"(.*.)?twitch.com$" = "safetwitch.privacyredirect.com"; # "(.*.)?imgur.com$" = "rimgo.privacyredirect.com";
"(.*.)?wikipedia.com$" = "wikiless.privacyredirect.com"; # "(.*.)?twitch.com$" = "safetwitch.privacyredirect.com";
"(.*.)?medium.com$" = "scribe.privacyredirect.com"; # "(.*.)?wikipedia.com$" = "wikiless.privacyredirect.com";
"(.*.)?stackoverflow.com$" = "anonymousoverflow.privacyredirect.com"; # "(.*.)?medium.com$" = "scribe.privacyredirect.com";
"(.*.)?github.com$" = "gothub.privacyredirect.com"; # "(.*.)?stackoverflow.com$" = "anonymousoverflow.privacyredirect.com";
# "(.*.)?github.com$" = "gothub.privacyredirect.com";
}; };
}; };
}; };

View file

@ -23,7 +23,8 @@ in {
stylix = { stylix = {
enable = true; enable = true;
autoEnable = true; autoEnable = true;
base16Scheme = cfg.scheme; # base16Scheme = cfg.scheme;
base16Scheme = ./synthwave84.yaml;
image = cfg.wallpaper; image = cfg.wallpaper;
cursor = { cursor = {
name = "phinger-cursors-light"; name = "phinger-cursors-light";

View file

@ -0,0 +1,24 @@
system: "base16"
name: "synthwave84"
slug: "synthwave84"
author: "Someone"
variant: "dark"
palette:
base00: "2a2139"
base01: "241b2f"
base02: "34294f"
base03: "8e6fe4"
base04: "c1c3e6"
base05: "f5f5ff"
base06: "ffffff"
base07: "fffdf8"
base08: "f92aad"
base09: "f97e72"
base0A: "ffe261"
base0B: "72f1b8"
base0C: "78dcff"
base0D: "36f9f6"
base0E: "ff7edb"
base0F: "bb8977"

View file

@ -1,5 +1,10 @@
{ pkgs, lib, config, ... }: {
let cfg = config.mods.unfree.nvidia; pkgs,
lib,
config,
...
}: let
cfg = config.mods.unfree.nvidia;
in { in {
options.mods.unfree.nvidia = { options.mods.unfree.nvidia = {
enable = lib.mkEnableOption { enable = lib.mkEnableOption {
@ -23,7 +28,7 @@ in {
# enable32Bit = true; # enable32Bit = true;
# }; # };
services.xserver.videoDrivers = [ "nvidia" "nvidia-dkms" ]; services.xserver.videoDrivers = ["nvidia"];
hardware.nvidia = { hardware.nvidia = {
modesetting.enable = true; modesetting.enable = true;

View file

@ -1,7 +1,6 @@
{pkgs ? import <nixpkgs> {}, ...}: { {pkgs ? import <nixpkgs> {}, ...}: {
default = pkgs.mkShell { default = pkgs.mkShell {
NIX_CONFIG = NIX_CONFIG = "extra-experimental-features = nix-command flakes ca-derivations";
"extra-experimental-features = nix-command flakes ca-derivations";
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
nix nix
git git

View file

@ -1,4 +1,7 @@
{ inputs, system, sources, ... }: { { inputs, system, sources, ... }:
let
pkgs = import inputs.nixpkgs { inherit system; };
in {
mkHost = host: mkHost = host:
inputs.nixpkgs.lib.nixosSystem { inputs.nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs system sources; }; specialArgs = { inherit inputs system sources; };
@ -11,4 +14,41 @@
inputs.impermanence.nixosModules.impermanence inputs.impermanence.nixosModules.impermanence
]; ];
}; };
# Build a standalone HM configuration for a given host NixOS config and
# home.nix file. osConfig is injected as a specialArg so all modules that
# reference it continue to work.
mkHome = { hostConfig, homeFile, username ? "muon" }:
inputs.home-manager.lib.homeManagerConfiguration {
inherit pkgs;
extraSpecialArgs = {
inherit inputs system sources;
osConfig = hostConfig.config;
};
modules = [
homeFile
inputs.self.outputs.homeManagerModules.default
inputs.stylix.homeManagerModules.stylix
({ osConfig, ... }: {
home.username = username;
home.homeDirectory = "/home/${username}";
# Mirror the stylix settings from the NixOS config so the standalone
# HM build has the same theme without re-declaring it.
stylix = {
enable = osConfig.stylix.enable;
autoEnable = osConfig.stylix.autoEnable;
base16Scheme = osConfig.stylix.base16Scheme;
image = osConfig.stylix.image;
cursor = osConfig.stylix.cursor;
# Forward individual font options; stylix derives fonts.packages
# internally as read-only so we must not forward that attr.
fonts.monospace = osConfig.stylix.fonts.monospace;
fonts.sansSerif = osConfig.stylix.fonts.sansSerif;
fonts.serif = osConfig.stylix.fonts.serif;
fonts.emoji = osConfig.stylix.fonts.emoji;
fonts.sizes = osConfig.stylix.fonts.sizes;
};
})
];
};
} }