commit 2ca4b5440a5b0ee491798bacb0d27fbcc792ab18 Author: eyjhb Date: Thu Aug 8 14:16:04 2024 +0200 initial commit diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..37f128c --- /dev/null +++ b/deploy.sh @@ -0,0 +1,27 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i bash -p jq nixos-anywhere +set -ex + +USERNAME="root" +IP="gerd.fricloud.dk" +NIXPKGS=$(jq -r '.nixpkgs.url' ./shared/sources/sources.json) + +export NIX_PATH="nixpkgs=$NIXPKGS" + +if [ "$1" == "initial-deploy" ]; then + echo "Initial deployment..." + NIX_TOP_LEVEL_PATH=$(nix build --impure -I nixos-config=./machines/gerd.nix --json --expr "(import {}).config.system.build.toplevel" | jq -r '.[].outputs.out') + NIX_DISKO_SCRIPT=$(nix build --impure -I nixos-config=./machines/gerd.nix --json --expr "(import {}).config.system.build.diskoScript" | jq -r '.[].outputs.out') + + nixos-anywhere --store-paths "$NIX_DISKO_SCRIPT" "$NIX_TOP_LEVEL_PATH" "$USERNAME@$IP" +else + echo "Deploying..." + REBUILD_ACTION="switch" + if [ -n "$1" ]; then + REBUILD_ACTION="$1" + fi + nixos-rebuild \ + -I nixos-config=./machines/gerd.nix \ + "$REBUILD_ACTION" --target-host "$USERNAME@$IP" + # -I "nixpkgs=$(jq -r '.nixpkgs.url' ./shared/sources/sources.json)" \ +fi diff --git a/machines/gerd.nix b/machines/gerd.nix new file mode 100644 index 0000000..d228511 --- /dev/null +++ b/machines/gerd.nix @@ -0,0 +1,49 @@ +{ modulesPath, config, lib, pkgs, ... }: + +let + sources = import ./../shared/sources/sources.nix; +in { + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + (modulesPath + "/profiles/qemu-guest.nix") + (sources.disko + "/module.nix") + ./../shared/modules + + ./gerd/disk-zfs.nix + + ./../shared/applications/server/nginx.nix + ./../shared/applications/state/ssh.nix + + ./gerd/services/forgejo.nix + ]; + + networking.hostName = "gerd"; + networking.hostId = "e1166ac9"; + networking.interfaces.enp1s0.ipv6.addresses = [ { address = "2a01:4f9:c012:743e::1"; prefixLength = 64; }]; + networking.defaultGateway6 = { address = "fe80::1"; interface = "enp1s0"; }; + boot.loader.grub = { + # no need to set devices, disko will add all devices that have a EF02 partition to the list already + # devices = [ ]; + efiSupport = true; + efiInstallAsRemovable = true; + }; + services.openssh.enable = true; + + mine.state.enable = true; + + boot.initrd.postDeviceCommands = lib.mkAfter '' + zfs rollback -r rpool/local/root@blank + ''; + + environment.systemPackages = with pkgs; [ + vim + jq + ]; + + users.users.root.openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPuma8g+U8Wh+4mLvZoV9V+ngPqxjuIG4zhsbaTeXq65 eyjhb@chronos" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGee4uz+HDOj4Y4ANOhWJhoc4mMLP1gz6rpKoMueQF2J rendal@popper" + ]; + + system.stateVersion = "24.05"; +} diff --git a/machines/gerd/disk-zfs.nix b/machines/gerd/disk-zfs.nix new file mode 100644 index 0000000..18512b5 --- /dev/null +++ b/machines/gerd/disk-zfs.nix @@ -0,0 +1,81 @@ +{ lib, ... }: + +let + makeZFSDatasets = datasets: (lib.mapAttrs' (n: v: lib.nameValuePair v.dataset ({ + type = "zfs_fs"; + mountpoint = n; + options.mountpoint = "legacy"; + } // (if v ? extra then v.extra else {}))) datasets); +in { + disko.devices = { + disk.disk1 = { + type = "disk"; + device = lib.mkDefault "/dev/sda"; + content = { + type = "gpt"; + partitions = { + boot = { + name = "boot"; + size = "1M"; + type = "EF02"; + }; + esp = { + name = "ESP"; + size = "500M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + }; + }; + + luks = { + size = "100%"; + content = { + type = "luks"; + name = "cryptroot"; + settings.allowDiscards = true; + # passwordFile = "/tmp/luks.pass"; + + content = { + type = "zfs"; + pool = "rpool"; + }; + }; + }; + }; + }; + }; + + zpool = { + rpool = { + type = "zpool"; + # rootFsOptions.compression = "zstd"; + rootFsOptions = { + compression = "on"; + atime = "off"; + acltype = "posixacl"; + xattr = "sa"; + + # test + # "com.sun:auto-snapshot" = "false"; + # "com.klarasystems:vdev_zaps_v2" = "false"; + }; + + datasets = let + baseDatasets = { + "/" = { dataset = "root"; extra = { postCreateHook = "zfs snapshot rpool/root@blank"; }; }; + "/nix".dataset = "local/nix"; + "/state/stash".dataset = "local/stash"; + "/state/home".dataset = "safe/home"; + "/state/root".dataset = "safe/persistent"; + + # extra datasets + "/srv/forgejo" = { dataset = "safe/svcs/forgejo"; extra.options.quota = "5G"; }; + }; + in (makeZFSDatasets baseDatasets); + }; + }; + }; +} diff --git a/machines/gerd/services/forgejo.nix b/machines/gerd/services/forgejo.nix new file mode 100644 index 0000000..36251e5 --- /dev/null +++ b/machines/gerd/services/forgejo.nix @@ -0,0 +1,32 @@ +{ config, ... }: + +{ + # https://wiki.nixos.org/wiki/Forgejo + services.forgejo = { + enable = true; + + stateDir = config.mine.zfsMounts."rpool/safe/svcs/forgejo"; + + settings = { + server = { + DOMAIN = "git.fricloud.dk"; + ROOT_URL = "https://git.fricloud.dk"; + HTTPPORT = 3000; + }; + + service.DISABLE_REGISTRATION = true; + }; + + }; + + services.nginx = { + virtualHosts."git.fricloud.dk" = { + forceSSL = true; + enableACME = true; + extraConfig = '' + client_max_body_size 512M; + ''; + locations."/".proxyPass = "http://localhost:3000"; + }; + }; +} diff --git a/machines/gerd/zrepl.nix b/machines/gerd/zrepl.nix new file mode 100644 index 0000000..3ce74bd --- /dev/null +++ b/machines/gerd/zrepl.nix @@ -0,0 +1,35 @@ +{ + services.zrepl = { + enable = true; + settings.jobs = [ + { + type = "push"; + name = "safesnapshots"; + + filesystems = [ + "rpool/safe<" + ]; + + snapshotting = { + type = "periodic"; + interval = "5m"; + prefix = "zrepl_"; + }; + + pruning = { + keep_sender = [ + { + type = "grid"; + regex = "^zrepl_.*"; + + # 1. keep all snapshots for 30 minutes + # 2. keep one every 15 minutes for 2 hours + # 3. keep one every hour for 1 day + grid = "1x30m(keep=all) | 8x15m | 14x1d"; + } + ]; + }; + } + ]; + }; +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e8d3353 --- /dev/null +++ b/readme.md @@ -0,0 +1,2 @@ +# Fricloud Server Configuration! +Bla bla bla, something better at some point, big TODO. diff --git a/shared/applications/server/nginx.nix b/shared/applications/server/nginx.nix new file mode 100644 index 0000000..facd738 --- /dev/null +++ b/shared/applications/server/nginx.nix @@ -0,0 +1,59 @@ +{ pkgs, lib, ... }: + +let + snakeOilCa = pkgs.runCommand "snakeoil-ca" { + buildInputs = [ pkgs.openssl ]; + } '' + mkdir "$out" + openssl req -newkey rsa:4096 -x509 -sha256 -days 36500 \ + -subj '/CN=Snakeoil CA' -nodes \ + -out "$out/ca.pem" -keyout "$out/ca.key" + ''; +in { + security.acme.defaults.email = "fricloudacme.cameo530@simplelogin.com"; + security.acme.acceptTerms = true; + + services.nginx = { + enable = true; + + recommendedOptimisation = true; + recommendedTlsSettings = true; + recommendedGzipSettings = true; + # recommendedBrotliSettings = true; + recommendedProxySettings = true; + + # only allow PFS-enabled ciphers with AES256 + sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL"; + + # give Nginx access to our certs + group = "acme"; + + # setup a default site + virtualHosts.default = { + default = lib.mkDefault true; + addSSL = true; + + sslCertificateKey = "${snakeOilCa}/ca.key"; + sslCertificate = "${snakeOilCa}/ca.pem"; + + root = pkgs.writeTextDir "index.html" '' + + + Nothing to see + + +

