diff --git a/.sops.yaml b/.sops.yaml index af9d39b..31130ff 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,7 +1,12 @@ keys: - &root age1y0tj3kt67pfnj38t9c8g2ghry3a0mhcq8rrqv5xr4jekwepxaelqzu3dkf - &user age16v8w7q4wmn22hhakq2uzaus2508rhldm7lcwh0kukshzjzyhuqesqz44ze + - &sandbox age1mr42xh9llvrggrvuawr5253qk7gygf4gcg9clqdvjecw3d6tj59qsqxswd creation_rules: + - path_regex: microvm/secrets/sandbox\.yaml$ + key_groups: + - age: + - *sandbox - path_regex: secrets/[^/]+\.yaml$ key_groups: - age: diff --git a/configuration.nix b/configuration.nix index 7be81c3..1fd0b01 100644 --- a/configuration.nix +++ b/configuration.nix @@ -15,9 +15,6 @@ path = "/root/.ssh/desktop"; mode = "0600"; }; - sops.secrets."deepseek_api_key" = { - owner = "agent"; - }; nix.settings.experimental-features = [ "nix-command" "flakes" @@ -96,14 +93,16 @@ enable = true; ports = [ 22 ]; settings = { - PasswordAuthentication = false; - KbdInteractiveAuthentication = false; + PasswordAuthentication = true; + KbdInteractiveAuthentication = true; PermitRootLogin = "no"; AllowUsers = [ "user" ]; }; }; + + ratbagd.enable = true; }; hardware = { @@ -171,10 +170,10 @@ }; systemd.tmpfiles.rules = [ - "d /home/public 2775 root public - -" - "d /home/public/pictures 2775 root public - -" + "d /home/user/dev 0770 user users -" + "d /home/user/pictures 0750 user users -" - "a+ /home/public - - - - d:g:public:rwX,d:m::rwX" - "a+ /home/public/pictures - - - - d:g:public:rwX,d:m::rwX" + "a+ /home/user/dev - - - - d:g:users:rwX,d:m::rwX" + "a+ /home/user/pictures - - - - d:g:users:rwX,d:m::rwX" ]; } diff --git a/executables/opencode.sh b/executables/opencode.sh new file mode 100644 index 0000000..8735693 --- /dev/null +++ b/executables/opencode.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +SSH_TARGET="sandbox@vsock%3" + +if [[ "$PWD" == /home/user* ]]; then + project="${PWD/\/home\/user/\/home\/sandbox}" +else + project="$(ssh "$SSH_TARGET" 'mktemp -d -p /tmp opencode-XXXXXX')" +fi + +exec ssh -t "$SSH_TARGET" opencode "$project" "$@" diff --git a/firejail.nix b/firejail.nix new file mode 100644 index 0000000..7a95668 --- /dev/null +++ b/firejail.nix @@ -0,0 +1,101 @@ +{ + config, + lib, + pkgs, + ... +}: +let + agentProfile = pkgs.writeText "agent.profile" '' + whitelist /nix/store + whitelist /nix/var + read-only /nix/store + whitelist ''${HOME}/.cache/nix + whitelist ''${HOME}/.local/state/nix + whitelist /etc/nix + whitelist /etc/nixos + whitelist /nix/var/nix/daemon-socket + whitelist ''${HOME}/dev + whitelist ''${HOME}/work + whitelist ''${HOME}/.config + + blacklist ''${HOME} + blacklist /etc/nixos/secrets + blacklist /etc/shadow + blacklist /etc/sudoers + blacklist /etc/sudoers.d + blacklist /root + + caps.drop all + nonewprivs + noroot + seccomp + + no3d + nodvd + notv + nou2f + novideo + + + ignore net none + + machine-id + disable-mnt + private-dev + private-tmp + private-cache + ''; +in +{ + programs.firejail = { + enable = true; + wrappedBinaries = { + claude = { + executable = "${pkgs.claude-code}/bin/claude"; + profile = agentProfile; + extraArgs = [ + "--private=~/.local/state/claude-home" + ]; + }; + chromium-sandbox = { + executable = "${pkgs.chromium}/bin/chromium"; + profile = "${pkgs.firejail}/etc/firejail/chromium-browser.profile"; + extraArgs = [ + "--netns=wg0ns" + "--whitelist=~/downloads" + "--env=TGK_THEME=Adwaita:dark" + "--dns=10.2.0.1" + "--private=/home/user/.local/state/chromium-home" + ]; + }; + google-chrome-stable = { + # executable = "${chrome-argumented}/bin/google-chrome-stable"; + executable = "${pkgs.google-chrome}/bin/google-chrome-stable"; + profile = "${pkgs.firejail}/etc/firejail/google-chrome-stable.profile"; + extraArgs = [ + "--env=GTK_THEME=Adwaita:dark" + "--netns=wg-br0ns" + "--dns=10.2.0.1" + "--whitelist=/home/user/downloads" + "--whitelist=/home/user/pictures" + ]; + }; + tor-browser = { + executable = "${pkgs.tor-browser}/bin/tor-browser"; + profile = "${pkgs.firejail}/etc/firejail/tor-browser-en-us.profile"; + extraArgs = [ + "--netns=wg0ns" + "--dns=1.1.1.1" + ]; + }; + freetube = { + executable = "${pkgs.freetube}/bin/freetube"; + profile = "${pkgs.firejail}/etc/firejail/freetube.profile"; + extraArgs = [ + "--netns=wg0ns" + "--dns=1.1.1.1" + ]; + }; + }; + }; +} diff --git a/flake.lock b/flake.lock index 66ebce0..819f475 100644 --- a/flake.lock +++ b/flake.lock @@ -24,15 +24,14 @@ "dms": { "inputs": { "flake-compat": "flake-compat", - "nixpkgs": "nixpkgs", - "quickshell": "quickshell" + "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1777675128, - "narHash": "sha256-2zuDs9Lju99dg8MsSPf1frKPPgCRakDn+CEGX71cHJ0=", + "lastModified": 1781822553, + "narHash": "sha256-lINO2V/B5MxB6RXGahrPlaL07WvFiJur1WDF6rTYuyk=", "owner": "AvengeMedia", "repo": "DankMaterialShell", - "rev": "c1cbd0994f5a3585dded85069f2c9103c54f5285", + "rev": "4203148cabb74abfb17e5c02f3b76c28b52601e7", "type": "github" }, "original": { @@ -41,6 +40,71 @@ "type": "github" } }, + "fenix": { + "inputs": { + "nixpkgs": [ + "ferrosonic", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1782550137, + "narHash": "sha256-XvgBjuAmdT7fvltu56lvZULtDHCr0BLVdEAdfQQlYqw=", + "owner": "nix-community", + "repo": "fenix", + "rev": "4febf815bd2c2f1181e027c5b07fb66d85317cc6", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "fenix_2": { + "inputs": { + "nixpkgs": [ + "ferrosonic", + "naersk", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src_2" + }, + "locked": { + "lastModified": 1752475459, + "narHash": "sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU=", + "owner": "nix-community", + "repo": "fenix", + "rev": "bf0d6f70f4c9a9cf8845f992105652173f4b617f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "ferrosonic": { + "inputs": { + "fenix": "fenix", + "naersk": "naersk", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1782822043, + "narHash": "sha256-UsStOiULt4i7hO2dasJI4MqwK0i8JzsJKgzqQHEW6mA=", + "owner": "jaidaken", + "repo": "ferrosonic", + "rev": "ac0a146b515979238b9577a4b13f30479b02b07c", + "type": "github" + }, + "original": { + "owner": "jaidaken", + "repo": "ferrosonic", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -103,11 +167,11 @@ ] }, "locked": { - "lastModified": 1777679572, - "narHash": "sha256-egYNbRrkn+6SwTHinhdb6WUfzzdC3nXfCRqS321VylY=", + "lastModified": 1781906751, + "narHash": "sha256-6Ld1PqmptFtFKblE+SynhRgyBApUWcmrISetWqWHeeo=", "owner": "nix-community", "repo": "home-manager", - "rev": "9cb587ade2aa1b4a7257f0238d41072690b0ca4f", + "rev": "37f21dfa5d27e71b75bacd9418b156f9265e312e", "type": "github" }, "original": { @@ -140,7 +204,7 @@ "impermanence": { "inputs": { "home-manager": "home-manager_2", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_3" }, "locked": { "lastModified": 1769548169, @@ -156,6 +220,49 @@ "type": "github" } }, + "microvm": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "spectrum": "spectrum" + }, + "locked": { + "lastModified": 1784320259, + "narHash": "sha256-9yPGZVcPV0W9Iq1qL4rfkNigKIIUk4jRk3YbJNr3pLA=", + "owner": "microvm-nix", + "repo": "microvm.nix", + "rev": "24ae5c9f140e4c4b06d132ea3fc7795b550dfe9e", + "type": "github" + }, + "original": { + "owner": "microvm-nix", + "repo": "microvm.nix", + "type": "github" + } + }, + "naersk": { + "inputs": { + "fenix": "fenix_2", + "nixpkgs": [ + "ferrosonic", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1782220280, + "narHash": "sha256-thLTFbp9D5Qknmh8q/v4FRpLGphUSijT3E86cbLYTXo=", + "owner": "nix-community", + "repo": "naersk", + "rev": "9aa07bb0256d300219b30622d2454e85f7f3667e", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, "niri-branch": { "inputs": { "nixpkgs": [ @@ -164,11 +271,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1771283045, - "narHash": "sha256-AgD3KAkrQ4cs34kKZE8v/+FyFTc1Vq2sOJaPrWiCRio=", + "lastModified": 1781817881, + "narHash": "sha256-pRPtSWYy9XsVnvjHhXhaGX3uwqFdfAn2fXvxQ0xKXnk=", "owner": "argosnothing", "repo": "niri", - "rev": "eab116015a5a4d8f027c915dbd7b0a90e1e9a5e1", + "rev": "cd36d0374d6e66bc07248899d02478f42719ecc2", "type": "github" }, "original": { @@ -203,11 +310,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776169885, - "narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=", + "lastModified": 1778443072, + "narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9", + "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", "type": "github" }, "original": { @@ -218,6 +325,22 @@ } }, "nixpkgs_2": { + "locked": { + "lastModified": 1782467914, + "narHash": "sha256-pGvFkM8N0xEkIIXDe5YYfbEAvHrk4IxBrjB/x8OomhE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e73de5be04e0eff4190a1432b946d469c794e7b4", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { "locked": { "lastModified": 1768564909, "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=", @@ -233,7 +356,7 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_4": { "locked": { "lastModified": 1744536153, "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", @@ -249,13 +372,13 @@ "type": "github" } }, - "nixpkgs_4": { + "nixpkgs_5": { "locked": { - "lastModified": 1777731324, - "narHash": "sha256-piLMdJYPP/9+/yiHxVMpqbAAoP8EnsqRO5921ilx0lk=", + "lastModified": 1781960011, + "narHash": "sha256-66fKJZTRbxFJ48dE3kDdCaHTbux6nmApvIQhtwcaI0E=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "38e436af6ec1a3b1c9b666ceea098bf5ef05fc66", + "rev": "c1b9ab3ad7c39b61915438b2222e3655b7d8a16f", "type": "github" }, "original": { @@ -264,13 +387,13 @@ "type": "github" } }, - "nixpkgs_5": { + "nixpkgs_6": { "locked": { - "lastModified": 1777578337, - "narHash": "sha256-Ad49moKWeXtKBJNy2ebiTQUEgdLyvGmTeykAQ9xM+Z4=", + "lastModified": 1781577229, + "narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "15f4ee454b1dce334612fa6843b3e05cf546efab", + "rev": "567a49d1913ce81ac6e9582e3553dd90a955875f", "type": "github" }, "original": { @@ -283,14 +406,14 @@ "nur": { "inputs": { "flake-parts": "flake-parts", - "nixpkgs": "nixpkgs_5" + "nixpkgs": "nixpkgs_6" }, "locked": { - "lastModified": 1777729540, - "narHash": "sha256-tF5WMS4SSSmDvEZ7qgOosh8q0BVdz/ynb4Wnruc1rgY=", + "lastModified": 1781959811, + "narHash": "sha256-ZKWWQbvWnZwHU6YTWB7uoIgeXw70z82mJglH7DmCTDg=", "owner": "nix-community", "repo": "NUR", - "rev": "1091dd1d0f6589dc9a88d808052dda9b85835670", + "rev": "8ccedaa4ba8b650594426145a6f67201b685359c", "type": "github" }, "original": { @@ -299,41 +422,55 @@ "type": "github" } }, - "quickshell": { - "inputs": { - "nixpkgs": [ - "dms", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1776854048, - "narHash": "sha256-lLbV66V3RMNp1l8/UelmR4YzoJ5ONtgvEtiUMJATH/o=", - "ref": "refs/heads/master", - "rev": "783c953987dc56ff0601abe6845ed96f1d00495a", - "revCount": 806, - "type": "git", - "url": "https://git.outfoxxed.me/quickshell/quickshell" - }, - "original": { - "rev": "783c953987dc56ff0601abe6845ed96f1d00495a", - "type": "git", - "url": "https://git.outfoxxed.me/quickshell/quickshell" - } - }, "root": { "inputs": { "disko": "disko", "dms": "dms", + "ferrosonic": "ferrosonic", "home-manager": "home-manager", "impermanence": "impermanence", + "microvm": "microvm", "niri-branch": "niri-branch", "niri-scratchpad": "niri-scratchpad", - "nixpkgs": "nixpkgs_4", + "nixpkgs": "nixpkgs_5", "nur": "nur", "sops-nix": "sops-nix" } }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1782469328, + "narHash": "sha256-EVrPvYr7VtAtiUWdKglwTSVT+KgQNcZ8EEWL/CnBC0o=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "a77548188dbecc332bdc292b01a5077ec770cd98", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "rust-analyzer-src_2": { + "flake": false, + "locked": { + "lastModified": 1752428706, + "narHash": "sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu+QEnJn2Sfg=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "591e3b7624be97e4443ea7b5542c191311aa141d", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, "rust-overlay": { "inputs": { "nixpkgs": [ @@ -342,11 +479,11 @@ ] }, "locked": { - "lastModified": 1767322002, - "narHash": "sha256-yHKXXw2OWfIFsyTjduB4EyFwR0SYYF0hK8xI9z4NIn0=", + "lastModified": 1781752752, + "narHash": "sha256-kVG5tV9hddPviGAgqf9sGSuStvv+HAB9onfKqGptV0k=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "03c6e38661c02a27ca006a284813afdc461e9f7e", + "rev": "c06d86dabe5b92982b9d67acccb9990d58da3a0e", "type": "github" }, "original": { @@ -357,7 +494,7 @@ }, "rust-overlay_2": { "inputs": { - "nixpkgs": "nixpkgs_3" + "nixpkgs": "nixpkgs_4" }, "locked": { "lastModified": 1772075164, @@ -380,11 +517,11 @@ ] }, "locked": { - "lastModified": 1777338324, - "narHash": "sha256-bc+ZZCmOTNq86/svGnw0tVpH7vJaLYvGLLKFYP08Q8E=", + "lastModified": 1781943681, + "narHash": "sha256-NFHmA7H47adqiyp+0iEOyZOQhmigDqA/NBAlf4imB6U=", "owner": "Mic92", "repo": "sops-nix", - "rev": "8eaee5c45428b28b8c47a83e4c09dccec5f279b5", + "rev": "420f8d2e9882911f65cfac15cc706f639ba96cca", "type": "github" }, "original": { @@ -393,6 +530,22 @@ "type": "github" } }, + "spectrum": { + "flake": false, + "locked": { + "lastModified": 1783694892, + "narHash": "sha256-xO8f7Qng+18FK2UlB9vcrkxCaQMCt5WjCH24aW/11eg=", + "ref": "refs/heads/main", + "rev": "24c4346e30fdea8d8e80f34aec3554a15a667d24", + "revCount": 1410, + "type": "git", + "url": "https://spectrum-os.org/git/spectrum" + }, + "original": { + "type": "git", + "url": "https://spectrum-os.org/git/spectrum" + } + }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/flake.nix b/flake.nix index a693e7d..a842bf2 100644 --- a/flake.nix +++ b/flake.nix @@ -7,10 +7,10 @@ url = "github:Mic92/sops-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; - # microvm = { - # url = "github:microvm-nix/microvm.nix/da28962a2ba84718895b7325f600686c3b4ee099"; - # inputs.nixpkgs.follows = "nixpkgs"; - # }; + microvm = { + url = "github:microvm-nix/microvm.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; disko = { url = "github:nix-community/disko/latest"; inputs.nixpkgs.follows = "nixpkgs"; @@ -31,6 +31,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; dms.url = "github:AvengeMedia/DankMaterialShell"; + ferrosonic.url = "github:jaidaken/ferrosonic"; }; outputs = @@ -39,6 +40,7 @@ sops-nix, impermanence, home-manager, + microvm, ... }: let @@ -49,7 +51,7 @@ config.allowUnfree = true; # overlays = [ inputs.neovim-nightly-overlay.overlays.default ]; }; - # microvm = inputs.microvm.nixosModules.host; + microvm = inputs.microvm.nixosModules.host; in { nixosConfigurations."${hostname}" = nixpkgs.lib.nixosSystem { @@ -59,7 +61,7 @@ nixpkgs impermanence home-manager - # microvm + microvm sops-nix ; hostname = hostname; @@ -73,9 +75,10 @@ ./packages.nix ./kernel ./home + ./firejail.nix inputs.sops-nix.nixosModules.sops - # inputs.microvm.nixosModules.host - # (import ./vms) + inputs.microvm.nixosModules.host + (import ./microvm/sandbox.nix) inputs.disko.nixosModules.disko inputs.impermanence.nixosModules.impermanence inputs.home-manager.nixosModules.home-manager @@ -105,6 +108,7 @@ }); } ); + ferrosonic = inputs.ferrosonic.packages.${prev.stdenv.hostPlatform.system}.default; }) inputs.nur.overlays.default (_: prev: { diff --git a/home/agents.nix b/home/agents.nix index 630454b..27fb52f 100644 --- a/home/agents.nix +++ b/home/agents.nix @@ -227,7 +227,7 @@ let enable = true; setSessionVariables = false; extraConfig = { - XDG_CACHE_HOME = "$HOME/.cache"; + XDG_CACHE_HOME = "${HOME}/.cache"; }; }; }; @@ -236,8 +236,8 @@ in { home-manager.users = lib.mkMerge [ (home-manager-config { - uid = 1002; - username = "agent"; + uid = 1001; + username = "sandbox"; }) ]; } diff --git a/home/nvim/default.nix b/home/nvim/default.nix index 4e9cc2e..ce34276 100644 --- a/home/nvim/default.nix +++ b/home/nvim/default.nix @@ -87,7 +87,7 @@ in less = { "prettierd" }, blade = { "blade-formatter" }, go = { "gofmt" }, - wgsl = { "wgsl_fmt" }, + wgsl = { "wgslfmt" }, odin = { "odinfmt" }, }, }) @@ -107,14 +107,6 @@ in ''; } leap-nvim - { - plugin = lsp_lines-nvim; - type = "lua"; - config = '' - require("lsp_lines").setup() - vim.keymap.set("n", "it", require("lsp_lines").toggle, { desc = "Toggle LSP lines" }) - ''; - } { plugin = mini-icons; type = "lua"; @@ -151,13 +143,13 @@ in # plugin = neotest-zig; # type = "lua"; # } - # { - # plugin = nvim-autopairs; - # type = "lua"; - # config = '' - # require('nvim-autopairs').setup() - # ''; - # } + { + plugin = nvim-autopairs; + type = "lua"; + config = '' + require('nvim-autopairs').setup() + ''; + } { plugin = nvim-dap; type = "lua"; @@ -380,23 +372,12 @@ in require("ts-autotag").setup() ''; } - # { - # plugin = toggleterm-nvim; - # type = "lua"; - # config = '' - # require("toggleterm").setup() - # vim.keymap.set("n", "nt", "ToggleTerm size=120 direction=tab name=ttermh", { desc = "Toggle tterm tab" }) - # vim.keymap.set("n", "ns", "ToggleTerm direction=vertical name=ttermv", { desc = "Toggle tterm vertical" }) - # vim.keymap.set("n", "", "ToggleTerm direction=float name=ttermf", { desc = "Toggle tterm float" }) - # ''; - # } { plugin = trouble-nvim; type = "lua"; config = '' require("trouble").setup({}) vim.keymap.set("n", "id", "Trouble diagnostics toggle", { desc = "Trouble project" }) - -- vim.keymap.set("n", "io", "Trouble diagnostics toggle filter.buf=0", { desc = "Trouble buffer" }) ''; } { diff --git a/home/nvim/plugins.lua b/home/nvim/plugins.lua index a0ea9f2..d68a383 100644 --- a/home/nvim/plugins.lua +++ b/home/nvim/plugins.lua @@ -152,15 +152,3 @@ vim.api.nvim_create_autocmd("CmdlineLeave", { vim.keymap.set({ "n", "x", "o" }, "s", "(leap)", { desc = "leap" }) vim.keymap.set({ "n", "x", "o" }, "S", "(leap-from-window)", { desc = "leap across window" }) - -require("neotest").setup({ - adapters = { - require("neotest-pest"), - }, -}) -vim.keymap.set("n", "pn", function() - require("neotest").run.run() -end, { desc = "test nearest" }) -vim.keymap.set("n", "pe", function() - require("neotest").run.run(vim.fn.expand("%")) -end, { desc = "test file" }) diff --git a/home/nvim/settings.lua b/home/nvim/settings.lua index 7cdbfb3..e4caa0d 100644 --- a/home/nvim/settings.lua +++ b/home/nvim/settings.lua @@ -114,7 +114,7 @@ vim.diagnostic.config({ virtual_lines = true, signs = true, underline = true, - update_in_insert = false, + update_in_insert = true, severity_sort = true, }) diff --git a/home/root.nix b/home/root.nix index f601343..77593f3 100644 --- a/home/root.nix +++ b/home/root.nix @@ -19,7 +19,7 @@ ssh = { enable = true; enableDefaultConfig = false; - matchBlocks = { + settings = { "icefox.sh" = { user = "git"; identityFile = "/root/.ssh/desktop"; diff --git a/home/user.nix b/home/user.nix index bccadfe..afe9204 100644 --- a/home/user.nix +++ b/home/user.nix @@ -27,9 +27,12 @@ sops.defaultSopsFile = ../secrets/home.yaml; sops.age.keyFile = "/.persist/${HOME}/.config/sops/age/keys.txt"; - sops.secrets."user/ssh/desktop" = { - path = "${HOME}/.ssh/desktop"; - mode = "0600"; + sops.secrets = { + "user/ssh/desktop" = { + path = "${HOME}/.ssh/desktop"; + mode = "0600"; + }; + "navidrome/user" = { }; }; home.file."/.ssh/desktop.pub".text = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILABd/iSJ4gn/ystDqNxLJTG0n0z5VIC9YXlmdUfOhHf desktop@icefox.sh"; @@ -59,13 +62,16 @@ systemd.user.services.waypipe-socket = { Unit = { Description = "start waypipe client"; + After = [ "niri.service" ]; + Requires = [ "niri.service" ]; }; Install = { WantedBy = [ "graphical-session.target" ]; }; Service = { ExecStart = "${pkgs.waypipe}/bin/waypipe --socket /tmp/waypipe.sock client"; - ExecStartPost = "${pkgs.acl}/bin/setfacl -m u:agent:rw /tmp/waypipe.sock"; + ExecStartPost = "${pkgs.acl}/bin/setfacl -m g:sandbox:rw /tmp/waypipe.sock"; + ExecStopPost = "${pkgs.coreutils}/bin/rm /tmp/waypipe.sock"; RuntimeDirectory = "waypipe"; Type = "simple"; Restart = "on-failure"; @@ -100,6 +106,24 @@ }; }; + xdg.configFile."ferrosonic/config.toml".text = '' + BaseURL = "https://navidrome.icefox.sh" + Username = "user" + PasswordFile = "${config.sops.secrets."navidrome/user".path}" + Theme = "Catppuccin" + Cava = true + CavaSize = 40 + Daemon = false + AutoContinue = false + RepeatMode = "Off" + CoverArt = true + CoverArtSize = 16 + Scrobble = true + Notifications = true + RateSwitchDelayMs = 500 + MusicFolderId = 1 + ''; + xdg.desktopEntries = { google-chrome = { name = "Google Chrome"; @@ -229,16 +253,17 @@ extraConfig = { SCREENSHOTS = "$HOME/pictures/screenshots"; - XDG_CACHE_HOME = "$HOME/.cache"; + XDG_CACHE_HOME = "${HOME}/.cache"; }; }; programs = { + opencode.enable = true; dank-material-shell.enable = true; ssh = { enable = true; enableDefaultConfig = false; - matchBlocks = { + settings = { "*" = { serverAliveInterval = 60; serverAliveCountMax = 3; @@ -275,15 +300,15 @@ user = { name = "felipematos"; email = "5471818+fnzr@users.noreply.github.com"; - signingkey = "E6C30AB4CD83BB00"; + signingkey = "${HOME}/.ssh/desktop.pub"; }; }; } ]; settings = { user = { - email = "felipe@icefox.sh"; - name = "icefox"; + email = "felipe@imatos.dev"; + name = "fmatos"; signingkey = "${HOME}/.ssh/desktop.pub"; }; gpg.format = "ssh"; @@ -330,7 +355,7 @@ }; safe = { directory = [ - "/home/agent/*" + "/home/sandbox/*" ]; }; }; @@ -339,9 +364,14 @@ home.packages = with pkgs; [ xrdb - (writeShellScriptBin "agent" '' - machinectl shell agent@ ${waypipe}/bin/waypipe --socket /run/waypipe.sock server fish - '') + # (writeShellScriptBin "agent" '' + # machinectl shell agent@ ${waypipe}/bin/waypipe --socket /run/waypipe.sock server fish + # '') + (writeShellApplication { + name = "opencode-sandbox"; + runtimeInputs = [ openssh ]; + text = builtins.readFile ../executables/opencode.sh; + }) (writeShellApplication { name = "tmux-sessionizer"; runtimeInputs = [ diff --git a/impermanence.nix b/impermanence.nix index b018315..7936f33 100644 --- a/impermanence.nix +++ b/impermanence.nix @@ -30,11 +30,11 @@ ]; }; - users.agent = { - files = [ - ".config/sops/age/keys.txt" - ]; - }; + # users.agent = { + # files = [ + # ".config/sops/age/keys.txt" + # ]; + # }; }; environment.persistence."/.nobackup" = { diff --git a/kernel/default.nix b/kernel/default.nix index 404a84e..6edd722 100644 --- a/kernel/default.nix +++ b/kernel/default.nix @@ -15,7 +15,6 @@ let "$@" ''; nsWrapper = pkgs.writeShellScriptBin "ns" '' - exec /run/wrappers/bin/doas ${nsExec}/bin/ns-raw "$@" ''; in @@ -24,17 +23,16 @@ in ./hardened.nix ./vfio.nix ./standard.nix - # ./apparmor.nix ]; custom.kernel.hardened.enable = true; custom.kernel.vfio.enable = false; custom.kernel.standard.enable = true; - # security.apparmor.enable = false; + security.apparmor.enable = false; specialisation.unhardened.configuration = { custom.kernel.hardened.enable = lib.mkForce false; - # security.apparmor.enable = lib.mkForce false; + security.apparmor.enable = lib.mkForce false; }; specialisation.vfio.configuration = { @@ -57,6 +55,10 @@ in runAs = "root"; keepEnv = true; persist = true; + setEnv = [ + "EDITOR" + "TERM" + ]; } { users = [ "user" ]; @@ -64,23 +66,6 @@ in noPass = true; keepEnv = false; } - { - users = [ "user" ]; - runAs = "agent"; - noPass = true; - keepEnv = true; - } - { - users = [ - "user" - "agent" - "work" - ]; - runAs = "root"; - noPass = true; - keepEnv = true; - cmd = "${nsExec}/bin/ns-raw"; - } ]; }; @@ -89,6 +74,19 @@ in security.pam.services.su.requireWheel = true; security.pam.services.newgrp.requireWheel = true; + security.polkit = { + enable = true; + extraConfig = '' + polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.machine1.host-shell" || + action.id == "org.freedesktop.machine1.login") && + subject.user == "user") { + return polkit.Result.YES; + } + }); + ''; + }; + boot = { loader = { systemd-boot.enable = true; diff --git a/microvm/sandbox.nix b/microvm/sandbox.nix new file mode 100644 index 0000000..cd530ed --- /dev/null +++ b/microvm/sandbox.nix @@ -0,0 +1,234 @@ +{ + impermanence, + sops-nix, + home-manager, + ... +}: +let + impermanenceModule = impermanence.nixosModules.impermanence; + sopsModule = sops-nix.nixosModules.sops; + homeManagerModule = home-manager.nixosModules.home-manager; +in +{ + microvm.host.useNotifySockets = true; + microvm.vms."sandbox" = { + config = + { + config, + pkgs, + lib, + ... + }: + { + imports = [ + impermanenceModule + sopsModule + homeManagerModule + ]; + + sops.defaultSopsFile = ./secrets/sandbox.yaml; + sops.age.keyFile = "/.persist/secrets/age-keys.txt"; + sops.secrets."root-password-hash" = { + neededForUsers = true; + }; + sops.secrets."sandbox-password-hash" = { + neededForUsers = true; + }; + sops.secrets."deepseek-api-key" = { + owner = "sandbox"; + mode = "0400"; + }; + + fileSystems."/.persist".neededForBoot = true; + + users.users.root = { + hashedPasswordFile = config.sops.secrets."root-password-hash".path; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILABd/iSJ4gn/ystDqNxLJTG0n0z5VIC9YXlmdUfOhHf desktop@icefox.sh" + ]; + }; + users.users.host = { + isNormalUser = true; + uid = 1000; + group = "users"; + }; + users.users.sandbox = { + isNormalUser = true; + uid = 1001; + group = "users"; + extraGroups = [ + "video" + "render" + ]; + hashedPasswordFile = config.sops.secrets."sandbox-password-hash".path; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILABd/iSJ4gn/ystDqNxLJTG0n0z5VIC9YXlmdUfOhHf desktop@icefox.sh" + ]; + }; + services.openssh.enable = true; + + boot.kernelModules = [ + "drm" + "virtio_gpu" + ]; + boot.blacklistedKernelModules = lib.mkForce [ ]; + + environment.sessionVariables = { + WAYLAND_DISPLAY = "wayland-1"; + DISPLAY = ":0"; + QT_QPA_PLATFORM = "wayland"; + GDK_BACKEND = "wayland"; + XDG_SESSION_TYPE = "wayland"; + }; + + hardware.graphics.enable = true; + system.stateVersion = lib.trivial.release; + + environment.systemPackages = with pkgs; [ + xdg-utils + tmux + waypipe + opencode + fd + ripgrep + podman-compose + ]; + + environment.persistence."/.persist" = { + enable = true; + hideMounts = true; + users.sandbox = { + directories = [ + ".local/share/opencode" + ".claude" + ]; + files = [ + ".claude.json" + ]; + }; + + }; + + home-manager = { + useGlobalPkgs = true; + users.sandbox = { + home.stateVersion = "26.11"; + xdg.configFile."containers/containers.conf".text = '' + [engine] + compose_warning_logs=false + events_logger="file" + + [containers] + log_driver="k8s-file" + ''; + xdg.configFile."opencode/opencode.json".text = builtins.toJSON { + "$schema" = "https://opencode.ai/config.json"; + provider = { + deepseek = { + npm = "@ai-sdk/openai-compatible"; + name = "DeepSeek"; + options = { + baseURL = "https://api.deepseek.com"; + apiKey = "{file:${config.sops.secrets."deepseek-api-key".path}}"; + }; + models = { + "deepseek-v4-pro" = { + name = "DeepSeek-V4-Pro"; + limit = { + context = 1048576; + output = 262144; + }; + options = { + reasoningEffort = "high"; + thinking.type = "enabled"; + }; + }; + "deepseek-v4-flash" = { + name = "DeepSeek-V4-Flash"; + limit = { + context = 1048576; + output = 262144; + }; + options = { + reasoningEffort = "high"; + thinking.type = "enabled"; + }; + }; + }; + }; + }; + }; + }; + }; + + virtualisation.containers.enable = true; + virtualisation.podman = { + enable = true; + dockerCompat = true; + defaultNetwork.settings.dns_enabled = true; + }; + microvm = { + vsock = { + cid = 3; + ssh.enable = true; + }; + writableStoreOverlay = "/nix/.rw-store"; + # graphics.enable = true; + mem = 4096; + interfaces = [ + { + type = "user"; + id = "vm-1"; + mac = "02:00:00:01:01:01"; + } + ]; + volumes = [ + { + mountPoint = "/var"; + image = "var.img"; + size = 256; + } + { + mountPoint = "/nix/.rw-store"; + image = "nix-store-overlay.img"; + size = 512; + } + { + mountPoint = "/.persist"; + image = "persist.img"; + size = 1; + } + ]; + shares = [ + { + proto = "virtiofs"; + tag = "projects"; + source = "/home/user/dev"; + mountPoint = "/home/sandbox/dev"; + } + { + proto = "virtiofs"; + tag = "work"; + source = "/home/user/work"; + mountPoint = "/home/sandbox/work"; + } + { + proto = "virtiofs"; + tag = "screenshots"; + source = "/home/user/pictures"; + mountPoint = "/home/sandbox/pictures"; + readOnly = true; + } + { + proto = "virtiofs"; + tag = "ro-store"; + source = "/nix/store"; + mountPoint = "/nix/.ro-store"; + readOnly = true; + } + ]; + hypervisor = "qemu"; + }; + }; + }; +} diff --git a/microvm/secrets/sandbox.yaml b/microvm/secrets/sandbox.yaml new file mode 100644 index 0000000..0b93259 --- /dev/null +++ b/microvm/secrets/sandbox.yaml @@ -0,0 +1,18 @@ +root-password-hash: ENC[AES256_GCM,data:YTQn1UGwgrlCD381u+Z7xXMgUoigKXp+Na1/Aj0GExikEeIs9SkBIcAyQ2Dw0FimL0wgG4z+AxFhdAnzRHcbecvvb3tRrTX3VjFt+CLPWscZar1jjNesPyV1inXI2IFW06twHbAl4oQHFQ==,iv:Va0B4aFUlavJd3AGzDbAnmAnAX3fEow4ByIQzfPbheM=,tag:OIjXY7ngZow7Y3tRFZ8iiA==,type:str] +sandbox-password-hash: ENC[AES256_GCM,data:bxVKDAsk6ao0D+xIgAR2Sh1msoPJGEE8P0+O9YGZS/F+DZl33r/JQbNFYJzZgbHDqzLDbab0tnzyRwbfepT2eV/KWaroQDFcBCLJ07t4lTOtdJ3mR/dB1UftrCvvmoPubVrguDPvftQLAg==,iv:tDErYBYhfPrOKyH7W41SUAFgT54bA7QvUYmo35GWMIs=,tag:vNJVKrlCwCuZl4z8KFby2Q==,type:str] +deepseek-api-key: ENC[AES256_GCM,data:pzg/7L92Ig+m3nhZBiYFSfdLuJFmamWWmPQwMxmctRS2kVQ=,iv:yCu+kPXkOxhfpVLlOvUpB/BwygedRaJviPe2D0tCW78=,tag:YTM7hx4UdZEsB9qfXs5LXQ==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzQXlvQWl3aWtSWjU5QWVN + K3JGSzg0RElZSkVxNWFmTDNMNzVHckFuZkM4CnJiVDFDTTZ3cDBrTG5RR2RTR3A0 + RmdDSVA1T1dDbUZTNWJQNzB1K0dhYnMKLS0tIDk3VVJBSUFLVjMrdzV5MTFzRXlF + N0ZZa3JnSXN2bHdLa1hoVFlmSUhXWWMK5JCznoaqS6svxcBl6UF9XqbfirZ9L2np + /CTlYyWRadRvRmxrNkYNdgGn4V+is9FG+Et0u8JHAMvI7x/+W99yGA== + -----END AGE ENCRYPTED FILE----- + recipient: age1mr42xh9llvrggrvuawr5253qk7gygf4gcg9clqdvjecw3d6tj59qsqxswd + lastmodified: "2026-07-18T18:23:56Z" + mac: ENC[AES256_GCM,data:d/vRaH5vFzTkb8WopwIDb9Ku7n0EXUR5PyE/fmYhDWcCGX71PcM23Y1DPX1SCY6ydnpxqtGxfRiJOZMfIAx+tCBKX5Szj2aPnKXpogtukZbWWT4pF5jYWnlXcA9R7b9CY0sSAXIetyKhAaH086PJ2i2tAx8TiqZNkUFyoR6g0+E=,iv:R+cOmhYJnVPMjaiL5J0zbFUR69sNR78Gz5ea3CV6X6Y=,tag:RlaTyampHdMtmzvHskM4Dw==,type:str] + unencrypted_suffix: _unencrypted + version: 3.13.1 diff --git a/networking.nix b/networking.nix index 65c34eb..72cb6ee 100644 --- a/networking.nix +++ b/networking.nix @@ -15,9 +15,8 @@ networking = { hostName = hostname; - nameservers = [ "192.168.88.3" ]; networkmanager.enable = false; - firewall.trustedInterfaces = [ "vlan66" ]; + nameservers = [ "192.168.88.3" ]; useDHCP = false; useNetworkd = true; @@ -72,20 +71,28 @@ linkConfig.RequiredForOnline = "routable"; }; - # netdevs."30-vlan66" = { - # netdevConfig = { - # Kind = "vlan"; - # Name = "vlan66"; - # }; - # vlanConfig = { - # Id = 66; - # }; - # }; + networks."30-vm-1" = { + matchConfig.Name = "vm-1"; + address = [ "10.0.0.1/24" ]; + networkConfig.IPMasquerade = "both"; + }; - # networks."30-vlan66" = { - # matchConfig.Name = "vlan66"; - # networkConfig.DHCP = "yes"; - # }; + }; + + networking.nat = { + enable = true; + internalInterfaces = [ "vm-*" ]; + externalInterface = "br0"; + }; + + services.resolved = { + enable = true; + settings = { + Resolve = { + Domains = [ "~." ]; + FallbackDNS = null; + }; + }; }; services.wireguard-netns = { @@ -113,46 +120,4 @@ }; }; }; - - # systemd.services."netns@wg0ns" = { - # description = "wg0 network namespace"; - # before = [ "network.target" ]; - # serviceConfig = { - # Type = "oneshot"; - # RemainAfterExit = true; - # ExecStart = pkgs.writers.writeBash "wg0ns-up" '' - # ${pkgs.coreutils}/bin/mkdir -p /etc/netns/wg0ns - # echo "nameserver $(cat ${config.sops.secrets."wg0/dns".path})" > /etc/netns/wg0ns/resolv.conf - # ${pkgs.iproute2}/bin/ip netns add wg0ns - # ''; - # ExecStop = "${pkgs.iproute2}/bin/ip netns del wg0ns"; - # }; - # }; - # - # systemd.services.wg0 = { - # description = "wg0 network interface"; - # bindsTo = [ "netns@wg0ns.service" ]; - # requires = [ "network-online.target" ]; - # after = [ "netns@wg0ns.service" ]; - # wants = [ "network-online.target" ]; - # wantedBy = [ "multi-user.target" ]; - # serviceConfig = { - # Type = "oneshot"; - # RemainAfterExit = true; - # ExecStart = pkgs.writers.writeBash "wg-up" '' - # ${pkgs.iproute2}/bin/ip link add wg0 type wireguard - # ${pkgs.iproute2}/bin/ip link set wg0 netns wg0ns - # ${pkgs.iproute2}/bin/ip -n wg0ns address add $(< ${config.sops.secrets."wg0/address".path}) dev wg0 - # ${pkgs.iproute2}/bin/ip netns exec wg0ns \ - # ${pkgs.wireguard-tools}/bin/wg setconf wg0 ${config.sops.secrets."wg0/conf".path} - # ${pkgs.iproute2}/bin/ip -n wg0ns link set lo up - # ${pkgs.iproute2}/bin/ip -n wg0ns link set wg0 up - # ${pkgs.iproute2}/bin/ip -n wg0ns route add default dev wg0 - # ''; - # ExecStop = pkgs.writers.writeBash "wg-down" '' - # ${pkgs.iproute2}/bin/ip -n wg0ns route del default dev wg0 - # ${pkgs.iproute2}/bin/ip -n wg0ns link del wg0 - # ''; - # }; - # }; } diff --git a/packages.nix b/packages.nix index 3c863b3..09b439f 100644 --- a/packages.nix +++ b/packages.nix @@ -14,12 +14,12 @@ bluetuith bluez bluez-tools - cargo + cava clang clang-tools clevis cliphist - chromium + ungoogled-chromium distrobox dos2unix dnsutils @@ -27,6 +27,7 @@ (import ./templates/extract.sh.nix { inherit pkgs; }) eza fd + ferrosonic ffmpeg file fira-code-symbols @@ -38,7 +39,6 @@ git gh ghostty - go google-chrome gopass gopass-jsonapi @@ -47,6 +47,7 @@ htmx-lsp2 imagemagick inkscape + # input-remapper pavucontrol pciutils poppler @@ -54,16 +55,7 @@ jq kitty lazygit - (pkgs.writeShellScriptBin "lf" '' - cd_file="/tmp/lf-lastdir" - - ${pkgs.lf}/bin/lf "$@" - - if [ -f "$cd_file" ]; then - cd "$(cat "$cd_file")" - rm "$cd_file" - fi - '') + lf libreoffice libvirt linux-firmware @@ -113,11 +105,13 @@ thunderbird tor-browser unzip + usbutils virt-manager virt-viewer vscode-langservers-extracted watchexec wayland + waypipe wgsl-analyzer # wineWowPackages.waylandFull wineWow64Packages.waylandFull @@ -185,51 +179,6 @@ localNetworkGameTransfers.openFirewall = true; }; - programs.firejail = { - enable = true; - wrappedBinaries = { - chromium-sandbox = { - executable = "${pkgs.chromium}/bin/chromium"; - profile = "${pkgs.firejail}/etc/firejail/chromium-browser.profile"; - extraArgs = [ - "--netns=wg0ns" - "--whitelist=/home/user/downloads" - "--env=TGK_THEME=Adwaita:dark" - "--dns=10.2.0.1" - "--private=/data/sandbox/chromium" - ]; - }; - google-chrome-stable = { - # executable = "${chrome-argumented}/bin/google-chrome-stable"; - executable = "${pkgs.google-chrome}/bin/google-chrome-stable"; - profile = "${pkgs.firejail}/etc/firejail/google-chrome-stable.profile"; - extraArgs = [ - "--env=GTK_THEME=Adwaita:dark" - "--netns=wg-br0ns" - "--dns=10.2.0.1" - "--whitelist=/home/user/downloads" - "--whitelist=/home/user/pictures" - ]; - }; - tor-browser = { - executable = "${pkgs.tor-browser}/bin/tor-browser"; - profile = "${pkgs.firejail}/etc/firejail/tor-browser-en-us.profile"; - extraArgs = [ - "--netns=wg0ns" - "--dns=1.1.1.1" - ]; - }; - freetube = { - executable = "${pkgs.freetube}/bin/freetube"; - profile = "${pkgs.firejail}/etc/firejail/freetube.profile"; - extraArgs = [ - "--netns=wg0ns" - "--dns=1.1.1.1" - ]; - }; - }; - }; - services.ollama = { enable = true; package = pkgs.ollama-cuda; @@ -239,6 +188,7 @@ "glm-5.1" ]; }; + # services.input-remapper.enable = true; # services.open-webui = { # enable = true; # port = 11347; diff --git a/secrets/home.yaml b/secrets/home.yaml index cfa4063..a7269df 100644 --- a/secrets/home.yaml +++ b/secrets/home.yaml @@ -1,4 +1,6 @@ deepseek_api_key: ENC[AES256_GCM,data:HJXkEErncaOb2up0B45qisM+rIHoNmX9n6RZrdGN2JZlur8=,iv:4Dx5AjtDhWfYKlRiUrADT7HdHy/F2sfkzuPKuLhBRt4=,tag:AC3Zm+NHnnIQDCnlmBDUcQ==,type:str] +navidrome: + user: ENC[AES256_GCM,data:TkPLvX3AxC7Lbnh6s2j1q3XrQE1u16ykah9rSjlYPnuH,iv:dtjM4O4A4ABVyHQHPAUH5dVopgwLlKSUcbqFpCWV2eA=,tag:W2PVbIaLGbX4SR2ZNOGmsQ==,type:str] root: password: ENC[AES256_GCM,data:qA7sbNvWvfmWiLX4pIYOzDmuCwc3+7I1KvTHHYF5jDHR0CNRuya0XglP8TNK5qGLEJkdmD9WphdWvYY60NTOf5NFhDnqtm3ZIw==,iv:B42jl40hQbRqMRfV39fNne0E3KKCwriAQ5MQ0DF4QQA=,tag:hH/u0Qw6A2is4XOOQpnr2Q==,type:str] ssh: @@ -13,8 +15,7 @@ user: legacy_rsa: ENC[AES256_GCM,data:PWBAlpIUbxVGDZlxJGvYRiUgeGnR7Bl1hXjzVC6MIt4hLPmc5SksBj5TmW+i6j312RLaFzYTb6SerFlFs6vn3Rtmk5Q7p/IO4JSKondJrsw1xw52YePP29FfIe7gtOxDK/F9YJhHb1VJQghQoWTN++iZjjg80iNVofBtAcrPPkyfH43Yw8In6jMla/p2w6vAhXAJS+8SpEdmdILOQT4OnDvkzi4Nwae2uFEWcGJI/kxstgAL97MTmu06qK9/zt8vuq6bTYeBOOFnasoIL+V/0A5dAz3aw8QwdSoRrN5+PEHmeB9y4+WCf10MTv8P2JxJ9ul4pCwZd2IEdg2gWSQa/vbSJdG7ZjaCih+blNEf8DTVtRQT9soTvti7MYYYziLfHw8LUT+c3nt+c0nXxVxOgRx0sGSuHsHT5HHGBOjw9PsZ+arZuwnIyrmfkm8TkvD+j7je2fqLACRBkp3CXg8a647To4GQXK0Y0yuJUpt18YSbVak9etGj5z7+PWs86NKHQ+gOrMxeirRuHGIa53FQSORYlpBZnM9FCAs4g5Fs4IZard/0LkX1lrhVUOk7OokRstiIWKJMOqRFuN+toZbCWMNVX4Hd8otSFG3U/vo3vK08TEF+HDIhp4rStPjJpOdyZStKyol6sKlHcVdMcgk79fmzesMZoSpITP/GTYe46xZ6vMQ2tvqvd0QcVJfBZuy4q2UUgjY+r8zgVRKw+Wo37YUUr4UFK2W5E23cLFkr+H9nV/cz08V1vUXk2eCi62czFXpjkpubQXysK246wHtdVFGpOmtDxicS4Zn3iqm/UxF5vKKguRaTdejXxfkRbsFhwn6/GrRAW6nxJLLVnSIzJEnNivt88m9cxmb1VYAHRZDNlGDK7ca8IhCb5yzPalRI0KqUMKen3MDLae+eEjcZH/lgGdhsZJg6Kxg/v0XC4dCJxNW+/ckIs5bjYMoQRfJ6OknAVFLwZcY0QZ+5EWrWNDkBEfPKub/r+KcF2sxIrv7KB0qXDsW4pCEcE+85i8xEiFs49ebeV4d42tsLzdYi9iVdZO4I1gzi929rbUFJLzVJCfkyn7Eic1u92UgBAa58lUFZ5LMEH9+P1V4Gm0GPIwXYvd4X+LIGhbqGDjGel+YeeLUHVRj4qeuoz1aLUCF8Cv0e3djBuhJ+tRSKdZM2aSz6PNrK2THfdFSiaHZmfH7McVXNum8TD8lhbkKIPDYdb2La3MVgvvgRN8Gw8L/Xkp/ivuFWXJ8Gc41AIUI2gY2mWbq1YAiwEuMeDJCXXAROKLQrd2rJYI7epbcw2EyLgtc90zsjWa1gFrHfBJVh23B69vWc5uEVyt/ryg3n4tp9QLjNpg0DRMjPTR+vEbsEHl7oLJdPO3XKof0h6QXXJ1ZtgHkSFS9MoXCnNhiC9ZKWA6PM31Yftp82RKiy5CSpf2AIVIOa3p9GUhVI0YlEN5tfejZuhybUL44JZ2FcVlhbC+zzLPIhROguHF0Az4Qg7tzKM9poin4xVV3HXs/mvXAcA8qnlQxnpuei3Lob4Iq1MUb9oNoA0QJ7nKIybSYUhKnGOq0Cv4ZVmxlk+RkXfTS4si3qEg3UxXjonP/e2oU334CsdSS32taP0KNTQzEP6j3AM4Zv/iEobz6XvRsKXHzbrG4UQefuQ+ZDw1DGlr/BKDLq9T+adGCWaCTKJChOIwoEQvij6rxtnGOJu9XC+/Tk8FVuzI/gPnRBOia0uUwaYskgoXmNjMgT+P8yNkdqx7MFJVdrFADyGhSaefVNszQlxfJlpAZtKazEltxmUdW6/pPoG42dfIcX+HxhbX1GqQa3japO7NrPQwLIw4LusBXd/ShLMyxkkYCcAidSgaSWgTWCzEIO5RbxRoxFA4qnEd6fqH61Y6W5Wg0Yk4DwWW/KcKn8xkgPdNk4hPq7IPL/x6Z1Dwb9zYf3T0G+97iezI9ZCNImd82aDOT5UcMZGYDD9yW9sEfohniaJcbu5DocNLTJDBa3UgiWluY7u+q4I/zPKN+9chA7h8p5BYAZJc03d1UBvHL8Mv2D7JIb/GlLhseRoV3A+b2qm6uwcxHA6zksAAx1XOZYlU88ls9ykeKKIB+AmlZEx4EuZG+E98DFp5dndMCYzL/1uy+LEsmTNrdmG+gl7bQlbYq5hOHaqtOAEsft/0VFzteRjg5Lagh0cqB1Rc92EGzQq84G5QYQb/DDZLC4i17SZEvwJoo3Ai476B6JpKefcQ5pPNf219mD/V/rodHMxm5vMC3hBQLpaSQ7ZtZ2jmwsxsWkrVaMU+NGBShqVLV+wsPlIWrSybnZC7hOLawZLev1CJ9Udiy82U1SyL9vB4dfHnhEra85nzdNvKwKzAwnr5mG2w1leJOzdg5kz8I0D98GFxmgrnOeQZ6S7/pjD7Qcw38exvEtCI8fpreAb7djbZN2sUNaSe8oitgukqNajBHlFA2z75P4dZ+IH+tTH25Inem/COojWnVQHsic03eus8x4jinbU6fxgZ6sT0SlyItTFgAAv3XqT3oHBiXIvF8DCcw7EtOeW1aZx6wR6D8JFLXTkMiXeug2wvQxV7MMXt7DBNUPqWLYnsrzAY+UaGkQtlnGiffDjCwbWkDfRpHMutMoYMk9o4yD26XTv3oV79Rod4+R39A8WEQhiHKPKFBS7TW+NttKMQVGgcmWvfRFeem4Iqy6JG22xxQqM9GK8gsUKa8MmEuLAQCGenXHIj+dOVWJugjG4IOdaQR3nB4LgwWIXHciYD9KmjlDsvd0MjQTc1bUBkS3aqOF6uvnWJ5LlWv5gs+OsFRMTe8wyqwCOLkj1EmmfJW3leEl/ONMkuaKCJTl7Yw1gto9itnC7rXFSIuGtLfW+4BSJQDYNRBskBkf81t1b6qejkS/3FFKEl8CzLdnuZU5WM5EOkR8ReAYTF/c4LYQjKDYbTAkXsuf9I8QbJXNwwJrP0C7zR4MgZQ5csKyxMNhxy5prTol5TIfCtwjy9UWOurI8WRwfqf6dyq31HtIDxD+elAJOlnZ+yHYH0Al/AETNhzWej35XJTMpYAK5wyBInM3enzBDqzEXQh4OLBwFMP0RaaZYnGTjbjyTUjmOj6GnMSbjwKECCEJFXfmpGsaxIG5RXIU71eu1x7k/Ifm3HgvQ9Q5Jw+EF6vYo1WNjOf9UbQhEhI1+uKEYEkwD/qFu07l9/WqLp3/nDNS66lnyag8jGKeO9WDb07WBBpxywtlN0vE8q9VmY7wXDdkaf5Gb13VwpaAyStANeP2Mp7XlmvYIFudzAY6kxkxCBA5rCY5guI0ebREHnJgR9ARzK3MD85X5abk8sY7Urx01tVdmx7eqVVrjBsEq65SUg8Z0jA7uVHTpOkVxboxJEWsdQN3JYYXz7Tg7M0t1PEYlxhUFIlIyvD8hrmUfxyZB0YgVI3faTezb+1I6xqAyxGFEVKPBzjyv36V9eMJmGB1wiZv3mNFqMUI9AMUHJAmIg==,iv:oGGm7bveDXNmxqe5Zina03vSVLJx9P3VBC/DbuI6KzY=,tag:taEL0E4Uinv1MQPxeRbPzA==,type:str] sops: age: - - recipient: age1y0tj3kt67pfnj38t9c8g2ghry3a0mhcq8rrqv5xr4jekwepxaelqzu3dkf - enc: | + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOZ3dERmxMbEJmSVhVRmVw TUZZL0FzTHprci9ENHFodnRVWmRSbk1ab2xnCnZYeXN3MWRJZENlYmtWeFM0azdR @@ -22,8 +23,8 @@ sops: bFhna3RyY1kxK0IyVWlnR2FpMXk3UjQKvJaecXqAecBljJ9cNcHX13nxSxVey3LG NdvJaMfEV4m3SqRF7YUoTzGYhucYjtX58E5SuvHnaP2qa21aDF+AZQ== -----END AGE ENCRYPTED FILE----- - - recipient: age16v8w7q4wmn22hhakq2uzaus2508rhldm7lcwh0kukshzjzyhuqesqz44ze - enc: | + recipient: age1y0tj3kt67pfnj38t9c8g2ghry3a0mhcq8rrqv5xr4jekwepxaelqzu3dkf + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSZnV1T0JKTXoyeUlZQ3o4 QkR4OHZUbzQrbjdSK2VLTDNzMU9qdlUvVVUwClI4SDhMZTRDRXY3NlJSZ3REdmpJ @@ -31,7 +32,8 @@ sops: NVlzQlQvMVc2dHZNK2pEWk80MStwY1UK38mxk1dJWi6XRKSwzcDA9qt8i1Grw2KJ ac2EbJIFwYLOaMNOKF9hu+NrMdruRpU/2B8HlYYZjpNWmLb9jwI4wQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-05-04T12:41:30Z" - mac: ENC[AES256_GCM,data:OxKBSbx/NrkprIHTdI+iM2yMh1lhTMYuS7UO09NzxXHkyEoXtacDm7XtWIrELIxDIpqQGkousKcNRDmfiejzkpD6aLpdR1S0exXe1A+heWUMB9D+I+an51Nuv/LBGKeelK71RAq2H0VQZQgZd6OuzDECsK07lxnxUAWo8rxhScs=,iv:0cREbkAyUz1a6ze74MKmfeX8RJjMIrwgRoTPeGADEiE=,tag:DJaNf7gckCDoYLzzw+SRUg==,type:str] + recipient: age16v8w7q4wmn22hhakq2uzaus2508rhldm7lcwh0kukshzjzyhuqesqz44ze + lastmodified: "2026-07-05T22:42:52Z" + mac: ENC[AES256_GCM,data:La8bfH1u7pmukx9OZTUS9gdZODiYcY07dMU08kEkEeojHGoh/VAUrBK3Sj5W33WnbqpXnuiRKgF34mqVav5T9TE0ds31PMc/VceWkd5pJ2crCjbmlR6Zm/XCHgilIO4IoGlCO5Zz+LXpsPbD3vv1AwmzYBRwgik69vyWiXwT0UA=,iv:vSzK3eE+Jpy//dzdOT+PHmdOOKmNlbiFDeDdtZ9Lz9o=,tag:Fo6IUvyyMxWy1gsa7faV7w==,type:str] unencrypted_suffix: _unencrypted - version: 3.12.2 + version: 3.13.1 diff --git a/secrets/vpn.yaml b/secrets/vpn.yaml index e2601b7..49dcd36 100644 --- a/secrets/vpn.yaml +++ b/secrets/vpn.yaml @@ -6,15 +6,14 @@ wg-br0: private_key: ENC[AES256_GCM,data:y8djTiZ00DIhdN1YNUNmjqCPeSUrS+YWKfr4iLT8h54fSlgDn1OPAoyJllM=,iv:Wz/EiISOlaiSXSIa4h8L2wJPm34rxpQkErpuqAR/uC8=,tag:RS100jf8INb12q6+hGQDiw==,type:str] conf: ENC[AES256_GCM,data:3TJ34y3NI1wO2Oyh7D72t2WmlOq/pMS43Ck3gDFV9QPSC7E3GHtc86aZqRQm8qbRqnkJco1+nlJZmsC+BQdmW+U5RkRL7vUyDmrCyeDyJvEkNIJLzcFi4lppbYXG3A+fRO2l5guQA5qNSQmnyux51yY11/OCahoFEEenL1wH+R0ugMK/LIPwK0/Y2u7c+7SvNwwtK9xQUlb4azqUsdLX0xkUFbBje2BgN50BB2TxLZJexUNjOTW9TE57tw3yI4VjYEvIyBXOK4uuuST+TcNJY5B45tjd8PmO9vPYDw==,iv:BdNVbiMjzGtX0rzkLBb/YCDxqBv15L7bJcLTPzvt/yM=,tag:NmEMTPasBSsg1GSkYi/HxA==,type:str] wg-us0: - private_key: ENC[AES256_GCM,data:PWzeqoYLICXjYL5AV+dxUxYn6RL/tUMkfTMYhm+OdC85cPBPU135Ov15nUk=,iv:1CdiayG3F4xp3eid/ilboFUOL4Y92RUqipASOPT8Jl0=,tag:cJulFquSHjmKF+lUegp37Q==,type:str] - conf: ENC[AES256_GCM,data:x4bpjE/icVqe3lXHRNWf5W9w8EVAiNyqmFUR2LslSrEz0ekmJ4YDKDuU//+VoomsZzHhY6GhKtEVJ4Me/xY0G1+dSBBgpZlO8UyZVg8aydsVmA09fHYgwKovzHXc0XM9cbbovf2yyK8kO+X8e3ODy1dSu+a1SA3YsEp2G46kbRRxTdYazeHMJ+hJdmCONEvx0p0NcPlPAW8eZWEsAbVT/eQzB3TnF2THy/t4oWrn4r/MVvcsWgc1LJ3rVJ6L8RREU/PYBJoyDMgAs3DP79E8iOQS+bwdJj0+gcrP,iv:Ngqn/EA3QpOnBxz+slSyn+oOO33hLZlGhAg+RIlN+wI=,tag:BZFjGxI0nRsdPn99rvijdA==,type:str] + private_key: ENC[AES256_GCM,data:rouU6q7Qn8wu93YL+uAWKsItXK2d21rRbTLLURk15wmC8YUytt/Hx8XNCbk=,iv:6m4+yS1LIZMfwZrWkb/4I4ABFLca24G8Nz3UTMQw8Q8=,tag:eTR7MW7Y9m34S5CRKIWsLw==,type:str] + conf: ENC[AES256_GCM,data:QTc6Y4VOT0UQKY9il4p4dCGYiGRYYF6Pi80ytwMweMWr1aHNl3RyFfSkAtCW3GErO6Y2OUdiwukSBKpmP6DDmBMZfhH/bQzWTFX+bAbcMBD6Ql3rMYl3rZ7pfbAZpa5a/tnzsfK+0ee8GfAo9IKci5gq7ic6E5zD9IsNPNxUH4HWNWfpSfnQo4Z6V/S05/Yb+GH4gQuX/K/eLh1b/8oWePpcRSKaQSSDzEd2NtvAv8S2LUwJxg/QYSC0CxrOGWXGdaDE,iv:05MM/v8OjjwSijhLAqlLeohYMbyV932QvgWsPhaCXLI=,tag:qpgaYyb4jkEhJTI46RMvhA==,type:str] wg-uk0: private_key: ENC[AES256_GCM,data:r1BEMvO+Q/6VTuC7N/iqWf1Cyp4WZsUitVF14n5QXPzYK9PoWgPGylQQpmg=,iv:wR1gsHzzu0C629bs7yGW6evjdieONyzMnc9vvtTEh2Q=,tag:lMk4DuTDFtOW0heIJ0Ef2A==,type:str] conf: ENC[AES256_GCM,data:tCQaQEJ1Pa0jw96q/LRtqhAWes04GkGOUYAlZu3W3C9EGHj123dYZiWXcUgymSsi999M3RbO2mWNllSl17zkV1iGZAkA23aQLKDUU0/m/SSCLm2ohJOdLKkGeRK41TMGFRRydfYZJ9rg0iJdacfXrX2t4RGWgtprSlS2crOcrAQ+jhMjCNxKuAg0gaJyUh1lcTmOohe0aBiqxGJE+mc4mKMwCiUytNoprAc2TrnKUTQ7/eyIv9hn0PeRvLL59KFNrcVpCb7VRkufYpYlNtO1zyl2oczYzw+IXCHt,iv:S74FbQLM5sXhvoSaUD0wuMeAd7U4rL/dneaVS3ah8Bo=,tag:0U/k1Cyy4XbvmnlApg2BYg==,type:str] sops: age: - - recipient: age1y0tj3kt67pfnj38t9c8g2ghry3a0mhcq8rrqv5xr4jekwepxaelqzu3dkf - enc: | + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtOHZSRkpBVVdUUk9OYUFH cVBra014WXJyRTJ0QWFKallLQlc0SXhNSlFBCmpwME92M2lCN2liVjZBRndlSVBk @@ -22,8 +21,8 @@ sops: OWhIdUxDcHB4Z3dTdDkyZWF6NEJCYzAKfPB9AZFQ08yqil+4AhIi6EMy8PXI4CAz lK4ON/M67T0UrlWN/m3pryOOr4Lj4oiZvdOR0BCO3kn4Pj0nq5jQOA== -----END AGE ENCRYPTED FILE----- - - recipient: age16v8w7q4wmn22hhakq2uzaus2508rhldm7lcwh0kukshzjzyhuqesqz44ze - enc: | + recipient: age1y0tj3kt67pfnj38t9c8g2ghry3a0mhcq8rrqv5xr4jekwepxaelqzu3dkf + - enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMSC9Td1NTMzk2NlJDTDNM UVUzTSt1dGkrUVRGT1UzeXcwR1REN1U0dW5JCnNJRzdKZHVyR0dzaUw2TlVzQnQ2 @@ -31,7 +30,8 @@ sops: OEZ4WUp1QlVrTkVTN1BHMG0yaXFuSk0KLw3ZuvWTurJDTpyoq5YafLm8YFT4v4Vh s+ay8ju3kA1CKjMF3gBQF08EoCdP/jU6tZerNwwcs17el5zIvRmG7Q== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-08T19:15:21Z" - mac: ENC[AES256_GCM,data:IxRNC13NPMIhVon0vCH3df+JxQlIPzv79H6JQzj8hkQNLxbFtPzk9DlHW1PWX90UAQbaXnzUM9uyHZhg4TypVsuKiEhpGyE4LXn/O7wD3U66YMkbqGrpKtnWbCQNdj///N5nDf71QwdLTEAzH+Kn9zRmiZ60rEu3w1Wm5vitB78=,iv:jWQvvO8kJDULepEUbWRym57whNpd23Q2PkXCDNQeJLo=,tag:wPBtn7Onr3S9ksEmdCZFzg==,type:str] + recipient: age16v8w7q4wmn22hhakq2uzaus2508rhldm7lcwh0kukshzjzyhuqesqz44ze + lastmodified: "2026-07-12T15:08:29Z" + mac: ENC[AES256_GCM,data:RiyjNVLep6kPx8oNno+v/GSCXQ0mo4SJFo54+KUyP/up79mPYTxLiuRsEZXO5P3d5pzMqMubQntDaMj8h6C4IJjojPIFvl4AeOveXkOeFz50ufeNBkwKEsDJgNNQfw/3vgq5rTR5WegsLzEMagXuDGwEJPA3uQQf0Ea2pXFO/Nw=,iv:YpTw0+Q+tBK3j3uEYiHLUzwygUFPrUanH8apNwpJwDM=,tag:o5opSInF5D9Q0OgB/7uf7Q==,type:str] unencrypted_suffix: _unencrypted - version: 3.11.0 + version: 3.13.1 diff --git a/storage.nix b/storage.nix index 8ecc9fd..edb6a45 100644 --- a/storage.nix +++ b/storage.nix @@ -32,6 +32,13 @@ "noatime" ]; }; + "@downloads" = { + mountpoint = "/home/user/downloads"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; }; }; }; diff --git a/tailscale.nix b/tailscale.nix new file mode 100644 index 0000000..af1693e --- /dev/null +++ b/tailscale.nix @@ -0,0 +1,273 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + cfg = config.services.tailscale-netns; + + instanceOptions = + { name, ... }: + { + options = { + authKeyFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to file containing a Tailscale auth key."; + }; + + hostname = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Tailscale hostname for this instance. + Defaults to "-". + ''; + }; + + advertiseRoutes = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Subnet routes to advertise (e.g. [ \"10.0.0.0/24\" ])."; + }; + + dns = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Bootstrap DNS server for the namespace before Tailscale is connected. + Defaults to `1.1.1.1`. Only used for the initial coordination server + connection; once Tailscale is up it manages DNS inside the namespace. + ''; + }; + + advertiseExitNode = mkOption { + type = types.bool; + default = false; + description = "Whether to advertise this node as an exit node."; + }; + + acceptRoutes = mkOption { + type = types.bool; + default = false; + description = "Whether to accept routes advertised by other nodes."; + }; + + exitNode = mkOption { + type = types.nullOr types.str; + default = null; + description = "Tailscale IP or hostname of exit node to use."; + }; + + extraUpFlags = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Extra flags passed to `tailscale up`."; + }; + + extraDaemonFlags = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Extra flags passed to `tailscaled`."; + }; + }; + }; + + mkUpFlags = + instanceCfg: + let + hostFlag = optional ( + instanceCfg.hostname != null + ) "--hostname=${escapeShellArg instanceCfg.hostname}"; + routeFlags = map (r: "--advertise-routes=${escapeShellArg r}") instanceCfg.advertiseRoutes; + exitFlag = optional instanceCfg.advertiseExitNode "--advertise-exit-node"; + acceptFlag = optional instanceCfg.acceptRoutes "--accept-routes"; + exitNodeFlag = optional ( + instanceCfg.exitNode != null + ) "--exit-node=${escapeShellArg instanceCfg.exitNode}"; + in + hostFlag ++ routeFlags ++ exitFlag ++ acceptFlag ++ exitNodeFlag ++ instanceCfg.extraUpFlags; + + mkAutoconnectScript = + name: instanceCfg: + pkgs.writers.writeBash "tailscale-autoconnect-${name}" '' + set -euo pipefail + + TS="${pkgs.tailscale}/bin/tailscale" + SOCKET="/run/tailscale-${name}/tailscaled.sock" + TS_ARGS="--socket $SOCKET" + + get_state() { + $TS $TS_ARGS status --json --peers=false 2>/dev/null | ${pkgs.jq}/bin/jq -r '.BackendState // "NoState"' + } + + last_state="" + while state="$(get_state)"; do + if [[ "$state" != "$last_state" ]]; then + case "$state" in + NeedsLogin|NeedsMachineAuth|Stopped) + echo "[tailscale-netns:${name}] server needs authentication, sending auth key" + $TS $TS_ARGS up \ + --auth-key "$(cat ${instanceCfg.authKeyFile})" \ + ${escapeShellArgs (mkUpFlags instanceCfg)} + ;; + Running) + echo "[tailscale-netns:${name}] tailscale is running" + ${pkgs.systemd}/bin/systemd-notify --ready + exit 0 + ;; + NoState) + echo "[tailscale-netns:${name}] tailscaled not ready yet" + ;; + *) + echo "[tailscale-netns:${name}] waiting for Running (state=$state)" + ;; + esac + fi + last_state="$state" + sleep 1 + done + ''; + + mkNetnsSetup = + index: name: instanceCfg: + let + ns = "ts-${name}ns"; + hostIP = "10.201.${toString index}.1/24"; + nsIP = "10.201.${toString index}.2/24"; + vethHost = "veth-ts-${name}"; + vethNS = "veth-ts-${name}-ns"; + in + { + "netns@ts-${name}" = { + description = "Network namespace for Tailscale instance ${name}"; + before = [ "network.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = pkgs.writers.writeBash "ts-${name}-netns-up" '' + set -euo pipefail + ${pkgs.coreutils}/bin/mkdir -p /etc/netns/${ns} + + # Build resolv.conf for the netns. Skip loopback addresses + # (systemd-resolved stub at 127.0.0.53 is not reachable from the netns). + NS_DNS="${if instanceCfg.dns != null then instanceCfg.dns else "1.1.1.1"}" + { + ${pkgs.gawk}/bin/awk '$1 == "nameserver" && $2 !~ /^127\./ && $2 != "::1" {print}' /etc/resolv.conf + echo "nameserver $NS_DNS" + } > /etc/netns/${ns}/resolv.conf + + ${pkgs.iproute2}/bin/ip netns add ${ns} + ${pkgs.iproute2}/bin/ip link add ${vethHost} type veth peer name ${vethNS} + ${pkgs.iproute2}/bin/ip link set ${vethNS} netns ${ns} + + ${pkgs.iproute2}/bin/ip addr add ${hostIP} dev ${vethHost} + ${pkgs.iproute2}/bin/ip link set ${vethHost} up + + ${pkgs.iproute2}/bin/ip -n ${ns} addr add ${nsIP} dev ${vethNS} + ${pkgs.iproute2}/bin/ip -n ${ns} link set ${vethNS} up + ${pkgs.iproute2}/bin/ip -n ${ns} link set lo up + + ${pkgs.iptables}/bin/iptables -t nat -C POSTROUTING -s ${nsIP} -j MASQUERADE 2>/dev/null \ + || ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s ${nsIP} -j MASQUERADE + ''; + ExecStop = pkgs.writers.writeBash "ts-${name}-netns-down" '' + set -euo pipefail + ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s ${nsIP} -j MASQUERADE 2>/dev/null || true + ${pkgs.iproute2}/bin/ip link del ${vethHost} 2>/dev/null || true + ${pkgs.iproute2}/bin/ip netns del ${ns} 2>/dev/null || true + ''; + }; + }; + + "tailscaled-${name}" = { + description = "Tailscale daemon for instance ${name}"; + bindsTo = [ "netns@ts-${name}.service" ]; + after = [ "netns@ts-${name}.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = pkgs.writers.writeBash "tailscaled-${name}-start" '' + exec ${pkgs.iproute2}/bin/ip netns exec ${ns} \ + ${pkgs.tailscale}/bin/tailscaled \ + --statedir=/var/lib/tailscale-${name} \ + --socket=/run/tailscale-${name}/tailscaled.sock \ + --tun=ts-${name} \ + --port=41641 \ + ${escapeShellArgs instanceCfg.extraDaemonFlags} + ''; + ExecStopPost = pkgs.writers.writeBash "tailscaled-${name}-stop" '' + ${pkgs.iproute2}/bin/ip netns exec ${ns} \ + ${pkgs.tailscale}/bin/tailscaled --cleanup \ + --statedir=/var/lib/tailscale-${name} \ + --socket=/run/tailscale-${name}/tailscaled.sock \ + 2>/dev/null || true + ''; + Restart = "on-failure"; + RuntimeDirectory = "tailscale-${name}"; + RuntimeDirectoryMode = "0755"; + StateDirectory = "tailscale-${name}"; + StateDirectoryMode = "0700"; + CacheDirectory = "tailscale-${name}"; + CacheDirectoryMode = "0750"; + + AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_RAW"; + LimitNOFILE = "infinity"; + }; + }; + }; + +in +{ + options.services.tailscale-netns = { + enable = mkEnableOption "Tailscale instances in isolated network namespaces"; + + instances = mkOption { + type = types.attrsOf (types.submodule instanceOptions); + default = { }; + description = "Tailscale instances configured in separate network namespaces."; + example = literalExpression '' + { + work = { + authKeyFile = "/run/secrets/ts-work/authkey"; + acceptRoutes = true; + }; + } + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services = listToAttrs ( + flatten ( + imap1 ( + index: elem: + let + name = elem.name; + instanceCfg = elem.value; + units = mkNetnsSetup index name instanceCfg; + in + [ + units."netns@ts-${name}" + units."tailscaled-${name}" + ] + ++ optional (instanceCfg.authKeyFile != null) { + "tailscaled-autoconnect-${name}" = { + description = "Tailscale autoconnect for instance ${name}"; + after = [ "tailscaled-${name}.service" ]; + requires = [ "tailscaled-${name}.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "notify"; + }; + script = "${mkAutoconnectScript name instanceCfg}"; + }; + } + ) (attrsToList cfg.instances) + ) + ); + }; +} diff --git a/users.nix b/users.nix index b56284c..4dc3158 100644 --- a/users.nix +++ b/users.nix @@ -7,7 +7,7 @@ imports = [ ./home/user.nix ./home/root.nix - ./home/agents.nix + # ./home/agents.nix ]; sops.secrets."user/password" = { neededForUsers = true; @@ -41,26 +41,25 @@ # ]; # linger = true; # }; - agent = { - uid = 1002; - homeMode = "770"; - home = "/home/agent"; - shell = pkgs.fish; - isNormalUser = true; - group = "agent"; - extraGroups = [ "public" ]; - linger = true; - }; - # sandbox = { - # uid = 1003; + # agent = { + # uid = 1002; # homeMode = "770"; - # home = "/home/sandbox"; + # home = "/home/agent"; # shell = pkgs.fish; # isNormalUser = true; - # group = "sandbox"; + # group = "agent"; # extraGroups = [ "public" ]; # linger = true; # }; + sandbox = { + uid = 1001; + homeMode = "770"; + home = "/home/sandbox"; + shell = pkgs.fish; + isNormalUser = true; + group = "users"; + linger = true; + }; user = { uid = 1000; homeMode = "700"; @@ -72,12 +71,11 @@ "libvirt" "systemd-journal" "kvm" - "public" - "agent" - "sandbox" "audio" "video" + "render" "bluetooth" + "users" # "work" ]; hashedPasswordFile = config.sops.secrets."user/password".path; @@ -86,9 +84,6 @@ }; groups = { user.gid = 1000; - agent.gid = 1002; - public.gid = 777; - # sandbox.gid = 1003; }; }; }