feat: nucnix

This commit is contained in:
Patrick 2024-12-14 21:45:46 +01:00
parent 1d2e32b8b0
commit d4e2805a87
Signed by: patrick
GPG key ID: 451F95EFB8BECD0F
14 changed files with 421 additions and 219 deletions

55
config/support/server.nix Normal file
View file

@ -0,0 +1,55 @@
{
environment = {
# Print the URL instead on servers
variables.BROWSER = "echo";
# Don't install the /lib/ld-linux.so.2 and /lib64/ld-linux-x86-64.so.2
# stubs. Server users should know what they are doing.
stub-ld.enable = false;
};
# Given that our systems are headless, emergency mode is useless.
# We prefer the system to attempt to continue booting so
# that we can hopefully still access it remotely.
boot.initrd.systemd.suppressedUnits = [
"emergency.service"
"emergency.target"
];
# Given that our systems are headless, emergency mode is useless.
# We prefer the system to attempt to continue booting so
# that we can hopefully still access it remotely.
systemd.enableEmergencyMode = false;
documentation.nixos.enable = false;
# No need for fonts on a server
fonts.fontconfig.enable = false;
programs.command-not-found.enable = false;
# freedesktop xdg files
xdg.autostart.enable = false;
xdg.icons.enable = false;
xdg.menus.enable = false;
xdg.mime.enable = false;
xdg.sounds.enable = false;
systemd = {
# For more detail, see:
# https://0pointer.de/blog/projects/watchdog.html
watchdog = {
# systemd will send a signal to the hardware watchdog at half
# the interval defined here, so every 7.5s.
# If the hardware watchdog does not get a signal for 15s,
# it will forcefully reboot the system.
runtimeTime = "15s";
# Forcefully reboot if the final stage of the reboot
# hangs without progress for more than 30s.
# For more info, see:
# https://utcc.utoronto.ca/~cks/space/blog/linux/SystemdShutdownWatchdog
rebootTime = "30s";
# Forcefully reboot when a host hangs after kexec.
# This may be the case when the firmware does not support kexec.
kexecTime = "1m";
};
};
}

View file

@ -1642,11 +1642,11 @@
"treefmt-nix": "treefmt-nix_3" "treefmt-nix": "treefmt-nix_3"
}, },
"locked": { "locked": {
"lastModified": 1733348844, "lastModified": 1734202825,
"narHash": "sha256-glufwHZDCoXjPrfvYSw8PrwQLyFVsg933gt/Gg4hlLE=", "narHash": "sha256-/9r2lRpVLG81uF7zxuk4LDnPZN0kk93tTclMA5KQK0E=",
"ref": "refs/heads/main", "ref": "refs/heads/main",
"rev": "3052ba7b255b8e3c333fcb318e79ce15c88dd2a7", "rev": "09fb938cb462681aaf6d7016e35a90d4995aad8c",
"revCount": 22, "revCount": 23,
"type": "git", "type": "git",
"url": "https://forge.lel.lol/patrick/nixp-meta.git" "url": "https://forge.lel.lol/patrick/nixp-meta.git"
}, },

View file

@ -16,6 +16,7 @@
../../config/support/initrd-ssh.nix ../../config/support/initrd-ssh.nix
../../config/support/physical.nix ../../config/support/physical.nix
../../config/support/secureboot.nix ../../config/support/secureboot.nix
../../config/support/server.nix
../../config/support/zfs.nix ../../config/support/zfs.nix
./net.nix ./net.nix
@ -28,58 +29,5 @@
}; };
nixpkgs.hostPlatform = "x86_64-linux"; nixpkgs.hostPlatform = "x86_64-linux";
# Given that our systems are headless, emergency mode is useless.
# We prefer the system to attempt to continue booting so
# that we can hopefully still access it remotely.
boot.initrd.systemd.suppressedUnits = [
"emergency.service"
"emergency.target"
];
environment = {
# Print the URL instead on servers
variables.BROWSER = "echo";
# Don't install the /lib/ld-linux.so.2 and /lib64/ld-linux-x86-64.so.2
# stubs. Server users should know what they are doing.
stub-ld.enable = false;
};
# Given that our systems are headless, emergency mode is useless.
# We prefer the system to attempt to continue booting so
# that we can hopefully still access it remotely.
systemd.enableEmergencyMode = false;
documentation.nixos.enable = false;
# No need for fonts on a server
fonts.fontconfig.enable = false;
programs.command-not-found.enable = false;
# freedesktop xdg files
xdg.autostart.enable = false;
xdg.icons.enable = false;
xdg.menus.enable = false;
xdg.mime.enable = false;
xdg.sounds.enable = false;
systemd = {
# For more detail, see:
# https://0pointer.de/blog/projects/watchdog.html
watchdog = {
# systemd will send a signal to the hardware watchdog at half
# the interval defined here, so every 7.5s.
# If the hardware watchdog does not get a signal for 15s,
# it will forcefully reboot the system.
runtimeTime = "15s";
# Forcefully reboot if the final stage of the reboot
# hangs without progress for more than 30s.
# For more info, see:
# https://utcc.utoronto.ca/~cks/space/blog/linux/SystemdShutdownWatchdog
rebootTime = "30s";
# Forcefully reboot when a host hangs after kexec.
# This may be the case when the firmware does not support kexec.
kexecTime = "1m";
};
};
topology.self.interfaces.lan.network = "home"; topology.self.interfaces.lan.network = "home";
} }

View file

@ -20,14 +20,6 @@
MulticastDNS = true; MulticastDNS = true;
}; };
}; };
"40-lan01" = {
dhcpV6Config.UseDNS = false;
dhcpV4Config.UseDNS = false;
ipv6AcceptRAConfig.UseDNS = false;
networkConfig = {
MulticastDNS = true;
};
};
}; };
boot.initrd.systemd.network = { boot.initrd.systemd.network = {
enable = true; enable = true;

31
hosts/nucnix/default.nix Normal file
View file

@ -0,0 +1,31 @@
{
inputs,
minimal,
lib,
...
}:
{
imports = [
inputs.nixos-hardware.nixosModules.common-pc
inputs.nixos-hardware.nixosModules.common-pc-ssd
inputs.nixos-hardware.nixosModules.common-cpu-intel
../../config/basic
../../config/support/initrd-ssh.nix
../../config/support/physical.nix
../../config/support/zfs.nix
../../config/support/server.nix
./net.nix
./fs.nix
] ++ lib.lists.optionals (!minimal) [ ./guests.nix ];
services.xserver = {
xkb = {
layout = "de";
};
};
nixpkgs.hostPlatform = "x86_64-linux";
topology.self.interfaces.lan.network = "home";
}

91
hosts/nucnix/fs.nix Normal file
View file

@ -0,0 +1,91 @@
{
config,
lib,
...
}:
{
disko.devices = {
disk = {
ssd = rec {
type = "disk";
device = "/dev/disk/by-id/${config.secrets.secrets.local.disko.nvme}";
content = with lib.disko.gpt; {
type = "gpt";
partitions = {
boot = (partEfi "1G") // {
device = "${device}-part1";
};
rpool = (partLuksZfs "ssd" "rpool" "100%") // {
device = "${device}-part2";
};
};
};
};
};
zpool = with lib.disko.zfs; {
rpool = mkZpool { datasets = impermanenceZfsDatasets; };
};
};
boot.kernel.sysctl."fs.inotify.max_user_instances" = 1024;
services.zrepl = {
enable = true;
settings = {
global = {
logging = [
{
type = "syslog";
level = "info";
format = "human";
}
];
# TODO Monitoring
};
jobs = [
#{
# type = "push";
# name = "push-to-remote";
#}
{
type = "snap";
name = "mach-schnipp-schusss";
filesystems = {
"rpool/local/state<" = true;
"rpool/local/guests<" = true;
"rpool/safe<" = true;
};
snapshotting = {
type = "periodic";
prefix = "zrepl-";
interval = "10m";
timestamp_format = "iso-8601";
};
pruning = {
keep = [
{
type = "regex";
regex = "^zrepl-.*$";
negate = true;
}
{
type = "grid";
grid = lib.concatStringsSep " | " [
"1x1d(keep=all)"
"142x1h(keep=2)"
"90x1d(keep=2)"
"500x7d"
];
regex = "^zrepl-.*$";
}
];
};
}
];
};
};
fileSystems."/state".neededForBoot = true;
fileSystems."/persist".neededForBoot = true;
}

182
hosts/nucnix/guests.nix Normal file
View file

