commit 3ab713a5b3901e2d1c83d9a9ba4cf4cfad232ec1
Author: Lukas Wurzinger <lukas@wrz.one>
Date:   Sun Feb 4 21:51:11 2024 +0100

    init

diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0e259d4
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e2a2cde
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# ✨ puter ✨
+
+This is my NixOS configuration. There are many like it, but this one is mine.
+
+## Partitioning
+
+```bash
+curl -O https://raw.githubusercontent.com/lukaswrz/puter/main/part
+chmod +x part
+./part /path/to/device
+```
diff --git a/class/desktop/codium.nix b/class/desktop/codium.nix
new file mode 100644
index 0000000..40442ef
--- /dev/null
+++ b/class/desktop/codium.nix
@@ -0,0 +1,3 @@
+{pkgs, ...}: {
+  environment.systemPackages = [pkgs.vscodium-fhs];
+}
diff --git a/class/desktop/compatibility.nix b/class/desktop/compatibility.nix
new file mode 100644
index 0000000..7d72476
--- /dev/null
+++ b/class/desktop/compatibility.nix
@@ -0,0 +1,67 @@
+{pkgs, ...}: {
+  environment.systemPackages = [
+    pkgs.appimage-run
+    pkgs.wineWowPackages.unstableFull
+  ];
+
+  services.envfs.enable = true;
+
+  programs.nix-ld = {
+    enable = true;
+    libraries = [
+      pkgs.alsa-lib
+      pkgs.atk
+      pkgs.at-spi2-atk
+      pkgs.at-spi2-core
+      pkgs.cairo
+      pkgs.cups
+      pkgs.curl
+      pkgs.dbus
+      pkgs.expat
+      pkgs.fontconfig
+      pkgs.freetype
+      pkgs.fuse
+      pkgs.fuse3
+      pkgs.gdk-pixbuf
+      pkgs.glib
+      pkgs.gtk3
+      pkgs.gtk4
+      pkgs.icu
+      pkgs.libappindicator
+      pkgs.libdrm
+      pkgs.libGL
+      pkgs.libglvnd
+      pkgs.libnotify
+      pkgs.libpulseaudio
+      pkgs.libunwind
+      pkgs.libusb1
+      pkgs.libuuid
+      pkgs.libxkbcommon
+      pkgs.libxml2
+      pkgs.mesa
+      pkgs.nspr
+      pkgs.nss
+      pkgs.openssl
+      pkgs.pango
+      pkgs.pipewire
+      pkgs.stdenv.cc.cc
+      pkgs.systemd
+      pkgs.vulkan-loader
+      pkgs.xorg.libX11
+      pkgs.xorg.libxcb
+      pkgs.xorg.libXcomposite
+      pkgs.xorg.libXcursor
+      pkgs.xorg.libXdamage
+      pkgs.xorg.libXext
+      pkgs.xorg.libXfixes
+      pkgs.xorg.libXi
+      pkgs.xorg.libxkbfile
+      pkgs.xorg.libXrandr
+      pkgs.xorg.libXrender
+      pkgs.xorg.libXScrnSaver
+      pkgs.xorg.libxshmfence
+      pkgs.xorg.libXtst
+      pkgs.zlib
+    ];
+  };
+}
diff --git a/class/desktop/default.nix b/class/desktop/default.nix
new file mode 100644
index 0000000..b2eaa69
--- /dev/null
+++ b/class/desktop/default.nix
@@ -0,0 +1,21 @@
+{
+  imports = [
+    ./codium.nix
+    ./compatibility.nix
+    ./docker.nix
+    ./fish.nix
+    ./flatpak.nix
+    ./fonts.nix
+    ./fs.nix
+    ./gamemode.nix
+    ./gtk.nix
+    ./hardware.nix
+    ./location.nix
+    ./mullvad.nix
+    ./networking.nix
+    ./pipewire.nix
+    ./plasma.nix
+    ./printing.nix
+    ./syncthing.nix
+  ];
+}
diff --git a/class/desktop/docker.nix b/class/desktop/docker.nix
new file mode 100644
index 0000000..a878015
--- /dev/null
+++ b/class/desktop/docker.nix
@@ -0,0 +1,3 @@
+{
+  virtualisation.docker.enable = true;
+}
diff --git a/class/desktop/fish.nix b/class/desktop/fish.nix
new file mode 100644
index 0000000..8f3a7c9
--- /dev/null
+++ b/class/desktop/fish.nix
@@ -0,0 +1,4 @@
+{pkgs, ...}: {
+  programs.fish.enable = true;
+  users.defaultUserShell = pkgs.fish;
+}
diff --git a/class/desktop/flatpak.nix b/class/desktop/flatpak.nix
new file mode 100644
index 0000000..3562804
--- /dev/null
+++ b/class/desktop/flatpak.nix
@@ -0,0 +1,25 @@
+{
+  config,
+  pkgs,
+  ...
+}: {
+  # FIXME: This is unnecessary when https://github.com/NixOS/nixpkgs/pull/262462 is merged
+  system.fsPackages = [pkgs.bindfs];
+  fileSystems = let
+    mkRoSymBind = path: {
+      device = path;
+      fsType = "fuse.bindfs";
+      options = ["ro" "resolve-symlinks" "x-gvfs-hide"];
+    };
+    aggregatedFonts = pkgs.buildEnv {
+      name = "system-fonts";
+      paths = config.fonts.packages;
+      pathsToLink = ["/share/fonts"];
+    };
+  in {
+    "/usr/share/icons" = mkRoSymBind "/run/current-system/sw/share/icons";
+    "/usr/share/fonts" = mkRoSymBind (aggregatedFonts + "/share/fonts");
+  };
+
+  services.flatpak.enable = true;
+}
diff --git a/class/desktop/fonts.nix b/class/desktop/fonts.nix
new file mode 100644
index 0000000..4bb7d0f
--- /dev/null
+++ b/class/desktop/fonts.nix
@@ -0,0 +1,25 @@
+{pkgs, ...}: {
+  fonts = {
+    enableDefaultPackages = true;
+
+    packages = with pkgs; [
+      noto-fonts
+      noto-fonts-extra
+      noto-fonts-cjk-sans
+      noto-fonts-cjk-serif
+      noto-fonts-emoji
+      (nerdfonts.override {fonts = ["Noto" "Iosevka"];})
+    ];
+
+    fontconfig = {
+      enable = true;
+
+      defaultFonts = {
+        monospace = ["NotoSansMono Nerd Font"];
+        sansSerif = ["Noto Sans"];
+        serif = ["Noto Serif"];
+        emoji = ["Noto Color Emoji"];
+      };
+    };
+  };
+}
diff --git a/class/desktop/fs.nix b/class/desktop/fs.nix
new file mode 100644
index 0000000..02e66b8
--- /dev/null
+++ b/class/desktop/fs.nix
@@ -0,0 +1,14 @@
+{
+  boot.initrd.luks.devices.main.device = "/dev/disk/by-label/cryptmain";
+
+  fileSystems = {
+    "/home" = {
+      device = "/dev/mapper/main";
+      fsType = "btrfs";
+      options = ["subvol=home" "compress=zstd" "noatime"];
+    };
+    "/nix".device = "/dev/mapper/main";
+    "/persist".device = "/dev/mapper/main";
+    "/var/log".device = "/dev/mapper/main";
+  };
+}
diff --git a/class/desktop/gamemode.nix b/class/desktop/gamemode.nix
new file mode 100644
index 0000000..36e3c0d
--- /dev/null
+++ b/class/desktop/gamemode.nix
@@ -0,0 +1,18 @@
+{
+  lib,
+  pkgs,
+  ...
+}: {
+  programs.gamemode = {
+    enable = true;
+    settings = {
+      general = {
+        renice = 10;
+      };
+      custom = {
+        start = "${lib.getExe pkgs.libnotify} 'GameMode started'";
+        end = "${lib.getExe pkgs.libnotify} 'GameMode stopped'";
+      };
+    };
+  };
+}
diff --git a/class/desktop/gtk.nix b/class/desktop/gtk.nix
new file mode 100644
index 0000000..0996747
--- /dev/null
+++ b/class/desktop/gtk.nix
@@ -0,0 +1,7 @@
+{pkgs, ...}: {
+  xdg.portal.extraPortals = [
+    pkgs.xdg-desktop-portal-gtk
+  ];
+
+  programs.dconf.enable = true;
+}
diff --git a/class/desktop/hardware.nix b/class/desktop/hardware.nix
new file mode 100644
index 0000000..7a3165a
--- /dev/null
+++ b/class/desktop/hardware.nix
@@ -0,0 +1,15 @@
+{pkgs, ...}: {
+  hardware = {
+    bluetooth.enable = true;
+    xone.enable = true;
+    xpadneo.enable = true;
+    opentabletdriver.enable = true;
+    opengl = {
+      driSupport32Bit = true;
+      extraPackages32 = [
+        pkgs.pkgsi686Linux.libvdpau-va-gl
+        pkgs.pkgsi686Linux.vaapiVdpau
+      ];
+    };
+  };
+}
diff --git a/class/desktop/location.nix b/class/desktop/location.nix
new file mode 100644
index 0000000..285b45d
--- /dev/null
+++ b/class/desktop/location.nix
@@ -0,0 +1,5 @@
+{
+  location.provider = "geoclue2";
+
+  services.automatic-timezoned.enable = true;
+}
diff --git a/class/desktop/mullvad.nix b/class/desktop/mullvad.nix
new file mode 100644
index 0000000..35f4b65
--- /dev/null
+++ b/class/desktop/mullvad.nix
@@ -0,0 +1,5 @@
+{
+  environment.persistence."/persist".directories = ["/etc/mullvad-vpn"];
+
+  services.mullvad-vpn.enable = true;
+}
diff --git a/class/desktop/networking.nix b/class/desktop/networking.nix
new file mode 100644
index 0000000..3ecec8f
--- /dev/null
+++ b/class/desktop/networking.nix
@@ -0,0 +1,40 @@
+{
+  environment.persistence."/persist".directories = ["/etc/NetworkManager"];
+
+  services.resolved.enable = true;
+  services.opensnitch.enable = true;
+
+  networking = {
+    networkmanager = {
+      enable = true;
+      dns = "systemd-resolved";
+    };
+    firewall = {
+      allowedTCPPorts = [
+        # Spotify track sync
+        57621
+        # Steam Remote Play
+        27036
+        # Source Dedicated Server SRCDS Rcon port
+        27015
+        # Syncthing TCP based sync protocol traffic
+        22000
+      ];
+      allowedUDPPorts = [
+        # Source Dedicated Server gameplay traffic
+        27015
+        # Syncthing QUIC based sync protocol traffic
+        22000
+        # Syncthing port for discovery broadcasts on IPv4 and multicasts on IPv6
+        21027
+      ];
+      allowedUDPPortRanges = [
+        # Steam Remote Play
+        {
+          from = 27031;
+          to = 27036;
+        }
+      ];
+    };
+  };
+}
diff --git a/class/desktop/pipewire.nix b/class/desktop/pipewire.nix
new file mode 100644
index 0000000..f09150e
--- /dev/null
+++ b/class/desktop/pipewire.nix
@@ -0,0 +1,10 @@
+{
+  security.rtkit.enable = true;
+  services.pipewire = {
+    enable = true;
+    wireplumber.enable = true;
+    alsa.enable = true;
+    pulse.enable = true;
+    jack.enable = true;
+  };
+}
diff --git a/class/desktop/plasma.nix b/class/desktop/plasma.nix
new file mode 100644
index 0000000..ec11146
--- /dev/null
+++ b/class/desktop/plasma.nix
@@ -0,0 +1,44 @@
+{pkgs, ...}: {
+  # TODO
+  # displayManager = {
+  #   defaultSession = "plasmawayland";
+  #   sddm = {
+  #     enable = true;
+  #     autoNumlock = true;
+  #     settings = {
+  #       Theme = {
+  #         CursorTheme = "breeze_cursors";
+  #       };
+  #     };
+  #   };
+  # };
+
+  services = {
+    xserver = {
+      enable = true;
+      desktopManager.plasma5.enable = true;
+      displayManager.sddm.enable = true;
+      excludePackages = with pkgs; [
+        xterm
+      ];
+    };
+  };
+
+  environment = {
+    systemPackages = [
+      pkgs.discover
+      pkgs.sddm-kcm
+    ];
+    sessionVariables = {
+      "SUDO_ASKPASS" = pkgs.writeShellScript "kdialogaskpass" ''
+        exec ${pkgs.kdialog} --password Askpass
+      '';
+      "MOZ_USE_XINPUT2" = "1";
+      "GDK_SCALE" = "1";
+    };
+  };
+
+  xdg.portal.xdgOpenUsePortal = true;
+
+  programs.kdeconnect.enable = true;
+}
diff --git a/class/desktop/printing.nix b/class/desktop/printing.nix
new file mode 100644
index 0000000..a7b3b55
--- /dev/null
+++ b/class/desktop/printing.nix
@@ -0,0 +1,10 @@
+{
+  services = {
+    printing = {
+      enable = true;
+      webInterface = true;
+      cups-pdf.enable = true;
+    };
+    system-config-printer.enable = true;
+  };
+}
diff --git a/class/desktop/syncthing.nix b/class/desktop/syncthing.nix
new file mode 100644
index 0000000..0f74af1
--- /dev/null
+++ b/class/desktop/syncthing.nix
@@ -0,0 +1,7 @@
+{
+  services.syncthing = {
+    enable = true;
+    overrideDevices = false;
+    overrideFolders = false;
+  };
+}
diff --git a/class/server/default.nix b/class/server/default.nix
new file mode 100644
index 0000000..fbf50e8
--- /dev/null
+++ b/class/server/default.nix
@@ -0,0 +1,6 @@
+{
+  imports = [
+    ./fs.nix
+    ./time.nix
+  ];
+}
diff --git a/class/server/fs.nix b/class/server/fs.nix
new file mode 100644
index 0000000..0b5e429
--- /dev/null
+++ b/class/server/fs.nix
@@ -0,0 +1,12 @@
+{
+  fileSystems = {
+    "/home" = {
+      device = "tmpfs";
+      fsType = "tmpfs";
+      options = ["size=4G" "mode=751"];
+    };
+    "/nix".device = "/dev/disk/by-label/main";
+    "/persist".device = "/dev/disk/by-label/main";
+    "/var/log".device = "/dev/disk/by-label/main";
+  };
+}
diff --git a/class/server/time.nix b/class/server/time.nix
new file mode 100644
index 0000000..47f2e72
--- /dev/null
+++ b/class/server/time.nix
@@ -0,0 +1,3 @@
+{
+  time.timeZone = "UTC";
+}
diff --git a/common/avahi.nix b/common/avahi.nix
new file mode 100644
index 0000000..2f549a2
--- /dev/null
+++ b/common/avahi.nix
@@ -0,0 +1,15 @@
+{
+  services.avahi = {
+    enable = true;
+    nssmdns4 = true;
+    nssmdns6 = true;
+    publish = {
+      enable = true;
+      addresses = true;
+      domain = true;
+      hinfo = true;
+      userServices = true;
+      workstation = true;
+    };
+  };
+}
diff --git a/common/bash.nix b/common/bash.nix
new file mode 100644
index 0000000..555b661
--- /dev/null
+++ b/common/bash.nix
@@ -0,0 +1,32 @@
+{
+  lib,
+  pkgs,
+  ...
+}: {
+  programs.direnv.enable = true;
+  programs.command-not-found.enable = false;
+
+  programs.bash = {
+    promptInit = ''
+      if [[ -v SSH_CLIENT && -v SSH_CONNECTION && -v SSH_TTY ]]; then
+        PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
+      else
+        PS1='\[\033[01;34m\]\w\[\033[00m\]\$ '
+      fi
+    '';
+    interactiveShellInit = ''
+      shopt -s histappend
+      HISTCONTROL='ignoredups:ignorespace'
+      HISTSIZE=1000
+      HISTFILESIZE=10000
+
+      shopt -s globstar
+      shopt -s nullglob
+      shopt -s extglob
+
+      shopt -s checkwinsize
+
+      eval "$(${lib.getExe pkgs.direnv} hook bash)"
+    '';
+  };
+}
diff --git a/common/boot.nix b/common/boot.nix
new file mode 100644
index 0000000..a77c2d5
--- /dev/null
+++ b/common/boot.nix
@@ -0,0 +1,15 @@
+{
+  boot = {
+    loader = {
+      systemd-boot = {
+        enable = true;
+        consoleMode = "max";
+      };
+
+      efi = {
+        canTouchEfiVariables = true;
+        efiSysMountPoint = "/boot";
+      };
+    };
+  };
+}
diff --git a/common/dbus.nix b/common/dbus.nix
new file mode 100644
index 0000000..7d270b2
--- /dev/null
+++ b/common/dbus.nix
@@ -0,0 +1,3 @@
+{
+  services.dbus.implementation = "broker";
+}
diff --git a/common/default.nix b/common/default.nix
new file mode 100644
index 0000000..36ffa85
--- /dev/null
+++ b/common/default.nix
@@ -0,0 +1,18 @@
+{
+  imports = [
+    ./avahi.nix
+    ./bash.nix
+    ./boot.nix
+    ./dbus.nix
+    ./fs.nix
+    ./fwupd.nix
+    ./nix.nix
+    ./opengl.nix
+    ./openssh.nix
+    ./readline.nix
+    ./ssh.nix
+    ./sudo.nix
+    ./swap.nix
+    ./users.nix
+  ];
+}
diff --git a/common/fs.nix b/common/fs.nix
new file mode 100644
index 0000000..82f96bc
--- /dev/null
+++ b/common/fs.nix
@@ -0,0 +1,38 @@
+{
+  fileSystems = {
+    "/" = {
+      device = "tmpfs";
+      fsType = "tmpfs";
+      options = ["size=4G" "mode=755"];
+    };
+    "/boot" = {
+      device = "/dev/disk/by-label/BOOT";
+      fsType = "vfat";
+    };
+    "/home".neededForBoot = true;
+    "/nix" = {
+      fsType = "btrfs";
+      options = ["subvol=nix" "compress=zstd" "noatime"];
+    };
+    "/persist" = {
+      fsType = "btrfs";
+      options = ["subvol=persist" "compress=zstd" "noatime"];
+      neededForBoot = true;
+    };
+    "/tmp" = {
+      device = "tmpfs";
+      fsType = "tmpfs";
+      options = ["size=8G" "mode=777"];
+    };
+    "/var/log" = {
+      fsType = "btrfs";
+      options = ["subvol=log" "compress=zstd" "noatime"];
+      neededForBoot = true;
+    };
+  };
+
+  environment.persistence."/persist" = {
+    directories = ["/var/lib" "/var/cache"];
+    files = ["/etc/machine-id"];
+  };
+}
diff --git a/common/fwupd.nix b/common/fwupd.nix
new file mode 100644
index 0000000..a62f709
--- /dev/null
+++ b/common/fwupd.nix
@@ -0,0 +1,3 @@
+{
+  services.fwupd.enable = true;
+}
diff --git a/common/nix.nix b/common/nix.nix
new file mode 100644
index 0000000..0dc6309
--- /dev/null
+++ b/common/nix.nix
@@ -0,0 +1,20 @@
+{
+  config,
+  inputs,
+  lib,
+  ...
+}: {
+  nix = {
+    registry = lib.mapAttrs (_: value: {flake = value;}) inputs;
+
+    nixPath = lib.mapAttrsToList (key: _: "${key}=flake:${key}") config.nix.registry;
+
+    settings = {
+      experimental-features = "nix-command flakes";
+      auto-optimise-store = true;
+    };
+  };
+
+  nixpkgs.config.allowUnfree = true;
+  hardware.enableAllFirmware = true;
+}
diff --git a/common/opengl.nix b/common/opengl.nix
new file mode 100644
index 0000000..cc22ba6
--- /dev/null
+++ b/common/opengl.nix
@@ -0,0 +1,10 @@
+{pkgs, ...}: {
+  hardware.opengl = {
+    enable = true;
+    driSupport = true;
+    extraPackages = with pkgs; [
+      pkgs.libvdpau-va-gl
+      pkgs.vaapiVdpau
+    ];
+  };
+}
diff --git a/common/openssh.nix b/common/openssh.nix
new file mode 100644
index 0000000..267880b
--- /dev/null
+++ b/common/openssh.nix
@@ -0,0 +1,23 @@
+{
+  environment.persistence."/persist".files = [
+    "/etc/ssh/ssh_host_ed25519_key"
+    "/etc/ssh/ssh_host_ed25519_key.pub"
+  ];
+
+  age.identityPaths = ["/persist/etc/ssh/ssh_host_ed25519_key"];
+
+  services.openssh = {
+    enable = true;
+    openFirewall = true;
+    hostKeys = [
+      {
+        path = "/etc/ssh/ssh_host_ed25519_key";
+        type = "ed25519";
+      }
+    ];
+    settings = {
+      PermitRootLogin = "no";
+      PasswordAuthentication = false;
+    };
+  };
+}
diff --git a/common/readline.nix b/common/readline.nix
new file mode 100644
index 0000000..9115947
--- /dev/null
+++ b/common/readline.nix
@@ -0,0 +1,21 @@
+{
+  environment.etc.inputrc.text = ''
+    set editing-mode vi
+
+    set completion-ignore-case on
+    set enable-bracketed-paste on
+    set show-all-if-ambiguous on
+    set show-mode-in-prompt on
+
+    set keymap vi-command
+    Control-l: clear-screen
+    Control-a: beginning-of-line
+    Tab: menu-complete
+    "\e[Z": complete
+    set keymap vi-insert
+    Control-l: clear-screen
+    Control-a: beginning-of-line
+    Tab: menu-complete
+    "\e[Z": complete
+  '';
+}
diff --git a/common/ssh.nix b/common/ssh.nix
new file mode 100644
index 0000000..71c2d04
--- /dev/null
+++ b/common/ssh.nix
@@ -0,0 +1,8 @@
+{lib, ...}: {
+  programs.ssh.startAgent = true;
+
+  environment.etc."ssh/ssh_config".text = lib.mkAfter ''
+    Compression yes
+    ServerAliveInterval 60
+  '';
+}
diff --git a/common/sudo.nix b/common/sudo.nix
new file mode 100644
index 0000000..4cac0ec
--- /dev/null
+++ b/common/sudo.nix
@@ -0,0 +1,8 @@
+{
+  security.sudo = {
+    enable = true;
+    execWheelOnly = true;
+    wheelNeedsPassword = true;
+    extraConfig = "Defaults lecture=\"never\"";
+  };
+}
diff --git a/common/swap.nix b/common/swap.nix
new file mode 100644
index 0000000..f8ddd9c
--- /dev/null
+++ b/common/swap.nix
@@ -0,0 +1,3 @@
+{
+  zramSwap.enable = true;
+}
diff --git a/common/users.nix b/common/users.nix
new file mode 100644
index 0000000..7ae4320
--- /dev/null
+++ b/common/users.nix
@@ -0,0 +1,21 @@
+{config, ...}: {
+  age.secrets.user-lukas.file = ../secrets/user-lukas.age;
+
+  users = {
+    mutableUsers = false;
+    users = {
+      root.hashedPassword = "!";
+      lukas = {
+        isNormalUser = true;
+        hashedPasswordFile = config.age.secrets.user-lukas.path;
+        openssh.authorizedKeys.keys = [
+          "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK4U9RzV/gVGBfrCOye7BlS11g5BS7SmuZ36n2ZIJyAX lukas@glacier"
+          "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAztZgcRBHqX8Wb2nAlP1qCKF205M3un/D1YnREcO7Dy lukas@flamingo"
+          "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMC6vIcPgOHiAnG1be8IQVePlrsxN/X9PEFJghS6EcOb lukas@scenery"
+        ];
+        extraGroups = ["wheel" "networkmanager" "gamemode" "syncthing"];
+        linger = true;
+      };
+    };
+  };
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..871252e
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,271 @@
+{
+  "nodes": {
+    "agenix": {
+      "inputs": {
+        "darwin": "darwin",
+        "home-manager": "home-manager",
+        "nixpkgs": "nixpkgs",
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1703433843,
+        "narHash": "sha256-nmtA4KqFboWxxoOAA6Y1okHbZh+HsXaMPFkYHsoDRDw=",
+        "owner": "ryantm",
+        "repo": "agenix",
+        "rev": "417caa847f9383e111d1397039c9d4337d024bf0",
+        "type": "github"
+      },
+      "original": {
+        "owner": "ryantm",
+        "repo": "agenix",
+        "type": "github"
+      }
+    },
+    "blobs": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1604995301,
+        "narHash": "sha256-wcLzgLec6SGJA8fx1OEN1yV/Py5b+U5iyYpksUY/yLw=",
+        "owner": "simple-nixos-mailserver",
+        "repo": "blobs",
+        "rev": "2cccdf1ca48316f2cfd1c9a0017e8de5a7156265",
+        "type": "gitlab"
+      },
+      "original": {
+        "owner": "simple-nixos-mailserver",
+        "repo": "blobs",
+        "type": "gitlab"
+      }
+    },
+    "darwin": {
+      "inputs": {
+        "nixpkgs": [
+          "agenix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1700795494,
+        "narHash": "sha256-gzGLZSiOhf155FW7262kdHo2YDeugp3VuIFb4/GGng0=",
+        "owner": "lnl7",
+        "repo": "nix-darwin",
+        "rev": "4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d",
+        "type": "github"
+      },
+      "original": {
+        "owner": "lnl7",
+        "ref": "master",
+        "repo": "nix-darwin",
+        "type": "github"
+      }
+    },
+    "flake-compat": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1668681692,
+        "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "009399224d5e398d03b22badca40a37ac85412a1",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "hardware": {
+      "locked": {
+        "lastModified": 1706834982,
+        "narHash": "sha256-3CfxA7gZ+DVv/N9Pvw61bV5Oe/mWfxYPyVQGqp9TMJA=",
+        "owner": "NixOS",
+        "repo": "nixos-hardware",
+        "rev": "83e571bb291161682b9c3ccd48318f115143a550",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "repo": "nixos-hardware",
+        "type": "github"
+      }
+    },
+    "home-manager": {
+      "inputs": {
+        "nixpkgs": [
+          "agenix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1703113217,
+        "narHash": "sha256-7ulcXOk63TIT2lVDSExj7XzFx09LpdSAPtvgtM7yQPE=",
+        "owner": "nix-community",
+        "repo": "home-manager",
+        "rev": "3bfaacf46133c037bb356193bd2f1765d9dc82c1",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "home-manager",
+        "type": "github"
+      }
+    },
+    "impermanence": {
+      "locked": {
+        "lastModified": 1706639736,
+        "narHash": "sha256-CaG4j9+UwBDfinxxvJMo6yOonSmSo0ZgnbD7aj2Put0=",
+        "owner": "nix-community",
+        "repo": "impermanence",
+        "rev": "cd13c2917eaa68e4c49fea0ff9cada45440d7045",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "impermanence",
+        "type": "github"
+      }
+    },
+    "mailserver": {
+      "inputs": {
+        "blobs": "blobs",
+        "flake-compat": "flake-compat",
+        "nixpkgs": "nixpkgs_2",
+        "nixpkgs-23_05": "nixpkgs-23_05",
+        "nixpkgs-23_11": "nixpkgs-23_11",
+        "utils": "utils"
+      },
+      "locked": {
+        "lastModified": 1706742486,
+        "narHash": "sha256-sv/MISTeD0rqeVivpZJpynboMWJp6i62OmrZX1rGl38=",
+        "owner": "simple-nixos-mailserver",
+        "repo": "nixos-mailserver",
+        "rev": "9e36323ae3dde787f761420465c3ae560f3dbf29",
+        "type": "gitlab"
+      },
+      "original": {
+        "owner": "simple-nixos-mailserver",
+        "repo": "nixos-mailserver",
+        "type": "gitlab"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1703013332,
+        "narHash": "sha256-+tFNwMvlXLbJZXiMHqYq77z/RfmpfpiI3yjL6o/Zo9M=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "54aac082a4d9bb5bbc5c4e899603abfb76a3f6d6",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs-23_05": {
+      "locked": {
+        "lastModified": 1704290814,
+        "narHash": "sha256-LWvKHp7kGxk/GEtlrGYV68qIvPHkU9iToomNFGagixU=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "70bdadeb94ffc8806c0570eb5c2695ad29f0e421",
+        "type": "github"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "ref": "nixos-23.05",
+        "type": "indirect"
+      }
+    },
+    "nixpkgs-23_11": {
+      "locked": {
+        "lastModified": 1706098335,
+        "narHash": "sha256-r3dWjT8P9/Ah5m5ul4WqIWD8muj5F+/gbCdjiNVBKmU=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "a77ab169a83a4175169d78684ddd2e54486ac651",
+        "type": "github"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "ref": "nixos-23.11",
+        "type": "indirect"
+      }
+    },
+    "nixpkgs_2": {
+      "locked": {
+        "lastModified": 1705856552,
+        "narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
+        "type": "github"
+      },
+      "original": {
+        "id": "nixpkgs",
+        "ref": "nixos-unstable",
+        "type": "indirect"
+      }
+    },
+    "nixpkgs_3": {
+      "locked": {
+        "lastModified": 1706732774,
+        "narHash": "sha256-hqJlyJk4MRpcItGYMF+3uHe8HvxNETWvlGtLuVpqLU0=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "b8b232ae7b8b144397fdb12d20f592e5e7c1a64d",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "agenix": "agenix",
+        "hardware": "hardware",
+        "impermanence": "impermanence",
+        "mailserver": "mailserver",
+        "nixpkgs": "nixpkgs_3"
+      }
+    },
+    "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": {
+      "locked": {
+        "lastModified": 1605370193,
+        "narHash": "sha256-YyMTf3URDL/otKdKgtoMChu4vfVL3vCMkRqpGifhUn0=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "5021eac20303a61fafe17224c087f5519baed54d",
+        "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..117efb4
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,63 @@
+{
+  description = "My NixOS configuration";
+
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+    hardware.url = "github:NixOS/nixos-hardware";
+    impermanence.url = "github:nix-community/impermanence";
+    agenix.url = "github:ryantm/agenix";
+    mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver";
+  };
+
+  outputs = {nixpkgs, ...} @ inputs: let
+    supportedSystems = ["x86_64-linux" "aarch64-linux"];
+
+    forEachSystem = f:
+      nixpkgs.lib.genAttrs supportedSystems (system: f (import nixpkgs {inherit system;}));
+
+    mkSystem = name: class:
+      inputs.nixpkgs.lib.nixosSystem {
+        specialArgs = {inherit inputs;};
+        modules = [
+          inputs.impermanence.nixosModules.impermanence
+          inputs.agenix.nixosModules.default
+          inputs.mailserver.nixosModule
+
+          ./common
+          (./class + "/${class}")
+          (./hosts + "/${name}")
+
+          ({lib, ...}: {networking.hostName = lib.mkDefault name;})
+        ];
+      };
+
+    hosts = {
+      glacier = "desktop";
+      flamingo = "desktop";
+      scenery = "desktop";
+      abacus = "server";
+      vessel = "server";
+    };
+  in {
+    nixosConfigurations = builtins.mapAttrs mkSystem hosts;
+
+    devShells = forEachSystem (pkgs: {
+      default = pkgs.mkShellNoCC {
+        packages = [
+          pkgs.nil
+          inputs.agenix.packages.${pkgs.system}.agenix
+          (pkgs.writeShellApplication {
+            name = "home";
+            runtimeInputs = [
+              pkgs.git
+              pkgs.flatpak
+            ];
+            text = builtins.readFile ./scripts/home.sh;
+          })
+        ];
+      };
+    });
+
+    formatter = forEachSystem (pkgs: pkgs.alejandra);
+  };
+}
diff --git a/home/bash/bashrc b/home/bash/bashrc
new file mode 100644
index 0000000..cc5b48e
--- /dev/null
+++ b/home/bash/bashrc
@@ -0,0 +1,42 @@
+source /etc/bashrc
+
+if [[ $- != *i* ]]; then
+  return
+fi
+
+alias cpr='cp --recursive'
+
+alias df='df --human-readable'
+
+alias du='du --human-readable'
+
+alias gia='git add'
+alias gic='git commit'
+alias gico='git checkout'
+alias gid='git diff'
+alias gidh='git diff HEAD'
+alias gi='git'
+alias gin='grep --ignore-case --line-number'
+alias gis='git status'
+
+alias g='grep'
+alias gn='grep --line-number'
+alias grep='grep --color'
+alias grin='grep --recursive --ignore-case --line-number'
+
+alias la='ls --all'
+alias lla='ls -l --all'
+alias ll='ls -l'
+alias l='ls'
+alias lsa='ls --all'
+alias lsla='ls -l --all'
+alias lsl='ls -l'
+alias ls='ls --color --classify'
+
+alias rmr='rm --recursive'
+alias rr='rm --recursive'
+
+alias s='sudo'
+
+alias ffmpeg='ffmpeg -hide_banner'
+alias ffprobe='ffprobe -hide_banner'
diff --git a/home/fish/config.fish b/home/fish/config.fish
new file mode 100644
index 0000000..e4572a6
--- /dev/null
+++ b/home/fish/config.fish
@@ -0,0 +1,147 @@
+if status is-interactive
+  stty -ixon
+  set fish_greeting
+
+  fish_vi_key_bindings
+
+  bind \ee edit_command_buffer
+
+  set fish_cursor_default     block      blink
+  set fish_cursor_insert      line       blink
+  set fish_cursor_replace_one underscore blink
+  set fish_cursor_visual      block
+
+  abbr --add --global l ls
+  abbr --add --global lsa ls -a
+  abbr --add --global la ls -a
+  abbr --add --global lsl ls -l
+  abbr --add --global ll ls -l
+  abbr --add --global lsla ls -la
+  abbr --add --global lla ls -la
+  abbr --add --global cp cp -n
+  abbr --add --global cpr cp -rn
+  abbr --add --global mv mv -n
+  abbr --add --global rm rm -i
+  abbr --add --global rmr rm -ri
+  abbr --add --global rr rm -ri
+  abbr --add --global v hx
+  abbr --add --global g git
+  abbr --add --global gc git commit
+  abbr --add --global gco git checkout
+  abbr --add --global gs git status
+  abbr --add --global gd git diff
+  abbr --add --global gdh git diff HEAD
+  abbr --add --global ga git add
+  abbr --add --global s sudo
+  abbr --add --global g grep
+  abbr --add --global gn grep -n
+  abbr --add --global gin grep -in
+  abbr --add --global grin grep -rin
+  abbr --add --global df df -h
+  abbr --add --global du du -h
+  abbr --add --global c cd
+  abbr --add --global cd. cd .
+  abbr --add --global cd.. cd ..
+
+  function ls; command ls --classify=auto --color=auto $argv; end
+  function ffmpeg; command ffmpeg -hide_banner $argv; end
+  function ffprobe; command ffprobe -hide_banner $argv; end
+  function ffplay; command ffplay -hide_banner $argv; end
+
+  function fish_prompt
+    if test $CMD_DURATION -gt 10000
+      echo -ne '\a'
+    end
+
+    set -l __last_command_exit_status $status
+
+    if not set -q -g __fish_arrow_functions_defined
+      set -g __fish_arrow_functions_defined
+      function _git_branch_name
+        set -l branch (git symbolic-ref --quiet HEAD 2>/dev/null)
+        if set -q branch[1]
+          echo (string replace -r '^refs/heads/' '' $branch)
+        else
+          echo (git rev-parse --short HEAD 2>/dev/null)
+        end
+      end
+
+      function _is_git_dirty
+        not command git diff-index --cached --quiet HEAD -- &>/dev/null
+        or not command git diff --no-ext-diff --quiet --exit-code &>/dev/null
+      end
+
+      function _is_git_repo
+        type -q git
+        or return 1
+        git rev-parse --git-dir >/dev/null 2>&1
+      end
+
+      function _hg_branch_name
+        echo (hg branch 2>/dev/null)
+      end
+
+      function _is_hg_dirty
+        set -l stat (hg status -mard 2>/dev/null)
+        test -n "$stat"
+      end
+
+      function _is_hg_repo
+        fish_print_hg_root >/dev/null
+      end
+
+      function _repo_branch_name
+        _$argv[1]_branch_name
+      end
+
+      function _is_repo_dirty
+        _is_$argv[1]_dirty
+      end
+
+      function _repo_type
+        if _is_hg_repo
+          echo hg
+          return 0
+        else if _is_git_repo
+          echo git
+          return 0
+        end
+        return 1
+      end
+    end
+
+    set -l cyan (set_color -o cyan)
+    set -l yellow (set_color -o yellow)
+    set -l red (set_color -o red)
+    set -l green (set_color -o green)
+    set -l blue (set_color -o blue)
+    set -l normal (set_color normal)
+
+    set -l prompt_color "$green"
+    if test $__last_command_exit_status != 0
+      set prompt_color "$red"
+    end
+
+    set -l prompt "$prompt_color\$"
+    if fish_is_root_user
+      set prompt "$prompt_color#"
+    end
+
+    set -l cwd $cyan(basename -- (prompt_pwd))
+
+    set -l repo_info
+    if set -l repo_type (_repo_type)
+      set -l repo_branch $red(_repo_branch_name $repo_type)
+      set repo_info "$blue $repo_type:($repo_branch$blue)"
+
+      if _is_repo_dirty $repo_type
+        set -l dirty "$yellow ✗"
+        set repo_info "$repo_info$dirty"
+      end
+    end
+
+    echo -n -s -- $cwd $repo_info ' ' $prompt ' '$normal
+  end
+
+  direnv hook fish | source
+end
diff --git a/home/helix/config.toml b/home/helix/config.toml
new file mode 100644
index 0000000..7b4e7bd
--- /dev/null
+++ b/home/helix/config.toml
@@ -0,0 +1,24 @@
+theme = "logarithmancy"
+[editor]
+auto-save = true
+bufferline = "multiple"
+cursorline = true
+line-number = "relative"
+rulers = [80]
+
+[editor.cursor-shape]
+insert = "bar"
+normal = "block"
+select = "underline"
+
+[editor.file-picker]
+hidden = false
+
+[editor.indent-guides]
+render = true
+
+[editor.lsp]
+display-messages = true
+
+[keys.normal]
+esc = ["collapse_selection", "keep_primary_selection"]
diff --git a/home/helix/themes/logarithmancy.toml b/home/helix/themes/logarithmancy.toml
new file mode 100644
index 0000000..26c3b97
--- /dev/null
+++ b/home/helix/themes/logarithmancy.toml
@@ -0,0 +1,5 @@
+inherits = "github_dark_high_contrast"
+"ui.background" = "none"
+
+["ui.virtual.indent-guide"]
+fg = "#2d3640"
diff --git a/home/readline/inputrc b/home/readline/inputrc
new file mode 100644
index 0000000..b7dae39
--- /dev/null
+++ b/home/readline/inputrc
@@ -0,0 +1,13 @@
+set editing-mode vi
+set completion-ignore-case on
+set enable-bracketed-paste on
+
+$if mode=vi
+set show-mode-in-prompt on
+set keymap vi-command
+Control-l: clear-screen
+Control-a: beginning-of-line
+set keymap vi-insert
+Control-l: clear-screen
+Control-a: beginning-of-line
+$endif
diff --git a/hosts/abacus/default.nix b/hosts/abacus/default.nix
new file mode 100644
index 0000000..6e6244c
--- /dev/null
+++ b/hosts/abacus/default.nix
@@ -0,0 +1,42 @@
+{modulesPath, ...}: {
+  imports = [
+    (modulesPath + "/profiles/qemu-guest.nix")
+
+    ./mailserver.nix
+    ./nextcloud.nix
+    ./nginx.nix
+    ./static.nix
+    ./vaultwarden.nix
+    # TODO: dendrite/conduit, gitea/forgejo
+  ];
+
+  nixpkgs.hostPlatform = "aarch64-linux";
+
+  boot.initrd.availableKernelModules = ["xhci_pci" "virtio_pci" "virtio_scsi" "usbhid" "sr_mod"];
+
+  system.stateVersion = "24.05";
+
+  powerManagement.cpuFreqGovernor = "performance";
+
+  networking = let
+    interface = "enp1s0";
+  in {
+    domain = "wrz.one";
+    interfaces.${interface}.ipv6.addresses = [
+      {
+        address = "2a01:4f9:c012:92b5::2";
+        prefixLength = 64;
+      }
+    ];
+    defaultGateway6 = {
+      address = "fe80::1";
+      inherit interface;
+    };
+    firewall.allowedTCPPorts = [80 443];
+  };
+
+  security.acme = {
+    defaults.email = "lukasatwrzdotone@gmail.com";
+    acceptTerms = true;
+  };
+}
diff --git a/hosts/abacus/mailserver.nix b/hosts/abacus/mailserver.nix
new file mode 100644
index 0000000..cb9fdcc
--- /dev/null
+++ b/hosts/abacus/mailserver.nix
@@ -0,0 +1,38 @@
+{config, ...}: let
+  inherit (config.networking) domain;
+  inherit (config.networking) fqdn;
+in {
+  age.secrets.mail-lukas.file = ../../secrets/mail-lukas.age;
+
+  environment.persistence."/persist".directories = [
+    config.mailserver.dkimKeyDirectory
+    config.mailserver.mailDirectory
+    config.mailserver.sieveDirectory
+  ];
+
+  mailserver = {
+    enable = true;
+    openFirewall = true;
+    inherit fqdn;
+    domains = [domain];
+
+    loginAccounts = {
+      "lukas@${domain}" = {
+        hashedPasswordFile = config.age.secrets.mail-lukas.path;
+        aliases = ["postmaster@${domain}"];
+      };
+    };
+
+    certificateScheme = "acme-nginx";
+  };
+
+  # FIXME: This is unnecessary when https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/issues/275 is closed
+  services.dovecot2.sieve.extensions = ["fileinto"];
+
+  services.nginx.virtualHosts."mta-sts.${domain}" = {
+    locations."= /.well-known/mta-sts.txt".return = ''200 "version: STSv1\nmode: enforce\nmx: ${fqdn}\nmax_age: 86400"'';
+    enableACME = true;
+    forceSSL = true;
+    quic = true;
+  };
+}
diff --git a/hosts/abacus/nextcloud.nix b/hosts/abacus/nextcloud.nix
new file mode 100644
index 0000000..40b43a6
--- /dev/null
+++ b/hosts/abacus/nextcloud.nix
@@ -0,0 +1,69 @@
+{
+  config,
+  pkgs,
+  ...
+}: let
+  hostName = "cloud.${config.networking.domain}";
+in {
+  age.secrets.nextcloud-lukas = {
+    file = ../../secrets/nextcloud-lukas.age;
+    owner = "nextcloud";
+    group = "nextcloud";
+  };
+
+  system.fsPackages = [pkgs.sshfs];
+  fileSystems."${config.services.nextcloud.home}/data/${config.services.nextcloud.config.adminuser}/files/remote" = {
+    device = "u385962@u385962.your-storagebox.de:/";
+    fsType = "sshfs";
+    options = [
+      "allow_other"
+      "IdentityFile=/persist/etc/ssh/ssh_host_ed25519_key"
+      "_netdev"
+      "reconnect"
+      "ServerAliveInterval=15"
+      "x-systemd.automount"
+    ];
+  };
+
+  services.nextcloud = {
+    enable = true;
+    package = pkgs.nextcloud28;
+
+    inherit hostName;
+    https = true;
+
+    configureRedis = true;
+
+    # TODO: news
+    extraApps = {
+      inherit
+        (config.services.nextcloud.package.packages.apps)
+        bookmarks
+        calendar
+        contacts
+        deck
+        forms
+        mail
+        maps
+        notes
+        phonetrack
+        tasks
+        ;
+    };
+    extraAppsEnable = true;
+
+    database.createLocally = true;
+    config = {
+      dbtype = "pgsql";
+
+      adminuser = "lukas";
+      adminpassFile = config.age.secrets.nextcloud-lukas.path;
+    };
+  };
+
+  services.nginx.virtualHosts.${hostName} = {
+    enableACME = true;
+    forceSSL = true;
+    quic = true;
+  };
+}
diff --git a/hosts/abacus/nginx.nix b/hosts/abacus/nginx.nix
new file mode 100644
index 0000000..f64f01f
--- /dev/null
+++ b/hosts/abacus/nginx.nix
@@ -0,0 +1,20 @@
+{
+  config,
+  pkgs,
+  ...
+}: {
+  environment.persistence."/persist".directories = ["/var/www"];
+
+  services.nginx = {
+    enable = true;
+    package = pkgs.nginxQuic;
+
+    recommendedBrotliSettings = true;
+    recommendedGzipSettings = true;
+    recommendedOptimisation = true;
+    recommendedProxySettings = true;
+    recommendedTlsSettings = true;
+    recommendedZstdSettings = true;
+    commonHttpConfig = "access_log syslog:server=unix:/dev/log;";
+  };
+}
diff --git a/hosts/abacus/static.nix b/hosts/abacus/static.nix
new file mode 100644
index 0000000..6c2f7e0
--- /dev/null
+++ b/hosts/abacus/static.nix
@@ -0,0 +1,20 @@
+{config, ...}: {
+  services.nginx = {
+    virtualHosts = let
+      inherit (config.networking) domain;
+    in {
+      ${domain} = {
+        root = "/var/www/${domain}";
+        enableACME = true;
+        forceSSL = true;
+        quic = true;
+      };
+      "log.${domain}" = {
+        root = "/var/www/log.${domain}";
+        enableACME = true;
+        forceSSL = true;
+        quic = true;
+      };
+    };
+  };
+}
diff --git a/hosts/abacus/vaultwarden.nix b/hosts/abacus/vaultwarden.nix
new file mode 100644
index 0000000..e065203
--- /dev/null
+++ b/hosts/abacus/vaultwarden.nix
@@ -0,0 +1,20 @@
+{config, ...}: let
+  inherit (config.networking) domain;
+in {
+  services.vaultwarden = {
+    enable = true;
+
+    config = {
+      SIGNUPS_ALLOWED = false;
+      ROCKET_ADDRESS = "127.0.0.1";
+      ROCKET_PORT = 8000;
+    };
+  };
+
+  services.nginx.virtualHosts."vault.${domain}" = {
+    locations."/".proxyPass = "http://${config.services.vaultwarden.config.ROCKET_ADDRESS}:${builtins.toString config.services.vaultwarden.config.ROCKET_PORT}";
+    enableACME = true;
+    forceSSL = true;
+    quic = true;
+  };
+}
diff --git a/hosts/flamingo/default.nix b/hosts/flamingo/default.nix
new file mode 100644
index 0000000..cf3d00d
--- /dev/null
+++ b/hosts/flamingo/default.nix
@@ -0,0 +1,25 @@
+{
+  inputs,
+  modulesPath,
+  ...
+}: {
+  imports = [
+    (modulesPath + "/installer/scan/not-detected.nix")
+
+    inputs.hardware.nixosModules.lenovo-thinkpad-t480
+  ];
+
+  nixpkgs.hostPlatform = "x86_64-linux";
+
+  boot = {
+    initrd.availableKernelModules = ["xhci_pci" "nvme" "usb_storage" "sd_mod"];
+    kernelModules = ["kvm-intel"];
+  };
+
+  system.stateVersion = "24.05";
+
+  powerManagement.cpuFreqGovernor = "powersave";
+
+  console.keyMap = "de";
+  services.xserver.layout = "de";
+}
diff --git a/hosts/glacier/default.nix b/hosts/glacier/default.nix
new file mode 100644
index 0000000..c4dfc09
--- /dev/null
+++ b/hosts/glacier/default.nix
@@ -0,0 +1,31 @@
+{
+  pkgs,
+  inputs,
+  modulesPath,
+  ...
+}: {
+  imports = [
+    (modulesPath + "/installer/scan/not-detected.nix")
+
+    inputs.hardware.nixosModules.common-cpu-amd
+    inputs.hardware.nixosModules.common-gpu-amd
+    inputs.hardware.nixosModules.common-pc-ssd
+
+    ./printing.nix
+  ];
+
+  nixpkgs.hostPlatform = "x86_64-linux";
+
+  boot = {
+    initrd = {
+      availableKernelModules = ["nvme" "ahci" "xhci_pci" "usbhid" "usb_storage" "sd_mod"];
+      kernelModules = ["amdgpu"];
+    };
+    kernelModules = ["kvm-amd"];
+    binfmt.emulatedSystems = ["aarch64-linux"];
+  };
+
+  system.stateVersion = "24.05";
+
+  powerManagement.cpuFreqGovernor = "performance";
+}
diff --git a/hosts/glacier/printing.nix b/hosts/glacier/printing.nix
new file mode 100644
index 0000000..f1fb132
--- /dev/null
+++ b/hosts/glacier/printing.nix
@@ -0,0 +1,6 @@
+{pkgs, ...}: {
+  services.printing.drivers = with pkgs; [
+    epson-escpr
+    epson-escpr2
+  ];
+}
diff --git a/hosts/scenery/default.nix b/hosts/scenery/default.nix
new file mode 100644
index 0000000..4089340
--- /dev/null
+++ b/hosts/scenery/default.nix
@@ -0,0 +1,25 @@
+{
+  inputs,
+  modulesPath,
+  ...
+}: {
+  imports = [
+    (modulesPath + "/installer/scan/not-detected.nix")
+
+    inputs.hardware.nixosModules.lenovo-thinkpad-x260
+  ];
+
+  nixpkgs.hostPlatform = "x86_64-linux";
+
+  boot = {
+    initrd.availableKernelModules = ["xhci_pci" "nvme" "usb_storage" "sd_mod"];
+    kernelModules = ["kvm-intel"];
+  };
+
+  system.stateVersion = "24.05";
+
+  powerManagement.cpuFreqGovernor = "powersave";
+
+  console.keyMap = "de";
+  services.xserver.layout = "de";
+}
diff --git a/hosts/vessel/backup.nix b/hosts/vessel/backup.nix
new file mode 100644
index 0000000..9575618
--- /dev/null
+++ b/hosts/vessel/backup.nix
@@ -0,0 +1,31 @@
+{
+  pkgs,
+  lib,
+  ...
+}: {
+  systemd.timers.local-backup = {
+    description = "Local rsync Backup";
+    wantedBy = ["timers.target"];
+    timerConfig = {
+      OnCalendar = "*-*-* 00:00:00";
+      Persistent = true;
+      Unit = "local-backup.service";
+    };
+  };
+
+  systemd.services.local-backup = {
+    description = "Local rsync Backup";
+    serviceConfig = {
+      Type = "oneshot";
+      ExecStart = ''${lib.getExe pkgs.rsync} --verbose --verbose --archive --update --delete /srv/storage/ /srv/backup/'';
+      User = "root";
+      Group = "root";
+    };
+  };
+
+  fileSystems."/srv/backup" = {
+    device = "/dev/disk/by-label/backup";
+    fsType = "btrfs";
+    options = ["subvol=main" "compress=zstd" "noatime"];
+  };
+}
diff --git a/hosts/vessel/default.nix b/hosts/vessel/default.nix
new file mode 100644
index 0000000..e5abe3e
--- /dev/null
+++ b/hosts/vessel/default.nix
@@ -0,0 +1,32 @@
+{
+  inputs,
+  modulesPath,
+  ...
+}: {
+  imports = [
+    (modulesPath + "/installer/scan/not-detected.nix")
+
+    inputs.hardware.nixosModules.common-cpu-intel
+    inputs.hardware.nixosModules.common-gpu-intel
+    inputs.hardware.nixosModules.common-pc-ssd
+
+    ./backup.nix
+  ];
+
+  nixpkgs.hostPlatform = "x86_64-linux";
+
+  boot = {
+    initrd.availableKernelModules = ["ahci" "xhci_pci" "usbhid" "usb_storage" "sd_mod"];
+    kernelModules = ["kvm-intel"];
+  };
+
+  system.stateVersion = "24.05";
+
+  powerManagement.cpuFreqGovernor = "performance";
+
+  fileSystems."/srv/storage" = {
+    device = "/dev/disk/by-label/storage";
+    fsType = "btrfs";
+    options = ["subvol=main" "compress=zstd" "noatime"];
+  };
+}
diff --git a/part b/part
new file mode 100755
index 0000000..17e9ddf
--- /dev/null
+++ b/part
@@ -0,0 +1,132 @@
+#!/usr/bin/env nix
+#! nix shell nixpkgs#bash nixpkgs#coreutils nixpkgs#findutils nixpkgs#util-linux nixpkgs#jq nixpkgs#btrfs-progs nixpkgs#dosfstools --command bash
+
+# shellcheck shell=bash
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+opts=$(getopt --options r:m:b:l:c: --longoptions=root:,mapping:,boot-label:,main-label:,cryptmain-label: --name "$0" -- "$@")
+
+eval set -- "$opts"
+
+root=/mnt
+mapping=main
+bootlbl=BOOT
+mainlbl=main
+cryptmainlbl=cryptmain
+while true; do
+  case "$1" in
+  -r | --root)
+    root=$2
+    shift 2
+    ;;
+  -m | --mapping)
+    mapping=$2
+    shift 2
+    ;;
+  -b | --boot-label)
+    bootlbl=${2^^}
+    shift 2
+    ;;
+  -l | --main-label)
+    mainlbl=$2
+    shift 2
+    ;;
+  -c | --cryptmain-label)
+    cryptmainlbl=$2
+    shift 2
+    ;;
+  --)
+    shift
+    break
+    ;;
+  esac
+done
+
+if [[ $# != 1 ]]; then
+  printf '%s\n' "$0: an argument specifying the block device is required" 1>&2
+  exit 1
+fi
+
+blkdev=$1
+
+sfdisk --label gpt --quiet -- "$blkdev" <<EOF
+,512M,C12A7328-F81F-11D2-BA4B-00A0C93EC93B;
+,,0FC63DAF-8483-4772-8E79-3D69D8477DE4;
+EOF
+
+parts=()
+json=$(sfdisk --json -- "$blkdev")
+while IFS= read -r k; do
+  parts+=("$(jq --argjson k "$k" --raw-output '.partitiontable.partitions[$k].node' <<<"$json")")
+done < <(jq '.partitiontable.partitions | keys[]' <<<"$json")
+
+bootfs="${parts[0]}"
+mainblkdev="${parts[1]}"
+
+mkfs.vfat -F 32 -n "$bootlbl" -- "$bootfs" >/dev/null
+
+while true; do
+  read -r -p 'Do you want your main partition to be encrypted [y/N]? ' luks
+  case "$luks" in
+  [Yy]*)
+    while true; do
+      read -r -s -p 'Enter password: ' password
+      printf '\n'
+      read -r -s -p 'Re-enter password: ' repassword
+      printf '\n'
+      if [[ "$password" == "$repassword" ]]; then
+        break
+      fi
+    done
+
+    cryptsetup luksFormat --batch-mode --label "$cryptmainlbl" "$mainblkdev" <<<"$password"
+    cryptsetup open "$mainblkdev" "$mapping" <<<"$password"
+
+    mainfs=/dev/mapper/$mapping
+    break
+    ;;
+  '' | [Nn]*)
+    mainfs=$mainblkdev
+    break
+    ;;
+  *) printf '%s\n' 'Please answer with yes or no' 1>&2 ;;
+  esac
+done
+
+mkfs.btrfs --force --quiet --label "$mainlbl" -- "$mainfs"
+mkdir --parents -- "$root"
+mount -- "$mainfs" "$root"
+
+declare -A vols
+while true; do
+  read -r -p 'Add a subvolume: ' vol
+  if [[ "$vol" == '' ]]; then
+    break
+  fi
+
+  read -r -p 'Add a subvolume path: ' path
+  if [[ "$path" == '' ]]; then
+    path="$vol"
+  fi
+
+  vols["$vol"]="$path"
+done
+
+for vol in "${!vols[@]}"; do
+  btrfs --quiet subvolume create -- "$root/$vol"
+done
+
+umount -- "$root"
+
+mount -t tmpfs -o size=2G,mode=755 tmpfs -- "$root"
+
+for vol in "${!vols[@]}"; do
+  mkdir --parents -- "$root/${vols["$vol"]}"
+  mount --options "subvol=$vol,compress=zstd,noatime" -- "$mainfs" "$root/${vols["$vol"]}"
+done
+
+mkdir -- "$root/boot"
+mount -- "$bootfs" "$root/boot"
diff --git a/scripts/home.sh b/scripts/home.sh
new file mode 100644
index 0000000..7c03702
--- /dev/null
+++ b/scripts/home.sh
@@ -0,0 +1,211 @@
+shopt -s nullglob
+
+opts=$(getopt --options n:e: --longoptions=name:,email: --name "$0" -- "$@")
+
+eval set -- "$opts"
+
+name=
+email=
+while true; do
+  case "$1" in
+  -n | --name)
+    name=$2
+    shift 2
+    ;;
+  -e | --email)
+    email=$2
+    shift 2
+    ;;
+  --)
+    shift
+    break
+    ;;
+  esac
+done
+
+choices=("$@")
+shift "$#"
+
+function chose() {
+  if [[ ''${#choices[@]} == 0 ]]; then
+    return 0
+  fi
+  local arg
+  for arg in "$@"; do
+    local choice
+    for choice in "''${choices[@]}"; do
+      if [[ "$arg" == "$choice" ]]; then
+        return 0
+      fi
+    done
+  done
+  return 1
+}
+
+if chose git; then
+  gitconfig=''${XDG_CONFIG_HOME:-$HOME/.config}/git/config
+
+  if [[ -n $name ]]; then
+    GIT_CONFIG_GLOBAL=$gitconfig git config --global -- user.name "$name"
+  fi
+  if [[ -n $email ]]; then
+    GIT_CONFIG_GLOBAL=$gitconfig git config --global -- user.email "$email"
+  fi
+
+  gitignore=$(GIT_CONFIG_GLOBAL=$gitconfig git config --global --get core.excludesFile 2>/dev/null || printf '%s\n' "''${XDG_CONFIG_HOME:-$HOME/.config}/git/ignore")
+  mkdir --parents -- "$(dirname -- "$gitignore")"
+  cat <<EOF >"$gitignore"
+.idea/
+.vscode/
+.iml
+*.sublime-workspace
+
+node_modules/
+vendor/
+
+log/
+*.log
+
+__pycache__/
+zig-cache/
+
+*.com
+*.class
+*.dll
+*.exe
+*.o
+*.so
+*.pyc
+*.pyo
+
+*.7z
+*.dmg
+*.gz
+*.iso
+*.jar
+*.rar
+*.tar
+*.zip
+*.msi
+
+*.sqlite
+*.sqlite3
+*.db
+*.db3
+*.s3db
+*.sl3
+*.rdb
+
+*.bak
+*.swp
+*.swo
+*~
+*#
+
+zig-out/
+
+.direnv/
+EOF
+  GIT_CONFIG_GLOBAL=$gitconfig git config --global -- core.excludesFile "$gitignore"
+fi
+
+if chose mpv; then
+  if flatpak info io.mpv.Mpv >/dev/null 2>&1; then
+mpvconf=$HOME/.var/app/io.mpv.Mpv/config/mpv/mpv.conf
+mkdir --parents -- "$(dirname -- "$mpvconf")"
+cat <<EOF >"$mpvconf"
+force-window=immediate
+keep-open=yes
+save-position-on-quit=yes
+
+screenshot-template="%f_%wH%wM%wS.%wT"
+
+scale=ewa_lanczossharp
+cscale=ewa_lanczossharp
+tscale=oversample
+
+interpolation=yes
+video-sync=display-resample
+vo=gpu
+profile=gpu-hq
+EOF
+  fi
+fi
+
+if chose firefox; then
+  if flatpak info org.mozilla.firefox >/dev/null 2>&1; then
+    ffparent=$HOME/.var/app/org.mozilla.firefox/.mozilla/firefox
+    for profile in "$ffparent"/*.default "$ffparent"/*.default-release; do
+      userjs=$profile/user.js
+      cat <<EOF >"$userjs"
+// Forms
+user_pref('signon.prefillForms', false);
+user_pref('signon.rememberSignons', false);
+user_pref('signon.autofillForms', false);
+user_pref('signon.formlessCapture.enabled', false);
+user_pref('browser.formfill.enable', false);
+
+// Pocket
+user_pref('extensions.pocket.enabled', false);
+
+// Sponsorships
+user_pref('browser.newtabpage.activity-stream.showSponsored', false);
+user_pref('browser.newtabpage.activity-stream.showSponsoredTopSites', false);
+user_pref('browser.newtabpage.activity-stream.feeds.section.topstories', false);
+user_pref('browser.newtabpage.activity-stream.feeds.topsites', false);
+user_pref('browser.newtabpage.activity-stream.section.highlights.includeBookmarks', false);
+user_pref('browser.newtabpage.activity-stream.section.highlights.includeDownloads', false);
+user_pref('browser.newtabpage.activity-stream.section.highlights.includeVisited', false);
+
+// VA-API (https://bugzilla.mozilla.org/show_bug.cgi?id=1610199)
+user_pref('media.ffmpeg.vaapi.enabled', true);
+
+// Telemetry
+user_pref('toolkit.telemetry.unified', false);
+user_pref('toolkit.telemetry.enabled', false);
+user_pref('toolkit.telemetry.server', 'data:,');
+user_pref('toolkit.telemetry.archive.enabled', false);
+user_pref('toolkit.telemetry.newProfilePing.enabled', false);
+user_pref('toolkit.telemetry.shutdownPingSender.enabled', false);
+user_pref('toolkit.telemetry.updatePing.enabled', false);
+user_pref('toolkit.telemetry.bhrPing.enabled', false);
+user_pref('toolkit.telemetry.firstShutdownPing.enabled', false);
+user_pref('toolkit.telemetry.coverage.opt-out', true);
+user_pref('toolkit.coverage.opt-out', true);
+user_pref('toolkit.coverage.endpoint.base', ''');
+user_pref('browser.ping-centre.telemetry', false);
+user_pref('app.shield.optoutstudies.enabled', false);
+user_pref('app.normandy.enabled', false);
+user_pref('app.normandy.api_url', ''');
+user_pref('breakpad.reportURL', ''');
+user_pref('browser.tabs.crashReporting.sendReport', false);
+user_pref('browser.crashReports.unsubmittedCheck.autoSubmit2', false);
+
+// Referer
+user_pref("network.http.referer.XOriginPolicy", 1);
+user_pref("network.http.referer.XOriginTrimmingPolicy", 0);
+EOF
+    done
+  fi
+fi
+
+if chose bash; then
+  bashrc=$HOME/.bashrc
+  ln --force --symbolic -- "$PWD/home/bash/bashrc" "$bashrc"
+fi
+
+if chose fish; then
+  fishconfig=${XDG_CONFIG_HOME:-$HOME/.config}/fish/config.fish
+  mkdir --parents -- "$(dirname -- "$fishconfig")"
+  ln --force --symbolic -- "$PWD/home/fish/config.fish" "$fishconfig"
+fi
+
+if chose helix; then
+  helixdir=${XDG_CONFIG_HOME:-$HOME/.config}/helix
+  rm --recursive --force -- "$helixdir"
+  ln --force --symbolic -- "$PWD/home/helix" "$helixdir"
+fi
+
+if chose syncthing; then
+  systemctl --user enable syncthing.service
+fi
diff --git a/secrets/mail-lukas.age b/secrets/mail-lukas.age
new file mode 100644
index 0000000..ba2bbb8
--- /dev/null
+++ b/secrets/mail-lukas.age
@@ -0,0 +1,11 @@
+age-encryption.org/v1
+-> ssh-ed25519 SFHVrw 3JZ4vApGhqF9iRQvfhkg8gIonZLGrBp9i9E1RZM7zn8
+3v08N6zWIuEIs+bt2GeWF60it9sDE4E2+hgoTbayv4k
+-> ssh-ed25519 S+dwQQ NfiaomfNXA5cJKzdPWJmJlHK4r2ZN24E2tymgROlogM
+29EKJivtkdnWOtTee56peTOgEjBM4gXVSlzUekBUKZU
+-> ssh-ed25519 5IO6QQ DifPg5bQ5C0h2URSfei3NV+sfBkeNs6tz/OSJzACcDw
+yV4UkgUsUUdZOpPoLgmJy9sJIrHIN/5esobFFJfsMC8
+-> ssh-ed25519 ffmsLw 1/Ur807TPTjuapdynnicK8k2ACiMRDZ4CQpgAyiAql0
+9/4FKZqBnk2Q/VY6j/UOCuwUpbwmOMrhNh7zIdRTvqk
+--- PXMswgq0lbERBdFOFPnc48j3r2t9aR3+SPenu0karWg
+���~指2�0������(���(C�����_W#���W����N�Is�Rp�7��4Jԝ���	��>�c�p"C8�+7:����CƓ�J�j�
\ No newline at end of file
diff --git a/secrets/nextcloud-lukas.age b/secrets/nextcloud-lukas.age
new file mode 100644
index 0000000..97f3358
--- /dev/null
+++ b/secrets/nextcloud-lukas.age
@@ -0,0 +1,12 @@
+age-encryption.org/v1
+-> ssh-ed25519 SFHVrw F1EZXe0gnSNWIhPqxkSPLUpU7yROj8mSClFjFjpvdV8
+4McyaDtxvEOI9CBLNMEimnFTtXGoUcVzfQ3zfmMl3o8
+-> ssh-ed25519 S+dwQQ WT+jOjytoIKg2cPlD1bchFYaKxTJ63nixignaTNOqBM
+kf5FYspdW859XaZL+mbnkchoUg4mFONuV8axas7RuLI
+-> ssh-ed25519 5IO6QQ b8cU+T+50PZ24o2YflQ9EEojxHDdnB9hlPdcggruhHM
+qPjpL4q6+0osKkseBlY0ACSZbnhHoPo7RMP31t7l/T4
+-> ssh-ed25519 ffmsLw 3Y8iqWTYOJUCNexfOkd3QfG4P5onmanDbh7gdUPYwzE
+smKtEI17pzGvXkiJT9jC4hoECCHm1sEd7rEu92BUBSY
+--- BEki7iC6CxE/6NEdkkjAVkBKgO5nuxqLxRu4JiGBcaY
+��\z�sG�������z������3�VR�Ӕ�
+� ��&�Xq0	;TK
\ No newline at end of file
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
new file mode 100644
index 0000000..176400a
--- /dev/null
+++ b/secrets/secrets.nix
@@ -0,0 +1,19 @@
+let
+  users = {
+    "lukas@flamingo" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAztZgcRBHqX8Wb2nAlP1qCKF205M3un/D1YnREcO7Dy";
+    "lukas@glacier" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK4U9RzV/gVGBfrCOye7BlS11g5BS7SmuZ36n2ZIJyAX";
+    "lukas@scenery" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMC6vIcPgOHiAnG1be8IQVePlrsxN/X9PEFJghS6EcOb";
+  };
+
+  hosts = {
+    glacier = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHrKpoDV/ImivtTZVbSsQ59IbGYVvSsKls4av2Zc9Nk8";
+    abacus = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHoUgClpkOlBEffQOb9KkVn970RwnIhU0OiVr7P2WVzg";
+    scenery = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEHDS4LGl73WhC7NSzFe0ghZ0EwLjuP/43GGS65pPpu0";
+    vessel = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKkYcOb1JPNLTJtob1TcuC08cH9P2APAhLR26RYd573d";
+    flamingo = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIInV+UpCZhoTwgkgnCzCPEu3TD5b5mu6tagRslljrFJ/";
+  };
+in {
+  "user-lukas.age".publicKeys = (builtins.attrValues users) ++ (builtins.attrValues hosts);
+  "mail-lukas.age".publicKeys = (builtins.attrValues users) ++ [hosts.abacus];
+  "nextcloud-lukas.age".publicKeys = (builtins.attrValues users) ++ [hosts.abacus];
+}
diff --git a/secrets/user-lukas.age b/secrets/user-lukas.age
new file mode 100644
index 0000000..f81036e
--- /dev/null
+++ b/secrets/user-lukas.age
@@ -0,0 +1,19 @@
+age-encryption.org/v1
+-> ssh-ed25519 SFHVrw /QFzbfKzJnpIMXadEiDJyQJneVbQWwaoQlQ8B85mFiQ
+0pH7idWoz/hQFa9lsdJoc3vN36znranVkOtiGaQpCxs
+-> ssh-ed25519 S+dwQQ /4ZF8giAG2BGML1Fy73ucSb4jZK0EDgAmgj05ymbJzA
+lVi9ad3aPL4G/GFS6eAcxdJ9jDHWco0m+UHTOfkfNbQ
+-> ssh-ed25519 5IO6QQ mPE6sTVXzyVe6UdKhhmFifaETPcgTcNtn2Ybwf+nQyY
+Zh2PzFw2zP/MiVpqRLANXe9jGj4GdtnyOxBsDemgoM4
+-> ssh-ed25519 ffmsLw rj8FVCxSa49C30ZNW+gKlHXj5fOiTfHuecfumr0TTWo
+JqzlRhRXMgdzmn8mic7CFzLfMHPHbH0q7Vo1dB4byaY
+-> ssh-ed25519 d2fKsw +jbHllavLuC5zykfwzCe3r7c+4mEn3D6FmFdgAKWCz0
+lkwdDPkFxlXlwwZ6cvJ+AxgtGqMvNtO9/PdNPjhYvRM
+-> ssh-ed25519 US6ATA PKNAfoRfY6MfnRLSOUAhX1EDpXNbPC8EVTtNZ1KoIEA
+6rYHtwcdt0qXJr9S8UZ/q3xmTo416sY2unOlnN+/oa0
+-> ssh-ed25519 2ktApw uDF57fuv7fq+LlQm93McI8xsBlZPDimeI7uXajXC9iM
+gr7aVLkhgZmyv0Q7KlHEJt370NeWXH22A0Avns7mN6U
+-> ssh-ed25519 Sm0lOA t0uGXeSo6JhBQh5FsH6Z3ZRR/eEm+MVwSyS8TVDj+kg
+2aSkF1Hk8NGdNh2RNlBByGgBasKvLPhhdDQRmHe/fUc
+--- x+N3g8ekH9yUa3vXP/2u4PtCeVKMEJLlEaLf2Nb1vHo
+-3�O�Ig�)�M7c�y�9(��i,Ezr�,�aK�԰zb��U�3�t]"�{��*t!Ӈ�(�3���`�y�lU;�[�8f�>E�ɛmU�2{+�Tk�^�W����N,]d��֝��Q����:��	Ш�_���u�
\ No newline at end of file