about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@google.com>2019-12-14T11·45+0000
committerVincent Ambo <tazjin@google.com>2019-12-14T11·45+0000
commit74b5054a43132580fba7e834efc4a39e013a5957 (patch)
tree655e372096e6a9a09486a16d142fbbd8e86a4882
parent3071ffdc8afa83c9ddf04537943087ca71b8d097 (diff)
parent3f199f96506d2559dd51d8fff629790e057c9149 (diff)
merge(nixos): Merge (unrelated) NixOS config history into infra/ r/132
-rw-r--r--infra/nixos/.gitignore3
-rw-r--r--infra/nixos/README.md23
-rw-r--r--infra/nixos/adho-configuration.nix200
-rw-r--r--infra/nixos/configuration.nix102
-rw-r--r--infra/nixos/desktop.nix82
-rw-r--r--infra/nixos/dotfiles.nix27
-rw-r--r--infra/nixos/dotfiles/alacritty.yml203
-rw-r--r--infra/nixos/dotfiles/config.fish40
-rw-r--r--infra/nixos/dotfiles/msmtprc24
-rw-r--r--infra/nixos/dotfiles/notmuch-config21
-rw-r--r--infra/nixos/dotfiles/offlineimaprc61
-rw-r--r--infra/nixos/dotfiles/tmux.conf14
-rw-r--r--infra/nixos/emacs.nix138
-rw-r--r--infra/nixos/home.nix75
-rw-r--r--infra/nixos/mail.nix83
-rw-r--r--infra/nixos/packages.nix132
-rw-r--r--infra/nixos/pkgs/pulseaudio-ctl.nix26
-rw-r--r--infra/nixos/pkgs/wallpapers.nix22
-rw-r--r--infra/nixos/stallo-configuration.nix39
19 files changed, 1315 insertions, 0 deletions
diff --git a/infra/nixos/.gitignore b/infra/nixos/.gitignore
new file mode 100644
index 0000000000..773fa16670
--- /dev/null
+++ b/infra/nixos/.gitignore
@@ -0,0 +1,3 @@
+hardware-configuration.nix
+local-configuration.nix
+result
diff --git a/infra/nixos/README.md b/infra/nixos/README.md
new file mode 100644
index 0000000000..4f2e870f90
--- /dev/null
+++ b/infra/nixos/README.md
@@ -0,0 +1,23 @@
+NixOS configuration
+===================
+
+My NixOS configuration! It configures most of the packages I require
+on my systems, sets up Emacs the way I need and does a bunch of other
+interesting things.
+
+In contrast with earlier versions of this configuration, the Nix
+channel versions are now pinned in Nix (see the beginning of
+[packages.nix][]).
+
+Machine-local configuration is kept in files with the naming scheme
+`$hostname-configuration.nix` and **must** be symlinked to
+`local-configuration.nix` before the first configuration run.
+
+I'm publishing this repository (and my [emacs configuration][]) as a
+convenience for myself, but also as a resource that people looking for
+example Nix or Emacs configurations can browse through.
+
+Feel free to ping me with any questions you might have.
+
+[packages.nix]: packages.nix
+[emacs configuration]: https://github.com/tazjin/emacs.d
diff --git a/infra/nixos/adho-configuration.nix b/infra/nixos/adho-configuration.nix
new file mode 100644
index 0000000000..11c5a97df5
--- /dev/null
+++ b/infra/nixos/adho-configuration.nix
@@ -0,0 +1,200 @@
+# Local configuration for 'adho' (Thinkpad T470s)
+{ config, pkgs, ...}:
+
+{
+  boot.initrd.luks.devices.adho.device = "/dev/disk/by-uuid/722006b0-9654-4ea1-8703-e0cf9ac1905e";
+  boot.kernelModules = [ "kvm-intel" ];
+
+  services.xserver.libinput.enable = true;
+  services.xserver.videoDrivers = [ "intel" ];
+  programs.light.enable = true;
+
+  # Office printer configuration
+  services.printing.enable  = true;
+  services.printing.drivers = [ pkgs.hplip ];
+  services.avahi.enable     = true;
+  services.avahi.nssmdns    = true;
+
+  # Enable VirtualBox to update Beatstep Pro firmware:
+  virtualisation.virtualbox.host.enable = true;
+  virtualisation.virtualbox.host.enableExtensionPack = true;
+
+  # Enable LXC/LXD for Nixini work
+  virtualisation.lxd.enable = true;
+
+  # Give me more entropy:
+  services.haveged.enable = true;
+
+  # Disable sandbox to let work-builds function:
+  nix.useSandbox = false;
+
+  # Yubikey related:
+  services.pcscd.enable = true;
+  environment.systemPackages = with pkgs; [
+    cfssl
+    libp11
+    opensc
+    yubico-piv-tool
+  ];
+
+  networking = {
+    hostName = "adho";
+    wireless.enable = true;
+    wireless.userControlled.enable = true;
+
+    wireless.networks = {
+      # Welcome to roast club!
+      "How do I computer?" = {
+        psk = "washyourface";
+      };
+
+      # On the go!
+      "Rumpetroll" = {
+        psk = "fisk1234";
+        # If this network exists, chances are that I want it:
+        priority = 10;
+      };
+
+      # Public places in Oslo:
+      "Abelone" = {
+        psk = "speakeasy";
+      };
+
+      "Wurst" = {
+        psk = "wurst2015";
+      };
+
+      "postkontoret" = {
+        psk = "postkontoret";
+      };
+
+      # Eugene's apartment:
+      "GET_5G_4FD250" = {
+        psk = "62636342";
+      };
+
+      # FSCONS 2017
+      "uioguest" = {};
+
+      # Hackeriet!
+      "hackeriet.no" = {
+        psk = "hackeriet.no";
+      };
+
+      # Cafe Sara
+      "Sara Nett" = {
+        psk = "sarabar1989";
+      };
+
+      # The Dubliner
+      "DubGjest" = {
+        # of course
+        psk = "Guinness";
+      };
+
+      "MAGNAT Guest" = {
+        psk = "elmolino021";
+      };
+
+      "BrewDog" = {
+        psk = "welovebeer";
+      };
+
+      # Dima's
+      "What's a Bad Idea?" = {
+        psk = "DQDxzrzIvy0YtDwH";
+      };
+
+      # Loke's
+      "VMC28F76E" = {
+        psk = "d2ftQnr6xppw";
+      };
+
+      "SafetyWiFi - Teknologihuset" = {
+        psk = "tech4ever";
+      };
+
+      "Selvaag Pluss" = {
+        psk = "detlilleekstra";
+      };
+
+      "Langler" = {
+        psk = "Oslo2018";
+      };
+
+      # Pils & Programmering
+      "BEKKguest" = {
+        psk = "guest7890";
+      };
+
+      "Homan-Gjest" = {
+        psk = "haveaniceday";
+      };
+
+      # Røverstaden
+      "Roverstaden" = {
+        psk = "r0verstaden2018";
+      };
+
+      "The Brew Dock" = {
+        psk = "realbeer";
+      };
+
+      "econ-guest" = {
+        psk = "Finance2010";
+      };
+
+      "KabelBox-2FD0" = {
+        psk = "92433048597489095671";
+      };
+
+      "TheKasbah" = {
+        psk = "couscous";
+      };
+
+      # Kitty's misspelled network.
+      "How do I Computer?" = {
+        psk = "herpderpponies";
+      };
+
+      # NixCon 2018
+      "Coin Street Community Builders " = {
+        psk = "3vents2016";
+      };
+
+      "KH2 Gjest" = {
+        psk = "haenfindag";
+      };
+
+      # Forest & Brown
+      "Forest Guest" = {
+        psk = "437B99AC5B";
+      };
+
+      "Gatwick FREE Wi-Fi" = {};
+      "mycloud" = {};
+      "Norwegian Internet Access" = {};
+      "NSB_INTERAKTIV" = {};
+      "The Thief" = {};
+      "espressohouse" = {};
+      "Gotanet Open" = {};
+      "wifi.flytoget.no" = {};
+      "AIRPORT" = {};
+      "ilcaffelovesyou" = {};
+      "WIFIonICE" = {};
+      "Lorry Gjest" = {};
+      "Amundsengjest" = {};
+      "Beer Palace Gjest" = {};
+      "ibis" = {};
+      "GoogleGuest" = {};
+    };
+  };
+
+  hardware.bluetooth.enable = true;
+
+  # Configure POSIX queue limits (for work)
+  systemd.tmpfiles.rules = let mqueue = "/proc/sys/fs/mqueue"; in [
+    "w ${mqueue}/msgsize_max - - - - ${toString (64 * 1024)}"
+    "w ${mqueue}/msg_max     - - - - 50"
+  ];
+}
diff --git a/infra/nixos/configuration.nix b/infra/nixos/configuration.nix
new file mode 100644
index 0000000000..57f5c56dc7
--- /dev/null
+++ b/infra/nixos/configuration.nix
@@ -0,0 +1,102 @@
+# 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, lib, pkgs, ... }:
+
+{
+  imports =
+    [
+    ./desktop.nix
+    ./dotfiles.nix
+    ./hardware-configuration.nix
+    ./local-configuration.nix
+    ./mail.nix
+    ./packages.nix
+    ];
+
+  # Use the systemd-boot EFI boot loader.
+  boot.loader.systemd-boot.enable = true;
+  boot.loader.efi.canTouchEfiVariables = true;
+  boot.cleanTmpDir = true;
+  hardware.pulseaudio.enable = true;
+  time.timeZone = "Europe/Oslo";
+
+  # Configure audio setup for JACK + Overtone
+  boot.kernelModules = [ "snd-seq" "snd-rawmidi" ];
+  hardware.pulseaudio.package = pkgs.pulseaudioFull;
+
+  # Update Intel microcode on boot (both machines have Intel CPUs):
+  hardware.cpu.intel.updateMicrocode = true;
+
+  networking = {
+    # Don't use ISP's DNS servers:
+    nameservers = [
+      "1.1.1.1"
+      "1.0.0.1"
+    ];
+
+    # Open Chromecast-related ports & servedir
+    firewall.allowedTCPPorts = [ 3000 5556 5558 ];
+  };
+
+  # Generate an immutable /etc/resolv.conf from the nameserver settings
+  # above (otherwise DHCP overwrites it):
+  environment.etc."resolv.conf" = with lib; with pkgs; {
+    source = writeText "resolv.conf" ''
+      ${concatStringsSep "\n" (map (ns: "nameserver ${ns}") config.networking.nameservers)}
+      options edns0
+    '';
+  };
+
+  # Configure emacs:
+  # (actually, that's a lie, this only installs emacs!)
+  services.emacs = {
+    install = true;
+    defaultEditor = true;
+    package = import ./emacs.nix { inherit pkgs; };
+  };
+
+  services.openssh.enable = true;
+
+  # Enable GNOME keyring (required for Evolution)
+  services.gnome3.gnome-keyring.enable = true;
+
+  virtualisation = {
+    # Configure Docker (with socket activation):
+    # Side note: ... why is this in virtualisation? ...
+    docker.enable = true;
+    docker.autoPrune.enable = true;
+  };
+
+  # Configure various other applications:
+  programs = {
+    java.enable = true;
+    java.package = pkgs.openjdk;
+
+    fish.enable = true;
+    ssh.startAgent = true;
+  };
+
+  services.postgresql.enable = true;
+
+  # Configure user account
+  users.defaultUserShell = pkgs.fish;
+  users.extraUsers.vincent = {
+    extraGroups = [ "wheel" "docker" "vboxusers" "lxd" ];
+    isNormalUser = true;
+    uid = 1000;
+    shell = pkgs.fish;
+  };
+
+  security.sudo = {
+    enable = true;
+    extraConfig = "wheel ALL=(ALL:ALL) SETENV: ALL";
+  };
+
+  # 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 = "18.03"; # Did you read the comment?
+}
diff --git a/infra/nixos/desktop.nix b/infra/nixos/desktop.nix
new file mode 100644
index 0000000000..07a6274a56
--- /dev/null
+++ b/infra/nixos/desktop.nix
@@ -0,0 +1,82 @@
+# Configuration for the desktop environment
+
+{ config, lib, pkgs, ... }:
+
+let emacs = import ./emacs.nix { inherit pkgs; };
+screenLock = pkgs.writeShellScriptBin "screen-lock" ''
+  find ${pkgs.wallpapers} -name "*.png" | shuf -n1 | xargs i3lock -f -t -i
+'';
+in {
+  # Configure basic X-server stuff:
+  services.xserver = {
+    enable = true;
+    layout = "us,no";
+    xkbOptions = "caps:super, grp:shifts_toggle, parens:swap_brackets";
+    exportConfiguration = true;
+
+    # Give EXWM permission to control the session.
+    displayManager.sessionCommands = "${pkgs.xorg.xhost}/bin/xhost +SI:localuser:$USER";
+
+    # Use the pre 18.09 default display manager (slim)
+    displayManager.slim.enable = true;
+  };
+
+  # Add a shell script with random screen lock wallpaper selection
+  environment.systemPackages = [ screenLock ];
+
+  # Apparently when you have house guests they complain about your screen tearing!
+  services.compton.enable = true;
+  services.compton.backend = "xrender";
+
+  # Configure desktop environment:
+  services.xserver.windowManager.session = lib.singleton {
+    name = "exwm";
+    start = ''
+      ${emacs}/bin/emacs --eval '(progn (server-start) (exwm-enable))'
+    '';
+  };
+
+  # Configure Redshift for Oslo
+  services.redshift = {
+    enable = true;
+    latitude = "59.911491";
+    longitude = "10.757933";
+  };
+
+  # Configure fonts
+  fonts = {
+    fonts = with pkgs; [
+      corefonts
+      font-awesome-ttf
+      input-fonts
+      noto-fonts-cjk
+      noto-fonts-emoji
+      powerline-fonts
+      helvetica-neue-lt-std
+    ];
+  };
+
+  # Configure random setting of wallpapers
+  systemd.user.services.feh-wp = {
+    description = "Randomly set wallpaper via feh";
+    serviceConfig = {
+      Type             = "oneshot";
+      WorkingDirectory = "${pkgs.wallpapers}/share/wallpapers";
+
+      # Manually shuffle because feh's --randomize option can't be restricted to
+      # just certain file types.
+      ExecStart = "${pkgs.bash}/bin/bash -c '${pkgs.fd}/bin/fd -atf | shuf | head -n1 | ${pkgs.findutils}/bin/xargs ${pkgs.feh}/bin/feh --bg-fill'";
+    };
+  };
+
+  systemd.user.timers.feh-wp = {
+    description = "Set a random wallpaper every hour";
+    wantedBy    = [ "graphical-session.target" ];
+    partOf      = [ "graphical-session.target" ];
+
+    timerConfig = {
+      OnActiveSec     = "1second";
+      OnUnitActiveSec = "1hour";
+    };
+  };
+}
diff --git a/infra/nixos/dotfiles.nix b/infra/nixos/dotfiles.nix
new file mode 100644
index 0000000000..2e952207d4
--- /dev/null
+++ b/infra/nixos/dotfiles.nix
@@ -0,0 +1,27 @@
+# Bundle configuration files into a derivation.
+# I call this derivation dotfiles despite that not technically being true
+# anymore ...
+
+{ config, pkgs, ...}:
+
+let dotfiles = pkgs.stdenv.mkDerivation {
+  name = "tazjins-dotfiles";
+
+  srcs = [
+    ./dotfiles
+  ];
+
+  installPhase = ''
+    mkdir -p $out
+    cp ./* $out/
+  '';
+};
+in {
+  # /etc/ is a special place in NixOS!
+  # Symlinks that need to be created there must be specified explicitly.
+  environment.etc = {
+    "alacritty.yml".source    = "${dotfiles}/alacritty.yml";
+    "fish/config.fish".source = "${dotfiles}/config.fish";
+    "tmux.conf".source        = "${dotfiles}/tmux.conf";
+  };
+}
diff --git a/infra/nixos/dotfiles/alacritty.yml b/infra/nixos/dotfiles/alacritty.yml
new file mode 100644
index 0000000000..d229fbdde6
--- /dev/null
+++ b/infra/nixos/dotfiles/alacritty.yml
@@ -0,0 +1,203 @@
+# Configuration for Alacritty, the GPU enhanced terminal emulator
+
+# Any items in the `env` entry below will be added as
+# environment variables. Some entries may override variables
+# set by alacritty it self.
+env:
+  TERM: xterm-256color
+
+window:
+  # TODO
+  decorations: full
+
+scrolling:
+  history: 10000
+  multiplier: 3
+  faux_multiplier: 3
+  auto_scroll: true # TODO
+
+# Display tabs using this many cells (changes require restart)
+tabspaces: 4
+
+# When true, bold text is drawn using the bright variant of colors.
+draw_bold_text_with_bright_colors: true
+
+# Font configuration (changes require restart)
+font:
+  # The normal (roman) font face to use.
+  normal:
+    family: Input Mono
+  bold:
+    family: Input Mono
+  italic:
+    family: Input Mono
+
+  # Point size of the font
+  size: 12.0
+
+  # Scale the font size based on the monitor's DPI.
+  scale_with_dpi: false
+
+# Use custom cursor colors. If true, display the cursor in the cursor.foreground
+# and cursor.background colors, otherwise invert the colors of the cursor.
+custom_cursor_colors: false
+
+# Colors (Gruber Darker)
+colors:
+  # Default colors
+  primary:
+    background: '0x181818'
+    foreground: '0xe4e4ef'
+
+  # Colors the cursor will use if `custom_cursor_colors` is true
+  cursor:
+    text: '0x000000'
+    cursor: '0xf5f5f5'
+
+  # Normal colors
+  normal:
+    black:   '0x282828'
+    red:     '0xf43841'
+    green:   '0x73c936'
+    yellow:  '0xffdd33'
+    blue:    '0x96a6c8'
+    magenta: '0x9e95c7'
+    cyan:    '0x1fad83'
+    white:   '0xf5f5f5'
+
+  # Bright colors
+  bright:
+    black:   '0x484848'
+    red:     '0xff4f58'
+    green:   '0x73c936'
+    yellow:  '0xffdd33'
+    blue:    '0x5f627f'
+    magenta: '0x9e95c7'
+    cyan:    '0x1fad83'
+    white:   '0xffffff'
+
+# Background opacity
+# Key bindings
+#
+# Each binding is defined as an object with some properties. Most of the
+# properties are optional. All of the alphabetical keys should have a letter for
+# the `key` value such as `V`. Function keys are probably what you would expect
+# as well (F1, F2, ..). The number keys above the main keyboard are encoded as
+# `Key1`, `Key2`, etc. Keys on the number pad are encoded `Number1`, `Number2`,
+# etc.  These all match the glutin::VirtualKeyCode variants.
+#
+# Possible values for `mods`
+# `Command`, `Super` refer to the super/command/windows key
+# `Control` for the control key
+# `Shift` for the Shift key
+# `Alt` and `Option` refer to alt/option
+#
+# mods may be combined with a `|`. For example, requiring control and shift
+# looks like:
+#
+# mods: Control|Shift
+#
+# The parser is currently quite sensitive to whitespace and capitalization -
+# capitalization must match exactly, and piped items must not have whitespace
+# around them.
+#
+# Either an `action`, `chars`, or `command` field must be present.
+#   `action` must be one of `Paste`, `PasteSelection`, `Copy`, or `Quit`.
+#   `chars` writes the specified string every time that binding is activated.
+#     These should generally be escape sequences, but they can be configured to
+#     send arbitrary strings of bytes.
+#   `command` must be a map containing a `program` string, and `args` array of
+#     strings. For example:
+#     - { ... , command: { program: "alacritty", args: ["-e", "vttest"] } }
+#
+# Want to add a binding (e.g. "PageUp") but are unsure what the X sequence
+# (e.g. "\x1b[5~") is? Open another terminal (like xterm) without tmux,
+# then run `showkey -a` to get the sequence associated to a key combination.
+key_bindings:
+  - { key: V,        mods: Control|Shift,    action: Paste               }
+  - { key: C,        mods: Control|Shift,    action: Copy                }
+  - { key: Q,        mods: Command, action: Quit                         }
+  - { key: W,        mods: Command, action: Quit                         }
+  - { key: Insert,   mods: Shift,   action: PasteSelection               }
+  - { key: Home,                    chars: "\x1bOH",   mode: AppCursor   }
+  - { key: Home,                    chars: "\x1b[H",   mode: ~AppCursor  }
+  - { key: End,                     chars: "\x1bOF",   mode: AppCursor   }
+  - { key: End,                     chars: "\x1b[F",   mode: ~AppCursor  }
+  - { key: PageUp,   mods: Shift,   chars: "\x1b[5;2~"                   }
+  - { key: PageUp,   mods: Control, chars: "\x1b[5;5~"                   }
+  - { key: PageUp,                  chars: "\x1b[5~"                     }
+  - { key: PageDown, mods: Shift,   chars: "\x1b[6;2~"                   }
+  - { key: PageDown, mods: Control, chars: "\x1b[6;5~"                   }
+  - { key: PageDown,                chars: "\x1b[6~"                     }
+  - { key: Left,     mods: Shift,   chars: "\x1b[1;2D"                   }
+  - { key: Left,     mods: Control, chars: "\x1b[1;5D"                   }
+  - { key: Left,     mods: Alt,     chars: "\x1b[1;3D"                   }
+  - { key: Left,                    chars: "\x1b[D",   mode: ~AppCursor  }
+  - { key: Left,                    chars: "\x1bOD",   mode: AppCursor   }
+  - { key: Right,    mods: Shift,   chars: "\x1b[1;2C"                   }
+  - { key: Right,    mods: Control, chars: "\x1b[1;5C"                   }
+  - { key: Right,    mods: Alt,     chars: "\x1b[1;3C"                   }
+  - { key: Right,                   chars: "\x1b[C",   mode: ~AppCursor  }
+  - { key: Right,                   chars: "\x1bOC",   mode: AppCursor   }
+  - { key: Up,       mods: Shift,   chars: "\x1b[1;2A"                   }
+  - { key: Up,       mods: Control, chars: "\x1b[1;5A"                   }
+  - { key: Up,       mods: Alt,     chars: "\x1b[1;3A"                   }
+  - { key: Up,                      chars: "\x1b[A",   mode: ~AppCursor  }
+  - { key: Up,                      chars: "\x1bOA",   mode: AppCursor   }
+  - { key: Down,     mods: Shift,   chars: "\x1b[1;2B"                   }
+  - { key: Down,     mods: Control, chars: "\x1b[1;5B"                   }
+  - { key: Down,     mods: Alt,     chars: "\x1b[1;3B"                   }
+  - { key: Down,                    chars: "\x1b[B",   mode: ~AppCursor  }
+  - { key: Down,                    chars: "\x1bOB",   mode: AppCursor   }
+  - { key: Tab,      mods: Shift,   chars: "\x1b[Z"                      }
+  - { key: F1,                      chars: "\x1bOP"                      }
+  - { key: F2,                      chars: "\x1bOQ"                      }
+  - { key: F3,                      chars: "\x1bOR"                      }
+  - { key: F4,                      chars: "\x1bOS"                      }
+  - { key: F5,                      chars: "\x1b[15~"                    }
+  - { key: F6,                      chars: "\x1b[17~"                    }
+  - { key: F7,                      chars: "\x1b[18~"                    }
+  - { key: F8,                      chars: "\x1b[19~"                    }
+  - { key: F9,                      chars: "\x1b[20~"                    }
+  - { key: F10,                     chars: "\x1b[21~"                    }
+  - { key: F11,                     chars: "\x1b[23~"                    }
+  - { key: F12,                     chars: "\x1b[24~"                    }
+  - { key: Back,                    chars: "\x7f"                        }
+  - { key: Back,     mods: Alt,     chars: "\x1b\x7f"                    }
+  - { key: Insert,                  chars: "\x1b[2~"                     }
+  - { key: Delete,                  chars: "\x1b[3~"                     }
+
+# Mouse bindings
+#
+# Currently doesn't support modifiers. Both the `mouse` and `action` fields must
+# be specified.
+#
+# Values for `mouse`:
+# - Middle
+# - Left
+# - Right
+# - Numeric identifier such as `5`
+#
+# Values for `action`:
+# - Paste
+# - PasteSelection
+# - Copy (TODO)
+mouse_bindings:
+  - { mouse: Middle, action: PasteSelection }
+
+mouse:
+  double_click: { threshold: 300 }
+  triple_click: { threshold: 300 }
+
+selection:
+  semantic_escape_chars: ",│`|:\"' ()[]{}<>"
+background_opacity: 1.0
+
+hide_cursor_when_typing: false
+
+# Live config reload (changes require restart)
+live_config_reload: true
+
+# Disable visual bell
+visual_bell:
+  duration: 0
diff --git a/infra/nixos/dotfiles/config.fish b/infra/nixos/dotfiles/config.fish
new file mode 100644
index 0000000000..de2c99ae60
--- /dev/null
+++ b/infra/nixos/dotfiles/config.fish
@@ -0,0 +1,40 @@
+# Configure classic prompt
+set fish_color_user --bold blue
+set fish_color_cwd --bold white
+
+# Enable colour hints in VCS prompt:
+set __fish_git_prompt_showcolorhints yes
+set __fish_git_prompt_color_prefix purple
+set __fish_git_prompt_color_suffix purple
+
+# Fish configuration
+set fish_greeting ""
+set PATH $HOME/.local/bin $HOME/.cargo/bin $PATH
+
+# Editor configuration
+set -gx EDITOR "emacsclient"
+set -gx ALTERNATE_EDITOR "emacs -q -nw"
+set -gx VISUAL "emacsclient"
+
+# Miscellaneous
+eval (direnv hook fish)
+
+# Useful command aliases
+alias gpr 'git pull --rebase'
+alias gco 'git checkout'
+alias gf 'git fetch'
+alias gap 'git add -p'
+alias pbcopy 'xclip -selection clipboard'
+alias edit 'emacsclient -n'
+alias servedir 'nix-shell -p haskellPackages.wai-app-static --run warp'
+
+# Old habits die hard (also ls is just easier to type):
+alias ls 'exa'
+
+# Fix up nix-env & friends for Nix 2.0
+export NIX_REMOTE=daemon
+
+# Fix display of fish in emacs' term-mode:
+function fish_title
+  true
+end
diff --git a/infra/nixos/dotfiles/msmtprc b/infra/nixos/dotfiles/msmtprc
new file mode 100644
index 0000000000..c6a9a7426a
--- /dev/null
+++ b/infra/nixos/dotfiles/msmtprc
@@ -0,0 +1,24 @@
+defaults
+
+port 587
+tls on
+tls_trust_file /etc/ssl/certs/ca-certificates.crt
+
+# Aprila mail
+account aprila
+from vincent@aprila.no
+host smtp.office365.com
+auth on
+user vincent@aprila.no
+passwordeval pass show aprila/office365-mail
+
+# Runbox mail
+account runbox
+from mail@tazj.in
+host mail.runbox.com
+auth on
+user mail@tazj.in
+passwordeval pass show general/runbox-tazjin
+
+# Use Runbox as default
+account default : runbox
diff --git a/infra/nixos/dotfiles/notmuch-config b/infra/nixos/dotfiles/notmuch-config
new file mode 100644
index 0000000000..eb8b2c79bb
--- /dev/null
+++ b/infra/nixos/dotfiles/notmuch-config
@@ -0,0 +1,21 @@
+# .notmuch-config - Configuration file for the notmuch mail system
+#
+# For more information about notmuch, see https://notmuchmail.org
+
+[database]
+path=/home/vincent/mail
+
+[user]
+name=Vincent Ambo
+primary_email=vincent@aprila.no
+other_email=mail@tazj.in;tazjin@gmail.com;
+
+[new]
+tags=unread;inbox;
+ignore=
+
+[search]
+exclude_tags=deleted;spam;draft;
+
+[maildir]
+synchronize_flags=true
diff --git a/infra/nixos/dotfiles/offlineimaprc b/infra/nixos/dotfiles/offlineimaprc
new file mode 100644
index 0000000000..dd4752f4c3
--- /dev/null
+++ b/infra/nixos/dotfiles/offlineimaprc
@@ -0,0 +1,61 @@
+[general]
+accounts = aprila, tazjin, gmail
+
+[DEFAULT]
+ssl = yes
+sslcacertfile = /etc/ssl/certs/ca-certificates.crt
+
+# Main work account:
+[Account aprila]
+localrepository = aprila-local
+remoterepository = aprila-remote
+
+[Repository aprila-local]
+type = Maildir
+localfolders = ~/mail/aprila
+
+[Repository aprila-remote]
+type = IMAP
+remotehost = outlook.office365.com
+remoteuser = vincent@aprila.no
+remotepassfile = ~/.config/mail/aprila-pass
+# Office365 is a naughty boy when it comes to IMAP. Even worse, they
+# also seem to have decided that they should change IMAP folder names
+# based on the UI language.
+#
+# I can't be bothered to implement the entire (relatively long)
+# blacklist, so I'll instead whitelist relevant folders:
+folderfilter = lambda folder: folder in ['INBOX', 'Arkiv', 'Sende element']
+
+# Private GMail account (old):
+[Account gmail]
+maxage = 90
+localrepository = gmail-local
+remoterepository = gmail-remote
+synclabels = yes
+
+[Repository gmail-local]
+type = GmailMaildir
+localfolders = ~/mail/gmail
+
+[Repository gmail-remote]
+type = Gmail
+remoteuser = tazjin@gmail.com
+remotepassfile = ~/.config/mail/gmail-pass
+folderfilter = lambda folder: folder == 'INBOX'
+
+# Main private account:
+[Account tazjin]
+localrepository = tazjin-local
+remoterepository = tazjin-remote
+
+[Repository tazjin-local]
+type = Maildir
+localfolders = ~/mail/tazjin
+
+[Repository tazjin-remote]
+type = IMAP
+remotehost = mail.runbox.com
+remoteuser = mail@tazj.in
+remotepassfile = ~/.config/mail/tazjin-pass
+auth_mechanisms = LOGIN
diff --git a/infra/nixos/dotfiles/tmux.conf b/infra/nixos/dotfiles/tmux.conf
new file mode 100644
index 0000000000..6c20ae0370
--- /dev/null
+++ b/infra/nixos/dotfiles/tmux.conf
@@ -0,0 +1,14 @@
+set -g status off
+set -gw mode-keys emacs
+setw -g mouse on
+
+# Correctly set window titles
+set -g set-titles on
+set -g set-titles-string "#W (#T)"
+
+# List of plugins
+set -g @plugin 'tmux-plugins/tpm'
+set -g @plugin 'tmux-plugins/tmux-yank'
+
+# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
+run '~/.tmux/plugins/tpm/tpm'
diff --git a/infra/nixos/emacs.nix b/infra/nixos/emacs.nix
new file mode 100644
index 0000000000..24426bc438
--- /dev/null
+++ b/infra/nixos/emacs.nix
@@ -0,0 +1,138 @@
+# Derivation for Emacs pre-configured with packages that I need.
+#
+# TODO: Fix sly (again)
+
+{ pkgs }:
+
+with pkgs; with emacsPackagesNg;
+let emacsWithPackages = (emacsPackagesNgGen emacs).emacsWithPackages;
+
+# As the EXWM-README points out, XELB should be built from source if
+# EXWM is.
+xelb = melpaBuild {
+  pname   = "xelb";
+  ename   = "xelb";
+  version = "0.15";
+  recipe  = builtins.toFile "recipe" ''
+    (xelb :fetcher github
+          :repo "ch11ng/xelb")
+  '';
+
+  packageRequires = [ cl-generic emacs ];
+
+  src = fetchFromGitHub {
+    owner  = "ch11ng";
+    repo   = "xelb";
+    rev    = "b8f168b401977098fe2b30f4ca32629c0ab6eb83";
+    sha256 = "1ack1h68x8ia0ji6wbhmayrakq35p5sgrrl6qvha3ns3pswc0pl9";
+ };
+};
+
+# EXWM pinned to a newer version than what is released due to a
+# potential fix for ch11ng/exwm#425.
+exwm = melpaBuild {
+  pname   = "exwm";
+  ename   = "exwm";
+  version = "0.19";
+  recipe  = builtins.toFile "recipe" ''
+    (exwm :fetcher github
+          :repo "ch11ng/exwm")
+  '';
+
+  packageRequires = [ xelb ];
+
+  src = fetchFromGitHub {
+    owner  = "ch11ng";
+    repo   = "exwm";
+    rev    = "472f7cb82b67b98843f10c12e6bda9b8ae7262bc";
+    sha256 = "19gflsrb19aijf2xcw7j2m658qad21nbwziw38s1h2jw66vhk8dj";
+ };
+};
+
+slyFixed = sly.overrideAttrs(_: {
+  recipe = builtins.toFile "recipe" ''
+(sly :repo "joaotavora/sly"
+     :fetcher github
+     :files ("*.el"
+             ("lib" "lib/*")
+             ("contrib" "contrib/*")
+             "doc/*.texi"
+             "doc/*.info"
+             "doc/dir"))
+'';
+});
+
+in emacsWithPackages(epkgs:
+  # Actual ELPA packages (the enlightened!)
+  (with epkgs.elpaPackages; [
+    ace-window
+    avy
+    company
+    pinentry
+    rainbow-mode
+    undo-tree
+    which-key
+  ]) ++
+
+  # MELPA packages:
+  (with epkgs.melpaPackages; [
+    browse-kill-ring
+    cargo
+    counsel
+    counsel-notmuch
+    dash
+    dash-functional
+    dockerfile-mode
+    edit-server
+    eglot
+    elixir-mode
+    erlang
+    elm-mode
+    exwm
+    go-mode
+    gruber-darker-theme
+    haskell-mode
+    ht
+    hydra
+    idle-highlight-mode
+    intero
+    ivy
+    ivy-pass
+    ivy-prescient
+    jq-mode
+    kotlin-mode
+    magit
+    markdown-mode
+    markdown-toc
+    meghanada
+    multi-term
+    multiple-cursors
+    nginx-mode
+    nix-mode
+    omnisharp
+    paredit
+    password-store
+    pg
+    pkgs.notmuch
+    prescient
+    rainbow-delimiters
+    restclient
+    rust-mode
+    s
+    slyFixed
+    smartparens
+    string-edit
+    swiper
+    telephone-line
+    terraform-mode
+    toml-mode
+    use-package
+    uuidgen
+    web-mode
+    websocket
+    yaml-mode
+  ]) ++
+
+  # Custom packaged Emacs packages:
+  [ xelb exwm ]
+)
diff --git a/infra/nixos/home.nix b/infra/nixos/home.nix
new file mode 100644
index 0000000000..236091cb01
--- /dev/null
+++ b/infra/nixos/home.nix
@@ -0,0 +1,75 @@
+# home-manager configuration used on ChromeOS systems
+
+{ config, pkgs, ... }:
+
+{
+  # Allow non-free software (fonts, IDEA, etc.):
+  nixpkgs.config.allowUnfree = true;
+
+  # Install various useful packages:
+  home.packages = with pkgs; [
+    bat
+    exa
+    gnupg
+    google-cloud-sdk
+    htop
+    pass
+    ripgrep
+    tdesktop
+    transmission
+    tree
+
+    # Fonts to make available in X11 applications:
+    input-fonts
+
+    # Emacs configuration stays in the normal ~/.emacs.d location (for
+    # now), hence this package is not installed via `programs.emacs`.
+    (import ./emacs.nix { inherit pkgs; })
+  ];
+
+  programs.git = {
+    enable = true;
+    userEmail = "mail@tazj.in";
+    userName = "Vincent Ambo";
+  };
+
+  services.gpg-agent = {
+    enable = true;
+    extraConfig = ''
+      pinentry-program ${pkgs.pinentry}/bin/pinentry-gtk-2
+      allow-emacs-pinentry
+    '';
+  };
+
+  # Let Home Manager install and manage itself.
+  programs.home-manager.enable = true;
+  manual.html.enable = true;
+
+  # Shell configuration
+  #
+  # There are some differences between the ChromeOS / NixOS
+  # configurations, so instead of fixing up the dotfile to support
+  # both I opted for keeping the configuration here.
+  programs.fish = {
+    enable = true;
+    interactiveShellInit = ''
+      # Configure classic prompt
+      set fish_color_user --bold blue
+      set fish_color_cwd --bold white
+
+      # Enable colour hints in VCS prompt:
+      set __fish_git_prompt_showcolorhints yes
+      set __fish_git_prompt_color_prefix purple
+      set __fish_git_prompt_color_suffix purple
+
+      # Fish configuration
+      set fish_greeting ""
+
+      # Fix up nix-env & friends for Nix 2.0
+      export NIX_REMOTE=daemon
+    '';
+  };
+
+  # Ensure fonts installed via Nix are picked up.
+  fonts.fontconfig.enableProfileFonts = true;
+}
diff --git a/infra/nixos/mail.nix b/infra/nixos/mail.nix
new file mode 100644
index 0000000000..a9375a1eea
--- /dev/null
+++ b/infra/nixos/mail.nix
@@ -0,0 +1,83 @@
+# This file configures offlineimap, notmuch and MSMTP.
+#
+# Some manual configuration is required the first time this is
+# applied:
+#
+# 1. Credential setup.
+# 2. Linking of MSMTP config (ln -s /etc/msmtprc ~/.msmtprc)
+# 3. Linking of notmuch config (ln -s /etc/notmuch-config ~/.notmuch-config)
+
+{ config, lib, pkgs, ... }:
+
+let offlineImapConfig = pkgs.writeText "offlineimaprc"
+  (builtins.readFile ./dotfiles/offlineimaprc);
+
+msmtpConfig = pkgs.writeText "msmtprc"
+  (builtins.readFile ./dotfiles/msmtprc);
+
+notmuchConfig = pkgs.writeText "notmuch-config"
+  (builtins.readFile ./dotfiles/notmuch-config);
+
+tagConfig = pkgs.writeText "notmuch-tags" ''
+  # Tag emacs-devel mailing list:
+  -inbox +emacs-devel -- to:emacs-devel@gnu.org OR cc:emacs-devel@gnu.org
+
+  # Tag nix-devel mailing list & discourse:
+  -inbox +nix-devel -- to:nix-devel@googlegroups.com OR from:nixos1@discoursemail.com
+
+  # Filter out Gitlab mails:
+  -inbox +gitlab -- from:gitlab@aprila.no
+
+  # Tag my own mail (from other devices) as sent:
+  -inbox +sent -- folder:"aprila/Sende element" OR from:vincent@aprila.no OR from:mail@tazj.in
+
+  # Drafts are always read, duh.
+  -unread -- tag:draft
+
+  # Tag development list
+  -inbox +aprila-dev -- to:dev@aprila.no OR cc:dev@aprila.no
+'';
+
+notmuchIndex = pkgs.writeShellScriptBin "notmuch-index" ''
+  echo "Indexing new mails in notmuch"
+
+  # Index new mail
+  ${pkgs.notmuch}/bin/notmuch new
+
+  # Apply tags
+  cat ${tagConfig} | ${pkgs.notmuch}/bin/notmuch tag --batch
+
+  echo "Done indexing new mails"
+'';
+in {
+  # Enable OfflineIMAP timer & service:
+  systemd.user.timers.offlineimap = {
+    description = "OfflineIMAP timer";
+    wantedBy    = [ "timers.target" ];
+
+    timerConfig = {
+      Unit       = "offlineimap.service";
+      OnCalendar = "*:0/2"; # every 2 minutes
+      Persistent = "true"; # persist timer state after reboots
+    };
+  };
+
+  systemd.user.services.offlineimap = {
+    description = "OfflineIMAP service";
+    path = with pkgs; [ pass notmuch ];
+
+    serviceConfig = {
+      Type            = "oneshot";
+      ExecStart       = "${pkgs.offlineimap}/bin/offlineimap -u syslog -o -c ${offlineImapConfig}";
+      ExecStartPost   = "${notmuchIndex}/bin/notmuch-index";
+      TimeoutStartSec = "2min";
+    };
+  };
+
+  # Link configuration files to /etc/ (from where they will be linked
+  # further):
+  environment.etc = {
+    "msmtprc".source = msmtpConfig;
+    "notmuch-config".source = notmuchConfig;
+  };
+}
diff --git a/infra/nixos/packages.nix b/infra/nixos/packages.nix
new file mode 100644
index 0000000000..b90a4a9ee8
--- /dev/null
+++ b/infra/nixos/packages.nix
@@ -0,0 +1,132 @@
+# This file contains configuration for packages to install.
+# It does not contain configuration for software that is already covered
+# by other NixOS options (e.g. emacs)
+
+{ config, pkgs, ... }:
+
+let
+  fetchChannel = { rev, sha256 }: import (fetchTarball {
+    inherit sha256;
+    url = "https://github.com/NixOS/nixpkgs-channels/archive/${rev}.tar.gz";
+  }) { config.allowUnfree = true; };
+
+  # Channels last updated: 2018-10-10
+  #
+  # Instead of relying on Nix channels and ending up with out-of-sync
+  # situations between machines, the commit for the stable Nix channel
+  # is pinned here.
+  stable = fetchChannel {
+    rev    = "d96c7a356383302db4426a0d5a8383af921d964f";
+    sha256 = "0hlhczh3m077rwrhp4smf3zd2sfj38h2c126bycv66m0aff0gycn";
+  };
+
+  # Certain packages from unstable are hand-picked into the package
+  # set.
+  unstable = fetchChannel {
+    rev    = "32bcd72bf28a971c9063a9cdcc32effe49f49331";
+    sha256 = "1f74m18r6xl9s55jbkj9bjhdxg2489kwjam4d96pf9rzq0i1f8li";
+  };
+in {
+  # Configure the Nix package manager
+  nixpkgs = {
+    config.allowUnfree = true;
+    # To use the pinned channel, the original package set is thrown
+    # away in the overrides:
+    config.packageOverrides = oldPkgs: stable // {
+      # Store whole unstable channel in case that other modules need
+      # it (see emacs.nix for example):
+      inherit unstable;
+
+      # Backport Exa from unstable until a fix for the Rust builder is
+      # backported.
+      #
+      # https://github.com/NixOS/nixpkgs/pull/48020
+      exa = unstable.exa;
+
+      wallpapers = import ./pkgs/wallpapers.nix;
+      pulseaudio-ctl = import pkgs/pulseaudio-ctl.nix;
+    };
+  };
+
+  # ... and declare packages to be installed.
+  environment.systemPackages = with pkgs; [
+    # Default nixos.* packages:
+    alacritty
+    binutils-unwrapped
+    chromium
+    curl
+    direnv
+    dnsutils
+    dotnet-sdk
+    evince
+    exa
+    extremetuxracer
+    fd
+    file
+    firefox-unwrapped
+    fish
+    gcc
+    git
+    gnumake
+    gnupg
+    google-cloud-sdk
+    gopass
+    hicolor-icon-theme
+    htop
+    i3lock
+    iftop
+    jq
+    kontemplate
+    kubernetes
+    lispPackages.quicklisp
+    lxappearance-gtk3
+    manpages
+    maven
+    mono
+    mq-cli
+    msmtp
+    ngrok
+    notmuch
+    numix-cursor-theme
+    numix-gtk-theme
+    numix-icon-theme
+    offlineimap
+    openjdk
+    openssl
+    openssl.dev
+    pass
+    pavucontrol
+    pkgconfig
+    pulseaudio-ctl
+    pwgen
+    ripgrep
+    rustup
+    sbcl
+    screen
+    siege
+    spotify
+    stdmanpages
+    systemd.dev
+    tdesktop
+    terraform
+    tig
+    tmux
+    tokei
+    transmission
+    tree
+    units
+    unzip
+    vlc
+    xclip
+    xfce.xfce4-screenshooter
+
+    # Haskell packages:
+    cabal-install
+    ghc
+    hlint
+    stack
+    stack2nix
+    haskellPackages.stylish-haskell
+    haskellPackages.yesod-bin
+  ];
+}
diff --git a/infra/nixos/pkgs/pulseaudio-ctl.nix b/infra/nixos/pkgs/pulseaudio-ctl.nix
new file mode 100644
index 0000000000..9651ea097f
--- /dev/null
+++ b/infra/nixos/pkgs/pulseaudio-ctl.nix
@@ -0,0 +1,26 @@
+with import <nixpkgs> {};
+
+stdenv.mkDerivation rec {
+  name = "pulseaudio-ctl-${version}";
+  version = "v1.66";
+
+  src = fetchzip {
+    url = "https://github.com/graysky2/pulseaudio-ctl/archive/${version}.tar.gz";
+    sha256 = "19a24w7y19551ar41q848w7r1imqkl9cpff4dpb7yry7qp1yjg0y";
+  };
+
+  buildFlags = ''PREFIX=$(out)'';
+
+  # Force Nix to detect the runtime dependency on 'bc'
+  preInstall = ''
+    sed -i 's|bc)|${bc}/bin/bc)|g' common/pulseaudio-ctl
+  '';
+
+  installFlags = ''PREFIX=$(out)'';
+
+  meta = with stdenv.lib; {
+    description = "Control pulseaudio volume from the shell or mapped to keyboard shortcuts";
+    homepage = "https://github.com/graysky2/pulseaudio-ctl";
+    license = licenses.mit;
+  };
+}
diff --git a/infra/nixos/pkgs/wallpapers.nix b/infra/nixos/pkgs/wallpapers.nix
new file mode 100644
index 0000000000..3a26a54fb7
--- /dev/null
+++ b/infra/nixos/pkgs/wallpapers.nix
@@ -0,0 +1,22 @@
+# Fetch my wallpapers from git
+with import <nixpkgs> {};
+
+stdenv.mkDerivation {
+  name    = "tazjins-wallpapers-1";
+
+  src = fetchgit {
+    url    = "https://git.tazj.in/tazjin/wallpapers.git";
+    rev    = "3bce73b605ba5f848cb4e7cc33058a2be3952c68";
+    sha256 = "1gjlazag7x005sf2bd6a7dw5p9ry5vjgzmvycsyiw3pv9b1gzc0j";
+  };
+
+  installPhase = ''
+    mkdir -p $out/share/wallpapers
+    cp -r $src/* $out/share/wallpapers
+  '';
+
+  meta = with stdenv.lib; {
+    description = "tazjin's wallpaper collection";
+    platforms = platforms.all;
+  };
+}
diff --git a/infra/nixos/stallo-configuration.nix b/infra/nixos/stallo-configuration.nix
new file mode 100644
index 0000000000..4af4a5fd4c
--- /dev/null
+++ b/infra/nixos/stallo-configuration.nix
@@ -0,0 +1,39 @@
+# Local configuration for 'stallo' (Home desktop PC)
+{ config, pkgs, ...}:
+
+{
+  boot.initrd.luks.devices.stallo-luks.device = "/dev/disk/by-uuid/b484cf1e-a27b-4785-8bd6-fa85a004b073";
+
+  # Use proprietary nvidia driver
+  services.xserver.videoDrivers = [ "nvidia" ];
+
+  # Enable 32-bit compatibility for Steam:
+  hardware.opengl.driSupport32Bit = true;
+  hardware.pulseaudio.support32Bit = true;
+
+  # Wine for Blizzard stuff
+  environment.systemPackages = with pkgs.unstable; [ wineWowPackages.staging winetricks ];
+
+  networking = {
+    hostName = "stallo";
+    wireless.enable = true;
+    wireless.networks = {
+      # Welcome to roast club!
+
+      "How do I computer fast?" = {
+        psk = "washyourface";
+        # Prefer 5Ghz unless the card is acting up.
+        priority = 10;
+      };
+
+      "How do I computer?" = {
+        psk = "washyourface";
+      };
+    };
+    # IPv6 at home, of course:
+    nameservers = [
+      "2606:4700:4700::1111"
+      "2606:4700:4700::1001"
+    ];
+  };
+}