commit fef2377502556d48fc56e8db7ee4048c1682ec10 Author: Fabian Hauser Date: Wed Oct 2 16:52:04 2024 +0300 Commit files for public release diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c923fff --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,53 @@ +name: CI + +on: + push: + pull_request: + +env: + ATTIC_AUTH_TOKEN: ${{ secrets.ATTIC_AUTH_TOKEN }} + +jobs: + build: + container: nixpkgs/nix-flakes:nixos-24.05 + steps: + - name: Initialize CI + uses: https://git.qo.is/qo.is/actions-nix-init@main + with: + token: ${{ secrets.CI_TOKEN }} + lfs: false + + - name: Add submodules to nix store to circumvent another nix bug + run: | + git clone https://git.qo.is/qo.is/infrastructure-private.git /tmp/private + cd /tmp/private + nix flake prefetch + + - name: Use attic cache + run: nix run .#cache use + + - name: Build + run: | + nix run .#cache watch & + nix build --max-jobs 12 --cores 12 + kill %1 + nix run .#cache push + + - name: Run Checks + run: | + nix run .#cache watch & + nix flake check + kill %1 + + - name: Deploy Docs + if: success() && github.ref == 'refs/heads/main' + run: | + nix run .#cache watch & + mkdir ~/.ssh/ + echo -e "Host lindberg-webapps.backplane.net.qo.is\n StrictHostKeyChecking no" >> ~/.ssh/config + (umask 0077 && printf "%s\n" "${{ secrets.SSH_DEPLOY_KEY }}" > ~/.ssh/id_ed25519) + # Remote build might be neccessary due to non-wheel nix users signing restrictions. + # However, the build should come from the cache anyway. + nix develop --command deploy --skip-checks --remote-build .#lindberg-webapps.\"docs-ops.qo.is\" + kill %1 + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7041d03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/configuration.nix +/result* +/host/*/result* +*.qcow2 +/.direnv +/book +/.sops.yaml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a3bd45d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "private"] + path = private + url = https://git.qo.is/qo.is/infrastructure-private.git + branch = main diff --git a/.nixd.json b/.nixd.json new file mode 100644 index 0000000..b1e6c19 --- /dev/null +++ b/.nixd.json @@ -0,0 +1,18 @@ +{ + "eval": { + "target": { + "args": ["-f", "default.nix"], + "installable": "" + } + }, + "formatting": { + "command": "nixfmt" + }, + "options": { + "enable": true, + "target": { + "args": [], + "installable": "" + } + } +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..fce5ab4 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "jnoortheen.nix-ide" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9239aa2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "nix.enableLanguageServer": true, + "nix.formatterPath": "nixfmt", + "nix.serverPath": "nixd", +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9730ef3 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# qo.is Infrastructure + +[This repository](https://gitlab.com/qo.is/infrastructure) contains the infrastructure configuration and documentation sources. + +Check out the current [rendered documentation on the deployed gitlab page](https://docs-ops.qo.is). + +## Structure + +`nixos-configurations`: Main nixos configuration for every host. +`defaults`: Configuration defaults +`modules`: Custom modules (e.g. for vpn and routers) + +## Building + +This repository requires [nix flakes](https://nixos.wiki/wiki/Flakes) + +- `nix build` + Build all host configurations and docs +- `nix build .#nixosConfigurations..config.system.build.toplevel` + Build a single host configuration with +- `nix build .#docs` + Build the documentation website + +## Development + +- `nix develop` + Development environment +- `nix flake check` + Execute the project's checks +- `nix fmt` + Autofix formatting + +### Working with the private submodule + +On changes: + +```bash +git add private +nix flake lock --update-input private +``` + +## Deployment + +`nix run .#deploy` + +See [Deployment](deployment.md) for details. + +## Secrets + +Secret management is done with [nix-sops](https://github.com/Mic92/sops-nix). + +Secrets are stored in `private/passwords.sops.yaml` (sysadmin passwords), +`private/nixos-configurations/secrets.sops.yaml` (shared secrets for all hosts) and +`private/nixos-configurations//secrets.sops.yaml` (host specific secrets). + +Usage: + +```bash +sops +sops-rekey +``` diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..d7925f2 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,33 @@ +# Summary + +- [Repository README](README.md) +- [Deployment](deploy/README.md) + +--- + +- [Network Topology](defaults/meta/network.md) +- [Hardware (generic)](defaults/hardware/README.md) +- [Backups](backups.md) +- [Updates](updates.md) +- [New Host Setup](nixos-configurations/setup.md) + + +# Services + +- [E-mail](email.md) +- [Git](nixos-modules/qois/git/README.md) +- [Nextcloud](defaults/nextcloud/README.md) +- [Nix Caches](nixos-configurations/lindberg-build/applications/README.md) +- [Static Pages](nixos-modules/qois/static-page/README.md) +- [VPN](defaults/vpn/README.md) +- [Vaultwarden](nixos-modules/vault/README.md) + +# Nixos Configurations + +- [calanda](nixos-configurations/calanda/README.md) +- [cyprianspitz](nixos-configurations/cyprianspitz/README.md) +- [fulberg](nixos-configurations/fulberg/README.md) +- [lindberg](nixos-configurations/lindberg/README.md) +- [stompert](nixos-configurations/stompert/README.md) +- [tierberg](nixos-configurations/tierberg/README.md) + diff --git a/backups.md b/backups.md new file mode 100644 index 0000000..07b9d15 --- /dev/null +++ b/backups.md @@ -0,0 +1,19 @@ +# Backups + +We use [borg](https://www.borgbackup.org/) to create encrypted and deduplicated backups. + +The backups are encrypted with a (key unlocked by a) secure passphrase that is deployed to the respective node and stored in the [pass repository](https://gitlab.com/qo.is/pass) resp. the sops files in this repository. + +Service specific restore instructions are given in the respective services' documentation. + +## Host Backups + +All hosts make automated backups. See Modules `qois.backup-client` and `qois.backup-server` for details. + +## Verify Backups Manually + +```bash +ssh root@lindberg-nextcloud.backplane.net.qo.is -- systemctl status borgbackup-job-system-cyprianspitz.service +ssh root@lindberg-webapps.backplane.net.qo.is -- systemctl status borgbackup-job-system-cyprianspitz.service +ssh root@lindberg.backplane.net.qo.is -- systemctl status borgbackup-job-system-cyprianspitz.service +``` diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..aecd1f8 --- /dev/null +++ b/book.toml @@ -0,0 +1,11 @@ +[book] +authors = ["qo.is contributers"] +language = "en" +multilingual = false +src = "." +title = "qo.is infrastructure docs" + +[preprocessor.cmdrun] + +[preprocessor.plantuml] +plantuml-cmd="plantuml" diff --git a/checks/default.nix b/checks/default.nix new file mode 100644 index 0000000..ae33fc0 --- /dev/null +++ b/checks/default.nix @@ -0,0 +1,23 @@ +{ + self, + system, + pkgs, + deployPkgs, + ... +}@inputs: +{ + ${system} = { + + # Check project formatting + format = pkgs.runCommand "nixfmt-check" { } '' + set -euo pipefail + cd ${self} + ${self.formatter.${system}}/bin/formatter . --check + mkdir $out + ''; + + #TODO(#29): Integration/System tests + + # Import deploy-rs tests + } // (deployPkgs.deploy-rs.lib.deployChecks self.deploy); +} diff --git a/defaults/backplane-net/README.md b/defaults/backplane-net/README.md new file mode 100644 index 0000000..e0ace3a --- /dev/null +++ b/defaults/backplane-net/README.md @@ -0,0 +1,5 @@ +## Backplane Overlay Network + +The `backplane.net.qo.is` overlay network connects all the hosts in a peer-to-peer fashion using [wgautomesh](https://git.deuxfleurs.fr/Deuxfleurs/wgautomesh). + +The definition of the connected hosts are in [defaults/meta/network-virtual.nix](../meta/network-virtual.nix). diff --git a/defaults/backplane-net/default.nix b/defaults/backplane-net/default.nix new file mode 100644 index 0000000..65137af --- /dev/null +++ b/defaults/backplane-net/default.nix @@ -0,0 +1,58 @@ +{ + pkgs, + lib, + config, + ... +}: +let + hostName = config.networking.hostName; + netName = "backplane"; + netConfig = config.qois.meta.network.virtual.${netName}; + hostNetConfig = netConfig.hosts.${hostName}; + wgDefaultPort = 51825; +in +{ + sops.secrets."wgautomesh/gossip-secret".restartUnits = [ "wgautomesh.service" ]; + + networking.wireguard.enable = true; + networking.wireguard.interfaces."wg-${netName}" = { + ips = [ "${hostNetConfig.v4.ip}/${builtins.toString netConfig.v4.prefixLength}" ]; + listenPort = if hostNetConfig.endpoint != null then hostNetConfig.endpoint.port else wgDefaultPort; + privateKeyFile = "/secrets/wireguard/private/${netName}"; + generatePrivateKeyFile = true; + }; + + systemd.network.wait-online.ignoredInterfaces = [ "wg-${netName}" ]; + + networking.firewall.allowedUDPPorts = + if hostNetConfig.endpoint != null then [ hostNetConfig.endpoint.port ] else [ wgDefaultPort ]; + + # Configure wgautomesh to setup peers. Make sure that the name is not used in the VPN module + services.wgautomesh = { + enable = true; + gossipSecretFile = builtins.toString config.sops.secrets."wgautomesh/gossip-secret".path; + openFirewall = true; + logLevel = "info"; + settings = { + interface = "wg-${netName}"; + + # Map meta network configuration to the format of wgautomesh and filter out peers with endpoints + peers = + let + reachableHosts = lib.filterAttrs ( + peerHostName: peerConfig: peerHostName != hostName # Not this host + ) netConfig.hosts; + in + lib.mapAttrsToList (_: peerConfig: { + address = peerConfig.v4.ip; + endpoint = + if peerConfig.endpoint != null then + with peerConfig.endpoint; "${fqdn}:${builtins.toString port}" + else + null; + pubkey = peerConfig.publicKey; + }) reachableHosts; + }; + }; + systemd.services.wgautomesh.requires = [ "wireguard-wg-backplane.service" ]; +} diff --git a/defaults/base-minimal/applications.nix b/defaults/base-minimal/applications.nix new file mode 100644 index 0000000..ed63b6a --- /dev/null +++ b/defaults/base-minimal/applications.nix @@ -0,0 +1,55 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + environment.systemPackages = + with pkgs; + [ + vim + tmux + killall + bc + rename + wipe + gnupg + ripgrep + ] + ++ [ + nix-index + nix-diff + ] + ++ [ + autojump + powerline-go + ] + ++ [ + # File Utilities + ack + unzip + iotop + tree + vim + vimPlugins.pathogen + vimPlugins.airline + git + git-lfs + ] + ++ [ + # Filesystem & Disk Utilities + parted + ] + ++ [ + # Networking Utilities + nmap + bind + curl + wget + rsync + iftop + mailutils + ]; +} diff --git a/defaults/base-minimal/default.nix b/defaults/base-minimal/default.nix new file mode 100644 index 0000000..4d530f9 --- /dev/null +++ b/defaults/base-minimal/default.nix @@ -0,0 +1,132 @@ +{ + config, + lib, + pkgs, + inputs, + ... +}: +{ + imports = [ + ./unfree.nix + ./applications.nix + ./overlays.nix + ./security.nix + ]; + + boot.loader.timeout = 2; + boot.tmp.useTmpfs = true; + boot.loader.grub.splashImage = null; + + console.keyMap = "de_CH-latin1"; + i18n.defaultLocale = "en_US.UTF-8"; + + boot.kernel.sysctl = { + "kernel.panic" = 20; # Reboot kernel on panic after this much seconds + }; + + boot.initrd.network.udhcpc.extraArgs = [ + "-A" + "900" # Wait for a DHCP lease on boot for 15mins + ]; + + systemd.watchdog = { + runtimeTime = "5m"; + rebootTime = "10m"; + }; + + users.mutableUsers = false; + users.users = { + root.openssh.authorizedKeys.keys = + with lib; + concatLists ( + mapAttrsToList ( + name: user: + if elem "wheel" user.extraGroups && name != "root" then user.openssh.authorizedKeys.keys else [ ] + ) config.users.users + ); + }; + + # Disable dependency on xorg + # TODO: Set environment.noXlibs on hosts that don't need any x libraries. + security.pam.services.su.forwardXAuth = lib.mkForce false; + + # Package management + nix = { + settings = { + trusted-users = [ + "root" + "@wheel" + ]; + substituters = [ + "https://${inputs.self.nixosConfigurations.lindberg-build.config.qois.nixpkgs-cache.hostname}?priority=39" + "https://cache.nixos.org?priority=40" + "https://attic.qo.is/qois-infrastructure" + ]; + trusted-public-keys = [ + "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + "qois-infrastructure:lh35ymN7Aoxm5Hz0S6JusxE+cYzMU+x9OMKjDVIpfuE=" + ]; + }; + gc = { + automatic = true; + dates = "weekly"; + options = "--delete-older-than 90d"; + }; + package = pkgs.nixFlakes; + extraOptions = '' + experimental-features = nix-command flakes + ''; + }; + + system.autoUpgrade = { + enable = true; + randomizedDelaySec = "30m"; + flags = [ + "--update-input" + "nixpkgs-nixos-2211" + "--commit-lock-file" + ]; + }; + + # Network services + networking.firewall = { + allowPing = true; + allowedTCPPorts = [ 22 ]; + }; + + services.openssh = { + enable = true; + settings.PasswordAuthentication = false; + + # temporary mitigation agains CVE-2024-6387 «regreSSHion» RCE + # See https://github.com/NixOS/nixpkgs/pull/323753#issuecomment-2199762128 + settings.LoginGraceTime = 0; + }; + + security.acme = { + acceptTerms = true; + defaults.email = "sysadmin@qo.is"; + }; + + # Default Settings + environment.etc = { + gitconfig.source = ./etc/gitconfig; + vimrc.source = ./etc/vimrc; + }; + + programs.autojump.enable = true; + programs.vim.defaultEditor = true; + + sops.defaultSopsFile = + let + defaultSopsPath = "${inputs.private}/nixos-configurations/${config.networking.hostName}/secrets.sops.yaml"; + in + lib.mkIf (builtins.pathExists defaultSopsPath) defaultSopsPath; + + services.fstrim.enable = true; + + qois.outgoing-server-mail.enable = true; + qois.backup-client.enable = true; + + systemd.extraConfig = "DefaultLimitNOFILE=4096"; +} diff --git a/defaults/base-minimal/etc/gitconfig b/defaults/base-minimal/etc/gitconfig new file mode 100644 index 0000000..1971e05 --- /dev/null +++ b/defaults/base-minimal/etc/gitconfig @@ -0,0 +1,31 @@ +[core] + packedGitWindowSize = 16m + packedGitLimit = 64m +[pack] + windowMemory = 64m + packSizeLimit = 64m + thread = 1 + deltaCacheSize = 1m +[color] + branch = auto + diff = auto + status = auto +[push] + default = simple +[pull] + rebase = true +[branch] + autosetuprebase = always +[commit] +# gpgsign = true +[tag] +# gpgsign = true + +[alias] + s = status --short --branch + a = add --patch + c = commit --message + l = log --color --graph --pretty=format:'%Cred%h%Creset - %C(bold)%s%Creset%C(yellow)%d%Creset %C(green)%an%Creset %C(cyan)%cr%Creset' --abbrev-commit + d = diff +[diff] +# noprefix = true diff --git a/defaults/base-minimal/etc/vimrc b/defaults/base-minimal/etc/vimrc new file mode 100644 index 0000000..fc66567 --- /dev/null +++ b/defaults/base-minimal/etc/vimrc @@ -0,0 +1,54 @@ + +" Use Vim settings, rather than Vi settings (much better!). +" This must be first, because it changes other options as a side effect. +" Avoid side effects when it was already reset. +if &compatible + set nocompatible +endif + +" Convenient command to see the difference between the current buffer and the +" file it was loaded from, thus the changes you made. +" Only define it when not defined already. +" Revert with: ":delcommand DiffOrig". +if !exists(":DiffOrig") + command DiffOrig vert new | set bt=nofile | r ++edit # | 0d_ | diffthis + \ | wincmd p | diffthis +endif + +" Don't wake up system with blinking cursor: +" http://www.linuxpowertop.org/known.php +let &guicursor = &guicursor . ",a:blinkon0" + + + + +"""""""""""""""""""""""""" +" Design Settings +"""""""""""""""""""""""""" +set background=dark +colorscheme elflord + +"""""""""""""""""""""""""" +" Other Settings +"""""""""""""""""""""""""" +set ignorecase " Ignore search case +set autoindent " Newline with automatic text indent +set ruler " Show current position +set pastetoggle= +set ignorecase +set hidden + +set splitbelow +set splitright + +set tabstop=2 +set shiftwidth=2 +set softtabstop=2 +set expandtab + +set listchars="eol:¬,tab:>·,trail:~,extends:>,precedes:<,space:␣" +set grepprg=ack\ -k + +filetype plugin indent on +syntax on + diff --git a/defaults/base-minimal/overlays.nix b/defaults/base-minimal/overlays.nix new file mode 100644 index 0000000..f89be8f --- /dev/null +++ b/defaults/base-minimal/overlays.nix @@ -0,0 +1,12 @@ +{ + config, + lib, + pkgs, + options, + ... +}: + +{ + nixpkgs.overlays = [ (import ../../overlays) ]; + nix.nixPath = options.nix.nixPath.default; +} diff --git a/defaults/base-minimal/security.nix b/defaults/base-minimal/security.nix new file mode 100644 index 0000000..a8a8801 --- /dev/null +++ b/defaults/base-minimal/security.nix @@ -0,0 +1,37 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; +{ + + # ########################################################################### + # Options taken from hardened kernel profile, see + # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix + # ########################################################################### + # Enable strict reverse path filtering (that is, do not attempt to route + # packets that "obviously" do not belong to the iface's network; dropped + # packets are logged as martians). + boot.kernel.sysctl."net.ipv4.conf.all.log_martians" = mkDefault true; + boot.kernel.sysctl."net.ipv4.conf.all.rp_filter" = mkDefault "1"; + boot.kernel.sysctl."net.ipv4.conf.default.log_martians" = mkDefault true; + boot.kernel.sysctl."net.ipv4.conf.default.rp_filter" = mkDefault "1"; + + # Ignore broadcast ICMP (mitigate SMURF) + boot.kernel.sysctl."net.ipv4.icmp_echo_ignore_broadcasts" = mkDefault true; + + # Ignore incoming ICMP redirects (note: default is needed to ensure that the + # setting is applied to interfaces added after the sysctls are set) + boot.kernel.sysctl."net.ipv4.conf.all.accept_redirects" = mkDefault false; + boot.kernel.sysctl."net.ipv4.conf.all.secure_redirects" = mkDefault false; + boot.kernel.sysctl."net.ipv4.conf.default.accept_redirects" = mkDefault false; + boot.kernel.sysctl."net.ipv4.conf.default.secure_redirects" = mkDefault false; + boot.kernel.sysctl."net.ipv6.conf.all.accept_redirects" = mkDefault false; + boot.kernel.sysctl."net.ipv6.conf.default.accept_redirects" = mkDefault false; + + # Ignore outgoing ICMP redirects (this is ipv4 only) + boot.kernel.sysctl."net.ipv4.conf.all.send_redirects" = mkDefault false; + boot.kernel.sysctl."net.ipv4.conf.default.send_redirects" = mkDefault false; +} diff --git a/defaults/base-minimal/unfree.nix b/defaults/base-minimal/unfree.nix new file mode 100644 index 0000000..4e751ae --- /dev/null +++ b/defaults/base-minimal/unfree.nix @@ -0,0 +1,22 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + nixpkgs.config.allowUnfreePredicate = + pkg: + builtins.elem (lib.getName pkg) [ + "corefonts" + "camingo-code" + "helvetica-neue-lt-std" + #"kochi-substitute-naga10" + "ttf-envy-code-r" + "vista-fonts" + "vista-fonts-chs" + "xkcd-font-unstable" + "ricty" + ]; +} diff --git a/defaults/base-vm/default.nix b/defaults/base-vm/default.nix new file mode 100644 index 0000000..c48196f --- /dev/null +++ b/defaults/base-vm/default.nix @@ -0,0 +1,39 @@ +{ + config, + lib, + modulesPath, + pkgs, + ... +}: + +{ + + imports = [ + ../base-minimal + (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.loader.grub.enable = true; + + system.autoUpgrade.allowReboot = true; + + services.qemuGuest.enable = true; + + boot.initrd.availableKernelModules = [ + "ahci" + "xhci_pci" + "sr_mod" + ]; + + # Taken from https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/minimal.nix + documentation.enable = lib.mkDefault false; + + documentation.doc.enable = lib.mkDefault false; + + documentation.info.enable = lib.mkDefault false; + + documentation.man.enable = lib.mkDefault false; + + documentation.nixos.enable = lib.mkDefault false; + +} diff --git a/defaults/base/applications.nix b/defaults/base/applications.nix new file mode 100644 index 0000000..2829400 --- /dev/null +++ b/defaults/base/applications.nix @@ -0,0 +1,32 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + environment.systemPackages = + with pkgs; + [ + pciutils + dmidecode + smartmontools + iw + efibootmgr + efitools + efivar + pwgen + powertop + lm_sensors + ] + ++ [ + # Filesystem & Disk Utilities + hdparm + smartmontools + ] + ++ [ + # Networking Utilities + tcpdump + ]; +} diff --git a/defaults/base/default.nix b/defaults/base/default.nix new file mode 100644 index 0000000..99a64fc --- /dev/null +++ b/defaults/base/default.nix @@ -0,0 +1,25 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + imports = [ + ../base-minimal + ./applications.nix + ]; + + # System Services + services.fwupd.enable = true; + + services.smartd = { + enable = true; + notifications.mail = { + enable = true; + mailer = "${pkgs.msmtp}/bin/sendmail"; + sender = "system@qo.is"; + recipient = "sysadmin@qo.is"; + }; + }; +} diff --git a/defaults/hardware/README.md b/defaults/hardware/README.md new file mode 100644 index 0000000..03a63cb --- /dev/null +++ b/defaults/hardware/README.md @@ -0,0 +1,15 @@ + +# APU + +## Setup + +To boot the nixos installer with the console port, add `console=ttyS0,115200n8` to the kernel command line in grub. + +# ASROCK Mainboards + +`F2`: Boot into BIOS +`F11`: Select boot device + +# NUC + +- [Boot Keybindings](https://www.intel.com/content/www/us/en/support/articles/000005672/boards-and-kits/desktop-boards.html) diff --git a/defaults/hardware/apu.nix b/defaults/hardware/apu.nix new file mode 100644 index 0000000..e4256dc --- /dev/null +++ b/defaults/hardware/apu.nix @@ -0,0 +1,38 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ + config, + lib, + pkgs, + modulesPath, + ... +}: + +{ + imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; + + boot.loader.grub.extraConfig = "\n serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1\n terminal_input serial\n terminal_output serial\n "; + boot.initrd.availableKernelModules = [ + "xhci_pci" + "ahci" + "ehci_pci" + "usbhid" + "usb_storage" + "sd_mod" + "sdhci_pci" + "igb" + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ + "kvm-amd" + "virtio" + "tun" + ]; + boot.extraModulePackages = [ ]; + boot.kernelParams = [ "console=ttyS0,115200n8" ]; + + # CPU Configuration + hardware.cpu.amd.updateMicrocode = true; + nix.settings.max-jobs = lib.mkDefault 4; +} diff --git a/defaults/hardware/apu1.nix b/defaults/hardware/apu1.nix new file mode 100644 index 0000000..9c6e00a --- /dev/null +++ b/defaults/hardware/apu1.nix @@ -0,0 +1,40 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ + config, + lib, + pkgs, + modulesPath, + ... +}: + +{ + imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; + + boot.loader.grub.extraConfig = "\n serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1\n terminal_input serial\n terminal_output serial\n "; + boot.initrd.availableKernelModules = [ + "ahci" + "ohci_pci" + "xhci_pci" + "ahci" + "ehci_pci" + "usbhid" + "usb_storage" + "sd_mod" + "sdhci_pci" + "r8169" + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ + "kvm-amd" + "virtio" + "tun" + ]; + boot.extraModulePackages = [ ]; + boot.kernelParams = [ "console=ttyS0,115200n8" ]; + + hardware.cpu.amd.updateMicrocode = true; + + nix.settings.max-jobs = lib.mkDefault 2; +} diff --git a/defaults/hardware/asrock-z790m.nix b/defaults/hardware/asrock-z790m.nix new file mode 100644 index 0000000..c90c220 --- /dev/null +++ b/defaults/hardware/asrock-z790m.nix @@ -0,0 +1,33 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ + config, + lib, + pkgs, + modulesPath, + ... +}: + +{ + imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; + + boot.initrd.availableKernelModules = [ + "xhci_pci" + "ahci" + "usbhid" + "usb_storage" + "sd_mod" + "e1000e" + "virtio-pci" + ]; + boot.initrd.kernelModules = [ ]; + # boot.kernelModules = [ "kvm-intel" "virtio" "tun" ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + # boot.kernelParams = [ "console=ttyS0,115200n8" ]; + + hardware.cpu.intel.updateMicrocode = true; + powerManagement.cpuFreqGovernor = "ondemand"; + nix.settings.max-jobs = lib.mkDefault 8; +} diff --git a/defaults/hardware/asrock.nix b/defaults/hardware/asrock.nix new file mode 100644 index 0000000..44b3f10 --- /dev/null +++ b/defaults/hardware/asrock.nix @@ -0,0 +1,27 @@ +{ + config, + lib, + pkgs, + modulesPath, + ... +}: +{ + imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; + + boot.initrd.availableKernelModules = [ + "nvme" + "usbhid" + "usb_storage" + "sd_mod" + "xhci_pci" + "ahci" + "virtio-pci" + "igb" + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-amd" ]; + boot.extraModulePackages = [ ]; + + hardware.cpu.amd.updateMicrocode = true; + nix.settings.max-jobs = lib.mkDefault 24; +} diff --git a/defaults/hardware/nuc.nix b/defaults/hardware/nuc.nix new file mode 100644 index 0000000..c90c220 --- /dev/null +++ b/defaults/hardware/nuc.nix @@ -0,0 +1,33 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ + config, + lib, + pkgs, + modulesPath, + ... +}: + +{ + imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; + + boot.initrd.availableKernelModules = [ + "xhci_pci" + "ahci" + "usbhid" + "usb_storage" + "sd_mod" + "e1000e" + "virtio-pci" + ]; + boot.initrd.kernelModules = [ ]; + # boot.kernelModules = [ "kvm-intel" "virtio" "tun" ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + # boot.kernelParams = [ "console=ttyS0,115200n8" ]; + + hardware.cpu.intel.updateMicrocode = true; + powerManagement.cpuFreqGovernor = "ondemand"; + nix.settings.max-jobs = lib.mkDefault 8; +} diff --git a/defaults/hardware/wle-regulatory-domain/ath10k-override-eeprom-regulatory-domain.patch b/defaults/hardware/wle-regulatory-domain/ath10k-override-eeprom-regulatory-domain.patch new file mode 100644 index 0000000..1b1f775 --- /dev/null +++ b/defaults/hardware/wle-regulatory-domain/ath10k-override-eeprom-regulatory-domain.patch @@ -0,0 +1,40 @@ +diff --unified --recursive --text archlinux-linux/drivers/net/wireless/ath/regd.c archlinux-linux-patched/drivers/net/wireless/ath/regd.c +--- a/drivers/net/wireless/ath/regd.c 2019-08-29 18:31:52.749909030 +0200 ++++ b/drivers/net/wireless/ath/regd.c 2019-08-29 18:33:33.318773763 +0200 +@@ -345,6 +345,8 @@ + struct ieee80211_channel *ch; + unsigned int i; + ++ return; ++ + for (band = 0; band < NUM_NL80211_BANDS; band++) { + if (!wiphy->bands[band]) + continue; +@@ -378,6 +380,8 @@ + { + struct ieee80211_supported_band *sband; + ++ return; ++ + sband = wiphy->bands[NL80211_BAND_2GHZ]; + if (!sband) + return; +@@ -407,6 +411,8 @@ + struct ieee80211_channel *ch; + unsigned int i; + ++ return; ++ + if (!wiphy->bands[NL80211_BAND_5GHZ]) + return; + +@@ -639,6 +645,9 @@ + const struct ieee80211_regdomain *regd; + + wiphy->reg_notifier = reg_notifier; ++ ++ return 0; ++ + wiphy->regulatory_flags |= REGULATORY_STRICT_REG | + REGULATORY_CUSTOM_REG; + diff --git a/defaults/hardware/wle-regulatory-domain/default.nix b/defaults/hardware/wle-regulatory-domain/default.nix new file mode 100644 index 0000000..fbf2cc7 --- /dev/null +++ b/defaults/hardware/wle-regulatory-domain/default.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + boot.kernelPatches = [ + { + name = "ath10k-override-eeprom-regulatory-domain"; + patch = ./ath10k-override-eeprom-regulatory-domain.patch; + extraConfig = '' + EXPERT y + CFG80211_CERTIFICATION_ONUS y + ATH_REG_DYNAMIC_USER_REG_HINTS y + ATH_REG_DYNAMIC_USER_CERT_TESTING y + ATH_REG_DYNAMIC_USER_CERT_TESTING y + ATH9K_DFS_CERTIFIED y + ATH10K_DFS_CERTIFIED y + ''; + } + ]; +} diff --git a/defaults/hardware/wle200nx.nix b/defaults/hardware/wle200nx.nix new file mode 100644 index 0000000..a8cf0f6 --- /dev/null +++ b/defaults/hardware/wle200nx.nix @@ -0,0 +1,11 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + services.hostapd.extraConfig = '' + ht_capab=[HT40-][HT40+][SHORT-GI-40][TX-STBC][RX-STBC1][DSSS_CCK-40] + ''; +} diff --git a/defaults/meta/default.nix b/defaults/meta/default.nix new file mode 100644 index 0000000..19bb5b6 --- /dev/null +++ b/defaults/meta/default.nix @@ -0,0 +1,13 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + imports = [ + ./hosts.nix + ./network-physical.nix + ./network-virtual.nix + ]; +} diff --git a/defaults/meta/hosts.json b/defaults/meta/hosts.json new file mode 100644 index 0000000..1befacf --- /dev/null +++ b/defaults/meta/hosts.json @@ -0,0 +1,44 @@ +{ + "fulberg": { + "hostName": "fulberg", + "sshKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDCG9qqpUOJ2RsohIqhMuw3YZZSrnPqhf5ayh5y0Cq/I" + }, + "calanda": { + "hostName": "calanda", + "sshKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKdoOZcFFRXIqEWqUnwCk/kqP8DZw6/4omDefCT6aNN4" + }, + "lindberg": { + "hostName": "lindberg", + "sshKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDksfXKLgPJVuWHAl/pxWRhghun8U6asTZNHa34u+gJw" + }, + "lindberg-nextcloud": { + "hostName": "lindberg-nextcloud", + "sshKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFR5U4yhZ2x/WN9dO+hVVSTCPMyv/1TB8mbuCXxexZOo" + }, + "lindberg-build": { + "hostName": "lindberg-build", + "sshKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMnDwwGiucyTI2U8o2rC53weJwp6dO8zcF7BZjkvVq7e" + }, + "lindberg-webapps": { + "hostName": "lindberg-webapps", + "sshKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJT99lj5OI+V1PlZl/T2ikBORwMiXjDfWpHYfq/GvUM5" + }, + "batzberg": { + "hostName": "batzberg" + }, + "tierberg": { + "hostName": "tierberg", + "sshKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJS2v0mUDJsNr1DHdgjxEQRnoVaEmExFfvHqpvagYLi6" + }, + "stompert": { + "hostName": "stompert", + "sshKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEKuqMPLbREFIrYcmReaRoHdz1TatpvlrZN14L6cikia" + }, + "router-coredump": { + "hostName": "router" + }, + "cyprianspitz": { + "hostName": "cyprianspitz", + "sshKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE4udYgCfxHEAkM9r8yaerk7l+BgW7039imM0moKpTbB" + } +} diff --git a/defaults/meta/hosts.nix b/defaults/meta/hosts.nix new file mode 100644 index 0000000..e1d4456 --- /dev/null +++ b/defaults/meta/hosts.nix @@ -0,0 +1,4 @@ +{ ... }: +{ + qois.meta.hosts = builtins.fromJSON (builtins.readFile ./hosts.json); +} diff --git a/defaults/meta/network-physical.nix b/defaults/meta/network-physical.nix new file mode 100644 index 0000000..3f20460 --- /dev/null +++ b/defaults/meta/network-physical.nix @@ -0,0 +1,114 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + qois.meta.network.physical = { + plessur-ext = { + v4 = { + id = "85.195.200.253"; + prefixLength = 24; + }; + v6 = { + id = "2a02:169:1e02::"; + prefixLength = 48; + }; + domain = "plessur-ext.net.qo.is"; + hosts = { + calanda = { + v4.ip = "85.195.200.253"; + }; + }; + }; + + plessur-dmz = { + v4 = { + id = "10.1.2.0"; + prefixLength = 24; + gateway = "10.1.2.1"; + nameservers = [ "10.1.2.1" ]; + }; + domain = "plessur-dmz.net.qo.is"; + + hosts = { + calanda = { + v4.ip = "10.1.2.1"; + }; + fulberg = { + v4.ip = "10.1.2.2"; + }; + }; + }; + + plessur-lan = { + v4 = { + id = "10.1.1.0"; + prefixLength = 24; + }; + domain = "plessur-lan.net.qo.is"; + + hosts = { + calanda = { + v4.ip = "10.1.1.1"; + }; + }; + }; + + eem-lan = { + domain = "eem-lan.net.qo.is"; + hosts = { + stompert.v4.ip = ""; # TODO + }; + }; + + riedbach-ext = { + # IP: Dynamic + domain = "riedbach-ext.net.qo.is"; + + hosts = { + lindberg = { + # TODO: This is the router, not really lindberg. + v4.ip = "145.40.194.243"; + }; + }; + }; + + lattenbach-ext = { + # Forwarded ports: + # udp:51820 -> 10.0.0.60:51820 + # tcp:51022 -> 10.0.0.60:22 + # tcp:51023 -> 10.0.0.60:2222 + domain = "lattenbach-ext.net.qo.is"; + hosts.router-coredump.v4.ip = "5.226.148.126"; + }; + + lattenbach-lan = { + # Coredump LAN + v4 = { + id = "10.0.0.0"; + prefixLength = 16; + }; + domain = "lattenbach-lan.net.qo.is"; + hosts = { + tierberg = { + v4.ip = "10.0.0.60"; + }; + }; + }; + + lattenbach-nas = { + # Coredump net between apu and nas + v4 = { + id = "192.168.254.0"; + prefixLength = 24; + }; + domain = "lattenbach-nas.net.qo.is"; + hosts = { + tierberg.v4.ip = "192.168.254.2"; + batzberg.v4.ip = "192.168.254.1"; + }; + }; + }; +} diff --git a/defaults/meta/network-virtual.nix b/defaults/meta/network-virtual.nix new file mode 100644 index 0000000..f0b8b3d --- /dev/null +++ b/defaults/meta/network-virtual.nix @@ -0,0 +1,114 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + qois.meta.network.virtual = + let + physical-network = config.qois.meta.network.physical; + in + { + vpn = { + v4 = { + id = "100.64.0.0"; + prefixLength = 10; + }; + domain = "vpn.qo.is"; + hosts = { }; + }; + + backplane = { + v4 = { + id = "10.250.0.0"; + prefixLength = 24; + }; + domain = "backplane.net.qo.is"; + + hosts = { + fulberg = { + v4.ip = "10.250.0.1"; + endpoint = { + fqdn = physical-network.plessur-ext.hosts.calanda.fqdn; + port = 51821; + }; + publicKey = "xcQOu+pp4ckNygcsLmJL1NmUzbbC+k3I7y+hJ9Ul4nk="; + persistentKeepalive = 25; + }; + lindberg = { + v4.ip = "10.250.0.2"; + #endpoint = { # TODO: Port forwarding + # fqdn = physical-network.riedbach-ext.hosts.lindberg.fqdn; + # port = 51821; + #}; + publicKey = "uxxdpFXSTnfTvzSEzrUq4DuWSILJD5tNj6ks2jhWF10="; + persistentKeepalive = 25; # TODO: Remove when port forwarding enabled + }; + lindberg-nextcloud = { + v4.ip = "10.250.0.3"; + publicKey = "6XGL4QKB8AMpm/VGcTgWqk9RiSws7DmY5TpIDkXbwlg="; + persistentKeepalive = 25; + }; + tierberg = { + v4.ip = "10.250.0.4"; + publicKey = "51j1l+pT9W61wx4y2KyUb1seLdCHs3FUKAjmrHBFz1w="; + persistentKeepalive = 25; + }; + stompert = { + v4.ip = "10.250.0.5"; + publicKey = "CHTjQbmN9WhbRCxKgowxpMx4c5Zu0NDk0rRXEvuB3XA="; + persistentKeepalive = 25; + }; + calanda = { + v4.ip = "10.250.0.6"; + publicKey = "WMuMCzo8e/aNeGP7256mhK0Fe+x06Ws7a9hOZDPCr0M="; + endpoint = { + fqdn = physical-network.plessur-ext.hosts.calanda.fqdn; + port = 51823; + }; + }; + lindberg-build = { + v4.ip = "10.250.0.7"; + publicKey = "eWuvGpNVl601VDIgshOm287dlZa/5gF9lL4SjYEbIG8="; + persistentKeepalive = 25; + }; + lindberg-webapps = { + v4.ip = "10.250.0.8"; + publicKey = "LOA3Kumg8FV4DJxONwv+/8l/jOQLJ6SD2k/RegerR04="; + persistentKeepalive = 25; + }; + cyprianspitz = { + v4.ip = "10.250.0.9"; + endpoint = { + fqdn = physical-network.plessur-ext.hosts.calanda.fqdn; + port = 51824; + }; + publicKey = "iLzHSgIwZz44AF7961mwEbK9AnSwcr+aKpd7XAAVTHo="; + }; + }; + }; + + lindberg-vms-nat = { + v4 = { + id = "10.247.0.0"; + prefixLength = 24; + }; + domain = "lindberg-vms-nat.net.qo.is"; + hosts = { + lindberg.v4.ip = "10.247.0.1"; + }; + }; + + cyprianspitz-vms-nat = { + v4 = { + id = "10.247.0.0"; + prefixLength = 24; + }; + domain = "cyprianspitz-vms-nat.net.qo.is"; + hosts = { + cyprianspitz.v4.ip = "10.248.0.1"; + }; + }; + }; +} diff --git a/defaults/meta/network.md b/defaults/meta/network.md new file mode 100644 index 0000000..3015417 --- /dev/null +++ b/defaults/meta/network.md @@ -0,0 +1,83 @@ +# Network + +This document provides an overview over the qo.is network structure. + +## Physical View + +```plantuml +@startuml +skinparam style strictuml +left to right direction + +package "plessur.net.qo.is" { + + entity mediaconvchur [ + Media + Converter + (Passive) + ] + + node calanda + node fulberg + + cloud plessurnet [ + LAN Plessur + ] + + mediaconvchur - "enp4" calanda + calanda "br0 (enp2, wlp1, wlp5)" --- plessurnet + calanda "enp4" -- "eno1" fulberg +} + +package "riedbach.net.qo.is" { + node riedbachrouter + + node lindberg + + riedbachrouter -- "enp5s0" lindberg +} + +package "eem.net.qo.is" { + node eemrouter + + node stompert + + eemrouter -- "enp2s0" stompert +} + +cloud internet[ +@ +] + +package "coredump.net.qo.is" { + node coredumprouter + + node tierberg + + coredumprouter -- "enpXs0" tierberg +} + +internet .. mediaconvchur: INIT7 Fiber (1G/1G) +internet .. riedbachrouter: iway Fiber (1G/1G) +internet .. eemrouter: KPN NL Fiber +internet .. coredumprouter: Openfactory DSL +@enduml +``` + +## DNS + +All Services are published under the *qo.is* domain name. Following services are available: + +`qo.is` Primery Domain - Redirect to docs.qo.is and some .well-known ressources + +{{#include ../backplane-net/README.md}} + +## Contacts + + +### Init7 + +- [Status Netzwerkdienste](https://www.init7.net/status/) +- [NOC E-Mail](mailto:noc@init7.net) +- +41 44 315 44 00 +- Init7 (Schweiz) AG, Technoparkstrasse 5, CH-8406 Winterthur diff --git a/defaults/nextcloud/README.md b/defaults/nextcloud/README.md new file mode 100644 index 0000000..0e135ad --- /dev/null +++ b/defaults/nextcloud/README.md @@ -0,0 +1,17 @@ +# Nextcloud + +Running on [cloud.qo.is](https://cloud.qo.is), contact someone from the board for administrative tasks. + +At this time, we do not enforce any size limits or alike. + +We have some globally configured shared folders for our family members. + +For user documentation, refer to the [upstream Nextcloud docs](https://docs.nextcloud.com/server/stable/user_manual/en/). Clients can be downloaded from [nextcloud.com/install](https://nextcloud.com/install/). + +## Backup / Restore + +1. Stop all related services: nextcloud, php-fpm, redis etc. +2. (mabe dump redis data?) +3. Import Database Backup +4. Restore `/var/lib/nextcloud`, which is currently a bind mount on `lindberg`'s `/mnt/data` volume +5. Resync nextcloud files and database, see [nextcloud docs](https://docs.nextcloud.com/server/latest/admin_manual/maintenance/restore.html) diff --git a/defaults/nextcloud/default.nix b/defaults/nextcloud/default.nix new file mode 100644 index 0000000..db21a58 --- /dev/null +++ b/defaults/nextcloud/default.nix @@ -0,0 +1,81 @@ +# Default configuration for hosts +{ + config, + lib, + pkgs, + ... +}: + +{ + + sops.secrets."nextcloud/admin" = with config.users.users.nextcloud; { + inherit group; + owner = name; + }; + + qois.postgresql.enable = true; + qois.backup-client.includePaths = [ config.services.nextcloud.home ]; + + services.nextcloud = { + enable = true; + https = true; + webfinger = true; + maxUploadSize = "10G"; + + database.createLocally = true; + + config = { + adminpassFile = config.sops.secrets."nextcloud/admin".path; + adminuser = "root"; + dbtype = "pgsql"; + }; + + phpOptions = { + "opcache.interned_strings_buffer" = "23"; + }; + + poolSettings = { + "pm" = "dynamic"; + "pm.max_children" = "256"; + "pm.max_requests" = "500"; + "pm.max_spare_servers" = "16"; + "pm.min_spare_servers" = "2"; + "pm.start_servers" = "8"; + }; + + configureRedis = true; + caching.redis = true; + + notify_push = { + enable = true; + bendDomainToLocalhost = true; + }; + + settings = { + log_type = "syslog"; + syslog_tag = "nextcloud"; + "memories.exiftool" = "${lib.getExe pkgs.exiftool}"; + "memories.vod.ffmpeg" = "${lib.getExe pkgs.ffmpeg-headless}"; + "memories.vod.ffprobe" = "${pkgs.ffmpeg-headless}/bin/ffprobe"; + preview_ffmpeg_path = "${lib.getExe pkgs.ffmpeg-headless}"; + mail_smtpmode = "sendmail"; + mail_domain = "qo.is"; + }; + }; + + services.phpfpm.pools.nextcloud.settings = { + "pm.max_children" = lib.mkForce "256"; + "pm.max_spare_servers" = lib.mkForce "16"; + "pm.start_servers" = lib.mkForce "8"; + }; + + users.users.nextcloud.extraGroups = [ "postdrop" ]; + + systemd.services.nextcloud-cron = { + path = [ pkgs.perl ]; + }; + + environment.systemPackages = with pkgs; [ + nodejs # required for Recognize + ]; +} diff --git a/defaults/vpn/README.md b/defaults/vpn/README.md new file mode 100644 index 0000000..962d94d --- /dev/null +++ b/defaults/vpn/README.md @@ -0,0 +1,122 @@ +# VPN + +On [vpn.qo.is](https://vpn.qo.is) we run a [Tailscale](https://tailscale.com) compatible VPN service. To use the service, you can use a normal Tailscale client with following additional configuration: + +| Option | Recommended value | Description | +|--------|-------------------|-------------| +| `accept-routes` | enabled (flag) | Accept direct routes to internal services | +| `exit-node` | `100.64.0.5` (lindberg) or `100.64.0.6` (cypriaspitz) | Use host as [exit node](#exit-nodes) | +| `login-server` | `https://vpn.qo.is` | Use our own VPN service and not tailscale's upstream one | + + +⚠️ Currently, if the client is in an IPv6 network, the transport is broken. See [#51](https://gitlab.com/qo.is/infrastructure/-/issues/51) for progress on this. + +## Exit nodes + +- `100.64.0.5`: lindberg (riedbach-net) +- `100.64.0.6`: cyprianspitz (plessur-net) + +Currently, name resolution for these do not work reliably on first starts, hence the IP must be used. This hould be fixed in the future. + +## User and Client Management + +To register a new client, you can generate a pre-auth key and insert it in the client: + +```bash +headscale preauthkeys create --user marlene.mayer +``` + +Or alternatively use the register command shown when configuring the VPN client. + +## ACL + +At this time, there are a few ACL rules to isolate a users host but do not expect them to be expected to be enforced - expect your client to be accessible by the whole network. + +## Exit Nodes + +To add an exit node, create a preauth secret on the `vpn.qo.is` host: + +```bash +headscale preauthkeys create --user srv --reusable +``` + +and configure the host as follows: + +```nix +# TODO: This should not be a snipped but a module + +{config, ...}: { + # Use this node as vpn exit node + services.tailscale = let meta = config.qois.meta; in { + enable = true; + openFirewall = true; + useRoutingFeatures = "server"; + authKeyFile = "/secrets/wireguard/tailscale-key"; # The preauth secret. TODO: Should be in sops. + extraUpFlags = [ + "--login-server=https://vpn.qo.is" + "--advertise-exit-node" + ( + with meta.network.virtual.backplane.v4; "--advertise-routes=${id}/${builtins.toString prefixLength}" + ) + "--advertise-tags=tag:srv" + ]; + }; +} +``` + +and register it in Headscale with: + +```bash +headscale nodes register -u srv -k nodekey:xyzxyzxyzxyzxyzxyzxyzxyz +``` + +With using the `srv` user, exit nodes and routes get automatically accepted as trusted. + +## Clients + +### NixOS + +Sample config: + +```nix +{ config, pkgs, ... }: { + services.tailscale = { + enable = true; + openFirewall = true; + useRoutingFeatures = "client"; + authKeyFile = "/secrets/wireguard/tailscale-key"; # This is the pre-auth secret. Make sure it's only accessible by root. + extraUpFlags = [ + "--operator" + "yourUserNameChangePlease" + "--accept-routes" + "--exit-node=100.64.0.5" + "--login-server=https://vpn.qo.is" + ]; + }; +} +``` + +### Mobile App + +> Android App: Tip 5 times on the tooltip dots to reveal server config option + +See [this Headscale documentation for more](https://headscale.net/android-client/#configuring-the-headscale-url) on how to configure the mobile app. Note that on restarts, sometimes you have to reopen/save the config dialog. If the Tailscale login site is shown, just close the browser with the ❌. + + +## Backup and Restore + +### Server + +1. `systemctl stop headscale` +2. Replace `/var/lib/headscale` +3. `systemctl start headscale` +4. Monitor logs for errors + +Note: `/var/lib/headscale` contains a sqlite database. + +### Clients + +1. `systemctl stop tailscaled` +2. Replace `/var/lib/tailscale` +3. `systemctl start tailscaled` +4. Monitor logs for errors diff --git a/defaults/webserver/default.nix b/defaults/webserver/default.nix new file mode 100644 index 0000000..2aa4aaf --- /dev/null +++ b/defaults/webserver/default.nix @@ -0,0 +1,18 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + services.nginx = { + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedGzipSettings = true; + recommendedBrotliSettings = true; + logError = "stderr warn"; + proxyResolveWhileRunning = true; + }; +} diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..b7a365e --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,29 @@ +# Deployment + +Note that you have to be connected to the `vpn.qo.is` +(or execute the deployment from a host that is in the `backplane.net.qo.is` overlay network) +and that you need to have SSH root access to the target machines. + + + +#### Deploy to all hosts + +```bash +nix run .#deploy-qois +``` + + +#### Deploy to selected target hosts + +```bash +nix run .#deploy-qois .# .# + +# e.g. +nix run .#deploy-qois .#fulberg +``` + +#### Deploy with extended timeouts (sometimes required for slow APU devices) + +```bash +nix run .#deploy-qois .#calanda -- --confirm-timeout 600 --activation-timeout 600 +``` diff --git a/deploy/default.nix b/deploy/default.nix new file mode 100644 index 0000000..5fb1a86 --- /dev/null +++ b/deploy/default.nix @@ -0,0 +1,12 @@ +{ + deployPkgs, + pkgs, + self, + ... +}@params: +with pkgs.lib; +pipe ./. [ + self.lib.loadSubmodulesFrom + (map (f: (import f params))) + (foldl recursiveUpdate { }) +] diff --git a/deploy/docs-ops/default.nix b/deploy/docs-ops/default.nix new file mode 100644 index 0000000..01c25d1 --- /dev/null +++ b/deploy/docs-ops/default.nix @@ -0,0 +1,17 @@ +{ + deployPkgs, + pkgs, + self, + system, + ... +}: +let + domain = "docs-ops.qo.is"; +in +{ + nodes.lindberg-webapps.profiles."${domain}" = { + sshUser = "nginx-${domain}"; + path = deployPkgs.deploy-rs.lib.activate.noop self.packages.${system}.docs; + profilePath = "/var/lib/nginx-${domain}/root"; + }; +} diff --git a/deploy/system/default.nix b/deploy/system/default.nix new file mode 100644 index 0000000..cdaf846 --- /dev/null +++ b/deploy/system/default.nix @@ -0,0 +1,20 @@ +{ + deployPkgs, + pkgs, + self, + system, + ... +}: +{ + nodes = pkgs.lib.mapAttrs (host: config: { + hostname = "${host}.backplane.net.qo.is"; + profiles.system = { + sshUser = "root"; + user = "root"; + activationTimeout = 420; + confirmTimeout = 120; + + path = deployPkgs.deploy-rs.lib.activate.nixos config; + }; + }) self.nixosConfigurations; +} diff --git a/dev-shells/default.nix b/dev-shells/default.nix new file mode 100644 index 0000000..ba8c83a --- /dev/null +++ b/dev-shells/default.nix @@ -0,0 +1,56 @@ +{ + pkgs, + system, + self, + ... +}: +{ + ${system}.default = pkgs.mkShell { + name = "qois-infrastructure-shell"; + buildInputs = + let + vscode-with-extensions = pkgs.vscode-with-extensions.override { + vscodeExtensions = with pkgs.vscode-extensions; [ jnoortheen.nix-ide ]; + vscode = pkgs.vscodium; + }; + in + [ vscode-with-extensions ] + ++ (with self.packages.${system}; [ + cache + deploy-qois + sops + sops-rekey + ]) + ++ (with pkgs; [ + attic-client + deploy-rs + nixd + nixfmt-rfc-style + nixos-anywhere + ssh-to-age + pssh + yq + jq + ]); + LANG = "C.UTF-8"; + LC_ALL = "C.UTF-8"; + shellHook = '' + # Bring xdg data dirs of dependencies and current program into the + # environment. This will allow us to get shell completion if any + # and there might be other benefits as well. + xdg_inputs=( "''${buildInputs[@]}" ) + for p in "''${xdg_inputs[@]}"; do + if [[ -d "$p/share" ]]; then + XDG_DATA_DIRS="''${XDG_DATA_DIRS}''${XDG_DATA_DIRS+:}$p/share" + fi + done + export XDG_DATA_DIRS + + # Make sure we support the pure case as well as non nixos cases + # where dynamic bash completions were not sourced. + #if ! type _completion_loader > /dev/null; then + # . ${pkgs.bash-completion}/etc/profile.d/bash_completion.sh + #fi + ''; + }; +} diff --git a/email.md b/email.md new file mode 100644 index 0000000..902badc --- /dev/null +++ b/email.md @@ -0,0 +1,20 @@ +# E-mail + +Currently, we don't host our own e-mail services, but we with a [cyon](https://www.cyon.ch) webhosting. + +The login for the [cyon admin panel](https://my.cyon.ch/) may be found in pass. + +E-Mail accounts should be created in a `first.lastname@qo.is` fashion. + +Alias/forwarding Domains may be added on an best effort basis. +Bills for these domains should go directly to the respective owner (i.e. should be registered with own accounts). + + +## System E-mails + +For groups, systems, services that require e-mail access, other accounts may be created. + +- Engineering receives mails on `sysadmin@qo.is` +- Servers send from `system@qo.is` +- `no-reply@qo.is` is blackholed and may be used by services that don't handle replies. +- Services include e.g. `vault@qo.is` for vaultwarden. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..55ab6ad --- /dev/null +++ b/flake.lock @@ -0,0 +1,309 @@ +{ + "nodes": { + "attic": { + "inputs": { + "crane": "crane", + "flake-compat": "flake-compat", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1726069220, + "narHash": "sha256-dAUWlC8uMJX9iovycfvJcg5nm3PzqJIRAOwN4z322zM=", + "owner": "zhaofengli", + "repo": "attic", + "rev": "416687e59c4f0b32742423458cab2c5ff8fe748a", + "type": "github" + }, + "original": { + "owner": "zhaofengli", + "repo": "attic", + "type": "github" + } + }, + "crane": { + "inputs": { + "nixpkgs": [ + "attic", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1722960479, + "narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=", + "owner": "ipetkov", + "repo": "crane", + "rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "deploy-rs": { + "inputs": { + "flake-compat": "flake-compat_2", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + }, + "locked": { + "lastModified": 1718194053, + "narHash": "sha256-FaGrf7qwZ99ehPJCAwgvNY5sLCqQ3GDiE/6uLhxxwSY=", + "owner": "serokell", + "repo": "deploy-rs", + "rev": "3867348fa92bc892eba5d9ddb2d7a97b9e127a8a", + "type": "github" + }, + "original": { + "owner": "serokell", + "repo": "deploy-rs", + "type": "github" + } + }, + "disko": { + "inputs": { + "nixpkgs": [ + "nixpkgs-nixos-stable" + ] + }, + "locked": { + "lastModified": 1726396892, + "narHash": "sha256-KRGuT5nGRAOT3heigRWg41tbYpTpapGhsWc+XjnIx0w=", + "owner": "nix-community", + "repo": "disko", + "rev": "51e3a7e51279fedfb6669a00d21dc5936c78a6ce", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "disko", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "attic", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1722555600, + "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8471fe90ad337a8074e957b69ca4d0089218391d", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1726042813, + "narHash": "sha256-LnNKCCxnwgF+575y0pxUdlGZBO/ru1CtGHIqQVfvjlA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "159be5db480d1df880a0135ca0bfed84c2f88353", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-nixos-stable": { + "locked": { + "lastModified": 1726320982, + "narHash": "sha256-RuVXUwcYwaUeks6h3OLrEmg14z9aFXdWppTWPMTwdQw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8f7492cce28977fbf8bd12c72af08b1f6c7c3e49", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-nixos-unstable": { + "locked": { + "lastModified": 1726243404, + "narHash": "sha256-sjiGsMh+1cWXb53Tecsm4skyFNag33GPbVgCdfj3n9I=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "345c263f2f53a3710abe117f28a5cb86d0ba4059", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1724316499, + "narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1702272962, + "narHash": "sha256-D+zHwkwPc6oYQ4G3A1HuadopqRwUY/JkMwHz1YF7j4Q=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e97b3e4186bcadf0ef1b6be22b8558eab1cdeb5d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "private": { + "inputs": { + "nixpkgs-nixos-unstable": [ + "nixpkgs-nixos-unstable" + ] + }, + "locked": { + "lastModified": 1727557927, + "narHash": "sha256-+dTv85ZXAatKiCu5VKTQkFE/RmWdlXwkuPvjOmfcPBI=", + "ref": "refs/heads/main", + "rev": "9a646336c5ad419ec79ae81a47d68213bdcbff92", + "revCount": 5, + "type": "git", + "url": "file:./private" + }, + "original": { + "type": "git", + "url": "file:./private" + } + }, + "root": { + "inputs": { + "attic": "attic", + "deploy-rs": "deploy-rs", + "disko": "disko", + "nixpkgs-nixos-stable": "nixpkgs-nixos-stable", + "nixpkgs-nixos-unstable": "nixpkgs-nixos-unstable", + "private": "private", + "sops-nix": "sops-nix" + } + }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs-nixos-unstable" + ], + "nixpkgs-stable": [ + "nixpkgs-nixos-stable" + ] + }, + "locked": { + "lastModified": 1726218807, + "narHash": "sha256-z7CoWbSOtsOz8TmRKDnobURkKfv6nPZCo3ayolNuQGc=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "f30b1bac192e2dc252107ac8a59a03ad25e1b96e", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..98f5cca --- /dev/null +++ b/flake.nix @@ -0,0 +1,63 @@ +{ + description = "qo.is infrastructure: Host and Network Configuration"; + nixConfig = { + extra-substituters = "https://attic.qo.is/qois-infrastructure"; + extra-trusted-public-keys = "qois-infrastructure:lh35ymN7Aoxm5Hz0S6JusxE+cYzMU+x9OMKjDVIpfuE="; + }; + inputs = { + attic.url = "github:zhaofengli/attic"; + deploy-rs.url = "github:serokell/deploy-rs"; + disko = { + url = "github:nix-community/disko"; + inputs.nixpkgs.follows = "nixpkgs-nixos-stable"; + }; + nixpkgs-nixos-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs-nixos-stable.url = "github:NixOS/nixpkgs/nixos-24.05"; + sops-nix = { + url = "github:Mic92/sops-nix"; + inputs = { + nixpkgs.follows = "nixpkgs-nixos-unstable"; + nixpkgs-stable.follows = "nixpkgs-nixos-stable"; + }; + }; + private.url = "git+file:./private"; + private.inputs.nixpkgs-nixos-unstable.follows = "nixpkgs-nixos-unstable"; + }; + + outputs = + { nixpkgs-nixos-unstable, deploy-rs, ... }@inputs: + let + system = "x86_64-linux"; + # Packages for development and build process + pkgs = import nixpkgs-nixos-unstable { inherit system; }; + deployPkgs = import nixpkgs-nixos-unstable { + inherit system; + overlays = [ + deploy-rs.overlay + (self: super: { + deploy-rs = { + inherit (pkgs) deploy-rs; + lib = super.deploy-rs.lib; + }; + }) + ]; + }; + importParams = inputs // { + inherit pkgs; + inherit deployPkgs; + inherit system; + }; + in + { + checks = import ./checks/default.nix importParams; + deploy = import ./deploy/default.nix importParams; + devShells = import ./dev-shells/default.nix importParams; + formatter.${system} = pkgs.writeShellScriptBin "formatter" '' + ${pkgs.findutils}/bin/find $1 -type f -name '*.nix' -exec ${pkgs.nixfmt-rfc-style}/bin/nixfmt ''${@:2} {} + + ''; + nixosConfigurations = import ./nixos-configurations/default.nix importParams; + nixosModules = import ./nixos-modules/default.nix importParams; + packages = import ./packages/default.nix importParams; + lib = import ./lib/default.nix importParams; + }; +} diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 0000000..404d93e --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,18 @@ +{ pkgs, ... }: +let + lib = pkgs.lib; + foldersWithNix = + path: + let + folders = lib.attrNames (lib.filterAttrs (n: t: t == "directory") (builtins.readDir path)); + isFolderWithDefaultNix = folder: lib.pathExists (lib.path.append path "./${folder}/default.nix"); + in + lib.filter isFolderWithDefaultNix folders; + +in +{ + inherit foldersWithNix; + + loadSubmodulesFrom = + path: map (folder: lib.path.append path "./${folder}/default.nix") (foldersWithNix path); +} diff --git a/nixos-configurations/calanda/README.md b/nixos-configurations/calanda/README.md new file mode 100644 index 0000000..e5ccea0 --- /dev/null +++ b/nixos-configurations/calanda/README.md @@ -0,0 +1 @@ +# calanda diff --git a/nixos-configurations/calanda/default.nix b/nixos-configurations/calanda/default.nix new file mode 100644 index 0000000..94e3ffa --- /dev/null +++ b/nixos-configurations/calanda/default.nix @@ -0,0 +1,21 @@ +{ config, pkgs, ... }: + +{ + imports = [ + ./networking.nix + ./filesystems.nix + + ../../defaults/hardware/apu.nix + + ../../defaults/base + ../../defaults/meta + ]; + + # This value determines the NixOS release from which the default + # settings for stateful data, like fi:le locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "23.05"; # Did you read the comment? +} diff --git a/nixos-configurations/calanda/filesystems.nix b/nixos-configurations/calanda/filesystems.nix new file mode 100644 index 0000000..ecb21b4 --- /dev/null +++ b/nixos-configurations/calanda/filesystems.nix @@ -0,0 +1,20 @@ +{ config, pkgs, ... }: +{ + + fileSystems."/" = { + device = "/dev/disk/by-uuid/16efc5db-0697-4f39-b64b-fc18ac318625"; + fsType = "btrfs"; + options = [ + "defaults" + "subvol=nixos" + "noatime" + ]; + }; + + swapDevices = [ { device = "/dev/disk/by-uuid/b5104a7c-4a4a-4048-a9f8-44ddb0082632"; } ]; + + boot.loader.grub = { + enable = true; + device = "/dev/sda"; + }; +} diff --git a/nixos-configurations/calanda/networking.nix b/nixos-configurations/calanda/networking.nix new file mode 100644 index 0000000..e909427 --- /dev/null +++ b/nixos-configurations/calanda/networking.nix @@ -0,0 +1,118 @@ +{ config, pkgs, ... }: + +let + meta = config.qois.meta; + plessur-dmz-net = meta.network.physical.plessur-dmz; + plessur-lan-net = meta.network.physical.plessur-lan; + getCalandaIp4 = net: net.hosts.calanda.v4.ip; +in +{ + imports = [ ../../defaults/backplane-net ]; + + networking.hostName = meta.hosts.calanda.hostName; + networking.domain = "ilanz.fh2.ch"; + networking.enableIPv6 = false; # TODO + + networking.useDHCP = false; + networking.interfaces.enp4s0.useDHCP = true; + + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; + + networking.interfaces.enp3s0 = { + ipv4.addresses = [ + { + inherit (plessur-dmz-net.v4) prefixLength; + address = getCalandaIp4 plessur-dmz-net; + } + ]; + }; + + # TODO: Metaize ips + services.qois.router = { + enable = true; + wanInterface = "enp4s0"; + wirelessInterfaces = [ "wlp5s0" ]; + lanInterfaces = [ "enp2s0" ]; + internalRouterIP = getCalandaIp4 plessur-lan-net; + dhcp = { + enable = true; + localDomain = "ilanz.fh2.ch"; # TODO: Legacy hostname + dhcpRange = "10.1.1.2,10.1.1.249"; + }; + recursiveDns = { + enable = true; + networkIdIp = plessur-lan-net.v4.id; + }; + wireless = { + enable = true; + wleInterface24Ghz = "wlp5s0"; + ssid = "hauser"; + }; + }; + + # DMZ + services.unbound.settings.server = { + interface = [ plessur-dmz-net.hosts.calanda.v4.ip ]; + access-control = [ + ''"${plessur-dmz-net.v4.id}/${toString plessur-dmz-net.v4.prefixLength}" allow'' + ]; + }; + networking.firewall.interfaces.enp3s0.allowedUDPPorts = [ 53 ]; + networking.nat.internalInterfaces = [ "enp3s0" ]; + + # DMZ Portforwarding + networking.nat.forwardPorts = + let + fulbergPort = ( + proto: port: { + destination = "10.1.2.2:${toString port}"; + proto = proto; + sourcePort = port; + loopbackIPs = [ "85.195.200.253" ]; + } + ); + cyprianspitzPort = ( + proto: port: { + destination = "10.1.1.11:${toString port}"; + proto = proto; + sourcePort = port; + loopbackIPs = [ "85.195.200.253" ]; + } + ); + in + [ + { + destination = "10.1.2.2:22"; + proto = "tcp"; + sourcePort = 8022; + } + { + destination = "10.1.2.2:2222"; + proto = "tcp"; + sourcePort = 8222; + } + { + destination = "10.1.1.11:2222"; + proto = "tcp"; + sourcePort = 8223; + } + ] + ++ map (fulbergPort "udp") [ + 51820 + 51821 + ] + ++ map (cyprianspitzPort "tcp") [ + 80 + 443 + ] + ++ map (cyprianspitzPort "udp") [ + 51824 + 1666 + 41641 + 3478 + 3479 + ]; +} diff --git a/nixos-configurations/cyprianspitz/README.md b/nixos-configurations/cyprianspitz/README.md new file mode 100644 index 0000000..cef3a7e --- /dev/null +++ b/nixos-configurations/cyprianspitz/README.md @@ -0,0 +1,32 @@ +# Host: Cyprianspitz + +## Operations {#_operations} + +Reboot requires passphrase. + +``` bash +# Get HDD Password: +sops decrypt --extract '["system"]["hdd"]' private/nixos-configurations/cyprianspitz/secrets.sops.yaml + +ssh -p 8223 -J root@calanda.plessur-ext.net.qo.is +``` + +## Hardware + +TODO + +- [Mainboard Manual](docs/z790m-itx-wifi.pdf) + + + +### Top Overview + +![](docs/top-view.jpg) + +### PCIE Side + +![](docs/pcie-side.jpg) + +### HDD Bay + +Note that slot 5 (the leftmost) SATA bay is not connected due to the mainboard only having 4 SATA plugs. diff --git a/nixos-configurations/cyprianspitz/applications/backup.nix b/nixos-configurations/cyprianspitz/applications/backup.nix new file mode 100644 index 0000000..241fba8 --- /dev/null +++ b/nixos-configurations/cyprianspitz/applications/backup.nix @@ -0,0 +1,12 @@ +{ pkgs, config, ... }: + +{ + qois.backup-server = { + enable = true; + backupStorageRoot = + let + dataDrive = config.disko.devices.lvm_vg.vg_data.lvs.lv_data.content.mountpoint; + in + dataDrive + "/backup"; + }; +} diff --git a/nixos-configurations/cyprianspitz/applications/default.nix b/nixos-configurations/cyprianspitz/applications/default.nix new file mode 100644 index 0000000..b14b66b --- /dev/null +++ b/nixos-configurations/cyprianspitz/applications/default.nix @@ -0,0 +1,10 @@ +{ config, pkgs, ... }: +{ + + imports = [ + ./backup.nix + ./vpn.nix + ]; + + qois.loadbalancer.enable = true; +} diff --git a/nixos-configurations/cyprianspitz/applications/vpn.nix b/nixos-configurations/cyprianspitz/applications/vpn.nix new file mode 100644 index 0000000..2fafbd0 --- /dev/null +++ b/nixos-configurations/cyprianspitz/applications/vpn.nix @@ -0,0 +1,4 @@ +{ config, pkgs, ... }: +{ + qois.vpn-server.enable = true; +} diff --git a/nixos-configurations/cyprianspitz/default.nix b/nixos-configurations/cyprianspitz/default.nix new file mode 100644 index 0000000..d06194d --- /dev/null +++ b/nixos-configurations/cyprianspitz/default.nix @@ -0,0 +1,28 @@ +{ config, pkgs, ... }: + +{ + imports = [ + ./applications + ./disko-config.nix + ./filesystems.nix + ./networking.nix + ./secrets.nix + ./virtualisation.nix + + ../../defaults/hardware/asrock-z790m.nix + + ../../defaults/base + ../../defaults/meta + ]; + + # Set your time zone. + time.timeZone = "Europe/Amsterdam"; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "24.05"; # Did you read the comment? +} diff --git a/nixos-configurations/cyprianspitz/disko-config.nix b/nixos-configurations/cyprianspitz/disko-config.nix new file mode 100644 index 0000000..8318980 --- /dev/null +++ b/nixos-configurations/cyprianspitz/disko-config.nix @@ -0,0 +1,132 @@ +{ pkgs, ... }: +{ + disko.devices = { + disk = rec { + data-1 = { + type = "disk"; + device = "/dev/disk/by-id/ata-ST16000NM000J-2TW103_ZRS110XA"; + content = { + type = "gpt"; + partitions = { + raid_data = { + size = "100%"; + content = { + type = "mdraid"; + name = "raid_data"; + }; + }; + }; + }; + }; + #data-2 = { # TODO + # type = "disk"; + # device = "/dev/disk/by-id/ata-TODO"; + # content = data-1.content; + #}; + system-1 = { + type = "disk"; + device = "/dev/disk/by-id/nvme-Lexar_SSD_NM790_1TB_NL8052R000144P2202"; + content = { + type = "gpt"; + partitions = { + boot = { + size = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot-primary"; + }; + }; + raid_system = { + start = "5G"; + size = "100%"; + content = { + type = "mdraid"; + name = "raid_system"; + }; + }; + }; + }; + }; + system-2 = { + type = "disk"; + device = "/dev/disk/by-id/nvme-Lexar_SSD_NM790_1TB_NL8052R002402P2202"; + content = pkgs.lib.recursiveUpdate system-1.content { + partitions.boot.content.mountpoint = "/boot-secondary"; + }; + }; + }; + + mdadm = { + "raid_system" = { + type = "mdadm"; + level = 1; + content = { + type = "luks"; + name = "crypted_system"; + passwordFile = "/run/secrets/system/hdd.key"; + settings = { + allowDiscards = true; + bypassWorkqueues = true; + }; + content = { + type = "lvm_pv"; + vg = "vg_system"; + }; + }; + }; + "raid_data" = { + type = "mdadm"; + level = 1; + content = { + type = "luks"; + name = "crypted_data"; + passwordFile = "/run/secrets/system/hdd.key"; + settings.allowDiscards = true; + content = { + type = "lvm_pv"; + vg = "vg_data"; + }; + }; + }; + }; + lvm_vg = { + vg_data = { + type = "lvm_vg"; + lvs = { + lv_data = { + size = "14TB"; + content = { + type = "filesystem"; + format = "btrfs"; + mountpoint = "/mnt/data"; + mountOptions = [ + "defaults" + "noatime" + ]; + }; + }; + }; + }; + vg_system = { + type = "lvm_vg"; + lvs = { + hv_cyprianspitz = { + size = "100GiB"; + content = { + type = "btrfs"; + mountOptions = [ + "defaults" + "noatime" + ]; + subvolumes = { + "/root".mountpoint = "/"; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/nixos-configurations/cyprianspitz/docs/pcie-side.jpg b/nixos-configurations/cyprianspitz/docs/pcie-side.jpg new file mode 100644 index 0000000..6620f1d Binary files /dev/null and b/nixos-configurations/cyprianspitz/docs/pcie-side.jpg differ diff --git a/nixos-configurations/cyprianspitz/docs/top-view.jpg b/nixos-configurations/cyprianspitz/docs/top-view.jpg new file mode 100644 index 0000000..3b4723a Binary files /dev/null and b/nixos-configurations/cyprianspitz/docs/top-view.jpg differ diff --git a/nixos-configurations/cyprianspitz/docs/z790m-itx-wifi.pdf b/nixos-configurations/cyprianspitz/docs/z790m-itx-wifi.pdf new file mode 100644 index 0000000..8a659e8 Binary files /dev/null and b/nixos-configurations/cyprianspitz/docs/z790m-itx-wifi.pdf differ diff --git a/nixos-configurations/cyprianspitz/filesystems.nix b/nixos-configurations/cyprianspitz/filesystems.nix new file mode 100644 index 0000000..918ba15 --- /dev/null +++ b/nixos-configurations/cyprianspitz/filesystems.nix @@ -0,0 +1,36 @@ +{ config, pkgs, ... }: +{ + + # Configurations are set in disko-config.nix! + + # mdadm.conf generated by `mdadm --detail --scan` + # TODO + boot.swraid.enable = true; + boot.swraid.mdadmConf = '' + MAILADDR root + ''; + + # TODO: RAID Monitoring + # TODO: Set spin-down time of physical disks + + services.fwupd.daemonSettings.EspLocation = pkgs.lib.mkForce config.disko.devices.disk.system-1.content.partitions.boot.content.mountpoint; + + # Use the systemd-boot EFI boot loader. + boot.loader.efi.canTouchEfiVariables = true; + boot.loader.grub = { + enable = true; + efiSupport = true; + mirroredBoots = [ + { + devices = [ "nodev" ]; + path = "/boot-primary"; + efiBootloaderId = "NixOS primary"; + } + { + devices = [ "nodev" ]; + path = "/boot-secondary"; + efiBootloaderId = "NixOS secondary"; + } + ]; + }; +} diff --git a/nixos-configurations/cyprianspitz/networking.nix b/nixos-configurations/cyprianspitz/networking.nix new file mode 100644 index 0000000..8147a3f --- /dev/null +++ b/nixos-configurations/cyprianspitz/networking.nix @@ -0,0 +1,97 @@ +{ config, pkgs, ... }: + +let + meta = config.qois.meta; +in +{ + networking.hostName = meta.hosts.cyprianspitz.hostName; + + imports = [ ../../defaults/backplane-net ]; + + networking.useDHCP = false; + networking.interfaces.enp0s31f6.useDHCP = true; + networking.interfaces.enp2s0.useDHCP = true; + + # Virtualization + networking.interfaces.vms-nat.useDHCP = false; + networking.interfaces.vms-nat.ipv4.addresses = [ + ( + let + netConfig = meta.network.virtual.cyprianspitz-vms-nat; + in + { + address = netConfig.hosts.cyprianspitz.v4.ip; + prefixLength = netConfig.v4.prefixLength; + } + ) + ]; + + networking.bridges.vms-nat.interfaces = [ ]; + networking.nat = { + enable = true; + internalInterfaces = [ "vms-nat" ]; + internalIPs = with meta.network.virtual.cyprianspitz-vms-nat.v4; [ + "${id}/${builtins.toString prefixLength}" + ]; + externalInterface = "enp0s31f6"; + }; + services.dnsmasq = + let + netConfig = meta.network.virtual.cyprianspitz-vms-nat; + in + { + enable = true; + resolveLocalQueries = false; + settings = { + interface = "vms-nat"; + bind-interfaces = true; + + domain-needed = true; + + domain = netConfig.domain; + dhcp-range = [ "10.248.0.2,10.248.0.253" ]; + dhcp-option = [ + "option:router,${netConfig.hosts.cyprianspitz.v4.ip}" + "option:domain-search,${netConfig.domain}" + ]; + dhcp-authoritative = true; + }; + }; + systemd.services.dnsmasq.bindsTo = [ "network-addresses-vms-nat.service" ]; + networking.firewall.interfaces.vms-nat = { + allowedUDPPorts = [ + 53 + 67 + ]; + allowedTCPPorts = [ 53 ]; + }; + + # Boot + boot.initrd.network.udhcpc.enable = true; + + services.qois.luks-ssh = { + enable = true; + interface = "eth0"; + sshPort = 2222; + sshHostKey = "/secrets/system/initrd-ssh-key"; + # TODO Solve sops dependency porblem: config.sops.secrets."system/initrd-ssh-key".path; + }; + + # Configure this node to be used as an vpn exit node + qois.backup-client.includePaths = [ "/var/lib/tailscale" ]; + services.tailscale = { + enable = true; + openFirewall = true; + useRoutingFeatures = "server"; + authKeyFile = config.sops.secrets."tailscale/key".path; + extraUpFlags = [ + "--login-server=https://vpn.qo.is" + "--advertise-exit-node" + ( + with meta.network.virtual.backplane.v4; "--advertise-routes=${id}/${builtins.toString prefixLength}" + ) + "--advertise-tags=tag:srv" + ]; + }; + +} diff --git a/nixos-configurations/cyprianspitz/secrets.nix b/nixos-configurations/cyprianspitz/secrets.nix new file mode 100644 index 0000000..0e9f62c --- /dev/null +++ b/nixos-configurations/cyprianspitz/secrets.nix @@ -0,0 +1,10 @@ +{ ... }: +{ + sops.secrets = { + "system/hdd" = { }; + "system/initrd-ssh-key" = { }; + "tailscale/key" = { + restartUnits = [ "tailscaled.service" ]; + }; + }; +} diff --git a/nixos-configurations/cyprianspitz/virtualisation.nix b/nixos-configurations/cyprianspitz/virtualisation.nix new file mode 100644 index 0000000..5905bdf --- /dev/null +++ b/nixos-configurations/cyprianspitz/virtualisation.nix @@ -0,0 +1,8 @@ +{ config, pkgs, ... }: +{ + virtualisation.libvirtd = { + enable = true; + onShutdown = "shutdown"; + }; + environment.systemPackages = [ pkgs.virtiofsd ]; +} diff --git a/nixos-configurations/default.nix b/nixos-configurations/default.nix new file mode 100644 index 0000000..6fccd8b --- /dev/null +++ b/nixos-configurations/default.nix @@ -0,0 +1,41 @@ +{ + self, + pkgs, + nixpkgs-nixos-stable, + disko, + attic, + sops-nix, + ... +}@inputs: +let + configs = self.lib.foldersWithNix ./.; +in +pkgs.lib.genAttrs configs ( + config: + nixpkgs-nixos-stable.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { + inherit inputs; + }; + modules = [ + self.nixosModules.default + ./${config}/default.nix + ( + { ... }: + { + imports = [ "${attic}/nixos/atticd.nix" ]; + services.atticd.useFlakeCompatOverlay = false; + } + ) + disko.nixosModules.disko + sops-nix.nixosModules.sops + ( + { ... }: + { + system.extraSystemBuilderCmds = "ln -s ${self} $out/nixos-configuration"; + imports = [ ./secrets.nix ]; + } + ) + ]; + } +) diff --git a/nixos-configurations/fulberg/README.md b/nixos-configurations/fulberg/README.md new file mode 100644 index 0000000..2db184e --- /dev/null +++ b/nixos-configurations/fulberg/README.md @@ -0,0 +1 @@ +# fulberg diff --git a/nixos-configurations/fulberg/applications/default.nix b/nixos-configurations/fulberg/applications/default.nix new file mode 100644 index 0000000..c915eb0 --- /dev/null +++ b/nixos-configurations/fulberg/applications/default.nix @@ -0,0 +1 @@ +{ ... }: { } diff --git a/nixos-configurations/fulberg/backup.nix b/nixos-configurations/fulberg/backup.nix new file mode 100644 index 0000000..e37c8e8 --- /dev/null +++ b/nixos-configurations/fulberg/backup.nix @@ -0,0 +1,35 @@ +{ config, pkgs, ... }: +{ + + qois.backup-server = { + enable = true; + backupStorageRoot = "/mnt/nas/backup"; + }; + + services.borgbackup.repos = + let + backupRoot = "/mnt/nas/backup"; + hostBackupRoot = "${backupRoot}/hosts"; + dataBackupRoot = "${backupRoot}/data"; + in + { + "lindberg-nextcloud" = { + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIpzfp9VqclbPJ42ZrkRpvjMSTeyq0qce03zCRXqIHMw backup@lindberg-nextcloud" + ]; + path = "${hostBackupRoot}/lindberg-nextcloud"; + }; + "lindberg-data" = { + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGTmyoVONC12MgOodvzdPpZzLSVwpkC6zkf+Rg0W36gy backup-data@lindberg" + ]; + path = "${dataBackupRoot}/lindberg"; + }; + "lindberg-build-system" = { + authorizedKeys = [ + "ssh-ed25519 AAAATODOTODOTODONTE5AAAAIGTmyoVONC12MgOodvzdPpZzLSVwpkC6zkf+Rg0W36gy backup-system@lindberg-build" + ]; + path = "${dataBackupRoot}/lindberg-build-system"; + }; + }; +} diff --git a/nixos-configurations/fulberg/default.nix b/nixos-configurations/fulberg/default.nix new file mode 100644 index 0000000..4605b9d --- /dev/null +++ b/nixos-configurations/fulberg/default.nix @@ -0,0 +1,22 @@ +{ config, pkgs, ... }: +{ + + imports = [ + ../../defaults/base + ../../defaults/hardware/apu.nix + ../../defaults/meta + ./applications + ./backup.nix + ./filesystems.nix + ./networking.nix + ./secrets.nix + ]; + + # This value determines the NixOS release from which the default + # settings for stateful data, like fi:le locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "23.05"; # Did you read the comment? +} diff --git a/nixos-configurations/fulberg/filesystems.nix b/nixos-configurations/fulberg/filesystems.nix new file mode 100644 index 0000000..547cd79 --- /dev/null +++ b/nixos-configurations/fulberg/filesystems.nix @@ -0,0 +1,31 @@ +{ config, pkgs, ... }: +{ + + fileSystems = { + "/" = { + device = "/dev/disk/by-uuid/360a6bc9-fc4e-4803-bd53-69320ac32ac5"; + fsType = "btrfs"; + options = [ + "defaults" + "subvol=nixos" + "noatime" + ]; + }; + "/mnt/nas" = { + device = "10.1.1.39:/qois"; + fsType = "nfs"; + options = [ + "defaults" + "noatime" + "soft" + ]; + }; + }; + + swapDevices = [ { device = "/dev/disk/by-uuid/73f91e99-d856-4504-b6b2-d60f855d6d95"; } ]; + + boot.loader.grub = { + enable = true; + device = "/dev/sda"; + }; +} diff --git a/nixos-configurations/fulberg/networking.nix b/nixos-configurations/fulberg/networking.nix new file mode 100644 index 0000000..125539d --- /dev/null +++ b/nixos-configurations/fulberg/networking.nix @@ -0,0 +1,48 @@ +{ config, pkgs, ... }: +let + meta = config.qois.meta; + plessur-dmz-net = meta.network.physical.plessur-dmz; + getCalandaIp4 = net: net.hosts.calanda.v4.ip; +in +{ + networking.hostName = meta.hosts.fulberg.hostName; + + imports = [ ../../defaults/backplane-net ]; + + # WWAN is currently not available due to a broken SIM-card. + #services.qois.wwan = { + # enable = true; + # apn = "gprs.swisscom.ch"; + # networkInterface = "wwp0s19u1u3i12"; + #}; + + networking.interfaces.enp1s0 = { + useDHCP = false; + ipv4.addresses = [ + { + inherit (plessur-dmz-net.v4) prefixLength; + address = plessur-dmz-net.hosts.fulberg.v4.ip; + } + ]; + }; + + networking.defaultGateway = plessur-dmz-net.v4.gateway; + networking.nameservers = plessur-dmz-net.v4.nameservers; + + # Configure this node to be used as an vpn exit node + qois.backup-client.includePaths = [ "/var/lib/tailscale" ]; + services.tailscale = { + enable = true; + openFirewall = true; + useRoutingFeatures = "server"; + authKeyFile = config.sops.secrets."tailscale/key".path; + extraUpFlags = [ + "--login-server=https://vpn.qo.is" + "--advertise-exit-node" + ( + with meta.network.virtual.backplane.v4; "--advertise-routes=${id}/${builtins.toString prefixLength}" + ) + "--advertise-tags=tag:srv" + ]; + }; +} diff --git a/nixos-configurations/fulberg/secrets.nix b/nixos-configurations/fulberg/secrets.nix new file mode 100644 index 0000000..b7aa8fe --- /dev/null +++ b/nixos-configurations/fulberg/secrets.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + sops.secrets = { + "tailscale/key" = { + restartUnits = [ "tailscale.service" ]; + }; + }; +} diff --git a/nixos-configurations/lindberg-build/applications/README.md b/nixos-configurations/lindberg-build/applications/README.md new file mode 100644 index 0000000..ac075df --- /dev/null +++ b/nixos-configurations/lindberg-build/applications/README.md @@ -0,0 +1,39 @@ +# Nix Caches + +## Nixpkgs Cache + +To put less load on the upstream nixpkgs CDN and speed up builds, we run a (public) nixpkgs cache on [nixpkgs-cache.qo.is](https://nixpkgs-cache.qo.is). To use it, configure nix like follows in your `nix.conf`: + +```nix +substituters = https://nixpkgs-cache.qo.is?priority=39 +``` + +Note that the [cache.nixos.org](https://cache.nixos.org) public key must also be trusted: + +```nix +trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= +``` + +See the [nix documentation](https://nixos.org/manual/nix/stable/command-ref/conf-file.html#conf-substituters) for details about substitutors. + +## Attic + +We use [attic](https://docs.attic.rs/) as a self hosted nix build cache. + +See [upstream documentation](https://docs.attic.rs/reference/attic-cli.html) for details on how to use it. + +### Server Administration + +Add users: + +```bash +# For example, to generate a token for Alice with read-write access to any cache starting with `dev-` and read-only access to `prod`, expiring in 2 years: + +atticadm make-token --sub "alice" --validity "2y" --pull "dev-*" --push "dev-*" --pull "prod" +``` + +### Client Usage + +`attic login qois https://attic.qo.is ` + +`attic use qois:cachename` diff --git a/nixos-configurations/lindberg-build/applications/attic.nix b/nixos-configurations/lindberg-build/applications/attic.nix new file mode 100644 index 0000000..2825897 --- /dev/null +++ b/nixos-configurations/lindberg-build/applications/attic.nix @@ -0,0 +1,77 @@ +{ config, pkgs, ... }: + +let + atticPort = 8080; + atticHostname = "attic.qo.is"; +in + +{ + + services.atticd = { + enable = true; + + # Replace with absolute path to your credentials file + # generate secret with + # nix run system#openssl rand 64 | base64 -w0 + # ATTIC_SERVER_TOKEN_HS256_SECRET_BASE64="output from openssl" + credentialsFile = config.sops.secrets."attic/server_token".path; + + settings = { + listen = "127.0.0.1:${builtins.toString atticPort}"; + allowed-hosts = [ "attic.qo.is" ]; + api-endpoint = "https://attic.qo.is/"; + + # Data chunking + # + # Warning: If you change any of the values here, it will be + # difficult to reuse existing chunks for newly-uploaded NARs + # since the cutpoints will be different. As a result, the + # deduplication ratio will suffer for a while after the change. + chunking = { + # The minimum NAR size to trigger chunking + # + # If 0, chunking is disabled entirely for newly-uploaded NARs. + # If 1, all NARs are chunked. + nar-size-threshold = 64 * 1024; # 64 KiB + + # The preferred minimum size of a chunk, in bytes + min-size = 16 * 1024; # 16 KiB + + # The preferred average size of a chunk, in bytes + avg-size = 64 * 1024; # 64 KiB + + # The preferred maximum size of a chunk, in bytes + max-size = 256 * 1024; # 256 KiB + }; + + database.url = "postgresql:///atticd?host=/run/postgresql"; + }; + }; + + imports = [ ../../../defaults/webserver ]; + + qois.postgresql.enable = true; + # Note: Attic cache availability is "best effort", so no artifacts are backed up. + + services.postgresql = { + ensureDatabases = [ "atticd" ]; + ensureUsers = [ + { + name = "atticd"; + ensureDBOwnership = true; + } + ]; + }; + + services.nginx = { + enable = true; + clientMaxBodySize = "1g"; + virtualHosts.${atticHostname} = { + kTLS = true; + forceSSL = true; + enableACME = true; + + locations."/".proxyPass = "http://127.0.0.1:${builtins.toString atticPort}"; + }; + }; +} diff --git a/nixos-configurations/lindberg-build/applications/default.nix b/nixos-configurations/lindberg-build/applications/default.nix new file mode 100644 index 0000000..7a0aa4c --- /dev/null +++ b/nixos-configurations/lindberg-build/applications/default.nix @@ -0,0 +1,11 @@ +{ config, pkgs, ... }: +{ + + imports = [ + ./gitlab-runner.nix + ./attic.nix + ./nixpkgs-cache.nix + ]; + + qois.git-ci-runner.enable = true; +} diff --git a/nixos-configurations/lindberg-build/applications/gitlab-runner.nix b/nixos-configurations/lindberg-build/applications/gitlab-runner.nix new file mode 100644 index 0000000..cf68c3a --- /dev/null +++ b/nixos-configurations/lindberg-build/applications/gitlab-runner.nix @@ -0,0 +1,27 @@ +{ config, pkgs, ... }: +{ + + services.gitlab-runner = { + enable = true; + + gracefulTimeout = "20min"; + + clear-docker-cache = { + enable = true; + dates = "monthly"; + }; + + services = { + default = { + runUntagged = true; + # File should contain at least these two variables: + # `CI_SERVER_URL` + # `REGISTRATION_TOKEN` + registrationConfigFile = config.sops.secrets."gitlab-runner/default-registration".path; + dockerImage = "debian:stable"; + limit = 42; # The magic value + maximumTimeout = 7200; # 2h oughta be enough for everyone + }; + }; + }; +} diff --git a/nixos-configurations/lindberg-build/applications/nixpkgs-cache.nix b/nixos-configurations/lindberg-build/applications/nixpkgs-cache.nix new file mode 100644 index 0000000..f097544 --- /dev/null +++ b/nixos-configurations/lindberg-build/applications/nixpkgs-cache.nix @@ -0,0 +1,8 @@ +{ config, pkgs, ... }: +{ + qois.nixpkgs-cache = { + enable = true; + hostname = "nixpkgs-cache.qo.is"; + dnsResolvers = [ config.qois.meta.network.virtual.lindberg-vms-nat.hosts.lindberg.v4.ip ]; + }; +} diff --git a/nixos-configurations/lindberg-build/backup.nix b/nixos-configurations/lindberg-build/backup.nix new file mode 100644 index 0000000..287f4ad --- /dev/null +++ b/nixos-configurations/lindberg-build/backup.nix @@ -0,0 +1,43 @@ +{ config, pkgs, ... }: + +let + vnet = config.qois.meta.network.virtual.backplane.hosts; + systemTargets = [ + "fulberg" + "tierberg" + ]; + systemJobs = builtins.listToAttrs ( + map (backupHost: { + name = "system-${backupHost}"; + value = { + repo = "borg@${vnet.${backupHost}.v4.ip}:."; + environment.BORG_RSH = "ssh -i /secrets/backup/system/ssh-key"; + + paths = [ + "/etc" + "/home" + "/var" + "/secrets" + ]; + exclude = [ + "/var/tmp" + "/var/cache" + "/var/lib/atticd" + "/var/cache/nginx/nixpkgs-cache" + ]; + + doInit = false; + encryption = { + mode = "repokey"; + passCommand = "cat /secrets/backup/system/password"; + }; + + startAt = "07:06"; + persistentTimer = true; + }; + }) systemTargets + ); +in +{ + services.borgbackup.jobs = systemJobs; +} diff --git a/nixos-configurations/lindberg-build/default.nix b/nixos-configurations/lindberg-build/default.nix new file mode 100644 index 0000000..7021494 --- /dev/null +++ b/nixos-configurations/lindberg-build/default.nix @@ -0,0 +1,26 @@ +{ config, pkgs, ... }: + +{ + imports = [ + ../../defaults/base-vm + ../../defaults/meta + ../../defaults/backplane-net + + ./applications + ./backup.nix + ./disko-config.nix + ./networking.nix + ./secrets.nix + ]; + + # Set your time zone. + time.timeZone = "Europe/Amsterdam"; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "23.11"; # Did you read the comment? +} diff --git a/nixos-configurations/lindberg-build/disko-config.nix b/nixos-configurations/lindberg-build/disko-config.nix new file mode 100644 index 0000000..d1cfaf8 --- /dev/null +++ b/nixos-configurations/lindberg-build/disko-config.nix @@ -0,0 +1,64 @@ +{ ... }: +{ + disko.devices.disk = { + system = { + type = "disk"; + device = "/dev/vda"; + content = { + type = "gpt"; + partitions = { + boot = { + # for grub MBR + size = "1M"; + type = "EF02"; + }; + system = { + size = "100%"; + content = { + type = "btrfs"; + subvolumes = { + "/nix" = { + mountpoint = "/nix"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; + "/root" = { + mountpoint = "/"; + mountOptions = [ "noatime" ]; + }; + }; + }; + }; + }; + }; + }; + nixpkgs_cache = { + type = "disk"; + device = "/dev/vdb"; + content = { + type = "gpt"; + partitions.nixpkgs_cache = { + size = "100%"; + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/var/cache/nginx/nixpkgs-cache"; + }; + }; + }; + }; + swap = { + type = "disk"; + device = "/dev/vdc"; + content = { + type = "gpt"; + partitions.swap = { + size = "100%"; + content.type = "swap"; + }; + }; + }; + }; +} diff --git a/nixos-configurations/lindberg-build/networking.nix b/nixos-configurations/lindberg-build/networking.nix new file mode 100644 index 0000000..3cbe068 --- /dev/null +++ b/nixos-configurations/lindberg-build/networking.nix @@ -0,0 +1,13 @@ +{ config, pkgs, ... }: + +{ + + networking.hostName = config.qois.meta.hosts.lindberg-build.hostName; + networking.useDHCP = false; + networking.interfaces.enp11s0.useDHCP = true; + + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; +} diff --git a/nixos-configurations/lindberg-build/secrets.nix b/nixos-configurations/lindberg-build/secrets.nix new file mode 100644 index 0000000..988fc1f --- /dev/null +++ b/nixos-configurations/lindberg-build/secrets.nix @@ -0,0 +1,11 @@ +{ ... }: +{ + sops.secrets = { + "attic/server_token" = { + restartUnits = [ "atticd.service" ]; + }; + "gitlab-runner/default-registration" = { + restartUnits = [ "gitlab-runner.service" ]; + }; + }; +} diff --git a/nixos-configurations/lindberg-nextcloud/applications/cloud.nix b/nixos-configurations/lindberg-nextcloud/applications/cloud.nix new file mode 100644 index 0000000..24e37d2 --- /dev/null +++ b/nixos-configurations/lindberg-nextcloud/applications/cloud.nix @@ -0,0 +1,21 @@ +{ config, pkgs, ... }: +let + host = "cloud.qo.is"; +in +{ + + imports = [ ../../../defaults/nextcloud ]; + + qois.postgresql.enable = true; + + services.nextcloud = { + hostName = host; + package = pkgs.nextcloud29; + settings.default_phone_region = "CH"; + }; + services.nginx.virtualHosts."${host}" = { + forceSSL = true; + enableACME = true; + kTLS = true; + }; +} diff --git a/nixos-configurations/lindberg-nextcloud/applications/default.nix b/nixos-configurations/lindberg-nextcloud/applications/default.nix new file mode 100644 index 0000000..6188fc9 --- /dev/null +++ b/nixos-configurations/lindberg-nextcloud/applications/default.nix @@ -0,0 +1,5 @@ +{ config, pkgs, ... }: +{ + + imports = [ ./cloud.nix ]; +} diff --git a/nixos-configurations/lindberg-nextcloud/backup.nix b/nixos-configurations/lindberg-nextcloud/backup.nix new file mode 100644 index 0000000..3b5da39 --- /dev/null +++ b/nixos-configurations/lindberg-nextcloud/backup.nix @@ -0,0 +1,8 @@ +{ config, pkgs, ... }: + +{ + + qois.backup-client.excludePaths = [ + "/var/lib/nextcloud/data" # Data is backed up on lindberg + ]; +} diff --git a/nixos-configurations/lindberg-nextcloud/default.nix b/nixos-configurations/lindberg-nextcloud/default.nix new file mode 100644 index 0000000..9300262 --- /dev/null +++ b/nixos-configurations/lindberg-nextcloud/default.nix @@ -0,0 +1,50 @@ +{ config, pkgs, ... }: + +{ + imports = [ + ../../defaults/backplane-net + ../../defaults/base-vm + ../../defaults/meta + ../../defaults/webserver + ./applications + ./backup.nix + ./secrets.nix + ]; + + boot.loader.grub.device = "/dev/vda"; + fileSystems."/" = { + device = "/dev/disk/by-uuid/5b6823ec-921f-400a-a7c0-3fe34d56ae12"; + fsType = "btrfs"; + options = [ "subvol=root" ]; + }; + + systemd.mounts = [ + { + what = "data/nextcloud"; + where = "/var/lib/nextcloud"; + type = "virtiofs"; + wantedBy = [ "multi-user.target" ]; + enable = true; + } + ]; + + networking.hostName = config.qois.meta.hosts.lindberg-nextcloud.hostName; + networking.useDHCP = false; + networking.interfaces.enp2s0.useDHCP = true; + + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; + + # Set your time zone. + time.timeZone = "Europe/Amsterdam"; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "22.05"; # Did you read the comment? +} diff --git a/nixos-configurations/lindberg-nextcloud/secrets.nix b/nixos-configurations/lindberg-nextcloud/secrets.nix new file mode 100644 index 0000000..3fa2f78 --- /dev/null +++ b/nixos-configurations/lindberg-nextcloud/secrets.nix @@ -0,0 +1,17 @@ +{ ... }: +let + backupConfiguration = { + restartUnits = [ + "borgbackup-job-system-fulberg.service" + "borgbackup-job-system-tierberg.service" + ]; + }; +in + +{ + sops.secrets = { + "backup/system/password" = backupConfiguration; + "backup/system/ssh-key" = backupConfiguration; + "nextcloud/admin" = { }; + }; +} diff --git a/nixos-configurations/lindberg-webapps/applications/README.md b/nixos-configurations/lindberg-webapps/applications/README.md new file mode 100644 index 0000000..b4445bb --- /dev/null +++ b/nixos-configurations/lindberg-webapps/applications/README.md @@ -0,0 +1,3 @@ +# Web Apps + +## fabianhauser.ch diff --git a/nixos-configurations/lindberg-webapps/applications/default.nix b/nixos-configurations/lindberg-webapps/applications/default.nix new file mode 100644 index 0000000..c635d21 --- /dev/null +++ b/nixos-configurations/lindberg-webapps/applications/default.nix @@ -0,0 +1,9 @@ +{ config, pkgs, ... }: +{ + + imports = [ ]; + + qois.vault.enable = true; + qois.git.enable = true; + qois.static-page.enable = true; +} diff --git a/nixos-configurations/lindberg-webapps/default.nix b/nixos-configurations/lindberg-webapps/default.nix new file mode 100644 index 0000000..0b37a67 --- /dev/null +++ b/nixos-configurations/lindberg-webapps/default.nix @@ -0,0 +1,25 @@ +{ config, pkgs, ... }: + +{ + imports = [ + ../../defaults/base-vm + ../../defaults/meta + ../../defaults/backplane-net + + ./applications + ./disko-config.nix + ./networking.nix + ./secrets.nix + ]; + + # Set your time zone. + time.timeZone = "Europe/Amsterdam"; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "23.11"; # Did you read the comment? +} diff --git a/nixos-configurations/lindberg-webapps/disko-config.nix b/nixos-configurations/lindberg-webapps/disko-config.nix new file mode 100644 index 0000000..8a7d268 --- /dev/null +++ b/nixos-configurations/lindberg-webapps/disko-config.nix @@ -0,0 +1,38 @@ +{ ... }: +{ + disko.devices.disk = { + system = { + type = "disk"; + device = "/dev/vda"; + content = { + type = "gpt"; + partitions = { + boot = { + # for grub MBR + size = "1M"; + type = "EF02"; + }; + system = { + size = "100%"; + content = { + type = "btrfs"; + subvolumes = { + "/nix" = { + mountpoint = "/nix"; + mountOptions = [ + "compress=zstd" + "noatime" + ]; + }; + "/root" = { + mountpoint = "/"; + mountOptions = [ "noatime" ]; + }; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/nixos-configurations/lindberg-webapps/networking.nix b/nixos-configurations/lindberg-webapps/networking.nix new file mode 100644 index 0000000..fd4b437 --- /dev/null +++ b/nixos-configurations/lindberg-webapps/networking.nix @@ -0,0 +1,13 @@ +{ config, pkgs, ... }: + +{ + + networking.hostName = config.qois.meta.hosts.lindberg-webapps.hostName; + networking.useDHCP = false; + networking.interfaces.enp1s0.useDHCP = true; + + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; +} diff --git a/nixos-configurations/lindberg-webapps/secrets.nix b/nixos-configurations/lindberg-webapps/secrets.nix new file mode 100644 index 0000000..9743ac0 --- /dev/null +++ b/nixos-configurations/lindberg-webapps/secrets.nix @@ -0,0 +1,4 @@ +{ ... }: +{ + sops.secrets = { }; +} diff --git a/nixos-configurations/lindberg/README.md b/nixos-configurations/lindberg/README.md new file mode 100644 index 0000000..c6957d9 --- /dev/null +++ b/nixos-configurations/lindberg/README.md @@ -0,0 +1,61 @@ +# Host: Lindberg + +## Operations {#_operations} + +Reboot requires passphrase (see pass `host/lindberg/hdd_luks`) + +``` bash +ssh -p 2222 root@lindberg.riedbach-ext.net.qo.is +``` + +## Hardware + +- [Mainboard Manual](docs/X570Pro4-mainboard-manual.pdf) + + +### Front / Back + +#### Front Overview + +![](docs/front_full.jpg) + +#### Front PCIE + +![](docs/front_pcie_overview.jpg) +![](docs/front_pcie_ssd.jpg) + +#### Front Cables + +![](docs/front_cables.jpg) + +#### Back + +![](docs/back_overview.jpg) + +### HDDs + +![](docs/back_hdds.jpg) + +#### HDD (0) + +![](docs/hdd_0.jpg) + +#### HDD (1) + +![](docs/hdd_1.jpg) + +#### HDD (3) + +![](docs/hdd_3.jpg) + +#### zvtaa02h + +![](docs/hdd_zvtaa02h.jpg) + +#### zvtaeypl + +![](docs/hdd_zvtaeypl.jpg) + +### SSD left + +![](docs/ssd_1_left.jpg) diff --git a/nixos-configurations/lindberg/applications/default.nix b/nixos-configurations/lindberg/applications/default.nix new file mode 100644 index 0000000..e0d34f0 --- /dev/null +++ b/nixos-configurations/lindberg/applications/default.nix @@ -0,0 +1,5 @@ +{ config, pkgs, ... }: +{ + + imports = [ ./loadbalancer.nix ]; +} diff --git a/nixos-configurations/lindberg/applications/loadbalancer.nix b/nixos-configurations/lindberg/applications/loadbalancer.nix new file mode 100644 index 0000000..97427d2 --- /dev/null +++ b/nixos-configurations/lindberg/applications/loadbalancer.nix @@ -0,0 +1,10 @@ +{ + config, + pkgs, + lib, + ... +}: + +{ + qois.loadbalancer.enable = true; +} diff --git a/nixos-configurations/lindberg/backup.nix b/nixos-configurations/lindberg/backup.nix new file mode 100644 index 0000000..bb9bb11 --- /dev/null +++ b/nixos-configurations/lindberg/backup.nix @@ -0,0 +1,24 @@ +{ config, pkgs, ... }: +{ + + qois.backup-client.includePaths = [ "/mnt/data" ]; + + services.borgbackup.jobs = { + data-local = { + repo = "/mnt/backup/disks/data"; + doInit = true; + paths = [ "/mnt/data/" ]; + prune.keep = { + within = "14d"; + weekly = 4; + monthly = 6; + yearly = -1; + }; + encryption = { + mode = "authenticated"; + passphrase = ""; + }; + startAt = "07:15"; + }; + }; +} diff --git a/nixos-configurations/lindberg/default.nix b/nixos-configurations/lindberg/default.nix new file mode 100644 index 0000000..b8ea27d --- /dev/null +++ b/nixos-configurations/lindberg/default.nix @@ -0,0 +1,29 @@ +{ config, pkgs, ... }: + +{ + imports = [ + ./applications + ./backup.nix + ./disko-config.nix + ./filesystems.nix + ./networking.nix + ./secrets.nix + ./virtualisation.nix + + ../../defaults/hardware/asrock.nix + + ../../defaults/base + ../../defaults/meta + ]; + + # Set your time zone. + time.timeZone = "Europe/Amsterdam"; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "21.11"; # Did you read the comment? +} diff --git a/nixos-configurations/lindberg/disko-config.nix b/nixos-configurations/lindberg/disko-config.nix new file mode 100644 index 0000000..724ad39 --- /dev/null +++ b/nixos-configurations/lindberg/disko-config.nix @@ -0,0 +1,192 @@ +{ pkgs, ... }: +{ + disko.devices = { + disk = rec { + data-1 = { + type = "disk"; + device = "/dev/disk/by-id/ata-ST18000NM003D-3DL103_ZVTAA02H"; + content = { + type = "gpt"; + partitions = { + raid_data = { + size = "100%"; + content = { + type = "mdraid"; + name = "raid_data"; + }; + }; + }; + }; + }; + data-2 = { + type = "disk"; + device = "/dev/disk/by-id/ata-ST18000NM003D-3DL103_ZVTAEYPL"; + content = data-1.content; + }; + backup = { + type = "disk"; + device = "/dev/disk/by-id/ata-WDC_WD40EFRX-68N32N0_WD-WCC7K5ZUA0VR"; + content = { + type = "gpt"; + partitions = { + backup = { + size = "100%"; + content = { + type = "luks"; + name = "crypted_backup"; + settings.allowDiscards = true; + askPassword = true; + content = { + type = "filesystem"; + format = "btrfs"; + mountpoint = "/mnt/backup"; + mountOptions = [ + "defaults" + "noatime" + ]; + }; + }; + }; + }; + }; + }; + system-1 = { + type = "disk"; + device = "/dev/disk/by-id/nvme-SAMSUNG_MZVL22T0HBLB-00B00_S677NE0NC01017"; + content = { + type = "gpt"; + partitions = { + boot = { + size = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot-primary"; + }; + }; + raid_system = { + start = "5G"; + size = "100%"; + content = { + type = "mdraid"; + name = "raid_system"; + }; + }; + }; + }; + }; + system-2 = { + type = "disk"; + device = "/dev/disk/by-id/nvme-Lexar_SSD_NM790_2TB_NLK644R000627P2202"; + content = pkgs.lib.recursiveUpdate system-1.content { + partitions.boot.content.mountpoint = "/boot-secondary"; + }; + }; + cache = { + type = "disk"; + device = "/dev/disk/by-id/ata-Samsung_SSD_840_PRO_Series_S12PNEAD274438F"; + content = { + type = "gpt"; + partitions = { + crypted_cache = { + size = "100%"; + content = { + type = "luks"; + name = "crypted_cache"; + settings.allowDiscards = true; + askPassword = true; + content = { + type = "lvm_pv"; + vg = "vg_cache"; + }; + }; + }; + }; + }; + }; + }; + + mdadm = { + "raid_system" = { + type = "mdadm"; + level = 1; + content = { + type = "luks"; + name = "crypted_system"; + settings = { + allowDiscards = true; + bypassWorkqueues = true; + }; + askPassword = true; + content = { + type = "lvm_pv"; + vg = "vg_system"; + }; + }; + }; + "raid_data" = { + type = "mdadm"; + level = 1; + content = { + type = "luks"; + name = "crypted_data"; + settings.allowDiscards = true; + askPassword = true; + content = { + type = "lvm_pv"; + vg = "vg_data"; + }; + }; + }; + }; + lvm_vg = { + vg_data = { + type = "lvm_vg"; + lvs = { + lv_data = { + size = "12TB"; + content = { + type = "filesystem"; + format = "btrfs"; + mountpoint = "/mnt/data"; + mountOptions = [ + "defaults" + "noatime" + ]; + }; + }; + }; + }; + vg_system = { + type = "lvm_vg"; + lvs = { + hv_lindberg = { + size = "100GiB"; + content = { + type = "btrfs"; + mountOptions = [ + "defaults" + "noatime" + ]; + subvolumes = { + "/root".mountpoint = "/"; + }; + }; + }; + }; + }; + vg_cache = { + type = "lvm_vg"; + lvs = { + lv_swap_lindberg = { + size = "10GiB"; + content = { + type = "swap"; + }; + }; + }; + }; + }; + }; +} diff --git a/nixos-configurations/lindberg/docs/X570Pro4-mainboard-manual.pdf b/nixos-configurations/lindberg/docs/X570Pro4-mainboard-manual.pdf new file mode 100644 index 0000000..cc0ca96 Binary files /dev/null and b/nixos-configurations/lindberg/docs/X570Pro4-mainboard-manual.pdf differ diff --git a/nixos-configurations/lindberg/docs/back_hdds.jpg b/nixos-configurations/lindberg/docs/back_hdds.jpg new file mode 100644 index 0000000..ba5b65b Binary files /dev/null and b/nixos-configurations/lindberg/docs/back_hdds.jpg differ diff --git a/nixos-configurations/lindberg/docs/back_overview.jpg b/nixos-configurations/lindberg/docs/back_overview.jpg new file mode 100644 index 0000000..d9b1c09 Binary files /dev/null and b/nixos-configurations/lindberg/docs/back_overview.jpg differ diff --git a/nixos-configurations/lindberg/docs/front_cables.jpg b/nixos-configurations/lindberg/docs/front_cables.jpg new file mode 100644 index 0000000..2face98 Binary files /dev/null and b/nixos-configurations/lindberg/docs/front_cables.jpg differ diff --git a/nixos-configurations/lindberg/docs/front_full.jpg b/nixos-configurations/lindberg/docs/front_full.jpg new file mode 100644 index 0000000..04dea9f Binary files /dev/null and b/nixos-configurations/lindberg/docs/front_full.jpg differ diff --git a/nixos-configurations/lindberg/docs/front_pcie_overview.jpg b/nixos-configurations/lindberg/docs/front_pcie_overview.jpg new file mode 100644 index 0000000..1e62748 Binary files /dev/null and b/nixos-configurations/lindberg/docs/front_pcie_overview.jpg differ diff --git a/nixos-configurations/lindberg/docs/front_pcie_ssd.jpg b/nixos-configurations/lindberg/docs/front_pcie_ssd.jpg new file mode 100644 index 0000000..610b3de Binary files /dev/null and b/nixos-configurations/lindberg/docs/front_pcie_ssd.jpg differ diff --git a/nixos-configurations/lindberg/docs/hdd_0.jpg b/nixos-configurations/lindberg/docs/hdd_0.jpg new file mode 100644 index 0000000..399e07c Binary files /dev/null and b/nixos-configurations/lindberg/docs/hdd_0.jpg differ diff --git a/nixos-configurations/lindberg/docs/hdd_1.jpg b/nixos-configurations/lindberg/docs/hdd_1.jpg new file mode 100644 index 0000000..10f6c33 Binary files /dev/null and b/nixos-configurations/lindberg/docs/hdd_1.jpg differ diff --git a/nixos-configurations/lindberg/docs/hdd_3.jpg b/nixos-configurations/lindberg/docs/hdd_3.jpg new file mode 100644 index 0000000..6b3dc64 Binary files /dev/null and b/nixos-configurations/lindberg/docs/hdd_3.jpg differ diff --git a/nixos-configurations/lindberg/docs/hdd_zvtaa02h.jpg b/nixos-configurations/lindberg/docs/hdd_zvtaa02h.jpg new file mode 100644 index 0000000..2d418f5 Binary files /dev/null and b/nixos-configurations/lindberg/docs/hdd_zvtaa02h.jpg differ diff --git a/nixos-configurations/lindberg/docs/hdd_zvtaeypl.jpg b/nixos-configurations/lindberg/docs/hdd_zvtaeypl.jpg new file mode 100644 index 0000000..9aa89c6 Binary files /dev/null and b/nixos-configurations/lindberg/docs/hdd_zvtaeypl.jpg differ diff --git a/nixos-configurations/lindberg/docs/ssd_1_left.jpg b/nixos-configurations/lindberg/docs/ssd_1_left.jpg new file mode 100644 index 0000000..42966dd Binary files /dev/null and b/nixos-configurations/lindberg/docs/ssd_1_left.jpg differ diff --git a/nixos-configurations/lindberg/filesystems.nix b/nixos-configurations/lindberg/filesystems.nix new file mode 100644 index 0000000..b594021 --- /dev/null +++ b/nixos-configurations/lindberg/filesystems.nix @@ -0,0 +1,38 @@ +{ config, pkgs, ... }: +{ + + # Configurations are set in disko-config.nix! + + # mdadm.conf generated by `mdadm --detail --scan` + boot.swraid.enable = true; + boot.swraid.mdadmConf = '' + MAILADDR root + ARRAY /dev/md/raid_system metadata=1.2 name=any:raid_system UUID=1becc692:aeb83b67:1c65da45:b8bd4b93 + ARRAY /dev/md/raid_data metadata=1.2 name=any:raid_data UUID=576eabb1:0722bc27:84d9314f:d0145000 + INACTIVE-ARRAY /dev/md125 metadata=1.2 name=nixos:md_data UUID=b9c36b6d:a2e0fa86:f6dbfe57:857cd0d2 + ''; + + # TODO: RAID Monitoring + # TODO: Set spin-down time of physical disks + + services.fwupd.daemonSettings.EspLocation = pkgs.lib.mkForce config.disko.devices.disk.system-1.content.partitions.boot.content.mountpoint; + + # Use the systemd-boot EFI boot loader. + boot.loader.efi.canTouchEfiVariables = true; + boot.loader.grub = { + enable = true; + efiSupport = true; + mirroredBoots = [ + { + devices = [ "nodev" ]; + path = "/boot-primary"; + efiBootloaderId = "NixOS primary"; + } + #{ + # devices = [ "nodev" ]; + # path = "/boot-secondary"; + # efiBootloaderId = "NixOS secondary"; + #} + ]; + }; +} diff --git a/nixos-configurations/lindberg/networking.nix b/nixos-configurations/lindberg/networking.nix new file mode 100644 index 0000000..e46c3b5 --- /dev/null +++ b/nixos-configurations/lindberg/networking.nix @@ -0,0 +1,93 @@ +{ config, pkgs, ... }: + +let + meta = config.qois.meta; +in +{ + networking.hostName = meta.hosts.lindberg.hostName; + + imports = [ ../../defaults/backplane-net ]; + + networking.useDHCP = false; + networking.interfaces.enp5s0.useDHCP = true; + + # Virtualization + networking.interfaces.vms-nat.useDHCP = false; + networking.interfaces.vms-nat.ipv4.addresses = [ + ( + let + netConfig = meta.network.virtual.lindberg-vms-nat; + in + { + address = netConfig.hosts.lindberg.v4.ip; + prefixLength = netConfig.v4.prefixLength; + } + ) + ]; + + networking.bridges.vms-nat.interfaces = [ ]; + networking.nat = { + enable = true; + internalInterfaces = [ "vms-nat" ]; + internalIPs = with meta.network.virtual.lindberg-vms-nat.v4; [ + "${id}/${builtins.toString prefixLength}" + ]; + externalInterface = "enp5s0"; + }; + services.dnsmasq = + let + netConfig = meta.network.virtual.lindberg-vms-nat; + in + { + enable = true; + resolveLocalQueries = false; + settings = { + interface = "vms-nat"; + bind-interfaces = true; + + domain-needed = true; + + domain = netConfig.domain; + dhcp-range = [ "10.247.0.2,10.247.0.253" ]; + dhcp-option = [ + "option:router,${netConfig.hosts.lindberg.v4.ip}" + "option:domain-search,${netConfig.domain}" + ]; + dhcp-authoritative = true; + }; + }; + systemd.services.dnsmasq.bindsTo = [ "network-addresses-vms-nat.service" ]; + networking.firewall.interfaces.vms-nat = { + allowedUDPPorts = [ + 53 + 67 + ]; + allowedTCPPorts = [ 53 ]; + }; + + # Boot + boot.initrd.network.udhcpc.enable = true; + + services.qois.luks-ssh = { + enable = true; + interface = "eth0"; + sshPort = 2222; + }; + + # Use this node as vpn exit node + qois.backup-client.includePaths = [ "/var/lib/tailscale" ]; + services.tailscale = { + enable = true; + openFirewall = true; + useRoutingFeatures = "server"; + authKeyFile = config.sops.secrets."tailscale/key".path; + extraUpFlags = [ + "--login-server=https://vpn.qo.is" + "--advertise-exit-node" + ( + with meta.network.virtual.backplane.v4; "--advertise-routes=${id}/${builtins.toString prefixLength}" + ) + "--advertise-tags=tag:srv" + ]; + }; +} diff --git a/nixos-configurations/lindberg/secrets.nix b/nixos-configurations/lindberg/secrets.nix new file mode 100644 index 0000000..b9121f7 --- /dev/null +++ b/nixos-configurations/lindberg/secrets.nix @@ -0,0 +1,19 @@ +{ ... }: +let + backupConfiguration = { + restartUnits = [ + "borgbackup-job-data-fulberg.service" + "borgbackup-job-data-tierberg.service" + ]; + }; +in + +{ + sops.secrets = { + "tailscale/key" = { + restartUnits = [ "tailscale.service" ]; + }; + "backup/data/password" = backupConfiguration; + "backup/data/ssh-key" = backupConfiguration; + }; +} diff --git a/nixos-configurations/lindberg/virtualisation.nix b/nixos-configurations/lindberg/virtualisation.nix new file mode 100644 index 0000000..5905bdf --- /dev/null +++ b/nixos-configurations/lindberg/virtualisation.nix @@ -0,0 +1,8 @@ +{ config, pkgs, ... }: +{ + virtualisation.libvirtd = { + enable = true; + onShutdown = "shutdown"; + }; + environment.systemPackages = [ pkgs.virtiofsd ]; +} diff --git a/nixos-configurations/secrets.nix b/nixos-configurations/secrets.nix new file mode 100644 index 0000000..0757d74 --- /dev/null +++ b/nixos-configurations/secrets.nix @@ -0,0 +1,11 @@ +{ inputs, ... }: +{ + sops.secrets = + let + allHostsSecretsFile = "${inputs.private}/nixos-configurations/secrets.sops.yaml"; + in + { + "msmtp/password".sopsFile = allHostsSecretsFile; + "wgautomesh/gossip-secret".sopsFile = allHostsSecretsFile; + }; +} diff --git a/nixos-configurations/setup.md b/nixos-configurations/setup.md new file mode 100644 index 0000000..542eb18 --- /dev/null +++ b/nixos-configurations/setup.md @@ -0,0 +1,70 @@ +# Setup of new hosts + +## Prepare Remote Machine + +1. Boot nixos installer image +2. Set a root password: `sudo passwd root` +3. Get host ip to connect to ssh with `ip a` + +## Verify configuration + +1. Verify the network device name in the configuration (e.g. `enp2s0`) + +## Installation + +```bash +nix develop + +# Set according to what we want +REMOTE_IP= +REMOTE_HOSTNAME= + +# Verify SSH works, accept newly generated host keys and create directory for system secrets +ssh root@$REMOTE_IP mkdir -p /run/secrets/system/ + +# Configure Secrets management +HOSTS_FILE="defaults/meta/hosts.json" +REMOTE_SSHKEY="`ssh-keyscan -q -t ed25519 $REMOTE_IP | cut --delimiter ' ' --fields 2-`" +git show ":$HOSTS_FILE" | jq ".${REMOTE_HOSTNAME}.sshKey=\"${REMOTE_SSHKEY}\"" > $HOSTS_FILE +sops-rekey + +# Check that: +# - you updated the age key +# - default interface name is correctly configured +# - you are 100% on the right REMOTE_IP (host will be wiped by disko) +# - if you use LUKS secrets, you created a secret "system.hdd" with the disk password: +# `sops set private/nixos-configurations/$REMOTE_HOSTNAME/secrets.sops.yaml '["system"]["test"]' "\"`pwgen -1 --ambiguous 20 1`\"" +# - if you use initrd ssh server (for remote luks unlock), create a "system.initrd-ssh-private" ssh key (); +# ```bash +# export SSH_KEYFILE=/tmp/${REMOTE_HOSTNAME}-initrd-ssh-key +# mkfifo -m 600 $SSH_KEYFILE +# ssh-keygen -q -t ed25519 -C "boot@${REMOTE_HOSTNAME}" -N "" -f $SSH_KEYFILE <<< "y\ny\n" & +# sops set private/nixos-configurations/$REMOTE_HOSTNAME/secrets.sops.yaml '["system"]["initrd-ssh-key"]' "\"`cat $SSH_KEYFILE`\"" +# rm $SSH_KEYFILE +# ``` + +# Install OS. ⚠️ This clears all local hdds with disko! +nixos-anywhere --copy-host-keys --flake ".#$REMOTE_HOSTNAME" root@$REMOTE_IP +# To use a jumphost, use `--ssh-option "ProxyJump=user@jumphost"` + + +# TODO: +## qois-setup-host $REMOTE_HOSTNAME $REMOTE_IP --[no]-luks [--generate-system-secrets] [--proxy user@jumphost] +## read: Did you update the AGE keys to the setup tools setup keys? [Enter] +## read: Did you check the interfaces names to be correct? [Enter] +## read: Are you 100% sure the command promt is corect? [Enter] + +# With LUKS key: +sops exec-file --no-fifo --filename secret.key private/nixos-configurations/$REMOTE_HOSTNAME/secrets.sops.yaml " + nixos-anywhere --copy-host-keys --flake .#$REMOTE_HOSTNAME root@$REMOTE_IP \ + --disk-encryption-keys /run/secrets/system/hdd.key <(yq --raw-output '.system.hdd' {}) \ + --disk-encryption-keys /run/secrets/system/initrd-ssh-key <(yq --raw-output '.system.\"initrd-ssh-key\"' {}) +" +``` + +## Post-Setup + +* Add backplane-vpn pubkey to `network-virtual.nix` configuration with + ```bash + wg pubkey < /secrets/wireguard/private/backplane + ``` diff --git a/nixos-configurations/stompert/README.md b/nixos-configurations/stompert/README.md new file mode 100644 index 0000000..8a46e06 --- /dev/null +++ b/nixos-configurations/stompert/README.md @@ -0,0 +1,7 @@ +# Operations {#_operations} + +Reboot requires passphrase (see pass `host/stompert/hdd_luks`) + +``` bash +ssh -p 2222 root@stompert.eem-ext.net.qo.is +``` diff --git a/nixos-configurations/stompert/default.nix b/nixos-configurations/stompert/default.nix new file mode 100644 index 0000000..e4cb9a4 --- /dev/null +++ b/nixos-configurations/stompert/default.nix @@ -0,0 +1,63 @@ +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + +{ config, pkgs, ... }: + +{ + imports = [ + ../../defaults/backplane-net + ../../defaults/hardware/apu.nix + ../../defaults/base + ../../defaults/meta + ]; + + boot.initrd.luks.devices."systems".device = "/dev/disk/by-uuid/5718bd19-cb7a-4728-9ec4-6b2be48215fc"; + + fileSystems."/" = { + device = "/dev/mapper/vg_systems-hv_stompert"; + fsType = "btrfs"; + options = [ "subvol=root" ]; + }; + + fileSystems."/boot" = { + device = "/dev/disk/by-uuid/bbe12368-1f81-4924-a12c-2edec886f7c8"; + fsType = "ext4"; + }; + + swapDevices = [ { device = "/dev/disk/by-uuid/851e1d05-569f-41ca-8ed9-d7ffba489ffe"; } ]; + + # Use the GRUB 2 boot loader. + boot.loader.grub.enable = true; + # boot.loader.grub.efiSupport = true; + # boot.loader.grub.efiInstallAsRemovable = true; + # boot.loader.efi.efiSysMountPoint = "/boot/efi"; + # Define on which hard drive you want to install Grub. + boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only + + services.qois.luks-ssh = { + enable = true; + interface = "eth1"; + sshPort = 2222; + }; + + networking.hostName = "stompert"; # Define your hostname. + + # The global useDHCP flag is deprecated, therefore explicitly set to false here. + # Per-interface useDHCP will be mandatory in the future, so this generated config + # replicates the default behaviour. + networking.useDHCP = false; + networking.interfaces.enp1s0.useDHCP = true; + networking.interfaces.enp2s0.useDHCP = true; + networking.interfaces.enp3s0.useDHCP = true; + networking.tempAddresses = "disabled"; + + # Set your time zone. + # time.timeZone = "Europe/Amsterdam"; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "22.11"; # Did you read the comment? +} diff --git a/nixos-configurations/tierberg/README.md b/nixos-configurations/tierberg/README.md new file mode 100644 index 0000000..e3a2232 --- /dev/null +++ b/nixos-configurations/tierberg/README.md @@ -0,0 +1,9 @@ +Access via `tierberg.coredump-ext.net.qo.is` `:51022` (SSH) and `:51023` (SSH-LUKS) + +## Operations + +Reboot requires passphrase (see pass `host/tierberg/hdd_luks) + +```bash +ssh -p 51023 root@tierberg.coredump-ext.net.qo.is +``` diff --git a/nixos-configurations/tierberg/backup.nix b/nixos-configurations/tierberg/backup.nix new file mode 100644 index 0000000..0a6dbca --- /dev/null +++ b/nixos-configurations/tierberg/backup.nix @@ -0,0 +1,35 @@ +{ config, pkgs, ... }: +{ + + qois.backup-server = { + enable = true; + backupStorageRoot = "/mnt/nas-backup-qois"; + }; + + services.borgbackup.repos = + let + backupRoot = "/mnt/nas-backup-qois"; + hostBackupRoot = "${backupRoot}/hosts"; + dataBackupRoot = "${backupRoot}/data"; + in + { + "lindberg-nextcloud" = { + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIpzfp9VqclbPJ42ZrkRpvjMSTeyq0qce03zCRXqIHMw backup@lindberg-nextcloud" + ]; + path = "${hostBackupRoot}/lindberg-nextcloud"; + }; + "lindberg-data" = { + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGTmyoVONC12MgOodvzdPpZzLSVwpkC6zkf+Rg0W36gy backup-data@lindberg" + ]; + path = "${dataBackupRoot}/lindberg-data"; + }; + "lindberg-build-system" = { + authorizedKeys = [ + "ssh-ed25519 AAAATODOTODOTODOTODOAAAAIGTmyoVONC12MgOodvzdPpZzLSVwpkC6zkf+Rg0W36gy backup-system@lindberg-build" + ]; + path = "${dataBackupRoot}/lindberg-build-system"; + }; + }; +} diff --git a/nixos-configurations/tierberg/default.nix b/nixos-configurations/tierberg/default.nix new file mode 100644 index 0000000..2408f89 --- /dev/null +++ b/nixos-configurations/tierberg/default.nix @@ -0,0 +1,24 @@ +{ config, pkgs, ... }: + +{ + imports = [ + ./networking.nix + ./filesystems.nix + ./backup.nix + + ../../defaults/hardware/apu1.nix + # wle600: Not used currently + + ../../defaults/base + ../../defaults/meta + ]; + + # Set your time zone. + # time.timeZone = "Europe/Amsterdam"; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "23.05"; # Did you read the comment? +} diff --git a/nixos-configurations/tierberg/filesystems.nix b/nixos-configurations/tierberg/filesystems.nix new file mode 100644 index 0000000..9e74fa1 --- /dev/null +++ b/nixos-configurations/tierberg/filesystems.nix @@ -0,0 +1,52 @@ +{ config, pkgs, ... }: +{ + + boot.initrd.luks.devices = { + "system".device = "/dev/disk/by-uuid/ac7f7ef2-280d-4b9f-8150-a6f11ecec1df"; + "swap".device = "/dev/disk/by-uuid/6ce21585-6813-46d0-9a98-ebcfa507bdb0"; + }; + + fileSystems = { + "/" = { + device = "/dev/disk/by-uuid/c775e380-b15f-499b-94f2-8caa27e6e0ff"; + fsType = "btrfs"; + options = [ + "defaults" + "subvol=nixos" + "noatime" + ]; + }; + + "/boot" = { + device = "/dev/disk/by-uuid/0b22a6bc-0721-49d6-9e66-1f8d9258f47b"; + fsType = "ext4"; + }; + "/mnt/nas-backup-qois" = { + device = "192.168.254.1:/raid0/data/_NAS_NFS_Exports_/backup-qois"; + fsType = "nfs"; + options = [ + "defaults" + "noatime" + "soft" + "vers=3" + ]; + }; + "/mnt/nas-backup-coredump" = { + device = "192.168.254.1:/raid0/data/_NAS_NFS_Exports_/backup-qois"; + fsType = "nfs"; + options = [ + "defaults" + "noatime" + "soft" + "vers=3" + ]; + }; + }; + + swapDevices = [ { device = "/dev/disk/by-uuid/e91f9aba-1e59-4d41-a772-f11d4314dc19"; } ]; + + boot.loader.grub = { + enable = true; + device = "/dev/sda"; + }; +} diff --git a/nixos-configurations/tierberg/networking.nix b/nixos-configurations/tierberg/networking.nix new file mode 100644 index 0000000..2d57100 --- /dev/null +++ b/nixos-configurations/tierberg/networking.nix @@ -0,0 +1,29 @@ +{ config, pkgs, ... }: + +let + meta = config.qois.meta; + lattenbach-nas-net = meta.network.physical.lattenbach-nas; +in +{ + networking.hostName = meta.hosts.tierberg.hostName; + + imports = [ ../../defaults/backplane-net ]; + + networking.enableIPv6 = false; # TODO + + networking.useDHCP = false; + networking.interfaces.enp1s0.useDHCP = true; + networking.interfaces.enp2s0.ipv4.addresses = [ + { + inherit (lattenbach-nas-net.v4) prefixLength; + address = lattenbach-nas-net.hosts.tierberg.v4.ip; + } + ]; + networking.interfaces.enp3s0.useDHCP = true; + + services.qois.luks-ssh = { + enable = true; + interface = "eth0"; + sshPort = 2222; + }; +} diff --git a/nixos-modules/default.nix b/nixos-modules/default.nix new file mode 100644 index 0000000..0afd3b1 --- /dev/null +++ b/nixos-modules/default.nix @@ -0,0 +1,8 @@ +inputs: { + default = + { config, pkgs, ... }: + { + + imports = (inputs.self.lib.loadSubmodulesFrom ./.) ++ [ inputs.private.nixosModules.default ]; + }; +} diff --git a/nixos-modules/luks-ssh/default.nix b/nixos-modules/luks-ssh/default.nix new file mode 100644 index 0000000..7f11b88 --- /dev/null +++ b/nixos-modules/luks-ssh/default.nix @@ -0,0 +1,109 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + cfg = config.services.qois.luks-ssh; +in +{ + options.services.qois.luks-ssh = { + enable = mkEnableOption "luks-ssh service"; + + interface = mkOption { + type = types.str; + example = "enp0"; + description = '' + Interface name. + ''; + }; + + ip = mkOption { + type = types.str; + example = "192.168.0.1"; + default = "dhcp"; + description = '' + Host IP Address or "dhcp" (default). + ''; + }; + + gateway = mkOption { + type = types.str; + default = null; + example = "192.168.0.1"; + description = '' + IP of gateway. May be null if ip is aquired by dhcp. + ''; + }; + + netmask = mkOption { + type = types.str; + default = null; + example = "192.168.0.1"; + description = '' + Netmask of internal network. May be null if ip is aquired by dhcp. + ''; + }; + + sshHostKey = mkOption { + type = types.str; + default = "/secrets/initrd_ssh_key_ed25519"; + description = '' + Hostkey for ssh connection. + The key is stored in an unencrypted form, + so it is strongly advised against using the normal host key. + + You can generate a host key with: + + ssh-keygen -t ed25519 -N "" -f /secrets/initrd_ssh_key_ed25519 + ''; + }; + + sshPort = mkOption { + type = types.addCheck types.int (n: n > 0 && n < 65536); + default = 2222; + description = '' + SSH Port of the initrd ssh server. + Should be different from default SSH port to prevent known hosts collissions. + ''; + }; + }; + + config = mkIf cfg.enable { + boot.initrd.network = { + enable = true; + ssh = { + enable = true; + port = cfg.sshPort; + authorizedKeys = + with lib; + concatLists ( + mapAttrsToList ( + name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else [ ] + ) config.users.users + ); + hostKeys = [ cfg.sshHostKey ]; + }; + postCommands = '' + echo 'cryptsetup-askpass' >> /root/.profile + ''; + }; + + boot.initrd.network.udhcpc.enable = cfg.ip == "dhcp"; + boot.kernelParams = + if cfg.ip == "dhcp" then + [ ] + else + [ + "ip=${cfg.ip}::${cfg.gateway}:${cfg.netmask}:${config.networking.hostName}:${cfg.interface}:none" + ]; # See boot.initrd.network.enable + + boot.initrd.postMountCommands = '' + ip link set ${cfg.interface} down + ''; + }; +} diff --git a/nixos-modules/meta/default.nix b/nixos-modules/meta/default.nix new file mode 100644 index 0000000..f7d9775 --- /dev/null +++ b/nixos-modules/meta/default.nix @@ -0,0 +1,13 @@ +{ + config, + lib, + pkgs, + options, + ... +}: +{ + imports = [ + ./hosts.nix + ./network.nix + ]; +} diff --git a/nixos-modules/meta/hosts.nix b/nixos-modules/meta/hosts.nix new file mode 100644 index 0000000..27526d0 --- /dev/null +++ b/nixos-modules/meta/hosts.nix @@ -0,0 +1,53 @@ +{ + config, + lib, + pkgs, + options, + ... +}: + +with lib; + +let + cfg = config.qois.meta.hosts; +in +{ + options.qois.meta.hosts = mkOption { + type = types.attrsOf ( + types.submodule ( + { name, ... }: + { + options = { + hostName = mkOption { + type = types.strMatching "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$"; + default = name; + description = "The host's name. See networking.hostName for more details."; + }; + + sshKey = mkOption { + type = types.nullOr (types.strMatching "^ssh-ed25519 [a-zA-Z0-9/+]{68}$"); + default = null; + example = "ssh-ed25519 AAAAbcdefgh....xyz root@myhost"; + description = lib.mdDoc '' + The ssh public key of ed25519 type. + + May be fetched with `ssh-keyscan example.com`. + ''; + }; + }; + } + ) + ); + default = { }; + description = "Host configuration properties options"; + }; + config = + let + hostsWithSshKey = lib.filterAttrs (name: hostCfg: hostCfg.sshKey != null) cfg; + in + { + programs.ssh.knownHosts = lib.mapAttrs (name: hostCfg: { + publicKey = hostCfg.sshKey; + }) hostsWithSshKey; + }; +} diff --git a/nixos-modules/meta/network.nix b/nixos-modules/meta/network.nix new file mode 100644 index 0000000..a17a9e9 --- /dev/null +++ b/nixos-modules/meta/network.nix @@ -0,0 +1,234 @@ +{ + config, + lib, + pkgs, + options, + ... +}: + +with lib; +with types; + +let + cfg = config.qois.meta.network; + mkStr = + description: + (mkOption { + type = str; + inherit description; + }); + mkOptStr = + description: + (mkOption { + type = nullOr str; + default = null; + inherit description; + }); + + mkNetworkIdOpts = + v: + assert v == 4 || v == 6; + submodule { + options = { + id = mkOption { + type = types.str; + description = '' + IPv${toString v} ID + ''; + }; + + prefixLength = mkOption { + type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128)); + description = '' + Subnet mask of the ip, specified as the number of + bits in the prefix (${if v == 4 then "24" else "64"}). + ''; + }; + + gateway = mkOption { + default = null; + type = nullOr str; + description = '' + Upstream Gateway IP + ''; + }; + + nameservers = mkOption { + default = null; + type = nullOr (listOf str); + description = "Nameserver IP"; + }; + }; + }; + mkFqdn = + host: domain: + mkOption { + type = str; + default = "${config.qois.meta.hosts.${host}.hostName}.${domain}"; + description = '' + The fully qualified domain name (FYDN) of this host inside of this specific + network. Defaults to the host attribute key and net domain. + ''; + }; +in +{ + options.qois.meta.network.physical = mkOption { + description = "Physical network configuration"; + type = attrsOf ( + submodule ( + { name, ... }: + let + networkName = name; + in + { + options = { + v4 = mkOption { type = (mkNetworkIdOpts 4); }; + v6 = mkOption { type = nullOr (mkNetworkIdOpts 6); }; + domain = mkStr "Network DNS Domain suffix"; + hosts = mkOption { + type = attrsOf ( + submodule ( + { name, ... }: + let + host = name; + in + { + options = { + v4 = mkOption { type = submodule { options.ip = mkStr "The V4 host IP address"; }; }; + v6 = mkOption { + default = null; + type = nullOr (submodule { + options.ip = mkStr "The V6 host IP address"; + }); + }; + fqdn = mkFqdn host cfg.physical.${networkName}.domain; + }; + } + ) + ); + }; + }; + } + ) + ); + default = { }; + }; + options.qois.meta.network.virtual = mkOption { + description = "Virtual network configuration"; + type = types.attrsOf ( + types.submodule ( + { name, ... }: + let + networkName = name; + in + { + options = { + v4 = mkOption { type = (mkNetworkIdOpts 4); }; + v6 = mkOption { + default = null; + type = nullOr (mkNetworkIdOpts 6); + }; + domain = mkStr "Network DNS Domain suffix"; + hosts = mkOption { + type = attrsOf ( + submodule ( + { name, ... }: + let + host = name; + in + { + options = { + v4 = mkOption { type = submodule { options.ip = mkStr "The V4 host IP address"; }; }; + v6 = mkOption { + default = null; + type = nullOr (submodule { + options.ip = mkStr "The V6 host IP address"; + }); + }; + + # Taken from https://github.com/NixOS/nixpkgs/blob/nixos-21.11/nixos/modules/services/networking/wireguard.nix: + publicKey = mkOption { + example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg="; + type = str; + description = "The base64 public key of the peer."; + }; + persistentKeepalive = mkOption { + default = null; + type = nullOr int; + example = 25; + description = '' + This is optional and is by default off, because most + users will not need it. It represents, in seconds, between 1 and 65535 + inclusive, how often to send an authenticated empty packet to the peer, + for the purpose of keeping a stateful firewall or NAT mapping valid + persistently. For example, if the interface very rarely sends traffic, + but it might at anytime receive traffic from a peer, and it is behind + NAT, the interface might benefit from having a persistent keepalive + interval of 25 seconds; however, most users will not need this.''; + }; + + # Endpoint Configuration: + endpoint = mkOption { + description = '' + FQDN and port of this vpn-endpoint. This option indicates this host is a VPN + server. + ''; + default = null; + type = nullOr (submodule { + options = { + fqdn = mkFqdn host cfg.virtual.${networkName}.domain; + port = mkOption { + type = types.addCheck types.int (n: n > 0 && n < 65536); + description = '' + The port on which the wireguard endpoint receives packages. + ''; + }; + }; + }); + }; + }; + } + ) + ); + }; + }; + } + ) + ); + default = { }; + }; + config = { + programs.ssh.knownHosts = + let + # hostname -> single network cfg attr -> ["known host's names"] + getHostNamesFromNetwork = + hostname: network: + if network.hosts ? ${hostname} && network.hosts.${hostname} != null then + let + hostCfg = network.hosts.${hostname}; + in + [ + "${hostname}.${network.domain}" + hostCfg.v4.ip + ] + ++ (if hostCfg.v6 != null then [ hostCfg.v6.ip ] else [ ]) + else + [ ]; + + # hostname -> attr of network defs -> ["known host's names"] + getHostNamesForNetworks = + hostname: networks: lib.flatten (map (getHostNamesFromNetwork hostname) (lib.attrValues networks)); + + # hostname -> ["known host's names"] + getHostNames = + hostname: + (getHostNamesForNetworks hostname cfg.virtual) ++ (getHostNamesForNetworks hostname cfg.physical); + + hostsWithPublicKey = lib.filterAttrs ( + hostName: hostConfig: hostConfig.sshKey != null + ) config.qois.meta.hosts; + in + mapAttrs (name: hostCfg: { extraHostNames = getHostNames name; }) hostsWithPublicKey; + + }; +} diff --git a/nixos-modules/nixpkgs-cache/default.nix b/nixos-modules/nixpkgs-cache/default.nix new file mode 100644 index 0000000..12c55ef --- /dev/null +++ b/nixos-modules/nixpkgs-cache/default.nix @@ -0,0 +1,83 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.qois.nixpkgs-cache; +in +with lib; +{ + options.qois.nixpkgs-cache = { + enable = mkEnableOption ''Enable nixpkgs cache server.''; + + hostname = mkOption { + type = types.str; + example = "mycache.myhost.org"; + description = "Hostname, under which the cache is served"; + }; + + timeout = mkOption { + type = types.str; + default = "90d"; + description = "Timespan after which cache entries should be removed."; + }; + + size = mkOption { + type = types.int; + default = 50; + description = "in GB; maximum size of the cache on disk."; + }; + + dnsResolvers = mkOption { + type = types.listOf types.str; + example = [ "8.8.8.8" ]; + description = '' + List of DNS resolvers to use for upstream cache hostname resolution. + Note: IPv6 is not supported currently. + ''; + }; + }; + + config = mkIf cfg.enable { + networking.hosts."127.0.0.1" = [ cfg.hostname ]; + services.nginx = { + enable = true; + resolver.ipv6 = false; # TODO(6): Support IPv6 + resolver.addresses = cfg.dnsResolvers; + + proxyCachePath.nixpkgs-cache = { + enable = true; + keysZoneName = "nixpkgs_cache"; + maxSize = "${builtins.toString cfg.size}G"; + keysZoneSize = "${builtins.toString (cfg.size * 3)}M"; # Assumes 3MB keys storage per GB + inactive = cfg.timeout; + }; + + virtualHosts.${cfg.hostname} = { + kTLS = true; + forceSSL = true; + enableACME = true; + + locations."/" = { + proxyPass = "https://cache.nixos.org"; + recommendedProxySettings = false; + extraConfig = '' + proxy_cache nixpkgs_cache; + proxy_cache_valid ${cfg.timeout}; + proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_504 http_403 http_404 http_429; + proxy_ignore_headers X-Accel-Expires Expires Cache-Control Set-Cookie; # Files are immutable so just keep them + proxy_cache_lock on; + proxy_ssl_server_name on; + proxy_ssl_session_reuse off; + proxy_ssl_verify on; + proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; + proxy_set_header Host cache.nixos.org; + ''; + }; + }; + }; + }; +} diff --git a/nixos-modules/outgoing-server-mail/default.nix b/nixos-modules/outgoing-server-mail/default.nix new file mode 100644 index 0000000..2f04382 --- /dev/null +++ b/nixos-modules/outgoing-server-mail/default.nix @@ -0,0 +1,47 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.qois.outgoing-server-mail; +in +with lib; +{ + options.qois.outgoing-server-mail = { + enable = mkEnableOption ''Enable outgoing emails for server.''; + }; + + config = mkIf cfg.enable { + + sops.secrets."msmtp/password" = { + owner = "root"; + group = config.users.groups.postdrop.name; + mode = "0440"; + }; + + users.groups.postdrop = { }; + + programs.msmtp = { + enable = true; + defaults = { + aliases = pkgs.writeText "aliases" '' + root: sysadmin@qo.is + ''; + port = 465; + tls = true; + tls_starttls = "off"; + + }; + accounts.default = { + auth = true; + host = "mail.cyon.ch"; + user = "system@qo.is"; + from = "no-reply@qo.is"; + passwordeval = "${pkgs.busybox}/bin/cat ${config.sops.secrets."msmtp/password".path}"; + }; + }; + }; +} diff --git a/nixos-modules/postgresql/default.nix b/nixos-modules/postgresql/default.nix new file mode 100644 index 0000000..e2a8e88 --- /dev/null +++ b/nixos-modules/postgresql/default.nix @@ -0,0 +1,22 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.qois.postgresql; +in +with lib; +{ + options.qois.postgresql = { + enable = mkEnableOption ''Enable postgresql services with defaults''; + }; + + config = mkIf cfg.enable { + services.postgresql.enable = true; + services.postgresqlBackup.enable = true; + qois.backup-client.includePaths = [ config.services.postgresqlBackup.location ]; + }; +} diff --git a/nixos-modules/qois/backup-client/README.md b/nixos-modules/qois/backup-client/README.md new file mode 100644 index 0000000..e2f6335 --- /dev/null +++ b/nixos-modules/qois/backup-client/README.md @@ -0,0 +1,6 @@ +# Backup Module + +This module creates a host-based backup job `system-${target-hostname}` (currently with borg). +The module has sensible defaults for a whole system, note however that individual services/paths must be included or excluded added manually. + +Target hosts should use the [Backup Server Module](../backup-server). diff --git a/nixos-modules/qois/backup-client/default.nix b/nixos-modules/qois/backup-client/default.nix new file mode 100644 index 0000000..5e45d82 --- /dev/null +++ b/nixos-modules/qois/backup-client/default.nix @@ -0,0 +1,103 @@ +{ + config, + lib, + options, + pkgs, + self, + ... +}: + +let + cfg = config.qois.backup-client; + defaultIncludePaths = [ + "/etc" + "/home" + "/root" + ]; + defaultExcludePaths = [ + "/root/.cache" + "/root/.config/borg" + ]; + defaultSopsPasswordFile = "system/backup/password"; +in +with lib; +{ + options.qois.backup-client = + let + pathsType = with types; listOf str; + in + { + enable = mkEnableOption "Enable this host to execute backups."; + + targets = mkOption { + type = with types; listOf (enum (attrNames config.qois.meta.hosts)); + default = [ + "cyprianspitz" + ]; + description = "Target hosts to make backups to. Must be configured to receive backups in the backplane network."; + }; + + includePaths = mkOption { + type = pathsType; + default = [ ]; + description = "Paths that are included in backup. The backup module always includes: ${concatStringsSep ", " defaultIncludePaths}"; + }; + + excludePaths = mkOption { + type = pathsType; + default = [ ]; + description = "Paths that are excluded in backup. The backup module always excludes: ${concatStringsSep ", " defaultExcludePaths}"; + }; + + passwordFile = mkOption { + type = with types; nullOr str; + default = null; + example = "config.sops.secrets.${defaultSopsPasswordFile}.path"; + description = "Path to password file. Taken from sops host secret ${defaultSopsPasswordFile} by default, must be randomly generated per host."; + }; + + networkName = mkOption { + type = types.enum (attrNames config.qois.meta.network.virtual); + default = "backplane"; + description = "Name of virtual network through which the backups should be done"; + }; + }; + + config.services.borgbackup.jobs = mkIf cfg.enable ( + builtins.listToAttrs ( + map (backupHost: { + name = "system-${backupHost}"; + value = { + repo = "borg@${config.qois.meta.network.virtual.${cfg.networkName}.hosts.${backupHost}.v4.ip}:."; + environment.BORG_RSH = "ssh -i /etc/ssh/ssh_host_ed25519_key"; + + paths = defaultIncludePaths ++ cfg.includePaths; + exclude = defaultExcludePaths ++ cfg.excludePaths; + + doInit = true; + encryption = { + mode = "repokey"; + passCommand = + let + passFile = + if cfg.passwordFile != null then + cfg.passwordFile + else + config.sops.secrets.${defaultSopsPasswordFile}.path; + in + "cat ${passFile}"; + }; + + startAt = "07:06"; + persistentTimer = true; + }; + }) cfg.targets + ) + ); + + config.sops.secrets = mkIf (cfg.enable && cfg.passwordFile == null) { + ${defaultSopsPasswordFile} = { + restartUnits = map (target: "borgbackup-job-system-${target}.service") cfg.targets; + }; + }; +} diff --git a/nixos-modules/qois/backup-server/README.md b/nixos-modules/qois/backup-server/README.md new file mode 100644 index 0000000..56d6505 --- /dev/null +++ b/nixos-modules/qois/backup-server/README.md @@ -0,0 +1,3 @@ +# Backup Server Module + +This backup module creates borg repositories for all the hosts configured with hosts. diff --git a/nixos-modules/qois/backup-server/default.nix b/nixos-modules/qois/backup-server/default.nix new file mode 100644 index 0000000..fe3c79f --- /dev/null +++ b/nixos-modules/qois/backup-server/default.nix @@ -0,0 +1,53 @@ +{ + config, + lib, + options, + pkgs, + self, + ... +}: + +let + cfg = config.qois.backup-server or { }; +in +with lib; +{ + options.qois.backup-server = { + enable = mkEnableOption "Enable backup hosting"; + + backupStorageRoot = mkOption { + type = with types; nullOr str; + default = "/mnt/backup"; + example = "/mnt/nas/backup"; + description = "Path where backups are stored if this host is used as a backup target."; + }; + + hosts = options.qois.meta.hosts // { + default = config.qois.meta.hosts; + }; + }; + + config = lib.mkIf cfg.enable { + services.borgbackup.repos = + let + hasSshKey = hostName: cfg.hosts.${hostName}.sshKey != null; + mkRepo = + hostName: + ( + let + name = "system-${hostName}"; + in + { + inherit name; + value = { + path = "${cfg.backupStorageRoot}/${name}"; + authorizedKeys = [ cfg.hosts.${hostName}.sshKey ]; + }; + } + ); + + hostsWithSshKeys = lib.filter hasSshKey (lib.attrNames cfg.hosts); + in + lib.listToAttrs (map mkRepo hostsWithSshKeys); + }; +} diff --git a/nixos-modules/qois/default.nix b/nixos-modules/qois/default.nix new file mode 100644 index 0000000..6a72f9f --- /dev/null +++ b/nixos-modules/qois/default.nix @@ -0,0 +1,10 @@ +{ + config, + pkgs, + inputs, + ... +}: +{ + + imports = inputs.self.lib.loadSubmodulesFrom ./.; +} diff --git a/nixos-modules/qois/git-ci-runner/README.md b/nixos-modules/qois/git-ci-runner/README.md new file mode 100644 index 0000000..e2bca9f --- /dev/null +++ b/nixos-modules/qois/git-ci-runner/README.md @@ -0,0 +1,8 @@ +# Git CI Runner + +Runner for the [Forgejo git instance](../git/README.md). +Currently registers a default runner with ubuntu OS. + +## Create Secret Token + +To create a new token for registration, follow the steps outlined in the [Forgejo documentation](https://forgejo.org/docs/latest/user/actions/#forgejo-runner). diff --git a/nixos-modules/qois/git-ci-runner/default.nix b/nixos-modules/qois/git-ci-runner/default.nix new file mode 100644 index 0000000..3efae16 --- /dev/null +++ b/nixos-modules/qois/git-ci-runner/default.nix @@ -0,0 +1,52 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.qois.git-ci-runner; + defaultInstanceName = "default"; +in +with lib; +{ + options.qois.git-ci-runner = { + enable = mkEnableOption "Enable qois git ci-runner service"; + + domain = mkOption { + type = types.str; + default = "git.qo.is"; + description = "Domain, under which the service is served."; + }; + }; + + config = mkIf cfg.enable { + + sops.secrets."forgejo/runner-token/${defaultInstanceName}".restartUnits = [ + "gitea-runner-${defaultInstanceName}.service" + ]; + + services.gitea-actions-runner = { + package = pkgs.forgejo-runner; + instances.${defaultInstanceName} = { + enable = true; + name = "${config.networking.hostName}-${defaultInstanceName}"; + url = "https://${cfg.domain}"; + tokenFile = config.sops.secrets."forgejo/runner-token/${defaultInstanceName}".path; + labels = [ + "ubuntu-latest:docker://gitea/runner-images:ubuntu-latest" + "ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04" + "docker:docker://code.forgejo.org/oci/alpine:3.20" + ]; + settings = { + log.level = "warn"; + runner = { + capacity = 30; + }; + cache.enable = true; # TODO: This should probably be a central cache server? + }; + }; + }; + }; +} diff --git a/nixos-modules/qois/git/README.md b/nixos-modules/qois/git/README.md new file mode 100644 index 0000000..6b822bc --- /dev/null +++ b/nixos-modules/qois/git/README.md @@ -0,0 +1,44 @@ +# Git + +## Configuration for Git Clients + +### Authentication + +To use oauth authentication, your git configuration should have something like: + +```ini +[credential] + helper = "libsecret" + helper = "cache --timeout 21600" + helper = "/usr/bin/git-credential-oauth" # See https://github.com/hickford/git-credential-oauth +``` + +On NixOS with HomeManager, this can be achieved by following home-manager config: + +```nix +programs.git.extraConfig.credential.helper = [ "libsecret" "cache --timeout 21600" ]; +programs.git-credential-oauth.enable = true; +``` + +## Administration + +### Create Accounts + +Accounts can be created by an admin in the [administrator area](https://git.qo.is/admin). + +- use their full `firstname.lastname@qo.is` email so users may be connected to a LDAP database in the future +- Username should be in form of "firstnamelastname" (Forgejo doesn't support usernames with dots) + +To create a new admin user from the commandline, run: + +```bash +sudo -u forgejo 'nix run nixpkgs#forgejo -- admin user create --config ~custom/conf/app.ini --admin --email "xy.z@qo.is" --username firstnamelastname --password Chur7000' +``` + +## Backup / Restore + +1. `systemctl stop forgejo.service` +2. Import Postgresql Database Backup +3. Restore `/var/lib/forgejo` +4. `systemctl start forgejo.service` + diff --git a/nixos-modules/qois/git/default.nix b/nixos-modules/qois/git/default.nix new file mode 100644 index 0000000..20ba2dd --- /dev/null +++ b/nixos-modules/qois/git/default.nix @@ -0,0 +1,81 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.qois.git; +in +with lib; +{ + options.qois.git = { + enable = mkEnableOption "Enable qois git service"; + + domain = mkOption { + type = types.str; + default = "git.qo.is"; + description = "Domain, under which the service is served."; + }; + }; + + config = mkIf cfg.enable { + qois.postgresql.enable = true; + + services.forgejo = { + enable = true; + database.type = "postgres"; + + lfs.enable = true; + + settings = { + DEFAULT.APP_NAME = cfg.domain; + server = { + DOMAIN = cfg.domain; + ROOT_URL = "https://${cfg.domain}"; + PROTOCOL = "http+unix"; + DISABLE_SSH = true; + }; + "ssh.minimum_key_sizes".RSA = 2047; + session.COOKIE_SECURE = true; + service.DISABLE_REGISTRATION = true; + mailer = { + ENABLED = true; + PROTOCOL = "sendmail"; + FROM = "git@qo.is"; + SENDMAIL_PATH = "${pkgs.msmtp}/bin/sendmail"; + # Note: The sendmail passwordeval has to use the coreutil cat (that is in the services path) + # instead of the busybox one due to filtered syscalls. + SENDMAIL_ARGS = "--passwordeval 'cat ${config.sops.secrets."msmtp/password".path}'"; + }; + log.LEVEL = "Warn"; + }; + }; + + qois.backup-client.includePaths = [ config.services.forgejo.stateDir ]; + + users.users.forgejo.extraGroups = [ "postdrop" ]; + systemd.services.forgejo.serviceConfig.ReadOnlyPaths = [ + config.sops.secrets."msmtp/password".path + ]; + + networking.hosts."127.0.0.1" = [ cfg.domain ]; + services.nginx = { + enable = true; + + virtualHosts.${cfg.domain} = { + kTLS = true; + forceSSL = true; + enableACME = true; + extraConfig = '' + client_max_body_size 512M; + ''; + locations."/" = { + proxyPass = "http://unix:${config.services.forgejo.settings.server.HTTP_ADDR}"; + proxyWebsockets = true; + }; + }; + }; + }; +} diff --git a/nixos-modules/qois/loadbalancer/default.nix b/nixos-modules/qois/loadbalancer/default.nix new file mode 100644 index 0000000..56c7208 --- /dev/null +++ b/nixos-modules/qois/loadbalancer/default.nix @@ -0,0 +1,169 @@ +{ + config, + pkgs, + lib, + ... +}: + +with lib; +let + # We assume that all static pages are hosted on lindberg-webapps + staticPages = pipe config.qois.static-page.pages [ + (mapAttrsToList (name: { domain, domainAliases, ... }: [ domain ] ++ domainAliases)) + flatten + (map (name: { + inherit name; + value = "lindberg-webapps"; + })) + listToAttrs + ]; + defaultDomains = staticPages // { + "cloud.qo.is" = "lindberg-nextcloud"; + + "build.qo.is" = "lindberg-build"; + "gitlab-runner.qo.is" = "lindberg-build"; + "nixpkgs-cache.qo.is" = "lindberg-build"; + "attic.qo.is" = "lindberg-build"; + + "vault.qo.is" = "lindberg-webapps"; + "git.qo.is" = "lindberg-webapps"; + + "kokus.raphael.li" = "lindberg-rzimmermann"; + "auth.raphael.li" = "lindberg-rzimmermann"; + "toolia.raphael.li" = "lindberg-rzimmermann"; + "ha.raphael.li" = "lindberg-rzimmermann"; + "www.raphael.li" = "lindberg-rzimmermann"; + + "vpn.qo.is" = "cyprianspitz-headscale"; + }; + getBackplaneIp = hostname: config.qois.meta.network.virtual.backplane.hosts.${hostname}.v4.ip; + defaultHostmap = + lib.pipe + [ + "lindberg-nextcloud" + "lindberg-build" + "lindberg-webapps" + ] + [ + (map (name: { + inherit name; + value = getBackplaneIp name; + })) + lib.listToAttrs + ]; + defaultExtraConfig = + let + headscalePort = toString 46084; + rzimmermannIp = "10.247.0.113"; + in + '' + # lindberg-rzimmermann (uses send-proxy-v2) + backend lindberg-rzimmermann-https + mode tcp + server s1 ${rzimmermannIp}:443 send-proxy-v2 + + backend lindberg-rzimmermann-http + mode http + server s1 ${rzimmermannIp}:80 + + # cyprianspitz headscale + backend cyprianspitz-headscale-http + mode http + server s1 ${getBackplaneIp "cyprianspitz"}:${headscalePort} + + backend cyprianspitz-headscale-https + mode tcp + server s1 ${getBackplaneIp "cyprianspitz"}:${headscalePort} + ''; + cfg = config.qois.loadbalancer; +in +{ + + options.qois.loadbalancer = with lib; { + enable = mkEnableOption "Enable services http+s loadbalancing"; + + domains = mkOption { + description = "Domain to hostname mappings"; + type = with lib.types; attrsOf str; + default = defaultDomains; + }; + + hostmap = mkOption { + description = "Hostname to IP mappings for TLS-TCP and http forwarding"; + type = with lib.types; attrsOf str; + default = defaultHostmap; + }; + + extraConfig = mkOption { + description = "Additional haproxy mapping configs. Amended to services.haproxy.config. Make sure indentations are correct."; + type = types.nullOr types.lines; + default = defaultExtraConfig; + }; + + }; + + config = + with lib; + mkIf cfg.enable { + + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; + + services.haproxy = + let + domainMappingFile = pipe cfg.domains [ + (mapAttrsToList (host: backend: "${host} ${backend}")) + concatLines + (pkgs.writeText "haproxy_backend_map") + ]; + genHttpBackend = hostName: ip: '' + + # Mapping for ${hostName} + backend ${hostName}-https + mode tcp + server s1 ${ip}:443 + + backend ${hostName}-http + mode http + server s1 ${ip}:80 + ''; + httpBackends = pipe cfg.hostmap [ + (mapAttrsToList genHttpBackend) + concatLines + ]; + in + { + enable = true; + config = '' + defaults + mode http + retries 3 + maxconn 2000 + timeout connect 5000 + timeout client 50000 + timeout server 50000 + + frontend http + mode http + bind *:80 + use_backend %[req.hdr(host),lower,map_dom(${domainMappingFile})]-http + + frontend https + bind *:443 + mode tcp + tcp-request inspect-delay 5s + tcp-request content accept if { req_ssl_hello_type 1 } + + use_backend %[req.ssl_sni,lower,map_dom(${domainMappingFile})]-https + + ## Generated Backends: + ${httpBackends} + + ## extraConfig + ${cfg.extraConfig} + ''; + }; + }; +} diff --git a/nixos-modules/qois/static-page/README.md b/nixos-modules/qois/static-page/README.md new file mode 100644 index 0000000..3c3252b --- /dev/null +++ b/nixos-modules/qois/static-page/README.md @@ -0,0 +1,6 @@ +# Static Pages + +This module enables static nginx sites, with data served from "/var/lib/nginx/$domain/root". + +To deploy the site, a user `nginx-$domain` is added, of which a `root` profile in the home folder can be deployed, e.g. with deploy-rs. + diff --git a/nixos-modules/qois/static-page/default-pages.nix b/nixos-modules/qois/static-page/default-pages.nix new file mode 100644 index 0000000..a89a947 --- /dev/null +++ b/nixos-modules/qois/static-page/default-pages.nix @@ -0,0 +1,26 @@ +{ + config, + pkgs, + lib, + ... +}: +{ + + qois.static-page.pages = { + "fabianhauser.ch" = { + domainAliases = [ + "www.fabianhauser.ch" + "fabianhauser.nl" + "www.fabianhauser.nl" + "www.fh2.ch" + "fh2.ch" + ]; + authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFsSCoClNpgW7x6YngP/CEFbyR8GEJ3V8NdUFvZ/6lj6 ci@git.qo.is" + ]; + }; + "docs-ops.qo.is".authorizedKeys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBS65v7n5ozOUjYGuO/dgLC9C5MUGL5kTnQnvWAYP5B3 ci@git.qo.is" + ]; + }; +} diff --git a/nixos-modules/qois/static-page/default.nix b/nixos-modules/qois/static-page/default.nix new file mode 100644 index 0000000..8ee20a0 --- /dev/null +++ b/nixos-modules/qois/static-page/default.nix @@ -0,0 +1,145 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.qois.static-page; +in +with lib; +{ + imports = [ ./default-pages.nix ]; + + options.qois.static-page = + let + pageType = + { name, ... }: + { + options = { + domain = mkOption { + type = types.str; + default = name; + description = '' + Primary domain, under which the site is served. + Only ASCII Domains are supported at this time. + Note that changing this changes the root folder of the vhost in /var/lib/nginx-$domain/root and the ssh user to "nginx-$domain". + ''; + }; + + domainAliases = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Domain aliases which are forwarded to the primary domain"; + }; + + authorizedKeys = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "SSH keys for deployment"; + }; + }; + } + + ; + in + { + enable = mkEnableOption "Enable static-page hosting"; + pages = mkOption { + type = types.attrsOf (types.submodule (pageType)); + }; + }; + + config = mkIf cfg.enable ( + let + pageConfigs = concatMapAttrs ( + name: page: + let + home = "/var/lib/nginx-${page.domain}"; + in + { + "${page.domain}" = page // { + inherit home; + user = "${config.services.nginx.user}-${page.domain}"; + root = "${home}/root"; + }; + } + ) cfg.pages; + + in + { + networking.hosts."127.0.0.1" = pipe pageConfigs [ + attrValues + (map (page: [ page.domain ] ++ page.domainAliases)) + flatten + ]; + + users = { + groups = concatMapAttrs ( + name: + { user, ... }: + { + "${user}" = { }; + } + ) pageConfigs; + users = + { + ${config.services.nginx.user}.extraGroups = mapAttrsToList (domain: getAttr "user") pageConfigs; + } + // (concatMapAttrs ( + name: + { + user, + home, + authorizedKeys, + ... + }: + { + ${user} = { + inherit home; + isSystemUser = true; + useDefaultShell = true; + homeMode = "750"; + createHome = true; + group = user; + openssh.authorizedKeys.keys = authorizedKeys; + }; + } + ) pageConfigs); + }; + + services.nginx = { + enable = true; + virtualHosts = + let + defaultVhostConfig = { + enableACME = true; + forceSSL = true; + kTLS = true; + }; + mkVhost = + { root, ... }: + defaultVhostConfig + // { + inherit root; + }; + mkAliasVhost = + { domainAliases, domain, ... }: + if (domainAliases == [ ]) then + { } + else + ({ + "${head domainAliases}" = defaultVhostConfig // { + serverAliases = tail domainAliases; + globalRedirect = domain; + }; + }); + aliasVhosts = concatMapAttrs (name: mkAliasVhost) pageConfigs; + + in + aliasVhosts // (mapAttrs (name: mkVhost) pageConfigs); + }; + } + ); +} diff --git a/nixos-modules/qois/vpn-server/default.nix b/nixos-modules/qois/vpn-server/default.nix new file mode 100644 index 0000000..5940292 --- /dev/null +++ b/nixos-modules/qois/vpn-server/default.nix @@ -0,0 +1,136 @@ +{ + config, + pkgs, + lib, + ... +}: +with lib; +let + cfg = config.qois.vpn-server; + cfgLoadbalancer = config.qois.loadbalancer; + defaultDnsRecords = mapAttrs ( + name: value: mkIf (cfgLoadbalancer.hostmap ? ${value}) cfgLoadbalancer.hostmap.${value} + ) cfgLoadbalancer.domains; +in +{ + + options.qois.vpn-server = { + enable = mkEnableOption "Enable vpn server services"; + dnsRecords = mkOption { + description = "DNS records to add to Hosts"; + type = with types; attrsOf str; + default = defaultDnsRecords; + }; + wheelUsers = mkOption { + description = "Usernames that can change configurations"; + type = with types; listOf str; + default = [ ]; + }; + }; + + config = mkIf cfg.enable ({ + + environment.systemPackages = [ pkgs.headscale ]; + + qois.backup-client.includePaths = + with config.services.headscale.settings; + ( + [ + db_path + private_key_path + noise.private_key_path + ] + ++ derp.paths + ); + + networking.firewall.checkReversePath = "loose"; + networking.firewall.allowedUDPPorts = [ + 41641 + ]; + services.headscale = + let + vnet = config.qois.meta.network.virtual; + vpnNet = vnet.vpn; + vpnNetPrefix = "${vpnNet.v4.id}/${builtins.toString vpnNet.v4.prefixLength}"; + backplaneNetPrefix = "${vnet.backplane.v4.id}/${builtins.toString vnet.backplane.v4.prefixLength}"; + in + { + enable = true; + address = vnet.backplane.hosts.cyprianspitz.v4.ip; + port = 46084; + settings = { + server_url = "https://${vpnNet.domain}:443"; + + tls_letsencrypt_challenge_type = "TLS-ALPN-01"; + tls_letsencrypt_hostname = vpnNet.domain; + + dns_config = { + nameservers = [ vnet.backplane.hosts.calanda.v4.ip ]; + domains = [ + vpnNet.domain + vnet.backplane.domain + ]; + magic_dns = true; + base_domain = vpnNet.domain; + extra_records = pipe cfg.dnsRecords [ + attrsToList + (map (val: val // { type = "A"; })) + ]; + }; + + ip_prefixes = [ vpnNetPrefix ]; + + acl_policy_path = pkgs.writeTextFile { + name = "acls"; + text = builtins.toJSON { + hosts = { + "clients" = vpnNetPrefix; + }; + groups = { + "group:wheel" = cfg.wheelUsers; + }; + tagOwners = { + "tag:srv" = [ "srv" ]; # srv tag ist owned by srv user + }; + autoApprovers = { + exitNode = [ + "tag:srv" + "group:wheel" + ]; + routes = { + ${backplaneNetPrefix} = [ "tag:srv" ]; + }; + }; + + acls = [ + # Allow all communication from and to srv tagged hosts + { + action = "accept"; + src = [ + "tag:srv" + "srv" + ]; + dst = [ "*:*" ]; + } + { + action = "accept"; + src = [ "*" ]; + dst = [ + "tag:srv:*" + "srv:*" + ]; + } + + # Allow access to all connected hosts for wheels + { + action = "accept"; + src = [ "group:wheel" ]; + dst = [ "*:*" ]; + } + ]; + }; + }; + }; + }; + }); +} diff --git a/nixos-modules/router-dhcp/default.nix b/nixos-modules/router-dhcp/default.nix new file mode 100644 index 0000000..6b63067 --- /dev/null +++ b/nixos-modules/router-dhcp/default.nix @@ -0,0 +1,670 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + routerCfg = config.services.qois.router; + cfg = config.services.qois.router.dhcp; +in +{ + options.services.qois.router.dhcp = { + enable = mkEnableOption "router dhcp service"; + + localDomain = mkOption { + type = types.str; + example = "example.com"; + description = '' + DNS-Domain of local network + ''; + }; + + dhcpRange = mkOption { + type = types.str; + example = "192.168.0.2,192.168.0.128"; + description = '' + Range of IP-adresses to distribute via dhcp in dnsmasq format. + ''; + }; + + localDnsPort = mkOption { + type = types.addCheck types.int (n: n >= 0 && n <= 65535); + example = "router"; + default = 5553; + description = '' + Port to expose dns to. Note that, if you use the recursiveDns role, + the recursive DNS server should use the default DNS port (53). + ''; + }; + }; + + config = mkIf cfg.enable { + services.dnsmasq.enable = true; + services.dnsmasq.extraConfig = '' + # Listen on this specific port instead of the standard DNS port + # (53). Setting this to zero completely disables DNS function, + # leaving only DHCP and/or TFTP. + port=${toString cfg.localDnsPort} + + # The following two options make you a better netizen, since they + # tell dnsmasq to filter out queries which the public DNS cannot + # answer, and which load the servers (especially the root servers) + # unnecessarily. If you have a dial-on-demand link they also stop + # these requests from bringing up the link unnecessarily. + + # Never forward plain names (without a dot or domain part) + domain-needed + # Never forward addresses in the non-routed address spaces. + bogus-priv + + + # Uncomment this to filter useless windows-originated DNS requests + # which can trigger dial-on-demand links needlessly. + # Note that (amongst other things) this blocks all SRV requests, + # so don't use it if you use eg Kerberos, SIP, XMMP or Google-talk. + # This option only affects forwarding, SRV records originating for + # dnsmasq (via srv-host= lines) are not suppressed by it. + #filterwin2k + + # Change this line if you want dns to get its upstream servers from + # somewhere other that /etc/resolv.conf + #resolv-file= + + # By default, dnsmasq will send queries to any of the upstream + # servers it knows about and tries to favour servers to are known + # to be up. Uncommenting this forces dnsmasq to try each query + # with each server strictly in the order they appear in + # /etc/resolv.conf + #strict-order + + # If you don't want dnsmasq to read /etc/resolv.conf or any other + # file, getting its servers from this file instead (see below), then + # uncomment this. + #no-resolv + + # If you don't want dnsmasq to poll /etc/resolv.conf or other resolv + # files for changes and re-read them then uncomment this. + no-poll + + # Add other name servers here, with domain specs if they are for + # non-public domains. + #server=/localnet/192.168.0.1 + + # Example of routing PTR queries to nameservers: this will send all + # address->name queries for 192.168.3/24 to nameserver 10.1.2.3 + #server=/3.168.192.in-addr.arpa/10.1.2.3 + + # Add local-only domains here, queries in these domains are answered + # from /etc/hosts or DHCP only. + local=/${config.networking.hostName}/ + + # Add domains which you want to force to an IP address here. + # The example below send any host in double-click.net to a local + # web-server. + #address=/double-click.net/127.0.0.1 + address=/${config.networking.hostName}.${cfg.localDomain}/${routerCfg.internalRouterIP} + + # --address (and --server) work with IPv6 addresses too. + #address=/www.thekelleys.org.uk/fe80::20d:60ff:fe36:f83 + + # You can control how dnsmasq talks to a server: this forces + # queries to 10.1.2.3 to be routed via eth1 + # server=10.1.2.3@eth1 + + # and this sets the source (ie local) address used to talk to + # 10.1.2.3 to 192.168.1.1 port 55 (there must be a interface with that + # IP on the machine, obviously). + # server=10.1.2.3@192.168.1.1#55 + + # If you want dnsmasq to change uid and gid to something other + # than the default, edit the following lines. + #user= + #group= + + # If you want dnsmasq to listen for DHCP and DNS requests only on + # specified interfaces (and the loopback) give the name of the + # interface (eg eth0) here. + # Repeat the line for more than one interface. + interface=${routerCfg.internalBridgeInterfaceName} + interface=lo + # Or you can specify which interface _not_ to listen on + #except-interface= + # Or which to listen on by address (remember to include 127.0.0.1 if + # you use this.) + #listen-address= + # If you want dnsmasq to provide only DNS service on an interface, + # configure it as shown above, and then use the following line to + # disable DHCP and TFTP on it. + no-dhcp-interface=lo + + # On systems which support it, dnsmasq binds the wildcard address, + # even when it is listening on only some interfaces. It then discards + # requests that it shouldn't reply to. This has the advantage of + # working even when interfaces come and go and change address. If you + # want dnsmasq to really bind only the interfaces it is listening on, + # uncomment this option. About the only time you may need this is when + # running another nameserver on the same machine. + bind-interfaces + + # If you don't want dnsmasq to read /etc/hosts, uncomment the + # following line. + no-hosts + # or if you want it to read another file, as well as /etc/hosts, use + # this. + #addn-hosts=/etc/banner_add_hosts + + # Set this (and domain: see below) if you want to have a domain + # automatically added to simple names in a hosts-file. + expand-hosts + + # Set the domain for dnsmasq. this is optional, but if it is set, it + # does the following things. + # 1) Allows DHCP hosts to have fully qualified domain names, as long + # as the domain part matches this setting. + # 2) Sets the "domain" DHCP option thereby potentially setting the + # domain of all systems configured by DHCP + # 3) Provides the domain part for "expand-hosts" + domain=${cfg.localDomain} + + # Set a different domain for a particular subnet + #domain=wireless.thekelleys.org.uk,192.168.2.0/24 + + # Same idea, but range rather then subnet + #domain=reserved.thekelleys.org.uk,192.68.3.100,192.168.3.200 + + # Uncomment this to enable the integrated DHCP server, you need + # to supply the range of addresses available for lease and optionally + # a lease time. If you have more than one network, you will need to + # repeat this for each network on which you want to supply DHCP + # service. + dhcp-range=${cfg.dhcpRange},48h + + # This is an example of a DHCP range where the netmask is given. This + # is needed for networks we reach the dnsmasq DHCP server via a relay + # agent. If you don't know what a DHCP relay agent is, you probably + # don't need to worry about this. + #dhcp-range=192.168.0.50,192.168.0.150,255.255.255.0,12h + + # This is an example of a DHCP range which sets a tag, so that + # some DHCP options may be set only for this network. + #dhcp-range=set:red,192.168.0.50,192.168.0.150 + + # Use this DHCP range only when the tag "green" is set. + #dhcp-range=tag:green,192.168.0.50,192.168.0.150,12h + + # Specify a subnet which can't be used for dynamic address allocation, + # is available for hosts with matching --dhcp-host lines. Note that + # dhcp-host declarations will be ignored unless there is a dhcp-range + # of some type for the subnet in question. + # In this case the netmask is implied (it comes from the network + # configuration on the machine running dnsmasq) it is possible to give + # an explicit netmask instead. + #dhcp-range=192.168.0.0,static + + # Enable DHCPv6. Note that the prefix-length does not need to be specified + # and defaults to 64 if missing/ + #dhcp-range=1234::2, 1234::500, 64, 12h + + # Do Router Advertisements, BUT NOT DHCP for this subnet. + #dhcp-range=1234::, ra-only + + # Do Router Advertisements, BUT NOT DHCP for this subnet, also try and + # add names to the DNS for the IPv6 address of SLAAC-configured dual-stack + # hosts. Use the DHCPv4 lease to derive the name, network segment and + # MAC address and assume that the host will also have an + # IPv6 address calculated using the SLAAC alogrithm. + #dhcp-range=1234::, ra-names + + # Do Router Advertisements, BUT NOT DHCP for this subnet. + # Set the lifetime to 46 hours. (Note: minimum lifetime is 2 hours.) + #dhcp-range=1234::, ra-only, 48h + + # Do DHCP and Router Advertisements for this subnet. Set the A bit in the RA + # so that clients can use SLAAC addresses as well as DHCP ones. + #dhcp-range=1234::2, 1234::500, slaac + + # Do Router Advertisements and stateless DHCP for this subnet. Clients will + # not get addresses from DHCP, but they will get other configuration information. + # They will use SLAAC for addresses. + #dhcp-range=1234::, ra-stateless + + # Do stateless DHCP, SLAAC, and generate DNS names for SLAAC addresses + # from DHCPv4 leases. + #dhcp-range=1234::, ra-stateless, ra-names + + # Do router advertisements for all subnets where we're doing DHCPv6 + # Unless overriden by ra-stateless, ra-names, et al, the router + # advertisements will have the M and O bits set, so that the clients + # get addresses and configuration from DHCPv6, and the A bit reset, so the + # clients don't use SLAAC addresses. + #enable-ra + + # Supply parameters for specified hosts using DHCP. There are lots + # of valid alternatives, so we will give examples of each. Note that + # IP addresses DO NOT have to be in the range given above, they just + # need to be on the same network. The order of the parameters in these + # do not matter, it's permissible to give name, address and MAC in any + # order. + + # Always allocate the host with Ethernet address 11:22:33:44:55:66 + # The IP address 192.168.0.60 + #dhcp-host=11:22:33:44:55:66,192.168.0.60 + + # Always set the name of the host with hardware address + # 11:22:33:44:55:66 to be "fred" + #dhcp-host=11:22:33:44:55:66,fred + + # Always give the host with Ethernet address 11:22:33:44:55:66 + # the name fred and IP address 192.168.0.60 and lease time 45 minutes + #dhcp-host=11:22:33:44:55:66,fred,192.168.0.60,45m + + # Give a host with Ethernet address 11:22:33:44:55:66 or + # 12:34:56:78:90:12 the IP address 192.168.0.60. Dnsmasq will assume + # that these two Ethernet interfaces will never be in use at the same + # time, and give the IP address to the second, even if it is already + # in use by the first. Useful for laptops with wired and wireless + # addresses. + #dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.60 + + # Give the machine which says its name is "bert" IP address + # 192.168.0.70 and an infinite lease + #dhcp-host=bert,192.168.0.70,infinite + + # Always give the host with client identifier 01:02:02:04 + # the IP address 192.168.0.60 + #dhcp-host=id:01:02:02:04,192.168.0.60 + + # Always give the host with client identifier "marjorie" + # the IP address 192.168.0.60 + #dhcp-host=id:marjorie,192.168.0.60 + + # Enable the address given for "judge" in /etc/hosts + # to be given to a machine presenting the name "judge" when + # it asks for a DHCP lease. + #dhcp-host=judge + + # Never offer DHCP service to a machine whose Ethernet + # address is 11:22:33:44:55:66 + #dhcp-host=11:22:33:44:55:66,ignore + + # Ignore any client-id presented by the machine with Ethernet + # address 11:22:33:44:55:66. This is useful to prevent a machine + # being treated differently when running under different OS's or + # between PXE boot and OS boot. + #dhcp-host=11:22:33:44:55:66,id:* + + # Send extra options which are tagged as "red" to + # the machine with Ethernet address 11:22:33:44:55:66 + #dhcp-host=11:22:33:44:55:66,set:red + + # Send extra options which are tagged as "red" to + # any machine with Ethernet address starting 11:22:33: + #dhcp-host=11:22:33:*:*:*,set:red + + # Give a fixed IPv6 address and name to client with + # DUID 00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2 + # Note the MAC addresses CANNOT be used to identify DHCPv6 clients. + # Note also the they [] around the IPv6 address are obilgatory. + #dhcp-host=id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5] + + # Ignore any clients which are not specified in dhcp-host lines + # or /etc/ethers. Equivalent to ISC "deny unknown-clients". + # This relies on the special "known" tag which is set when + # a host is matched. + #dhcp-ignore=tag:!known + + # Send extra options which are tagged as "red" to any machine whose + # DHCP vendorclass string includes the substring "Linux" + #dhcp-vendorclass=set:red,Linux + + # Send extra options which are tagged as "red" to any machine one + # of whose DHCP userclass strings includes the substring "accounts" + #dhcp-userclass=set:red,accounts + + # Send extra options which are tagged as "red" to any machine whose + # MAC address matches the pattern. + #dhcp-mac=set:red,00:60:8C:*:*:* + + # If this line is uncommented, dnsmasq will read /etc/ethers and act + # on the ethernet-address/IP pairs found there just as if they had + # been given as --dhcp-host options. Useful if you keep + # MAC-address/host mappings there for other purposes. + #read-ethers + + # Send options to hosts which ask for a DHCP lease. + # See RFC 2132 for details of available options. + # Common options can be given to dnsmasq by name: + # run "dnsmasq --help dhcp" to get a list. + # Note that all the common settings, such as netmask and + # broadcast address, DNS server and default route, are given + # sane defaults by dnsmasq. You very likely will not need + # any dhcp-options. If you use Windows clients and Samba, there + # are some options which are recommended, they are detailed at the + # end of this section. + + # Override the default route supplied by dnsmasq, which assumes the + # router is the same machine as the one running dnsmasq. + #dhcp-option=3,1.2.3.4 + dhcp-option=6,${routerCfg.internalRouterIP} + + # Do the same thing, but using the option name + #dhcp-option=option:router,1.2.3.4 + + # Override the default route supplied by dnsmasq and send no default + # route at all. Note that this only works for the options sent by + # default (1, 3, 6, 12, 28) the same line will send a zero-length option + # for all other option numbers. + #dhcp-option=3 + + # Set the NTP time server addresses to 192.168.0.4 and 10.10.0.5 + #dhcp-option=option:ntp-server,192.168.0.4,10.10.0.5 + + # Send DHCPv6 option. Note [] around IPv6 addresses. + #dhcp-option=option6:dns-server,[1234::77],[1234::88] + + # Send DHCPv6 option for namservers as the machine running + # dnsmasq and another. + #dhcp-option=option6:dns-server,[::],[1234::88] + + # Set the NTP time server address to be the same machine as + # is running dnsmasq + #dhcp-option=42,0.0.0.0 + + # Set the NIS domain name to "welly" + #dhcp-option=40,welly + + # Set the default time-to-live to 50 + #dhcp-option=23,50 + + # Set the "all subnets are local" flag + #dhcp-option=27,1 + + # Send the etherboot magic flag and then etherboot options (a string). + #dhcp-option=128,e4:45:74:68:00:00 + #dhcp-option=129,NIC=eepro100 + + # Specify an option which will only be sent to the "red" network + # (see dhcp-range for the declaration of the "red" network) + # Note that the tag: part must precede the option: part. + #dhcp-option = tag:red, option:ntp-server, 192.168.1.1 + + # The following DHCP options set up dnsmasq in the same way as is specified + # for the ISC dhcpcd in + # http://www.samba.org/samba/ftp/docs/textdocs/DHCP-Server-Configuration.txt + # adapted for a typical dnsmasq installation where the host running + # dnsmasq is also the host running samba. + # you may want to uncomment some or all of them if you use + # Windows clients and Samba. + #dhcp-option=19,0 # option ip-forwarding off + #dhcp-option=44,0.0.0.0 # set netbios-over-TCP/IP nameserver(s) aka WINS server(s) + #dhcp-option=45,0.0.0.0 # netbios datagram distribution server + #dhcp-option=46,8 # netbios node type + + # Send an empty WPAD option. This may be REQUIRED to get windows 7 to behave. + #dhcp-option=252,"\n" + + # Send RFC-3397 DNS domain search DHCP option. WARNING: Your DHCP client + # probably doesn't support this...... + dhcp-option=option:domain-search,${cfg.localDomain} + + # Send RFC-3442 classless static routes (note the netmask encoding) + #dhcp-option=121,192.168.1.0/24,1.2.3.4,10.0.0.0/8,5.6.7.8 + + # Send vendor-class specific options encapsulated in DHCP option 43. + # The meaning of the options is defined by the vendor-class so + # options are sent only when the client supplied vendor class + # matches the class given here. (A substring match is OK, so "MSFT" + # matches "MSFT" and "MSFT 5.0"). This example sets the + # mtftp address to 0.0.0.0 for PXEClients. + #dhcp-option=vendor:PXEClient,1,0.0.0.0 + + # Send microsoft-specific option to tell windows to release the DHCP lease + # when it shuts down. Note the "i" flag, to tell dnsmasq to send the + # value as a four-byte integer - that's what microsoft wants. See + # http://technet2.microsoft.com/WindowsServer/en/library/a70f1bb7-d2d4-49f0-96d6-4b7414ecfaae1033.mspx?mfr=true + #dhcp-option=vendor:MSFT,2,1i + + # Send the Encapsulated-vendor-class ID needed by some configurations of + # Etherboot to allow is to recognise the DHCP server. + #dhcp-option=vendor:Etherboot,60,"Etherboot" + + # Send options to PXELinux. Note that we need to send the options even + # though they don't appear in the parameter request list, so we need + # to use dhcp-option-force here. + # See http://syslinux.zytor.com/pxe.php#special for details. + # Magic number - needed before anything else is recognised + #dhcp-option-force=208,f1:00:74:7e + # Configuration file name + #dhcp-option-force=209,configs/common + # Path prefix + #dhcp-option-force=210,/tftpboot/pxelinux/files/ + # Reboot time. (Note 'i' to send 32-bit value) + #dhcp-option-force=211,30i + + # Set the boot filename for netboot/PXE. You will only need + # this is you want to boot machines over the network and you will need + # a TFTP server; either dnsmasq's built in TFTP server or an + # external one. (See below for how to enable the TFTP server.) + #dhcp-boot=pxelinux.0 + + # The same as above, but use custom tftp-server instead machine running dnsmasq + #dhcp-boot=pxelinux,server.name,192.168.1.100 + + # Boot for Etherboot gPXE. The idea is to send two different + # filenames, the first loads gPXE, and the second tells gPXE what to + # load. The dhcp-match sets the gpxe tag for requests from gPXE. + #dhcp-match=set:gpxe,175 # gPXE sends a 175 option. + #dhcp-boot=tag:!gpxe,undionly.kpxe + #dhcp-boot=mybootimage + + # Encapsulated options for Etherboot gPXE. All the options are + # encapsulated within option 175 + #dhcp-option=encap:175, 1, 5b # priority code + #dhcp-option=encap:175, 176, 1b # no-proxydhcp + #dhcp-option=encap:175, 177, string # bus-id + #dhcp-option=encap:175, 189, 1b # BIOS drive code + #dhcp-option=encap:175, 190, user # iSCSI username + #dhcp-option=encap:175, 191, pass # iSCSI password + + # Test for the architecture of a netboot client. PXE clients are + # supposed to send their architecture as option 93. (See RFC 4578) + #dhcp-match=peecees, option:client-arch, 0 #x86-32 + #dhcp-match=itanics, option:client-arch, 2 #IA64 + #dhcp-match=hammers, option:client-arch, 6 #x86-64 + #dhcp-match=mactels, option:client-arch, 7 #EFI x86-64 + + # Do real PXE, rather than just booting a single file, this is an + # alternative to dhcp-boot. + #pxe-prompt="What system shall I netboot?" + # or with timeout before first available action is taken: + #pxe-prompt="Press F8 for menu.", 60 + + # Available boot services. for PXE. + #pxe-service=x86PC, "Boot from local disk" + + # Loads /pxelinux.0 from dnsmasq TFTP server. + #pxe-service=x86PC, "Install Linux", pxelinux + + # Loads /pxelinux.0 from TFTP server at 1.2.3.4. + # Beware this fails on old PXE ROMS. + #pxe-service=x86PC, "Install Linux", pxelinux, 1.2.3.4 + + # Use bootserver on network, found my multicast or broadcast. + #pxe-service=x86PC, "Install windows from RIS server", 1 + + # Use bootserver at a known IP address. + #pxe-service=x86PC, "Install windows from RIS server", 1, 1.2.3.4 + + # If you have multicast-FTP available, + # information for that can be passed in a similar way using options 1 + # to 5. See page 19 of + # http://download.intel.com/design/archives/wfm/downloads/pxespec.pdf + + + # Enable dnsmasq's built-in TFTP server + #enable-tftp + + # Set the root directory for files available via FTP. + #tftp-root=/var/ftpd + + # Make the TFTP server more secure: with this set, only files owned by + # the user dnsmasq is running as will be send over the net. + #tftp-secure + + # This option stops dnsmasq from negotiating a larger blocksize for TFTP + # transfers. It will slow things down, but may rescue some broken TFTP + # clients. + #tftp-no-blocksize + + # Set the boot file name only when the "red" tag is set. + #dhcp-boot=net:red,pxelinux.red-net + + # An example of dhcp-boot with an external TFTP server: the name and IP + # address of the server are given after the filename. + # Can fail with old PXE ROMS. Overridden by --pxe-service. + #dhcp-boot=/var/ftpd/pxelinux.0,boothost,192.168.0.3 + + # If there are multiple external tftp servers having a same name + # (using /etc/hosts) then that name can be specified as the + # tftp_servername (the third option to dhcp-boot) and in that + # case dnsmasq resolves this name and returns the resultant IP + # addresses in round robin fasion. This facility can be used to + # load balance the tftp load among a set of servers. + #dhcp-boot=/var/ftpd/pxelinux.0,boothost,tftp_server_name + + # Set the limit on DHCP leases, the default is 150 + #dhcp-lease-max=150 + + # The DHCP server needs somewhere on disk to keep its lease database. + # This defaults to a sane location, but if you want to change it, use + # the line below. + #dhcp-leasefile=/var/lib/misc/dnsmasq.leases + + # Set the DHCP server to authoritative mode. In this mode it will barge in + # and take over the lease for any client which broadcasts on the network, + # whether it has a record of the lease or not. This avoids long timeouts + # when a machine wakes up on a new network. DO NOT enable this if there's + # the slightest chance that you might end up accidentally configuring a DHCP + # server for your campus/company accidentally. The ISC server uses + # the same option, and this URL provides more information: + # http://www.isc.org/files/auth.html + dhcp-authoritative + + # Run an executable when a DHCP lease is created or destroyed. + # The arguments sent to the script are "add" or "del", + # then the MAC address, the IP address and finally the hostname + # if there is one. + #dhcp-script=/bin/echo + + # Set the cachesize here. + #cache-size=150 + + # If you want to disable negative caching, uncomment this. + #no-negcache + + # Normally responses which come form /etc/hosts and the DHCP lease + # file have Time-To-Live set as zero, which conventionally means + # do not cache further. If you are happy to trade lower load on the + # server for potentially stale date, you can set a time-to-live (in + # seconds) here. + #local-ttl= + + # If you want dnsmasq to detect attempts by Verisign to send queries + # to unregistered .com and .net hosts to its sitefinder service and + # have dnsmasq instead return the correct NXDOMAIN response, uncomment + # this line. You can add similar lines to do the same for other + # registries which have implemented wildcard A records. + #bogus-nxdomain=64.94.110.11 + + # If you want to fix up DNS results from upstream servers, use the + # alias option. This only works for IPv4. + # This alias makes a result of 1.2.3.4 appear as 5.6.7.8 + #alias=1.2.3.4,5.6.7.8 + # and this maps 1.2.3.x to 5.6.7.x + #alias=1.2.3.0,5.6.7.0,255.255.255.0 + # and this maps 192.168.0.10->192.168.0.40 to 10.0.0.10->10.0.0.40 + #alias=192.168.0.10-192.168.0.40,10.0.0.0,255.255.255.0 + + # Change these lines if you want dnsmasq to serve MX records. + + # Return an MX record named "maildomain.com" with target + # servermachine.com and preference 50 + #mx-host=maildomain.com,servermachine.com,50 + + # Set the default target for MX records created using the localmx option. + #mx-target=servermachine.com + + # Return an MX record pointing to the mx-target for all local + # machines. + #localmx + + # Return an MX record pointing to itself for all local machines. + #selfmx + + # Change the following lines if you want dnsmasq to serve SRV + # records. These are useful if you want to serve ldap requests for + # Active Directory and other windows-originated DNS requests. + # See RFC 2782. + # You may add multiple srv-host lines. + # The fields are ,,,, + # If the domain part if missing from the name (so that is just has the + # service and protocol sections) then the domain given by the domain= + # config option is used. (Note that expand-hosts does not need to be + # set for this to work.) + + # A SRV record sending LDAP for the example.com domain to + # ldapserver.example.com port 389 + #srv-host=_ldap._tcp.example.com,ldapserver.example.com,389 + + # A SRV record sending LDAP for the example.com domain to + # ldapserver.example.com port 389 (using domain=) + #domain=example.com + #srv-host=_ldap._tcp,ldapserver.example.com,389 + + # Two SRV records for LDAP, each with different priorities + #srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,1 + #srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,2 + + # A SRV record indicating that there is no LDAP server for the domain + # example.com + #srv-host=_ldap._tcp.example.com + + # The following line shows how to make dnsmasq serve an arbitrary PTR + # record. This is useful for DNS-SD. (Note that the + # domain-name expansion done for SRV records _does_not + # occur for PTR records.) + #ptr-record=_http._tcp.dns-sd-services,"New Employee Page._http._tcp.dns-sd-services" + + # Change the following lines to enable dnsmasq to serve TXT records. + # These are used for things like SPF and zeroconf. (Note that the + # domain-name expansion done for SRV records _does_not + # occur for TXT records.) + + #Example SPF. + #txt-record=example.com,"v=spf1 a -all" + + #Example zeroconf + #txt-record=_http._tcp.example.com,name=value,paper=A4 + + # Provide an alias for a "local" DNS name. Note that this _only_ works + # for targets which are names from DHCP or /etc/hosts. Give host + # "bert" another name, bertrand + #cname=bertand,bert + + # For debugging purposes, log each DNS query as it passes through + # dnsmasq. + #log-queries + + # Log lots of extra information about DHCP transactions. + #log-dhcp + ''; + + systemd.services.dnsmasq = { + bindsTo = [ "network-addresses-${routerCfg.internalBridgeInterfaceName}.service" ]; + }; + }; +} diff --git a/nixos-modules/router-dns/default.nix b/nixos-modules/router-dns/default.nix new file mode 100644 index 0000000..f1c13fc --- /dev/null +++ b/nixos-modules/router-dns/default.nix @@ -0,0 +1,70 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + routerCfg = config.services.qois.router; + dhcpCfg = config.services.qois.router.dhcp; + cfg = config.services.qois.router.recursiveDns; +in +{ + options.services.qois.router.recursiveDns = { + enable = mkEnableOption "router recursive dns service"; + + networkIdIp = mkOption { + type = types.str; + example = "192.168.0.0"; + description = '' + Network ID IP of local network. + ''; + }; + }; + + config = mkIf cfg.enable { + services.unbound = + let + revIpDomain = concatStringsSep "." (reverseList (take 3 (splitString "." cfg.networkIdIp))); + in + { + enable = true; + settings = { + server = { + interface = [ + "127.0.0.1" + routerCfg.internalRouterIP + ]; + access-control = [ + ''"127.0.0.0/24" allow'' + ''"${cfg.networkIdIp}/${toString routerCfg.internalPrefixLength}" allow'' + ]; + do-not-query-localhost = "no"; + private-domain = [ + "${dhcpCfg.localDomain}." + "${revIpDomain}.in-addr.arpa." + ]; + domain-insecure = [ + "${dhcpCfg.localDomain}." + "${revIpDomain}.in-addr.arpa." + ]; + local-zone = ''"${revIpDomain}.in-addr.arpa" transparent''; + }; + + forward-zone = [ + { + name = "${dhcpCfg.localDomain}."; + forward-addr = "127.0.0.1@${toString dhcpCfg.localDnsPort}"; + } + { + name = "${revIpDomain}.in-addr.arpa."; + forward-addr = "127.0.0.1@${toString dhcpCfg.localDnsPort}"; + } + ]; + }; + }; + }; +} diff --git a/nixos-modules/router-wireless-ap/default.nix b/nixos-modules/router-wireless-ap/default.nix new file mode 100644 index 0000000..6edbd7e --- /dev/null +++ b/nixos-modules/router-wireless-ap/default.nix @@ -0,0 +1,107 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + routerCfg = config.services.qois.router; + cfg = config.services.qois.router.wireless; +in +{ + options.services.qois.router.wireless = { + enable = mkEnableOption "router wireless service"; + + wleInterface24Ghz = mkOption { + type = with types; nullOr str; + default = null; + example = "wlp1"; + description = '' + Wireless interface name for 2.4 GHz wireless band. + ''; + }; + + ssid = mkOption { + type = types.str; + example = "MyNetwork"; + description = '' + Wireless network SSID. + ''; + }; + + passphrase = mkOption { + type = types.str; + description = '' + Passphrase of wireless network. May be encrypted with wpa_passphrase <wleSSID> <passphrase>. + ''; + }; + + regulatoryCountryCode = mkOption { + type = types.str; + default = "US"; + description = '' + Regulatory wireless country code. + ''; + }; + }; + + config = + let + wle24GhzEnabled = cfg.wleInterface24Ghz != null; + in + mkIf cfg.enable { + boot.extraModprobeConfig = '' + options cfg80211 ieee80211_regdom=${cfg.regulatoryCountryCode} + ''; + + systemd.services.hostapd.after = [ "lan-netdev.service" ]; + + services.hostapd = { + enable = wle24GhzEnabled; + + radios.${cfg.wleInterface24Ghz} = { + wifi4.enable = true; + wifi4.capabilities = [ + "HT40-" + "HT40+" + "SHORT-GI-40" + "TX-STBC" + "RX-STBC1" + "DSSS_CCK-40" + ]; + wifi5.enable = false; + networks.${cfg.wleInterface24Ghz} = { + # hostapd requires bss to have names with the interface. + ssid = cfg.ssid; + authentication = { + mode = "wpa2-sha256"; + enableRecommendedPairwiseCiphers = true; + wpaPasswordFile = /secrets/wifi_${cfg.ssid}; + }; + settings = { + wme_enabled = 1; + ieee80211w = 0; + sae_require_mfp = 0; + wpa_key_mgmt = lib.mkForce "WPA-PSK"; + wpa_pairwise = lib.mkForce "CCMP"; + rsn_pairwise = lib.mkForce "CCMP"; + bridge = "lan"; + }; + }; + + settings = { + wme_enabled = 1; + ieee80211w = 0; + sae_require_mfp = 0; + wpa = 2; + wpa_key_mgmt = lib.mkForce "WPA-PSK"; + wpa_pairwise = lib.mkForce "CCMP"; + rsn_pairwise = lib.mkForce "CCMP"; + }; + }; + }; + }; +} diff --git a/nixos-modules/router/README.md b/nixos-modules/router/README.md new file mode 100644 index 0000000..b4a000f --- /dev/null +++ b/nixos-modules/router/README.md @@ -0,0 +1,12 @@ +# Router Role {#_router_role} + +The `router` role set is applied on hosts which serve the rule of a SOHO +router. + +Features: + +- NAT and basic Firewalling (`router`) +- Recursive DNS with `unbound` (DNSSEC validated) (`router-dns`) +- Local DHCP and local DNS hostname resolution with `dnsmasq` + (`router-dhcp`) +- Wireless with `hostapd` (`router-wireless-ap`) diff --git a/nixos-modules/router/default.nix b/nixos-modules/router/default.nix new file mode 100644 index 0000000..c6bd125 --- /dev/null +++ b/nixos-modules/router/default.nix @@ -0,0 +1,103 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + cfg = config.services.qois.router; +in +{ + options.services.qois.router = { + enable = mkEnableOption "router service"; + + wanInterface = mkOption { + type = with types; nullOr str; + example = "enp0"; + default = null; + description = '' + WAN interface name. + ''; + }; + + wirelessInterfaces = mkOption { + type = types.listOf types.str; + example = [ + "wlp1" + "wlp2" + ]; + default = [ ]; + description = '' + Wireless interfaces names. + ''; + }; + + lanInterfaces = mkOption { + type = types.listOf types.str; + example = [ + "enp1" + "enp2" + ]; + default = [ ]; + description = '' + LAN interfaces names. + ''; + }; + + internalRouterIP = mkOption { + type = types.str; + example = "192.168.0.1"; + description = '' + Internal IP of router. + ''; + }; + + internalPrefixLength = mkOption { + type = types.addCheck types.int (n: n >= 0 && n <= 32); + default = 24; + description = '' + Subnet mask of the network, specified as the number of + bits in the prefix (24). + ''; + }; + + internalBridgeInterfaceName = mkOption { + type = types.str; + default = "lan"; + description = '' + Name of the virtual internal interface. + ''; + }; + }; + + config = mkIf cfg.enable { + networking = { + enableIPv6 = false; # TODO + nat = + if cfg.wanInterface == null then + { } + else + { + enable = true; + externalInterface = cfg.wanInterface; + internalInterfaces = [ cfg.internalBridgeInterfaceName ]; + }; + + bridges.${cfg.internalBridgeInterfaceName}.interfaces = cfg.lanInterfaces; # Note: The wlp interface is added by hostapd. + interfaces.${cfg.internalBridgeInterfaceName} = { + ipv4 = { + addresses = [ + { + address = cfg.internalRouterIP; + prefixLength = cfg.internalPrefixLength; + } + ]; + }; + }; + firewall.trustedInterfaces = [ cfg.internalBridgeInterfaceName ]; + }; + }; +} diff --git a/nixos-modules/vault/README.md b/nixos-modules/vault/README.md new file mode 100644 index 0000000..337b152 --- /dev/null +++ b/nixos-modules/vault/README.md @@ -0,0 +1,37 @@ +# Vaultwarden / Bitwarden + +To use our Vaultwarden instance, you can use the regular +[Bitwarden apps](https://bitwarden.com/download/) with our custom server when logging in: + +Username: `first.lastname@qo.is` +Server Name: `https://vault.qo.is` + +## Create Accounts + +We currently [allow signups](https://vault.qo.is/#/register) for `@qo.is` email addresses. + +Please instruct users to: + +- use their full `firstname.lastname@qo.is` email so users may be connected to a LDAP database in the future +- remember that the login password is used to encrypt the password database and should therefor be good. +- the password cannot be reset without loosing all the passwords. + Use of [Emergency Contacts](https://bitwarden.com/help/emergency-access/) or Organizations may be advisable. + + +## Administration + +An admin panel is available under [vault.qo.is/admin](https://vault.qo.is/admin). +The password is saved in the pass database under `vaultwarden-admin`. + +In the administration panel, users and organizations may be managed. +Instance settings should be changed with the nixos module in the infrastructure repository only. + + +## Backup / Restore + +1. `systemctl stop vaultwarden.service` +2. Import Postgresql Database Backup +3. Restore `/var/lib/bitwarden_rs` +4. `systemctl start vaultwarden.service` +5. Click `Force clients to resync` in the [Administration interface under _Users_](https://vault.qo.is/admin/users/overview) + diff --git a/nixos-modules/vault/default.nix b/nixos-modules/vault/default.nix new file mode 100644 index 0000000..e61b5fe --- /dev/null +++ b/nixos-modules/vault/default.nix @@ -0,0 +1,90 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.qois.vault; +in +with lib; +{ + options.qois.vault = { + enable = mkEnableOption "Enable qois vault service"; + + domain = mkOption { + type = types.str; + default = "vault.qo.is"; + description = "Domain, under which the service is served."; + }; + }; + + config = mkIf cfg.enable { + + services.vaultwarden = { + enable = true; + dbBackend = "postgresql"; + environmentFile = config.sops.secrets."vaultwarden/environment-file".path; + config = { + DATA_FOLDER = "/var/lib/bitwarden_rs"; + DATABASE_URL = "postgresql:///vaultwarden"; + + DOMAIN = "https://${cfg.domain}"; + ROCKET_PORT = 8222; + + USE_SENDMAIL = true; + SMTP_FROM = "vault@qo.is"; + SMTP_FROM_NAME = cfg.domain; + + SIGNUPS_ALLOWED = false; + INVITATIONS_ALLOWED = false; + SIGNUPS_DOMAINS_WHITELIST = "qo.is"; + SIGNUPS_VERIFY = true; + + EXPERIMENTAL_CLIENT_FEATURE_FLAGS = "fido2-vault-credentials"; + SHOW_PASSWORD_HINT = false; + TRASH_AUTO_DELETE_DAYS = 30; + }; + }; + + qois.postgresql.enable = true; + + qois.backup-client.includePaths = [ config.services.vaultwarden.config.DATA_FOLDER ]; + + services.postgresql = + let + name = config.users.users.vaultwarden.name; + in + { + ensureUsers = [ + { + inherit name; + ensureDBOwnership = true; + } + ]; + ensureDatabases = [ name ]; + }; + + # See https://search.nixos.org/options?channel=unstable&show=services.vaultwarden.environmentFile + sops.secrets."vaultwarden/environment-file".restartUnits = [ "vaultwarden.service" ]; + + systemd.services.vaultwarden.path = [ pkgs.msmtp ]; + users.users.vaultwarden.extraGroups = [ "postdrop" ]; + + networking.hosts."127.0.0.1" = [ cfg.domain ]; + services.nginx = { + enable = true; + + virtualHosts.${cfg.domain} = { + kTLS = true; + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://localhost:${toString config.services.vaultwarden.config.ROCKET_PORT}"; + proxyWebsockets = true; + }; + }; + }; + }; +} diff --git a/nixos-modules/wwan/README.md b/nixos-modules/wwan/README.md new file mode 100644 index 0000000..655a021 --- /dev/null +++ b/nixos-modules/wwan/README.md @@ -0,0 +1,11 @@ +# WWAN Module {#_wwan_module} + +This module configures WWAN adapters that support MBIM + +## Current limitations {#_current_limitations} + +- IPv4 tested only +- Currently, it is not simple to get network failures or address + updates via a hook or so. + - A systemd timer to update the configuration is executed every 2 + minutes to prevent longer downtimes. diff --git a/nixos-modules/wwan/default.nix b/nixos-modules/wwan/default.nix new file mode 100644 index 0000000..54b9555 --- /dev/null +++ b/nixos-modules/wwan/default.nix @@ -0,0 +1,145 @@ +# Based on https://github.com/jgillich/nixos/blob/master/services/ppp.nix +# Tipps and tricks under https://www.hackster.io/munoz0raul/how-to-use-gsm-3g-4g-in-embedded-linux-systems-9047cf#toc-configuring-the-ppp-files-5 +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + cfg = config.services.qois.wwan; + + mbim-ip-configured = pkgs.writeScriptBin "mbim-ip-configured" ( + '' + #!${pkgs.stdenv.shell} + MBIM_INTERFACE=${cfg.mbimInterface} + '' + + (readFile ./mbim-ip.bash) + ); + + mbim-check-status = pkgs.writeScriptBin "mbim-check-status" '' + #!${pkgs.stdenv.shell} + if ! systemctl is-active --quiet wwan.service; then + # Skip check if wwan is not running + exit 0 + fi + + if ! mbim-network ${cfg.mbimInterface} status | grep -q "Status: activated"; then + echo "WWAN device is currently in disabled state, triggering restart." + systemctl restart wwan.service + fi + ''; +in +{ + options.services.qois.wwan = { + enable = mkEnableOption "wwan client service"; + + apn = mkOption { + type = types.str; + description = '' + APN domain of provider. + ''; + }; + + apnUser = mkOption { + type = types.str; + default = ""; + description = '' + APN username (optional). + ''; + }; + + apnPass = mkOption { + type = types.str; + default = ""; + description = '' + APN password (optional). + ''; + }; + + apnAuth = mkOption { + type = types.enum [ + "PAP" + "CHAP" + "MSCHAPV2" + "" + ]; + default = ""; + description = '' + APN authentication type, one of ${concatMapStringsSep ", " show values} (optional). + ''; + }; + + mbimProxy = mkOption { + type = types.bool; + default = true; + description = '' + Whether to use the mbim proxy or not. + ''; + }; + + mbimInterface = mkOption { + type = types.str; + default = "/dev/cdc-wdm0"; + description = '' + MBIM Interface which the connection will use. + ''; + }; + + networkInterface = mkOption { + type = types.str; + description = "Name of the WWAN network interface"; + }; + }; + + config = mkIf cfg.enable { + systemd.services = { + "wwan" = { + description = "WWAN connectivity"; + wantedBy = [ "network.target" ]; + bindsTo = [ "network-addresses-${cfg.networkInterface}.service" ]; + path = with pkgs; [ + libmbim + iproute + ]; + + serviceConfig = { + ExecStart = "${mbim-ip-configured}/bin/mbim-ip-configured start ${cfg.networkInterface}"; + ExecStop = "${mbim-ip-configured}/bin/mbim-ip-configured stop ${cfg.networkInterface}"; + + RemainAfterExit = true; + }; + }; + "wwan-check" = { + description = "Check WWAN connectivity and restart if disabled"; + path = with pkgs; [ libmbim ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${mbim-check-status}/bin/mbim-check-status"; + }; + }; + }; + systemd.timers."wwan-check" = { + description = "WWAN connectivity check"; + wantedBy = [ "timers.target" ]; + timerConfig = { + Unit = "wwan-check"; + OnBootSec = "2m"; + OnUnitActiveSec = "1m"; + }; + }; + + environment.etc."mbim-network.conf".text = '' + APN=${cfg.apn} + APN_USER=${cfg.apnUser} + APN_PASS=${cfg.apnPass} + APN_AUTH=${cfg.apnAuth} + PROXY=${optionalString cfg.mbimProxy "yes"} + ''; + + networking.interfaces.${cfg.networkInterface}.useDHCP = false; + }; +} diff --git a/nixos-modules/wwan/mbim-ip.bash b/nixos-modules/wwan/mbim-ip.bash new file mode 100644 index 0000000..fafc841 --- /dev/null +++ b/nixos-modules/wwan/mbim-ip.bash @@ -0,0 +1,329 @@ +#!/usr/bin/env bash +############################################################################### +# Configuration +############################################################################### +MODE=$1 +DEV=$2 + +if [ "$DEBUG" == "" ]; then + DEBUG="false" +fi + +if [ "$MBIM_INTERFACE" == "" ]; then + MBIM_INTERFACE="/dev/cdc-wdm0" +fi + +############################################################################### +# Global Variables +############################################################################### +previous_state="none" +state="none" +skip_line=0 +ipv4_addresses=() +ipv4_gateway="" +ipv4_dns=() +ipv4_mtu="" +ipv6_addresses=() +ipv6_gateway="" +ipv6_dns=() +ipv6_mtu="" + +export previous_state state skip_line \ + ipv4_addresses ipv4_gateway ipv4_dns ipv4_mtu \ + ipv6_addresses ipv6_gateway ipv6_dns ipv6_mtu + +############################################################################### +# Function +############################################################################### + +function print_debug { + if [ "$DEBUG" != "false" ]; then + echo "[State: $state] $1" >&2 + fi +} + +function print_full_configuration { + if [[ "${#ipv4_addresses[@]}" > 0 ]]; then + printf "IPv4: " + printf '%s, ' "${ipv4_addresses[@]}" + printf "\n" + + printf "GW: $ipv4_gateway\n" + + printf "DNS: " + printf '%s, ' "${ipv4_dns[@]}" + printf "\n" + + printf "MTU: $ipv4_mtu\n" + fi + + if [[ "${#ipv6_addresses[@]}" > 0 ]]; then + echo + printf "IPv6: " + printf '%s, ' "${ipv6_addresses[@]}" + printf "\n" + + printf "GW: $ipv6_gateway\n" + + printf "DNS: " + printf '%s, ' "${ipv6_dns[@]}" + printf "\n" + + printf "MTU: $ipv6_mtu\n" + fi +} + +function next_state { + previous_state="$state" + state="$1" +} + +function parse_ip { + # IP [0]: '10.134.203.177/30' + local line_re="IP \[([0-9]+)\]: '(.+)'" + local input=$1 + if [[ $input =~ $line_re ]]; then + local ip_cnt=${BASH_REMATCH[1]} + local ip=${BASH_REMATCH[2]} + fi + echo "$ip" +} + +function parse_dns { + # IP [0]: '10.134.203.177/30' + local line_re="DNS \[([0-9]+)\]: '(.+)'" + local input=$1 + if [[ $input =~ $line_re ]]; then + local dns_cnt=${BASH_REMATCH[1]} + local dns=${BASH_REMATCH[2]} + fi + echo "$dns" +} + +function parse_gateway { + # Gateway: '10.134.203.178' + local line_re="Gateway: '(.+)'" + local input=$1 + if [[ $input =~ $line_re ]]; then + local gw=${BASH_REMATCH[1]} + fi + echo "$gw" +} + +function parse_mtu { + # MTU: '1500' + local line_re="MTU: '([0-9]+)'" + local input=$1 + if [[ $input =~ $line_re ]]; then + local mtu=${BASH_REMATCH[1]} + fi + echo "$mtu" +} + +function parse_input_state_machine { + state="start" + while true; do + if [[ "$skip_line" == 0 ]]; then + read line || break # TODO: Clean up + else + skip_line=0 + fi + case "$state" in + "start") + read line || break # first line is empty, read a new one #TODO: This is not very clean... + case "$line" in + *"configuration available: 'none'"*) + # Skip none state + # TODO: This is a workaround of the original parser's shortcomming + continue + ;; + *"IPv4 configuration available"*) + next_state "ipv4_ip" + continue + ;; + *"IPv6 configuration available"*) + next_state "ipv6_ip" + continue + ;; + *) + next_state "exit" + continue + ;; + esac + ;; + "error") + echo "Error in pattern matchin of state $previous_state. Exiting." >&2 + exit 2 + ;; + "exit") + break + ;; + "ipv4_ip") + ipv4=$(parse_ip "$line") + if [ -z "$ipv4" ]; then + if [[ "${#ipv4_addresses[@]}" < 1 ]]; then + next_state "error" + continue + else + next_state "ipv4_gateway" + skip_line=1 + continue + fi + fi + print_debug "$ipv4" + ipv4_addresses+=("$ipv4") + ;; + "ipv4_gateway") + gw=$(parse_gateway "$line") + if [ -z "$gw" ]; then + next_state "error" + continue + fi + print_debug "$gw" + ipv4_gateway="$gw" + next_state "ipv4_dns" + ;; + "ipv4_dns") + ipv4=$(parse_dns "$line") + if [ -z "$ipv4" ]; then + if [[ "${#ipv4_dns[@]}" < 1 ]]; then + next_state "error" + continue + else + next_state "ipv4_mtu" + skip_line=1 + continue + fi + fi + print_debug "$ipv4" + ipv4_dns+=("$ipv4") + ;; + "ipv4_mtu") + mtu=$(parse_mtu "$line") + if [ -z "$mtu" ]; then + next_state "error" + continue + fi + print_debug "$mtu" + ipv4_mtu="$mtu" + next_state "start" + ;; + "ipv6_ip") + ipv6=$(parse_ip "$line") + if [ -z "$ipv6" ]; then + if [[ "${#ipv6_addresses[@]}" < 1 ]]; then + next_state "error" + continue + else + next_state "ipv6_gateway" + skip_line=1 + continue + fi + fi + print_debug "$ipv6" + ipv6_addresses+=("$ipv6") + ;; + "ipv6_gateway") + gw=$(parse_gateway "$line") + if [ -z "$gw" ]; then + next_state "error" + continue + fi + print_debug "$gw" + ipv6_gateway="$gw" + next_state "ipv6_dns" + ;; + "ipv6_dns") + ipv6=$(parse_dns "$line") + if [ -z "$ipv6" ]; then + if [[ "${#ipv6_dns[@]}" < 1 ]]; then + next_state "error" + continue + else + next_state "ipv6_mtu" + skip_line=1 + continue + fi + fi + print_debug "$ipv6" + ipv6_dns+=("$ipv6") + ;; + "ipv6_mtu") + mtu=$(parse_mtu "$line") + if [ -z "$mtu" ]; then + next_state "error" + continue + fi + print_debug "$mtu" + ipv6_mtu="$mtu" + next_state "start" + ;; + *) + print_debug "Invalid state (came from $previous_state). Exiting." + exit 0 + ;; + esac + done +} + + +interface_stop(){ + ip addr flush dev $DEV + ip route flush dev $DEV + + ip -6 addr flush dev $DEV + ip -6 route flush dev $DEV + + #TODO: Nameserver? +} + +interface_start() { + ip link set $DEV up + + if [[ "${#ipv4_addresses[@]}" > 0 ]]; then + ip addr add ${ipv4_addresses[@]} dev $DEV broadcast + #TODO: Works for multiple addresses? + ip link set $DEV mtu $ipv4_mtu + ip route add default via $ipv4_gateway dev $DEV + #TODO: nameserver ${ipv4_dns[@]} + else + echo "No IPv4 address, skipping v4 configuration..." + fi + + if [[ "${#ipv6_addresses[@]}" > 0 ]]; then + ip -6 addr add ${ipv6_addresses[@]} dev $DEV #TODO: Works for multiple addresses? + ip -6 route add default via $ipv6_gateway dev $DEV + ip -6 link set $DEV mtu $ipv6_mtu + #TODO: nameserver ${ipv6_dns[@]}" + else + echo "No IPv6 address, skipping v6 configuration..." + fi +} + +############################################################################### +# Execution +############################################################################### +set -x +set -e +echo "NOTE: This script does not yet support nameserver configuration." + +case "$MODE" in + "start") + mbim-network $MBIM_INTERFACE start + sleep 1 + mbimcli -d $MBIM_INTERFACE -p --query-ip-configuration=0 | { + parse_input_state_machine + print_full_configuration + interface_stop + interface_start + } + ;; + "stop") + mbim-network $MBIM_INTERFACE stop + interface_stop + ;; + *) + echo "USAGE: $0 start|stop INTERFACE" >&2 + echo "You can set an env variable DEBUG to gather debugging output." >&2 + exit 1 + ;; +esac diff --git a/overlays/default.nix b/overlays/default.nix new file mode 100644 index 0000000..430b766 --- /dev/null +++ b/overlays/default.nix @@ -0,0 +1,5 @@ +self: super: { + lib = (super.lib or { }) // { + qois = import ../lib { lib = self.lib; }; + }; +} diff --git a/packages/all.nix b/packages/all.nix new file mode 100644 index 0000000..6b03325 --- /dev/null +++ b/packages/all.nix @@ -0,0 +1,11 @@ +{ + self, + system, + pkgs, + ... +}: +with pkgs.lib; +let + nixosConfigs = mapAttrsToList (n: v: v.config.system.build.toplevel) self.nixosConfigurations; +in +pkgs.linkFarmFromDrvs "allHosts" (nixosConfigs ++ [ self.packages.${system}.docs ]) diff --git a/packages/cache.nix b/packages/cache.nix new file mode 100644 index 0000000..f7fb517 --- /dev/null +++ b/packages/cache.nix @@ -0,0 +1,36 @@ +{ pkgs, ... }: +pkgs.writeShellApplication { + name = "cache"; + meta.description = "Access the infrastructure's attic cache. Mostly used in CI."; + runtimeInputs = [ + pkgs.attic-client + pkgs.findutils + pkgs.gnugrep + ]; + text = '' + SERVER="https://attic.qo.is/" + CACHE_NAME="qois" + CACHE_REPO="$CACHE_NAME:qois-infrastructure" + if [ -z "$ATTIC_AUTH_TOKEN" ]; then + echo "Please set the \$ATTIC_AUTH_TOKEN environment variable to access the cache." + exit 3 + fi + attic login "$CACHE_NAME" "$SERVER" "$ATTIC_AUTH_TOKEN" + + case "$1" in + use) + attic use "$CACHE_REPO" + ;; + watch) + attic watch-store "$CACHE_REPO" + ;; + push) + RESULT_PATH="./result" + # Add build dependencies as well + nix-store -qR --include-outputs "$(nix-store -qd $RESULT_PATH)" | grep -v '\.drv$' \ + | xargs attic push "$CACHE_REPO" "$RESULT_PATH" + ;; + + esac + ''; +} diff --git a/packages/default.nix b/packages/default.nix new file mode 100644 index 0000000..4c3e54b --- /dev/null +++ b/packages/default.nix @@ -0,0 +1,17 @@ +{ system, ... }@inputs: +{ + ${system} = + let + all = import ./all.nix inputs; + in + { + inherit all; + default = all; + cache = import ./cache.nix inputs; + deploy-qois = import ./deploy-qois.nix inputs; + docs = import ./docs.nix inputs; + sops = import ./sops.nix inputs; + sops-config = import ./sops-config.nix inputs; + sops-rekey = import ./sops-rekey.nix inputs; + }; +} diff --git a/packages/deploy-qois.nix b/packages/deploy-qois.nix new file mode 100644 index 0000000..4504dff --- /dev/null +++ b/packages/deploy-qois.nix @@ -0,0 +1,14 @@ +{ + pkgs, + self, + system, + ... +}: +pkgs.writeShellApplication { + name = "deploy-qois"; + meta.description = "Deploy configuration to specificed targets."; + runtimeInputs = [ pkgs.deploy-rs ]; + text = '' + deploy --interactive --targets "''${@:-${self}}" + ''; +} diff --git a/packages/docs.nix b/packages/docs.nix new file mode 100644 index 0000000..2e79eed --- /dev/null +++ b/packages/docs.nix @@ -0,0 +1,16 @@ +{ pkgs, self, ... }: +let + version = self.rev or self.dirtyRev; +in +pkgs.stdenv.mkDerivation { + inherit version; + name = "qois-docs-${version}"; + buildInputs = with pkgs; [ + mdbook + mdbook-cmdrun + mdbook-plantuml + plantuml + ]; + src = ../.; + buildPhase = "mdbook build --dest-dir $out"; +} diff --git a/packages/sops-config.nix b/packages/sops-config.nix new file mode 100644 index 0000000..c78b95d --- /dev/null +++ b/packages/sops-config.nix @@ -0,0 +1,87 @@ +{ + pkgs, + self, + system, + ... +}: +let + metaHostConfigs = import ../defaults/meta/hosts.nix { inherit pkgs; }; + userPgpKeys = + let + keysFolder = "${self.inputs.private}/sops_keys"; + gpgFingerprintsFile = + pkgs.runCommand "userPgpKeys" + { + src = keysFolder; + buildInputs = with pkgs; [ + gnupg + gnugrep + ]; + } + '' + echo -n "[ " > $out + for KEY in $src/*.asc; do + FINGERPRINT=` + gpg --homedir /tmp/.gnupg --with-colons --show-keys "$KEY" \ + | grep ^fpr \ + | grep --max-count 1 --only-matching --extended-regexp '[0-9A-Z]{40}' \ + | cut -c -40 + ` + echo -n "\"$FINGERPRINT\" " >> $out + done + echo "]" >> $out + ''; + in + import "${gpgFingerprintsFile}"; + userAgeKeys = [ ]; + serverAgeKeys = + let + getHostsWithSshKeys = pkgs.lib.filterAttrs (name: cfg: cfg ? sshKey); + mapHostToAgeKey = builtins.mapAttrs ( + name: cfg: + pkgs.lib.readFile ( + pkgs.runCommand "sshToAgeKey" + { + buildInputs = [ pkgs.ssh-to-age ]; + } + '' + echo "${cfg.sshKey}" | ssh-to-age -o $out + '' + ) + ); + in + mapHostToAgeKey (getHostsWithSshKeys metaHostConfigs.qois.meta.hosts); + toCommaList = builtins.concatStringsSep ","; +in +pkgs.writeText ".sops.yaml" ( + '' + # This file was generated by nix, see packages/sops-config.nix for details. + '' + + builtins.toJSON { + keys = userPgpKeys ++ userAgeKeys ++ builtins.attrValues serverAgeKeys; + creation_rules = + [ + # Secrets for administrators (a.k.a. passwords) + { + path_regex = "private/passwords\.sops\.(yaml|json|env|ini)$"; + pgp = toCommaList userPgpKeys; + age = toCommaList userAgeKeys; + } + + # Secrets for all hosts + { + path_regex = "private/nixos-configurations/secrets\.sops\.(yaml|json|env|ini)$"; + pgp = toCommaList userPgpKeys; + age = toCommaList (userAgeKeys ++ builtins.attrValues serverAgeKeys); + } + ] + ++ + + # Server specific secrets + (pkgs.lib.mapAttrsToList (serverName: serverKey: { + path_regex = "private/nixos-configurations/${serverName}/secrets\.sops\.(yaml|json|env|ini)$"; + pgp = toCommaList userPgpKeys; + age = toCommaList (userAgeKeys ++ [ serverKey ]); + }) serverAgeKeys); + } +) diff --git a/packages/sops-rekey.nix b/packages/sops-rekey.nix new file mode 100644 index 0000000..a345e24 --- /dev/null +++ b/packages/sops-rekey.nix @@ -0,0 +1,17 @@ +{ + pkgs, + self, + system, + ... +}: +pkgs.writeShellApplication { + name = "sops-rekey"; + meta.description = "Rekey all sops secrets with changed keys"; + runtimeInputs = [ + pkgs.findutils + self.packages.${system}.sops + ]; + text = '' + find . -regex '.*\.sops\..*$' -type f -exec sops updatekeys {} \; + ''; +} diff --git a/packages/sops.nix b/packages/sops.nix new file mode 100644 index 0000000..e37608e --- /dev/null +++ b/packages/sops.nix @@ -0,0 +1,20 @@ +{ + pkgs, + self, + system, + ... +}: +pkgs.writeShellApplication { + name = "sops"; + meta.description = "Run SOPS with the generated configuration"; + runtimeInputs = with pkgs; [ + sops + gitMinimal + nix + ]; + text = '' + FLAKE_ROOT="$(git rev-parse --show-toplevel)" + nix build --out-link "$FLAKE_ROOT/.sops.yaml" "$FLAKE_ROOT#sops-config" + sops --config "''${FLAKE_ROOT}/.sops.yaml" "''${@}" + ''; +} diff --git a/private b/private new file mode 160000 index 0000000..9a64633 --- /dev/null +++ b/private @@ -0,0 +1 @@ +Subproject commit 9a646336c5ad419ec79ae81a47d68213bdcbff92 diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/updates.md b/updates.md new file mode 100644 index 0000000..81a414d --- /dev/null +++ b/updates.md @@ -0,0 +1,49 @@ +# Updates + +To update the infrastructure, do the following steps: + +```bash + +# Update inputs +nix flake update + +# Check builds +nix build + +# Push updates to git remote +git add flake.lock +git commit +git push +``` + +Deploy updates: + +```bash +nix develop + +# Deploy vms +deploy-qois .#lindberg-nextcloud .#lindberg-build + +# Deploy fast physical hosts +deploy-qois .#lindberg + +# Deploy slow physical hosts (maybe do individually) +deploy-qois --confirm-timeout 600 --activation-timeout 600 --targets .#fulberg .#tierberg .#stompert .#stompert + +``` + +After deploying updates, verify that all services run as expected. +For kernel updates, it might be required to reboot machines, which can be done in parallel with e.g.: + +```bash +pssh -l root -H lindberg-nextcloud.backplane.net.qo.is -H lindberg-build.backplane.net.qo.is reboot +``` + +## Aplication Updates + +Some applications have pinned versions to prevent problems due to accidental upgrades. +The version switch has to be done manually by switching the package used. + +This includes the modules for: + +- `nextcloud`