@ -0,0 +1,182 @@
{
config,
stateVersion,
inputs,
lib,
minimal,
nodes,
...
}:
let
domainOf =
hostName:
let
domains =
{
};
in
"${domains.${hostName}}.${config.secrets.secrets.global.domains.web}";
# TODO hard coded elisabeth nicht so schön
ipOf = hostName: nodes."elisabeth-${hostName}".config.wireguard.elisabeth.ipv4;
in
{
services.nginx =
let
blockOf =
hostName:
{
virtualHostExtraConfig ? "",
maxBodySize ? "500M",
port ? 3000,
upstream ? hostName,
protocol ? "http",
}:
{
upstreams.${hostName} = {
servers."${ipOf upstream}:${toString port}" = { };
extraConfig = ''
zone ${hostName} 64k ;
keepalive 5 ;
'';
};
virtualHosts.${domainOf hostName} = {
forceSSL = true;
useACMEHost = "web";
locations."/" = {
proxyPass = "${protocol}://${hostName}";
proxyWebsockets = true;
X-Frame-Options = "SAMEORIGIN";
};
extraConfig =
''
client_max_body_size ${maxBodySize} ;
''
+ virtualHostExtraConfig;
};
};
proxyProtect =
hostName: cfg: allowedGroup:
lib.mkMerge [
(blockOf hostName cfg)
{
virtualHosts.${domainOf hostName} = {
locations."/".extraConfig = ''
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
# pass information via X-User and X-Email headers to backend,
# requires running with --set-xauthrequest flag
auth_request_set $user $upstream_http_x_auth_request_preferred_username;
# Set the email to our own domain in case user change their mail
auth_request_set $email "''${upstream_http_x_auth_request_preferred_username}@${config.secrets.secrets.global.domains.web}";
proxy_set_header X-User $user;
proxy_set_header X-Email $email;
# if you enabled --cookie-refresh, this is needed for it to work with auth_request
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
'';
locations."/oauth2/" = {
proxyPass = "http://oauth2-proxy";
extraConfig = ''
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
'';
};
locations."= /oauth2/auth" = {
proxyPass =
"http://oauth2-proxy/oauth2/auth"
+ lib.optionalString allowedGroup "?allowed_groups=${hostName}_access";
extraConfig = ''
internal;
proxy_set_header X-Scheme $scheme;
# nginx auth_request includes headers but not body
proxy_set_header Content-Length "";
proxy_pass_request_body off;
'';
};
};
}
];
in
lib.mkMerge [
{
enable = false;
recommendedSetup = true;
}
];
guests =
let
mkGuest = guestName: {
autostart = true;
zfs."/state" = {
pool = "rpool";
dataset = "local/guests/${guestName}";
};
zfs."/persist" = {
pool = "rpool";
dataset = "safe/guests/${guestName}";
};
modules = [
../../config/basic
../../config/services/${guestName}.nix
{
node.secretsDir = config.node.secretsDir + "/${guestName}";
networking.nftables.firewall.zones.untrusted.interfaces = [
config.guests.${guestName}.networking.mainLinkName
];
systemd.network.networks."10-${config.guests.${guestName}.networking.mainLinkName}" = {
DHCP = lib.mkForce "no";
address = [
(lib.net.cidr.hostCidr
config.secrets.secrets.global.net.ips."${config.guests.${guestName}.nodeName}"
config.secrets.secrets.global.net.privateSubnetv4
)
(lib.net.cidr.hostCidr
config.secrets.secrets.global.net.ips."${config.guests.${guestName}.nodeName}"
config.secrets.secrets.global.net.privateSubnetv6
)
];
gateway = [ (lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4) ];
};
}
];
};
mkMicrovm = guestName: cfg: {
${guestName} = mkGuest guestName cfg // {
backend = "microvm";
microvm = {
system = "x86_64-linux";
macvtap = "lan";
baseMac = config.secrets.secrets.local.networking.interfaces.lan01.mac;
};
extraSpecialArgs = {
inherit (inputs.self) nodes;
inherit (inputs.self.pkgs.x86_64-linux) lib;
inherit inputs minimal stateVersion;
};
};
};
mkContainer = guestName: cfg: {
${guestName} = mkGuest guestName cfg // {
backend = "container";
container.macvlan = "lan";
extraSpecialArgs = {
inherit
lib
nodes
inputs
minimal
stateVersion
;
};
};
};
in
{ };
}

54
hosts/nucnix/net.nix Normal file
View file

