Compare commits

...

4 commits

Author SHA1 Message Date
root
6447486643
more sandbox config 2026-07-18 18:59:14 -03:00
root
0f1a81c2aa
sandboxing 2026-07-18 11:45:31 -03:00
root
5f70e8be94
ferrosonic 2026-07-05 20:00:36 -03:00
root
749bfdf50d
2026-07-04 2026-07-04 09:54:45 -03:00
23 changed files with 1025 additions and 310 deletions

View file

@ -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:

View file

@ -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"
];
}

12
executables/opencode.sh Normal file
View file

@ -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" "$@"

101
firejail.nix Normal file
View file

@ -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"
];
};
};
};
}

269
flake.lock generated
View file

@ -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,

View file

@ -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: {

View file

@ -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";
})
];
}

View file

@ -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", "<localleader>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", "<leader>nt", "<cmd>ToggleTerm size=120 direction=tab name=ttermh<cr>", { desc = "Toggle tterm tab" })
# vim.keymap.set("n", "<leader>ns", "<cmd>ToggleTerm direction=vertical name=ttermv<cr>", { desc = "Toggle tterm vertical" })
# vim.keymap.set("n", "<C-n>", "<cmd>ToggleTerm direction=float name=ttermf<cr>", { desc = "Toggle tterm float" })
# '';
# }
{
plugin = trouble-nvim;
type = "lua";
config = ''
require("trouble").setup({})
vim.keymap.set("n", "<localleader>id", "<cmd>Trouble diagnostics toggle<cr>", { desc = "Trouble project" })
-- vim.keymap.set("n", "<localleader>io", "<cmd>Trouble diagnostics toggle filter.buf=0<cr>", { desc = "Trouble buffer" })
'';
}
{

View file

@ -152,15 +152,3 @@ vim.api.nvim_create_autocmd("CmdlineLeave", {
vim.keymap.set({ "n", "x", "o" }, "s", "<Plug>(leap)", { desc = "leap" })
vim.keymap.set({ "n", "x", "o" }, "S", "<Plug>(leap-from-window)", { desc = "leap across window" })
require("neotest").setup({
adapters = {
require("neotest-pest"),
},
})
vim.keymap.set("n", "<localleader>pn", function()
require("neotest").run.run()
end, { desc = "test nearest" })
vim.keymap.set("n", "<localleader>pe", function()
require("neotest").run.run(vim.fn.expand("%"))
end, { desc = "test file" })

View file

@ -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,
})

View file

@ -19,7 +19,7 @@
ssh = {
enable = true;
enableDefaultConfig = false;
matchBlocks = {
settings = {
"icefox.sh" = {
user = "git";
identityFile = "/root/.ssh/desktop";

View file

@ -27,10 +27,13 @@
sops.defaultSopsFile = ../secrets/home.yaml;
sops.age.keyFile = "/.persist/${HOME}/.config/sops/age/keys.txt";
sops.secrets."user/ssh/desktop" = {
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";
sops.secrets."user/ssh/legacy_ed25519" = {
@ -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 = [

View file

@ -30,11 +30,11 @@
];
};
users.agent = {
files = [
".config/sops/age/keys.txt"
];
};
# users.agent = {
# files = [
# ".config/sops/age/keys.txt"
# ];
# };
};
environment.persistence."/.nobackup" = {

View file

@ -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;

234
microvm/sandbox.nix Normal file
View file

@ -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";
};
};
};
}

View file

@ -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

View file

@ -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
# '';
# };
# };
}

View file

@ -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;

View file

@ -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: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,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

View file

@ -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

View file

@ -32,6 +32,13 @@
"noatime"
];
};
"@downloads" = {
mountpoint = "/home/user/downloads";
mountOptions = [
"compress=zstd"
"noatime"
];
};
};
};
};

273
tailscale.nix Normal file
View file

@ -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 "<system-hostname>-<instance-name>".
'';
};
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)
)
);
};
}

View file

@ -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;
};
};
}