Like I said, nothing to see here

+ + + ''; + }; + }; + + users.groups.acme = {}; + + networking.firewall = { + allowedTCPPorts = [80 443]; + allowedUDPPorts = [443]; + }; +} + diff --git a/shared/applications/state/ssh.nix b/shared/applications/state/ssh.nix new file mode 100644 index 0000000..653f470 --- /dev/null +++ b/shared/applications/state/ssh.nix @@ -0,0 +1,6 @@ +{ + environment.persistence.root.files = [ + "/etc/ssh/ssh_host_ed25519_key" + "/etc/ssh/ssh_host_rsa_key" + ]; +} diff --git a/shared/modules/default.nix b/shared/modules/default.nix new file mode 100644 index 0000000..bead858 --- /dev/null +++ b/shared/modules/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./state.nix + ./easy-zfs-mounts.nix + ]; +} diff --git a/shared/modules/easy-zfs-mounts.nix b/shared/modules/easy-zfs-mounts.nix new file mode 100644 index 0000000..ded1b7a --- /dev/null +++ b/shared/modules/easy-zfs-mounts.nix @@ -0,0 +1,18 @@ +{ config, lib, ... }: + +with lib; + +let + cfg = config.mine.zfsMounts; +in { + options.mine.zfsMounts = mkOption { + type = types.attrsOf types.str; + default = {}; + }; + + config = { + mine.zfsMounts = let + zfsFilesystems = lib.filterAttrs (_: v: v.fsType == "zfs") config.fileSystems; + in lib.mapAttrs' (_: v: lib.nameValuePair v.device v.mountPoint) zfsFilesystems; + }; +} diff --git a/shared/modules/state.nix b/shared/modules/state.nix new file mode 100644 index 0000000..10264a7 --- /dev/null +++ b/shared/modules/state.nix @@ -0,0 +1,33 @@ +{ config, lib, ... }: + +with lib; + +let + sources = import ./../sources/sources.nix; +in { + options.mine.state.enable = mkOption { + type = types.bool; + default = false; + }; + + imports = [ + (sources.impermanence + "/nixos.nix") + ]; + + config = mkIf config.mine.state.enable { + environment.persistence = { + root = { + persistentStoragePath = "/state/root"; + + files = [ + "/etc/machine-id" + ]; + + directories = [ + "/var/lib/nixos" + "/var/log" + ]; + }; + }; + }; +} diff --git a/shared/sources/sources.json b/shared/sources/sources.json new file mode 100644 index 0000000..113b50c --- /dev/null +++ b/shared/sources/sources.json @@ -0,0 +1,38 @@ +{ + "disko": { + "branch": "master", + "description": "Declarative disk partitioning and formatting using nix [maintainer=@Lassulus]", + "homepage": "", + "owner": "nix-community", + "repo": "disko", + "rev": "0257e44f4ad472b54f19a6dd1615aee7fa48ed49", + "sha256": "1csaqxijzchbi4bwr6s2vfalqc939ln3acn64vqc0b7y80yx8shl", + "type": "tarball", + "url": "https://github.com/nix-community/disko/archive/0257e44f4ad472b54f19a6dd1615aee7fa48ed49.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "impermanence": { + "branch": "master", + "description": "Modules to help you handle persistent state on systems with ephemeral root storage [maintainer=@talyz]", + "homepage": "", + "owner": "nix-community", + "repo": "impermanence", + "rev": "23c1f06316b67cb5dabdfe2973da3785cfe9c34a", + "sha256": "1c99hc2mv0f5rjxj97wcypyrpi5i3xmpi3sd2fnw2481jxgqn5h3", + "type": "tarball", + "url": "https://github.com/nix-community/impermanence/archive/23c1f06316b67cb5dabdfe2973da3785cfe9c34a.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs": { + "branch": "nixos-24.05", + "description": "Nix Packages collection", + "homepage": null, + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "883180e6550c1723395a3a342f830bfc5c371f6b", + "sha256": "01axrf25mahbxmp6vgfgx09dflbyaavr5liynkp6rpm4lkacr27f", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/883180e6550c1723395a3a342f830bfc5c371f6b.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/shared/sources/sources.nix b/shared/sources/sources.nix new file mode 100644 index 0000000..fe3dadf --- /dev/null +++ b/shared/sources/sources.nix @@ -0,0 +1,198 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + + fetch_tarball = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + + fetch_git = name: spec: + let + ref = + spec.ref or ( + if spec ? branch then "refs/heads/${spec.branch}" else + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!" + ); + submodules = spec.submodules or false; + submoduleArg = + let + nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; + emptyArgWithWarning = + if submodules + then + builtins.trace + ( + "The niv input \"${name}\" uses submodules " + + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " + + "does not support them" + ) + { } + else { }; + in + if nixSupportsSubmodules + then { inherit submodules; } + else emptyArgWithWarning; + in + builtins.fetchGit + ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); + + fetch_local = spec: spec.path; + + fetch_builtin-tarball = name: throw + ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=tarball -a builtin=true''; + + fetch_builtin-url = name: throw + ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=file -a builtin=true''; + + # + # Various helpers + # + + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 + sanitizeName = name: + ( + concatMapStrings (s: if builtins.isList s then "-" else s) + ( + builtins.split "[^[:alnum:]+._?=-]+" + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) + ) + ); + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: system: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import { } + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs name spec + else if spec.type == "tarball" then fetch_tarball pkgs name spec + else if spec.type == "git" then fetch_git name spec + else if spec.type == "local" then fetch_local spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball name + else if spec.type == "builtin-url" then fetch_builtin-url name + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # If the environment variable NIV_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + replace = name: drv: + let + saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 + range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatMapStrings = f: list: concatStrings (map f list); + concatStrings = builtins.concatStringsSep ""; + + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 + optionalAttrs = cond: as: if cond then as else { }; + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs + ( + name: spec: + if builtins.hasAttr "outPath" spec + then + abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) + config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null + , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile) + , system ? builtins.currentSystem + , pkgs ? mkPkgs sources system + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; + +in +mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); }