@ -0,0 +1,54 @@
{ config, lib, ... }:
{
networking = {
inherit (config.secrets.secrets.local.networking) hostId;
};
systemd.network.networks = {
"10-lan01" = {
address = [
(lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips.${config.node.name}
config.secrets.secrets.global.net.privateSubnetv4
)
];
gateway = [ (lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4) ];
#matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac;
matchConfig.Name = "lan";
dhcpV6Config.UseDNS = false;
dhcpV4Config.UseDNS = false;
ipv6AcceptRAConfig.UseDNS = false;
networkConfig = {
MulticastDNS = true;
};
};
};
boot.initrd.systemd.network = {
enable = true;
networks = {
# redo the network cause the livesystem has macvlans
"10-lan01" = {
address = [
(lib.net.cidr.hostCidr config.secrets.secrets.global.net.ips.${config.node.name}
config.secrets.secrets.global.net.privateSubnetv4
)
];
gateway = [ (lib.net.cidr.host 1 config.secrets.secrets.global.net.privateSubnetv4) ];
matchConfig.MACAddress = config.secrets.secrets.local.networking.interfaces.lan01.mac;
dhcpV6Config.UseDNS = false;
dhcpV4Config.UseDNS = false;
ipv6AcceptRAConfig.UseDNS = false;
networkConfig = {
IPv6PrivacyExtensions = "yes";
MulticastDNS = true;
};
};
};
};
networking.nftables.firewall.zones.untrusted.interfaces = [ "lan" ];
# To be able to ping containers from the host, it is necessary
# to create a macvlan on the host on the VLAN 1 network.
networking.macvlans.lan = {
interface = "lan01";
mode = "bridge";
};
}

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDS0gxZD8aIAAKBtt7gyMHZ2KloQPlHxS+LsQY/62SzE

Binary file not shown.

View file

@ -21,6 +21,7 @@ in
name = "Home-manager options for the main user"; name = "Home-manager options for the main user";
merge = _loc: defs: (map (x: x.value) defs); merge = _loc: defs: (map (x: x.value) defs);
}; };
default = { };
}; };
hm-all = mkOption { hm-all = mkOption {
description = "Home-manager options for the primary User and root."; description = "Home-manager options for the primary User and root.";
@ -28,6 +29,7 @@ in
name = "Home-manager options for the all users"; name = "Home-manager options for the all users";
merge = _loc: defs: (map (x: x.value) defs); merge = _loc: defs: (map (x: x.value) defs);
}; };
default = { };
}; };
}; };
config.home-manager.users = mkMerge [ config.home-manager.users = mkMerge [

View file

@ -1,154 +0,0 @@
{
lib,
pkgs,
config,
...
}:
let
inherit (lib)
getExe
mkEnableOption
mkIf
mkOption
mkPackageOption
types
;
cfg = config.services.actual;
configFile = formatType.generate "config.json" cfg.settings;
dataDir = "/var/lib/actual";
formatType = pkgs.formats.json { };
in
{
options.services.actual = {
enable = mkEnableOption "actual, a privacy focused app for managing your finances";
package = mkPackageOption pkgs "actual-server" { };
user = mkOption {
type = types.str;
default = "actual";
description = ''
User to run actual as.
::: {.note}
If left as the default value this user will automatically be created
on system activation, otherwise the sysadmin is responsible for
ensuring the user exists.
:::
'';
};
group = mkOption {
type = types.str;
default = "actual";
description = ''
Group under which to run.
::: {.note}
If left as the default value this group will automatically be created
on system activation, otherwise the sysadmin is responsible for
ensuring the user exists.
:::
'';
};
openFirewall = mkOption {
default = false;
type = types.bool;
description = "Whether to open the firewall for the specified port.";
};
settings = mkOption {
default = { };
description = "Server settings, refer to (the documentation)[https://actualbudget.org/docs/config/] for available options.";
type = types.submodule {
freeformType = formatType.type;
options = {
hostname = mkOption {
type = types.str;
description = "The address to listen on";
default = "::";
};
port = mkOption {
type = types.port;
description = "The port to listen on";
default = 3000;
};
};
config = {
serverFiles = "${dataDir}/server-files";
userFiles = "${dataDir}/user-files";
inherit dataDir;
};
};
};
};
config = mkIf cfg.enable {
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.port ];
users.groups = mkIf (cfg.group == "actual") {
${cfg.group} = { };
};
users.users = mkIf (cfg.user == "actual") {
${cfg.user} = {
isSystemUser = true;
inherit (cfg) group;
home = dataDir;
};
};
systemd.services.actual = {
description = "Actual server, a local-first personal finance app";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment.ACTUAL_CONFIG_PATH = configFile;
serviceConfig = {
ExecStart = getExe cfg.package;
User = cfg.user;
Group = cfg.group;
StateDirectory = "actual";
WorkingDirectory = dataDir;
LimitNOFILE = "1048576";
PrivateTmp = true;
PrivateDevices = true;
StateDirectoryMode = "0700";
Restart = "always";
# Hardening
CapabilityBoundingSet = "";
LockPersonality = true;
#MemoryDenyWriteExecute = true; # Leads to coredump because V8 does JIT
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
ProtectSystem = "strict";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"@pkey"
];
UMask = "0077";
};
};
};
}

Binary file not shown.