feat: add initial config
This commit is contained in:
commit
fb98563bb6
26 changed files with 576 additions and 0 deletions
6
.sops.yaml
Normal file
6
.sops.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: ^secrets/.*(?:$|\.(ya?ml|json|env|txt|key|pub))$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- age1g5q3hwnpgsas682jkq0zmee3zqggucfe0v5ec0a6pv7wzexadehqne66cj
|
||||||
|
- age13p5fukn4eetfqr5jvuc7xv6gwpn8hthq59ay5k2chm0c0409r9jq97dpyz
|
||||||
4
config/endpoints.nix
Normal file
4
config/endpoints.nix
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
let
|
||||||
|
piTunnelEndpoints = import ../config/endpoints/pi_tunnel.nix;
|
||||||
|
in
|
||||||
|
piTunnelEndpoints ++ []
|
||||||
13
config/endpoints/pi_tunnel.nix
Normal file
13
config/endpoints/pi_tunnel.nix
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
let
|
||||||
|
ports = [80 443 8448];
|
||||||
|
entry = port:
|
||||||
|
{
|
||||||
|
type = "forwarding";
|
||||||
|
listenPort = port;
|
||||||
|
content = {
|
||||||
|
port = 10000 + port;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
map entry ports
|
||||||
10
config/network.nix
Normal file
10
config/network.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
tunnel = {
|
||||||
|
host = "127.0.0.1";
|
||||||
|
allowedPorts = [
|
||||||
|
10080
|
||||||
|
10443
|
||||||
|
18448
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
22
config/openssh.nix
Normal file
22
config/openssh.nix
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
let
|
||||||
|
secrets = import ../intermediate/secrets.nix;
|
||||||
|
users = builtins.attrNames secrets.source.openssh.users;
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
ssh_users = users;
|
||||||
|
|
||||||
|
extraConfig = {
|
||||||
|
users = {
|
||||||
|
"autossh-incoming" = ''
|
||||||
|
PasswordAuthentication no
|
||||||
|
PermitTTY no
|
||||||
|
X11Forwarding no
|
||||||
|
AllowAgentForwarding no
|
||||||
|
PermitTunnel no
|
||||||
|
AllowTcpForwarding remote
|
||||||
|
PermitListen localhost:*
|
||||||
|
PermitListen 127.0.0.1:*
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
25
config/secrets.nix
Normal file
25
config/secrets.nix
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
openssh = {
|
||||||
|
users = {
|
||||||
|
"autossh-incoming" = {
|
||||||
|
pub_keys = {
|
||||||
|
file = ../secrets/openssh/autossh_incoming/pub_keys;
|
||||||
|
path = "/var/lib/autossh-incoming/.ssh/authorized_keys";
|
||||||
|
owner = "autossh-incoming";
|
||||||
|
group = "autossh-incoming";
|
||||||
|
mode = "0600";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nudelerde = {
|
||||||
|
pub_keys = {
|
||||||
|
file = ../secrets/openssh/nudelerde/pub_keys;
|
||||||
|
path = "/home/nudelerde/.ssh/authorized_keys";
|
||||||
|
owner = "nudelerde";
|
||||||
|
group = "users";
|
||||||
|
mode = "0600";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
6
config/services.nix
Normal file
6
config/services.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
nginx = {
|
||||||
|
enable = true;
|
||||||
|
acmeEmail = null;
|
||||||
|
};
|
||||||
|
}
|
||||||
18
configuration.nix
Normal file
18
configuration.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{ config, pkgs, ... }:
|
||||||
|
let
|
||||||
|
sopsNixVersion = "8f093d0d2f08f37317778bd94db5951d6cce6c46";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
"${builtins.fetchTarball "https://github.com/Mic92/sops-nix/archive/${sopsNixVersion}.tar.gz"}/modules/sops"
|
||||||
|
./hardware-configuration.nix
|
||||||
|
./system
|
||||||
|
./services
|
||||||
|
./programs
|
||||||
|
];
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [ wget tree ];
|
||||||
|
|
||||||
|
system.stateVersion = "25.11";
|
||||||
|
}
|
||||||
|
|
||||||
30
hardware-configuration.nix
Normal file
30
hardware-configuration.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||||
|
# and may be overwritten by future invocations. Please make changes
|
||||||
|
# to /etc/nixos/configuration.nix instead.
|
||||||
|
{ config, lib, pkgs, modulesPath, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports =
|
||||||
|
[ (modulesPath + "/profiles/qemu-guest.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk" ];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
fileSystems."/" =
|
||||||
|
{ device = "/dev/disk/by-uuid/ec5f3d98-74b1-4c33-aad2-c9d26b38958d";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/boot" =
|
||||||
|
{ device = "/dev/disk/by-uuid/5936-3F61";
|
||||||
|
fsType = "vfat";
|
||||||
|
options = [ "fmask=0022" "dmask=0022" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
swapDevices = [ ];
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
}
|
||||||
36
intermediate/nginx.nix
Normal file
36
intermediate/nginx.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
let
|
||||||
|
lib = import <nixpkgs/lib>;
|
||||||
|
endpoints = (import ../validation/endpoints.nix).getValidatedEndpoints (import ../config/endpoints.nix);
|
||||||
|
net = import ../config/network.nix;
|
||||||
|
tunnelPolicy = import ../validation/tunnel_ports.nix;
|
||||||
|
|
||||||
|
normalizeEndpoint = endpoint:
|
||||||
|
endpoint // {
|
||||||
|
content = endpoint.content // {
|
||||||
|
host = if endpoint.type == "forwarding" then net.tunnel.host else endpoint.content.host;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
normalizedEndpoints = map normalizeEndpoint endpoints;
|
||||||
|
|
||||||
|
_forwardPortChecks = map (endpoint:
|
||||||
|
if endpoint.content.host == net.tunnel.host && !(tunnelPolicy.isAllowedTunnelPort endpoint.content.port) then
|
||||||
|
throw "Forwarding endpoint listenPort=${toString endpoint.listenPort} targets tunnel-backed local port ${toString endpoint.content.port}, which is not listed in config/network.nix tunnel.allowedPorts."
|
||||||
|
else
|
||||||
|
null
|
||||||
|
) normalizedEndpoints;
|
||||||
|
|
||||||
|
mkStreamServer = endpoint: ''
|
||||||
|
server {
|
||||||
|
listen ${toString endpoint.listenPort};
|
||||||
|
proxy_pass ${endpoint.content.host}:${toString endpoint.content.port};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
streamConfig = lib.concatStringsSep "\n" (map mkStreamServer normalizedEndpoints);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
validatedEndpoints = normalizedEndpoints;
|
||||||
|
inherit streamConfig;
|
||||||
|
nginxUsedPorts = lib.unique (map (endpoint: endpoint.listenPort) normalizedEndpoints);
|
||||||
|
}
|
||||||
79
intermediate/secrets.nix
Normal file
79
intermediate/secrets.nix
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
let
|
||||||
|
lib = import <nixpkgs/lib>;
|
||||||
|
secretsConfig = (import ../validation/secrets.nix).getSecretsConfig (import ../config/secrets.nix);
|
||||||
|
|
||||||
|
getRuntimePath = path:
|
||||||
|
"/run/secrets/${builtins.concatStringsSep "_" path}";
|
||||||
|
|
||||||
|
defaultMetadata = {
|
||||||
|
path = null;
|
||||||
|
owner = null;
|
||||||
|
group = null;
|
||||||
|
mode = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
normalizeLeaf = path: node:
|
||||||
|
if builtins.isString node || builtins.isPath node then
|
||||||
|
{
|
||||||
|
file = node;
|
||||||
|
metadata = defaultMetadata;
|
||||||
|
}
|
||||||
|
else if builtins.isAttrs node && node ? file then
|
||||||
|
{
|
||||||
|
file = node.file;
|
||||||
|
metadata = {
|
||||||
|
path = node.path or null;
|
||||||
|
owner = node.owner or null;
|
||||||
|
group = node.group or null;
|
||||||
|
mode = node.mode or null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw "Invalid secret leaf at ${builtins.concatStringsSep "." path}: must be string, path, or attrset with 'file'";
|
||||||
|
|
||||||
|
flattenTree = path: node:
|
||||||
|
if builtins.isAttrs node && !(node ? file) then
|
||||||
|
lib.concatMap (name:
|
||||||
|
flattenTree (path ++ [ name ]) node.${name}
|
||||||
|
) (builtins.attrNames node)
|
||||||
|
else
|
||||||
|
let
|
||||||
|
normalized = normalizeLeaf path node;
|
||||||
|
in
|
||||||
|
[ {
|
||||||
|
inherit path;
|
||||||
|
file = normalized.file;
|
||||||
|
metadata = normalized.metadata;
|
||||||
|
} ];
|
||||||
|
|
||||||
|
entries = flattenTree [] secretsConfig;
|
||||||
|
|
||||||
|
isReady = entry:
|
||||||
|
builtins.pathExists entry.file;
|
||||||
|
|
||||||
|
readyEntries = builtins.filter isReady entries;
|
||||||
|
missingEntries = builtins.filter (entry: !(isReady entry)) entries;
|
||||||
|
|
||||||
|
mkSopsSecrets = sourceEntries:
|
||||||
|
builtins.listToAttrs (map (entry:
|
||||||
|
let
|
||||||
|
secretName = builtins.concatStringsSep "_" entry.path;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = secretName;
|
||||||
|
value = {
|
||||||
|
sopsFile = entry.file;
|
||||||
|
format = "binary";
|
||||||
|
path = if entry.metadata.path != null then entry.metadata.path else getRuntimePath entry.path;
|
||||||
|
owner = if entry.metadata.owner != null then entry.metadata.owner else "root";
|
||||||
|
group = if entry.metadata.group != null then entry.metadata.group else "root";
|
||||||
|
mode = if entry.metadata.mode != null then entry.metadata.mode else "0400";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
) sourceEntries);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
source = secretsConfig;
|
||||||
|
byName = mkSopsSecrets readyEntries;
|
||||||
|
missing = missingEntries;
|
||||||
|
}
|
||||||
3
programs/default.nix
Normal file
3
programs/default.nix
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
}
|
||||||
19
secrets/openssh/autossh_incoming/pub_keys
Normal file
19
secrets/openssh/autossh_incoming/pub_keys
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:Z1kf7mA5QD004A/NLI/71YL8yFedXBwNFjke5jAv+43BzLyx+xd0swx1Y7MvHPrqQ82Dil04VWyP03oSHGWkMYGqXcY0rRMJZlB5Ky249Tz7muYa0eeukEfWZrZrgiaH,iv:oUFWYvlXc2QBSFe1tG6ylZmo+ft6SZE84bf2goY/pQU=,tag:a+gAB3X5/CK3nlE9pU5ctA==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g5q3hwnpgsas682jkq0zmee3zqggucfe0v5ec0a6pv7wzexadehqne66cj",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnSnYvVUdzN1BWYlYyZWdm\nY0ZBcm5pczJObG5SWjlRVG5jY3EvVkthNzM0CkdYYk9mdVlkcDd5bHJHQUlBbllj\ncDNjMlBsYUc0KzVZaTcwemE4Tk9heDAKLS0tIHB1VDVtTUdZTWVxSGZ0YVN3bnJu\ncDBpTlVPNHlla3Z4UjEyenZFTWxMbEkKoo00iOt7ijUIfa1JH2kV/XP8pRrHl3S4\nCfNDMwckHPMFJcfPv5W7HIFE/qICUdQddIjeKwKCEwzuBLYxIDZs9w==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipient": "age13p5fukn4eetfqr5jvuc7xv6gwpn8hthq59ay5k2chm0c0409r9jq97dpyz",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtV2F3ekFrVWJZa1oxcW5t\nRjZlYnZRV1R6QmR3b25MbG10b0cyckpGaXowCjgwTTJpenJGYzJJSis5UmE2ZWVK\ndW9RMEhLUmczZ3dPSktERlRtRHF6TlkKLS0tIGZvQXoyNzlKeW5sNEF4dkQrOXl2\nelRYV253cngwdllVVWUzMGRiUSs5cTQKmo7YEYpPeOt3NyzxvvkwndUcLf2Mxr5F\nqd4ET6CIvh0J8N74QpIA4QPpi0dXVKlGNZ4Yr+HouKgr31gKse8Urw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-04-04T19:52:29Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:jGgaLSj5mv7aR/h1TtfZlBg26Mi5w6JJiI5gj4Ij+ODYAeCq9r/tz4Kl9nPp8js0mgUFdXkJ1HHkkkJ+Ty+O0qEQCSh3hHeXcWSE3sOQVcqXLLfmFiJXHuX5cKpQrjZSJdaEK38wOUY67mrP1JpB1RDsp3AFUSgMx6do8TBtLHQ=,iv:qX+uscNvOkGI+05ew5thMGVe/58Mf8Cpli+dugtM2KM=,tag:zfUwrTjzlvo8kz21fM9Zxw==,type:str]",
|
||||||
|
"unencrypted_suffix": "_unencrypted",
|
||||||
|
"version": "3.12.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
secrets/openssh/nudelerde/pub_keys
Normal file
19
secrets/openssh/nudelerde/pub_keys
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:a2lXkJJzcxy1uNn5eWeytRzw8LjAXhRyMB4siqBhogqv6m5omFU8erKtPjx26yejVFnmgp/612Q65D0gVi5A+qEButRTPiPEdgh5svzcj38cUNaqC4NQqFc6mv2LJjJfKGulUS1PgIMeLk2rUMsP0zLQHweFzDjOblew1vQUdTfcvSETpaG/fKa/8FTfx88eOCvAYJuhI6jJDOtOuAjlq+OXVW+cGBV9vLCrQfty0rVnNp274TNcctvJmjPde0UhwfmbX4fumgUc62vnUVrkdKgcwVQ1QuhLvSB2ySa4OdYBcxNk19xrDLG0KHWkyk6KWf6enogO0+E8pLRmU/onoygxohA76GBdy9cStJZFl8YT3sTk+eQDicdXo1pjVaBE5PtC/qzIiq8y04egtf/1+q1NishztmPqAp8+jGNdqyjLPZlYKkPKD/LLc8LNMT6Ko69zg0vMl5E16pwAJJE+qgMTSrfdoQ/XxOf2KDm14fTRVvnhdJfT0l9WOtumkleyd3S1S/q6ZVRN/IvPa7TUhplradfUdDYgKL9DW1JtdtBLBuer2Lbw6jnAAJD4JyNBEmgXDCPIewl5Ubre7vwr4FWvKQApN8jmkHpJAvjDOHBpMMAvXTUbz/cZhmiCSHOWmYkMWKgpaW78MiYOoYrrwpeEJaKuH2ZeKBup5GifAz8u1AozrBE7bzO97JeH4glLacHbQ0BLWOinjxIpFAZH+0g8xP/cFpu8M6lCoaN5N50BwFDQcAUGR5NfbsBBg02tAHuDYs9l9fdhBPBZXtHdyo9hRtRybY3o3uzTHWVr8Hii9FfOrYyKANNv/QCa1u2xgbsw2c3yJJqDoetP/dtimzqEW4K4fLCBDJsz/hmkgpx6Pvv8WQ3MCsjUfuf9RRnbGR6zkoibJMxpU6RQoXQvgbW38nskF6/qXXgnDQURypRuX4L4JTHP8nZqmgldS28MhpyMHfAnl8lXzL2Spf5vCnjrI1qsAiz4LJ+iCFn/ne4dwlQiztdypQ==,iv:JCePPGSh9GO3hctfjGSzJ8o6nQXT5WA38XzFIGxMx14=,tag:4WtGrtNQIuZOR0OBQ7FWlQ==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g5q3hwnpgsas682jkq0zmee3zqggucfe0v5ec0a6pv7wzexadehqne66cj",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3Ky9vMk5OTkVHc0d2UXlL\nWGY0Q3REcVF5bHZCQTZqMkxyc3llUmErL2hNClcweVg3dDNFaUtrRjdzRHN3YzRq\nQ1lvNFR3U3lSU015MFlDNE8zUUxpaVUKLS0tIGZNUnRKbzczSWo1czhyOFBKUXFN\nTTJyWHhDOSt3RzRjbkRGKzlCdndIYmsKe7CUIoSFtFFCFF0DdCF7C3BUP8gAyvM/\naJWvqSBFC9BUyMN39q1qltZagVZjdo3xoQQ9YbCmgnHhKOLOyPYsag==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipient": "age13p5fukn4eetfqr5jvuc7xv6gwpn8hthq59ay5k2chm0c0409r9jq97dpyz",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4aWZYa3hPUHBoU2JIdmY1\nQVhrYkJUaTFoL2EycDN4V3VOQlhtTUFzcEFNCjN4QnNKVTNSVjJUTWpPVEVCU0d3\ncDZ3ZkNvd0R4d2lLNzVHZkhzOUxLTlUKLS0tIFRHVGtjWWFtblBOR0o2czJ6SFdr\nKzloQmcxQUx1QURZTVZHaVlyK1VrZkEKWzgu4cEb5kS7MNs09rAFvMmcp8oE0lFO\nMf2XYWELnakh2p68MWNjrKlsg/P1iPtMKs8EQLLBz3+DDl0dy6Bdig==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-04-04T19:32:16Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:uNrX4EQWcyYS/QV0cConm9x45rNeSC0RzKhiKQ8235+TLHzwBE2prD2tVPBQtHQ2HzhVNd8Uw1LwF6R1ZgDJimcDL3oeLMrbIHvl8RcQ5c5jgYsfUI7X4P182QUXxEQ14o/xhCoK7rtjPqY+069DMp3VEzdsbDuRDf1HkGy3Ljo=,iv:o3A65kabdjViYUrzXoVppybg5ROnbhU/1QQBHO1Xl80=,tag:6nCI8KpqaaWeqCyA7NPHXg==,type:str]",
|
||||||
|
"unencrypted_suffix": "_unencrypted",
|
||||||
|
"version": "3.12.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
services/default.nix
Normal file
7
services/default.nix
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./openssh.nix
|
||||||
|
./nginx.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
25
services/nginx.nix
Normal file
25
services/nginx.nix
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
serviceConfig = import ../config/services.nix;
|
||||||
|
nginxModel = import ../intermediate/nginx.nix;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = nginxModel.validatedEndpoints != [];
|
||||||
|
message = "No endpoints configured. Add endpoint declarations under config/endpoints/.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = serviceConfig.nginx.enable;
|
||||||
|
|
||||||
|
recommendedProxySettings = true;
|
||||||
|
recommendedTlsSettings = true;
|
||||||
|
recommendedOptimisation = true;
|
||||||
|
recommendedGzipSettings = true;
|
||||||
|
streamConfig = nginxModel.streamConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = nginxModel.nginxUsedPorts;
|
||||||
|
}
|
||||||
32
services/openssh.nix
Normal file
32
services/openssh.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{ ... }:
|
||||||
|
let
|
||||||
|
lib = import <nixpkgs/lib>;
|
||||||
|
opensshConfig = import ../config/openssh.nix;
|
||||||
|
|
||||||
|
userExtraConfig =
|
||||||
|
if opensshConfig ? extraConfig && opensshConfig.extraConfig ? users && builtins.isAttrs opensshConfig.extraConfig.users then
|
||||||
|
opensshConfig.extraConfig.users
|
||||||
|
else
|
||||||
|
{};
|
||||||
|
|
||||||
|
renderedUserMatches = lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (user: cfg: ''
|
||||||
|
Match User ${user}
|
||||||
|
${cfg}
|
||||||
|
'') userExtraConfig
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PasswordAuthentication = true;
|
||||||
|
PermitRootLogin = "no";
|
||||||
|
GatewayPorts = "no";
|
||||||
|
AllowUsers = opensshConfig.ssh_users;
|
||||||
|
};
|
||||||
|
extraConfig = renderedUserMatches;
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||||
|
}
|
||||||
5
system/boot.nix
Normal file
5
system/boot.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
|
}
|
||||||
9
system/default.nix
Normal file
9
system/default.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./sops.nix
|
||||||
|
./users.nix
|
||||||
|
./network.nix
|
||||||
|
./boot.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
15
system/network.nix
Normal file
15
system/network.nix
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
networking.hostName = "nixos-proxy";
|
||||||
|
networking.useDHCP = false;
|
||||||
|
networking.interfaces.eth0.ipv4.addresses = [
|
||||||
|
{ address = "193.31.24.99"; prefixLength = 22; }
|
||||||
|
];
|
||||||
|
networking.defaultGateway = "193.31.24.1";
|
||||||
|
networking.interfaces.eth0.ipv6.addresses = [
|
||||||
|
{ address = "2a03:4000:2b:1836:748d:84ff:fe53:2a09"; prefixLength = 64; }
|
||||||
|
];
|
||||||
|
networking.defaultGateway6 = { address = "fe80::1"; interface = "eth0"; };
|
||||||
|
networking.nameservers = [ "1.1.1.1" "9.9.9.9" "2606:4700:4700::1111" "2620:fe::fe" ];
|
||||||
|
}
|
||||||
14
system/sops.nix
Normal file
14
system/sops.nix
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
secretData = import ../intermediate/secrets.nix;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
sops = {
|
||||||
|
age.keyFile = "/var/lib/sops-nix/key.txt";
|
||||||
|
secrets = secretData.byName;
|
||||||
|
};
|
||||||
|
|
||||||
|
warnings = lib.optional (secretData.missing != [])
|
||||||
|
"Some SOPS source files are missing or not yet encrypted; no runtime secrets will be provisioned for: ${builtins.concatStringsSep ", " (map (item: builtins.concatStringsSep "_" item.path) secretData.missing)}";
|
||||||
|
}
|
||||||
32
system/users.nix
Normal file
32
system/users.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
users.mutableUsers = false;
|
||||||
|
|
||||||
|
users.users.nudelerde = {
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
hashedPassword = "$y$j9T$NiaiVxQKs0C1V4VdCFKBO.$P6RfBDTyJfPJJzKyHf9PJEy9Ku5M6AU57U98nVD6wP6";
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.autossh-incoming = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "autossh-incoming";
|
||||||
|
createHome = true;
|
||||||
|
home = "/var/lib/autossh-incoming";
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.autossh-incoming = {};
|
||||||
|
|
||||||
|
security.sudo.extraRules = [
|
||||||
|
{
|
||||||
|
users = [ "nudelerde" ];
|
||||||
|
commands = [
|
||||||
|
{
|
||||||
|
command = "ALL";
|
||||||
|
options = [ "NOPASSWD" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
78
validation/endpoints.nix
Normal file
78
validation/endpoints.nix
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
let
|
||||||
|
lib = import <nixpkgs/lib>;
|
||||||
|
|
||||||
|
allowedEndpointKeys = [ "type" "listenPort" "content" ];
|
||||||
|
allowedContentKeys = [ "host" "port" ];
|
||||||
|
|
||||||
|
ensureNoUnknownKeys = context: obj: allowedKeys:
|
||||||
|
let
|
||||||
|
unknown = builtins.filter (key: !(lib.elem key allowedKeys)) (builtins.attrNames obj);
|
||||||
|
in
|
||||||
|
if unknown != [] then
|
||||||
|
throw "${context} contains unknown keys: ${builtins.concatStringsSep ", " unknown}"
|
||||||
|
else
|
||||||
|
obj;
|
||||||
|
|
||||||
|
ensurePort = value:
|
||||||
|
builtins.isInt value && value > 0 && value <= 65535;
|
||||||
|
|
||||||
|
validateEndpointShape = index: endpoint:
|
||||||
|
let
|
||||||
|
_ = if builtins.isAttrs endpoint then null else throw "Endpoint at index ${toString index} must be an attrset.";
|
||||||
|
__ = ensureNoUnknownKeys "Endpoint at index ${toString index}" endpoint allowedEndpointKeys;
|
||||||
|
typeValue =
|
||||||
|
if endpoint ? type && builtins.isString endpoint.type then
|
||||||
|
endpoint.type
|
||||||
|
else
|
||||||
|
throw "Endpoint at index ${toString index} must define type as a string.";
|
||||||
|
_type =
|
||||||
|
if lib.elem typeValue [ "forwarding" "proxy" ] then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
throw "Endpoint at index ${toString index} type must be 'forwarding' or 'proxy'.";
|
||||||
|
_listenPort =
|
||||||
|
if endpoint ? listenPort && ensurePort endpoint.listenPort then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
throw "Endpoint at index ${toString index} must define listenPort as int in range 1..65535.";
|
||||||
|
contentValue =
|
||||||
|
if endpoint ? content then
|
||||||
|
endpoint.content
|
||||||
|
else
|
||||||
|
throw "Endpoint at index ${toString index} must define content.";
|
||||||
|
_content =
|
||||||
|
let
|
||||||
|
_attrs = if builtins.isAttrs contentValue then null else throw "Endpoint at index ${toString index} must define content as an attrset.";
|
||||||
|
____ = ensureNoUnknownKeys "Endpoint content at index ${toString index}" contentValue allowedContentKeys;
|
||||||
|
in
|
||||||
|
if !(contentValue ? port) || !ensurePort contentValue.port then
|
||||||
|
throw "Endpoint at index ${toString index} must define content.port as int in range 1..65535."
|
||||||
|
else if typeValue == "proxy" && (!(contentValue ? host) || !builtins.isString contentValue.host || contentValue.host == "") then
|
||||||
|
throw "Proxy endpoint at index ${toString index} must define content.host as non-empty string."
|
||||||
|
else if typeValue == "forwarding" && contentValue ? host then
|
||||||
|
throw "Forwarding endpoint at index ${toString index} must not define content.host."
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
in
|
||||||
|
endpoint;
|
||||||
|
|
||||||
|
validateEndpointsShape = endpoints:
|
||||||
|
if !builtins.isList endpoints then
|
||||||
|
throw "config/endpoints.nix must evaluate to a list of endpoint attrsets."
|
||||||
|
else
|
||||||
|
lib.imap0 validateEndpointShape endpoints;
|
||||||
|
|
||||||
|
validateUniqueHostPath = endpoints:
|
||||||
|
let
|
||||||
|
keyFor = endpoint: toString endpoint.listenPort;
|
||||||
|
keys = map keyFor endpoints;
|
||||||
|
in
|
||||||
|
if builtins.length keys == builtins.length (lib.unique keys) then
|
||||||
|
endpoints
|
||||||
|
else
|
||||||
|
throw "Duplicate listenPort detected in config/endpoints.nix.";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
getValidatedEndpoints = endpoints:
|
||||||
|
validateUniqueHostPath (validateEndpointsShape endpoints);
|
||||||
|
}
|
||||||
29
validation/secrets.nix
Normal file
29
validation/secrets.nix
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
let
|
||||||
|
lib = import <nixpkgs/lib>;
|
||||||
|
helperNames = [ "getSecretsConfig" ];
|
||||||
|
|
||||||
|
validateTree = context: node:
|
||||||
|
if builtins.isAttrs node then
|
||||||
|
lib.mapAttrs (name: value:
|
||||||
|
if lib.elem name helperNames then
|
||||||
|
value
|
||||||
|
else
|
||||||
|
validateTree "${context}.${name}" value
|
||||||
|
) node
|
||||||
|
else
|
||||||
|
throw "${context} must be an attrset at non-leaf level.";
|
||||||
|
|
||||||
|
getSecretsConfig = secretsConfig:
|
||||||
|
let
|
||||||
|
_ =
|
||||||
|
if builtins.isAttrs secretsConfig then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
throw "config/secrets.nix must evaluate to an attrset.";
|
||||||
|
__ = validateTree "config/secrets.nix" secretsConfig;
|
||||||
|
in
|
||||||
|
secretsConfig;
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
inherit getSecretsConfig validateTree;
|
||||||
|
}
|
||||||
10
validation/tunnel_ports.nix
Normal file
10
validation/tunnel_ports.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
let
|
||||||
|
net = import ../config/network.nix;
|
||||||
|
allowedPorts = net.tunnel.allowedPorts;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit allowedPorts;
|
||||||
|
|
||||||
|
isAllowedTunnelPort = port:
|
||||||
|
builtins.isInt port && builtins.elem port allowedPorts;
|
||||||
|
}
|
||||||
30
validation/web.nix
Normal file
30
validation/web.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
let
|
||||||
|
lib = import <nixpkgs/lib>;
|
||||||
|
|
||||||
|
validateFile = siteName: index: file:
|
||||||
|
let
|
||||||
|
_ = if builtins.isAttrs file then null else throw "web.${siteName}.files[${toString index}] must be an attrset.";
|
||||||
|
__ = if file ? path && builtins.isString file.path then null else throw "web.${siteName}.files[${toString index}].path must be a string.";
|
||||||
|
___ = if file ? content && builtins.isString file.content then null else throw "web.${siteName}.files[${toString index}].content must be a string.";
|
||||||
|
____ = if file ? contentType && builtins.isString file.contentType && file.contentType != "" then null else throw "web.${siteName}.files[${toString index}].contentType must be a non-empty string.";
|
||||||
|
_____ = if file ? status && builtins.isInt file.status then null else throw "web.${siteName}.files[${toString index}].status must be an int.";
|
||||||
|
in
|
||||||
|
file;
|
||||||
|
|
||||||
|
validateSite = siteName: site:
|
||||||
|
let
|
||||||
|
_ = if builtins.isAttrs site then null else throw "web.${siteName} must be an attrset.";
|
||||||
|
__ = if site ? files && builtins.isList site.files then null else throw "web.${siteName}.files must be a list.";
|
||||||
|
___ = lib.imap0 (index: file: validateFile siteName index file) site.files;
|
||||||
|
in
|
||||||
|
site;
|
||||||
|
|
||||||
|
getWebConfig = webConfig:
|
||||||
|
if builtins.isAttrs webConfig then
|
||||||
|
lib.mapAttrs validateSite webConfig
|
||||||
|
else
|
||||||
|
throw "config/web.nix must evaluate to an attrset.";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit getWebConfig;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue