about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--COPYING20
-rw-r--r--README79
-rwxr-xr-xactivate26
-rw-r--r--config/alacritty/config.yml496
-rw-r--r--config/alacritty/default.nix3
-rw-r--r--config/firefox/beta/D6695.diff281
-rw-r--r--config/firefox/default.nix6
-rw-r--r--config/firefox/module.nix11
-rw-r--r--config/firefox/nightly/D6695.diff159
-rw-r--r--config/firefox/overlay.nix18
-rw-r--r--config/firefox/profiles.ini8
-rw-r--r--config/firefox/release/D6695.diff405
-rw-r--r--config/firefox/user.js1
-rw-r--r--config/git/attributes1
-rw-r--r--config/git/config.nix55
-rw-r--r--config/git/default.nix8
-rw-r--r--config/git/ignore22
-rw-r--r--config/gnupg/default.nix5
-rw-r--r--config/gnupg/dirmngr.conf1
-rw-r--r--config/gnupg/gpg-agent.conf.nix9
-rw-r--r--config/gnupg/gpg.conf2
-rw-r--r--config/gnupg/module.nix23
-rw-r--r--config/isync/default.nix7
-rw-r--r--config/isync/imappass.gpgbin0 -> 605 bytes
-rw-r--r--config/isync/mbsyncrc.nix26
-rw-r--r--config/kakoune/default.nix12
-rw-r--r--config/kakoune/kakrc.nix96
-rw-r--r--config/less/default.nix5
-rw-r--r--config/msmtp/default.nix9
-rw-r--r--config/msmtp/msmtprc.nix24
-rw-r--r--config/msmtp/smtppass.gpgbin0 -> 605 bytes
-rw-r--r--config/neomutt/default.nix7
-rw-r--r--config/neomutt/muttrc.nix52
-rw-r--r--config/sway/config.nix215
-rw-r--r--config/sway/default.nix7
-rw-r--r--config/tmux/config.nix90
-rw-r--r--config/tmux/default.nix12
-rw-r--r--config/tools.nix40
-rw-r--r--config/weechat/default.nix5
-rw-r--r--config/weechat/module.nix12
-rw-r--r--config/zsh/default.nix20
-rw-r--r--config/zsh/zshenv.nix9
-rw-r--r--config/zsh/zshrc.nix195
-rw-r--r--modules/home/default.nix87
-rw-r--r--modules/locale/default.nix8
-rw-r--r--modules/nix/default.nix49
-rw-r--r--modules/shell/default.nix45
-rw-r--r--modules/users/default.nix30
-rw-r--r--modules/workstation/default.nix11
-rw-r--r--modules/workstation/fonts/default.nix5
-rw-r--r--modules/workstation/hardware/default.nix6
-rw-r--r--modules/workstation/mail/default.nix7
-rw-r--r--modules/workstation/mail/isync/default.nix36
-rw-r--r--modules/workstation/mail/mutt/default.nix7
-rw-r--r--modules/workstation/networking/default.nix36
-rw-r--r--modules/workstation/windowing/default.nix12
-rw-r--r--modules/workstation/yubikey/default.nix7
-rw-r--r--nixpkgs-overlays/zzzzzz-config/default.nix63
-rw-r--r--sys/x220.nix49
60 files changed, 2942 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000000..750baebf4151
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+result
+result-*
diff --git a/COPYING b/COPYING
new file mode 100644
index 000000000000..a306d8d92e11
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,20 @@
+Copyright (c) 2018 Alyssa Ross
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README b/README
new file mode 100644
index 000000000000..44dd2a2fc6b5
--- /dev/null
+++ b/README
@@ -0,0 +1,79 @@
+                        ===================
+                        Alyssa's collection
+                        of Nix expressions.
+                        ===================
+
+This is a large collection of Nix expressions.
+
+The primary way to use the expressions is by applying a profile to a
+NixOS or nix-darwin system. See the sys directory for available
+profiles.  A profile can be installed on a system using the `activate'
+script.
+
+These profiles are structured in a way that is very different to NixOS
+configurations in several respects.  First, rather than setting NIX_PATH
+to a mutable tree, these profiles copy the tree into the Nix store at
+build time, then set NIX_PATH to point to that.  This means that
+angle-bracket path syntax (e.g. <nixpkgs>) points to exactly the
+expression that it pointed to when the system was built.  To use a
+mutable working tree in Nix expressions, relative or absolute paths can
+be used instead.  Another advantage of doing things this way is that the
+Nix expressions used to build a given system can always be recovered
+from the Nix store, since they will be a dependency of the system
+derivation.
+
+Another important difference is how programs are configured.  Typically,
+programs are configured on NixOS using modules, but this approach can
+only support a single, system-level configuration for a program.  It is
+desirable to be able to be able to configure a program on a per-user
+basis, or even to allow a user to instantiate multiple versions of the
+same program, with different configurations.  Rather than the module
+approach, these expressions use utility functions to create small
+wrapper programs that apply the desired configuration by setting the
+appropriate command line flag or environment variable, then exec into
+the program itself.  Creating multiple instances of the same program is
+then trivial, by using two different invocations of the helper function.
+The configured programs are defined in the config directory, and the
+aforementioned utility functions are available in config/tools.nix.  The
+configured packages are added to Nixpkgs under the pkgsConfigured
+attribute; defined in nixpkgs-overlays/zzzzzz-config/default.nix.
+
+Whereas NixOS configurations are typically made up of configuration
+files along with seperate repositories or channels for Nixpkgs (and
+possibly some overlays), here a single tree is used.  This means that
+version control history will always be able to identify which version
+of Nixpkgs is compatible with which version of the configuration files,
+while also allowing custom downstream changes to be made to Nixpkgs.
+No other approach provides both of these features in such a convenient
+way.  With the exception of some custom modifications, any upstream
+trees (Nixpkgs, various overlays) are left intact in the tree, for ease
+of patch generation against those upstream trees.  The structure of the
+tree is deliberately designed so that it can be used as a single entry
+in a NIX_PATH.  <nixpkgs> will automatically point to the nixpkgs
+subdirectory, and it will automatically load the overlays in the
+nixpkgs-overlays subdirectory through <nixpkgs-overlays>.  Other
+structures would be possible, but only this one has this feature, and so
+it made sense to choose it.
+
+In constrast to NixOS, an attempt is made here to assist with per-user
+application state management through the `home' module.  This module
+allows activation scripts to be defined on a per-directory basis inside
+a user's home.  For programs like gpg, which require configuration files
+to be in the same directory as mutable program state, this approach
+allows configuration to be stored read-only in the Nix store, and then
+symlinked into the state directory, allowing this type of program to
+still realise some of the benefits of Nix-based configuration.  However,
+unlike other forms of program configuration as described above, this
+can't simply be done by wrapping a program (at least, without increasing
+the startup time of a program too much), and so a module has to be used.
+These modules are, by convention, stored in the appropriate subdirectory
+of the config directory, rather than in the modules directory, so that
+all program-specific configuration for a particular program lives in one
+place.
+
+Note: MIT license does not apply to the packages built by Nixpkgs,
+merely to the package descriptions (Nix expressions, build scripts, and
+so on). It also might not apply to patches included in the tree, which
+may be derivative works of the packages to which they apply. The
+aforementioned artifacts are all covered by the licenses of the
+respective packages.
diff --git a/activate b/activate
new file mode 100755
index 000000000000..72550ebb2876
--- /dev/null
+++ b/activate
@@ -0,0 +1,26 @@
+#! /usr/bin/env bash
+
+# IMPORTANT: Don't use nix-shell shebang!
+# If a config has generated an invalid NIX_PATH, it still needs to be
+# possible to rescue the system by activating a different configuration.
+
+set -ueo pipefail
+
+d="$(dirname $0)"
+
+if [ "$#" -eq 0 ]; then
+    echo -e "\e[31;1merror:\e[0m must specify a system definition" >&2
+    echo "Available system definitions are:" >&2
+    find $d/sys -name *.nix | awk -F / '{print "- " $NF}' | sed 's/\.nix$//' >&2
+    exit 1
+fi
+
+sys="$1"
+shift
+
+export NIX_PATH=nixos-config=$d/sys/$sys.nix:$d
+link="$(mktemp -d)/result"
+nix build -f '<nixpkgs/nixos>' --out-link "$link" "$@" system
+path="$(readlink "$link")"
+nix-env -p /nix/var/nix/profiles/system --set "$path"
+exec "$path/bin/switch-to-configuration" switch
diff --git a/config/alacritty/config.yml b/config/alacritty/config.yml
new file mode 100644
index 000000000000..8cef63fa7c33
--- /dev/null
+++ b/config/alacritty/config.yml
@@ -0,0 +1,496 @@
+# 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 itself.
+#env:
+  # TERM variable
+  #
+  # This value is used to set the `$TERM` environment variable for
+  # each instance of Alacritty. If it is not present, alacritty will
+  # check the local terminfo database and use 'alacritty' if it is
+  # available, otherwise 'xterm-256color' is used.
+  #TERM: xterm-256color
+
+window:
+  # Window dimensions (changes require restart)
+  #
+  # Specified in number of columns/lines, not pixels.
+  # If both are `0`, this setting is ignored.
+  dimensions:
+    columns: 80
+    lines: 24
+
+  # Window padding (changes require restart)
+  #
+  # Blank space added around the window in pixels. This padding is scaled
+  # by DPI and the specified value is always added at both opposing sides.
+  padding:
+    x: 2
+    y: 2
+
+  # Spread additional padding evenly around the terminal content.
+  dynamic_padding: false
+
+  # Window decorations
+  #
+  # Values for `decorations`:
+  #     - full: Borders and title bar
+  #     - none: Neither borders nor title bar
+  decorations: none
+
+  # When true, alacritty starts maximized.
+  start_maximized: false
+
+scrolling:
+  # Maximum number of lines in the scrollback buffer.
+  # Specifying '0' will disable scrolling.
+  history: 0
+
+  # Number of lines the viewport will move for every line scrolled when
+  # scrollback is enabled (history > 0).
+  multiplier: 3
+
+  # Faux Scrolling
+  #
+  # The `faux_multiplier` setting controls the number of lines the terminal
+  # should scroll when the alternate screen buffer is active. This is used
+  # to allow mouse scrolling for applications like `man`.
+  #
+  # Specifying `0` will disable faux scrolling.
+  faux_multiplier: 3
+
+  # Scroll to the bottom when new text is written to the terminal.
+  auto_scroll: false
+
+# Spaces per Tab (changes require restart)
+#
+# This setting defines the width of a tab in cells.
+#
+# Some applications, like Emacs, rely on knowing about the width of a tab.
+# To prevent unexpected behavior in these applications, it's also required to
+# change the `it` value in terminfo when altering this setting.
+tabspaces: 8
+
+# Font configuration (changes require restart)
+#
+# Important font attributes like antialiasing, subpixel aa, and hinting can be
+# controlled through fontconfig. Specifically, the following attributes should
+# have an effect:
+#   - hintstyle
+#   - antialias
+#   - lcdfilter
+#   - rgba
+#
+# For instance, if you wish to disable subpixel antialiasing, you might set the
+# rgba property to `none`. If you wish to completely disable antialiasing, you
+# can set antialias to `false`.
+#
+# Please see these resources for more information on how to use fontconfig:
+#   - https://wiki.archlinux.org/index.php/font_configuration#Fontconfig_configuration
+#   - file:///usr/share/doc/fontconfig/fontconfig-user.html
+font:
+  # Normal (roman) font face
+  normal:
+    family: monospace
+    # The `style` can be specified to pick a specific face.
+    #style: Regular
+
+  # Bold font face
+  bold:
+    family: monospace
+    # The `style` can be specified to pick a specific face.
+    #style: Bold
+
+  # Italic font face
+  italic:
+    family: monospace
+    # The `style` can be specified to pick a specific face.
+    #style: Italic
+
+  # Point size
+  size: 7.0
+
+  # Offset is the extra space around each character. `offset.y` can be thought of
+  # as modifying the line spacing, and `offset.x` as modifying the letter spacing.
+  offset:
+    x: 0
+    y: 0
+
+  # Glyph offset determines the locations of the glyphs within their cells with
+  # the default being at the bottom. Increasing `x` moves the glyph to the right,
+  # increasing `y` moves the glyph upwards.
+  glyph_offset:
+    x: 0
+    y: 0
+
+# Display the time it takes to redraw each frame.
+render_timer: false
+
+# Keep the log file after quitting Alacritty.
+persistent_logging: false
+
+# If `true`, bold text is drawn using the bright color variants.
+draw_bold_text_with_bright_colors: true
+
+# Colors (Tomorrow Night Bright)
+colors:
+  # Default colors
+  primary:
+    background: '0x000000'
+    foreground: '0xeaeaea'
+
+    # Bright and dim foreground colors
+    #
+    # The dimmed foreground color is calculated automatically if it is not present.
+    # If the bright foreground color is not set, or `draw_bold_text_with_bright_colors`
+    # is `false`, the normal foreground color will be used.
+    #dim_foreground: '0x9a9a9a'
+    #bright_foreground: '0xffffff'
+
+  # Cursor colors
+  #
+  # Colors which should be used to draw the terminal cursor. If these are unset,
+  # the cursor color will be the inverse of the cell color.
+  #cursor:
+  #  text: '0x000000'
+  #  cursor: '0xffffff'
+
+  # Normal colors
+  normal:
+    black:   '0x000000'
+    red:     '0xd54e53'
+    green:   '0xb9ca4a'
+    yellow:  '0xe6c547'
+    blue:    '0x7aa6da'
+    magenta: '0xc397d8'
+    cyan:    '0x70c0ba'
+    white:   '0xffffff'
+
+  # Bright colors
+  bright:
+    black:   '0x666666'
+    red:     '0xff3334'
+    green:   '0x9ec400'
+    yellow:  '0xe7c547'
+    blue:    '0x7aa6da'
+    magenta: '0xb77ee0'
+    cyan:    '0x54ced6'
+    white:   '0xffffff'
+
+  # Dim colors
+  #
+  # If the dim colors are not set, they will be calculated automatically based
+  # on the `normal` colors.
+  dim:
+    black:   '0x333333'
+    red:     '0xf2777a'
+    green:   '0x99cc99'
+    yellow:  '0xffcc66'
+    blue:    '0x6699cc'
+    magenta: '0xcc99cc'
+    cyan:    '0x66cccc'
+    white:   '0xdddddd'
+
+  # Indexed Colors
+  #
+  # The indexed colors include all colors from 16 to 256.
+  # When these are not set, they're filled with sensible defaults.
+  #indexed_colors:
+  #  - { index: 16, color: '0x000000' }
+
+# Visual Bell
+#
+# Any time the BEL code is received, Alacritty "rings" the visual bell. Once
+# rung, the terminal background will be set to white and transition back to the
+# default background color. You can control the rate of this transition by
+# setting the `duration` property (represented in milliseconds). You can also
+# configure the transition function by setting the `animation` property.
+#
+# Values for `animation`:
+#   - Ease
+#   - EaseOut
+#   - EaseOutSine
+#   - EaseOutQuad
+#   - EaseOutCubic
+#   - EaseOutQuart
+#   - EaseOutQuint
+#   - EaseOutExpo
+#   - EaseOutCirc
+#   - Linear
+#
+# Specifying a `duration` of `0` will disable the visual bell.
+visual_bell:
+  animation: EaseOutExpo
+  duration: 1
+
+# Background opacity
+#
+# Window opacity as a floating point number from `0.0` to `1.0`.
+# The value `0.0` is completely transparent and `1.0` is opaque.
+background_opacity: 1.0
+
+# Mouse bindings
+#
+# Available fields:
+#   - mouse
+#   - action
+#   - mods (optional)
+#
+# Values for `mouse`:
+#   - Middle
+#   - Left
+#   - Right
+#   - Numeric identifier such as `5`
+#
+# All available `mods` and `action` values are documented in the key binding
+# section.
+mouse_bindings:
+  - { mouse: Middle, action: PasteSelection }
+
+mouse:
+  # Click settings
+  #
+  # The `double_click` and `triple_click` settings control the time
+  # alacritty should wait for accepting multiple clicks as one double
+  # or triple click.
+  double_click: { threshold: 300 }
+  triple_click: { threshold: 300 }
+
+  # If this is `true`, the cursor is temporarily hidden when typing.
+  hide_when_typing: false
+
+  url:
+    # URL launcher
+    #
+    # This program is executed when clicking on a text which is recognized as a URL.
+    # The URL is always added to the command as the last parameter.
+    launcher: xdg-open
+
+    # URL modifiers
+    #
+    # These are the modifiers that need to be held down for opening URLs when clicking
+    # on them. The available modifiers are documented in the key binding section.
+    #modifiers: Control|Shift
+
+selection:
+  semantic_escape_chars: ",│`|:\"' ()[]{}<>"
+
+  # When set to `true`, selected text will be copied to both the primary and
+  # the selection clipboard. Otherwise, it will only be copied to the selection
+  # clipboard.
+  save_to_clipboard: false
+
+dynamic_title: true
+
+cursor:
+  # Cursor style
+  #
+  # Values for 'style':
+  #   - ▇ Block
+  #   - _ Underline
+  #   - | Beam
+  style: Block
+
+  # If this is `true`, the cursor will be rendered as a hollow box when the
+  # window is not focused.
+  unfocused_hollow: true
+
+# Live config reload (changes require restart)
+live_config_reload: true
+
+# Shell
+#
+# You can set `shell.program` to the path of your favorite shell, e.g. `/bin/fish`.
+# Entries in `shell.args` are passed unmodified as arguments to the shell.
+#shell:
+#  program: /bin/bash
+#  args:
+#    - --login
+
+# Key bindings
+#
+# Key bindings are specified as a list of objects. Each binding will specify
+# a key and modifiers required to trigger it, terminal modes where the binding
+# is applicable, and what should be done when the key binding fires. It can
+# either send a byte sequnce to the running application (`chars`), execute
+# a predefined action (`action`) or fork and execute a specified command plus
+# arguments (`command`).
+#
+# Example:
+#   `- { key: V, mods: Command, action: Paste }`
+#
+# Available fields:
+#   - key
+#   - mods (optional)
+#   - chars | action | command (exactly one required)
+#   - mode (optional)
+#
+# Values for `key`:
+#   - `A` -> `Z`
+#   - `F1` -> `F12`
+#   - `Key1` -> `Key0`
+#
+#   A full list with available key codes can be found here:
+#   https://docs.rs/glutin/*/glutin/enum.VirtualKeyCode.html#variants
+#
+#   Instead of using the name of the keys, the `key` field also supports using
+#   the scancode of the desired key. Scancodes have to be specified as a
+#   decimal number.
+#   This command will allow you to display the hex scancodes for certain keys:
+#     `showkey --scancodes`
+#
+# Values for `mods`:
+#   - Command
+#   - Control
+#   - Shift
+#   - Alt
+#
+#   Multiple `mods` can be combined using `|` like this: `mods: Control|Shift`.
+#   Whitespace and capitalization is relevant and must match the example.
+#
+# Values for `chars`:
+#   The `chars` field writes the specified string to the terminal. This makes
+#   it possible to pass escape sequences.
+#   To find escape codes for bindings like `PageUp` ("\x1b[5~"), you can run
+#   the command `showkey -a` outside of tmux.
+#   Note that applications use terminfo to map escape sequences back to
+#   keys. It is therefore required to update the terminfo when
+#   changing an escape sequence.
+#
+# Values for `action`:
+#   - Paste
+#   - PasteSelection
+#   - Copy
+#   - IncreaseFontSize
+#   - DecreaseFontSize
+#   - ResetFontSize
+#   - ScrollPageUp
+#   - ScrollPageDown
+#   - ScrollToTop
+#   - ScrollToBottom
+#   - ClearHistory
+#   - Hide
+#   - Quit
+#   - ClearLogNotice
+#
+# Values for `command`:
+#   The `command` field must be a map containing a `program` string and
+#   an `args` array of command line parameter strings.
+#
+#   Example:
+#       `command: { program: "alacritty", args: ["-e", "vttest"] }`
+#
+# Values for `mode`:
+#   - ~AppCursor
+#   - AppCursor
+#   - ~AppKeypad
+#   - AppKeypad
+key_bindings:
+  - { key: V,        mods: Control|Shift,    action: Paste               }
+  - { key: C,        mods: Control|Shift,    action: Copy                }
+  - { key: Paste,                   action: Paste                        }
+  - { key: Copy,                    action: Copy                         }
+  - { key: Q,        mods: Command, action: Quit                         }
+  - { key: W,        mods: Command, action: Quit                         }
+  - { key: Insert,   mods: Shift,   action: PasteSelection               }
+  - { key: Key0,     mods: Control, action: ResetFontSize                }
+  - { key: Equals,   mods: Control, action: IncreaseFontSize             }
+  - { key: Subtract, mods: Control, action: DecreaseFontSize             }
+  - { key: L,        mods: Control, action: ClearLogNotice               }
+  - { key: L,        mods: Control, chars: "\x0c"                        }
+  - { 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: Tab,      mods: Shift,   chars: "\x1b[Z"                      }
+  - { key: Back,                    chars: "\x7f"                        }
+  - { key: Back,     mods: Alt,     chars: "\x1b\x7f"                    }
+  - { key: Insert,                  chars: "\x1b[2~"                     }
+  - { key: Delete,                  chars: "\x1b[3~"                     }
+  - { 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: 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: F1,       mods: Shift,   chars: "\x1b[1;2P"                   }
+  - { key: F2,       mods: Shift,   chars: "\x1b[1;2Q"                   }
+  - { key: F3,       mods: Shift,   chars: "\x1b[1;2R"                   }
+  - { key: F4,       mods: Shift,   chars: "\x1b[1;2S"                   }
+  - { key: F5,       mods: Shift,   chars: "\x1b[15;2~"                  }
+  - { key: F6,       mods: Shift,   chars: "\x1b[17;2~"                  }
+  - { key: F7,       mods: Shift,   chars: "\x1b[18;2~"                  }
+  - { key: F8,       mods: Shift,   chars: "\x1b[19;2~"                  }
+  - { key: F9,       mods: Shift,   chars: "\x1b[20;2~"                  }
+  - { key: F10,      mods: Shift,   chars: "\x1b[21;2~"                  }
+  - { key: F11,      mods: Shift,   chars: "\x1b[23;2~"                  }
+  - { key: F12,      mods: Shift,   chars: "\x1b[24;2~"                  }
+  - { key: F1,       mods: Control, chars: "\x1b[1;5P"                   }
+  - { key: F2,       mods: Control, chars: "\x1b[1;5Q"                   }
+  - { key: F3,       mods: Control, chars: "\x1b[1;5R"                   }
+  - { key: F4,       mods: Control, chars: "\x1b[1;5S"                   }
+  - { key: F5,       mods: Control, chars: "\x1b[15;5~"                  }
+  - { key: F6,       mods: Control, chars: "\x1b[17;5~"                  }
+  - { key: F7,       mods: Control, chars: "\x1b[18;5~"                  }
+  - { key: F8,       mods: Control, chars: "\x1b[19;5~"                  }
+  - { key: F9,       mods: Control, chars: "\x1b[20;5~"                  }
+  - { key: F10,      mods: Control, chars: "\x1b[21;5~"                  }
+  - { key: F11,      mods: Control, chars: "\x1b[23;5~"                  }
+  - { key: F12,      mods: Control, chars: "\x1b[24;5~"                  }
+  - { key: F1,       mods: Alt,     chars: "\x1b[1;6P"                   }
+  - { key: F2,       mods: Alt,     chars: "\x1b[1;6Q"                   }
+  - { key: F3,       mods: Alt,     chars: "\x1b[1;6R"                   }
+  - { key: F4,       mods: Alt,     chars: "\x1b[1;6S"                   }
+  - { key: F5,       mods: Alt,     chars: "\x1b[15;6~"                  }
+  - { key: F6,       mods: Alt,     chars: "\x1b[17;6~"                  }
+  - { key: F7,       mods: Alt,     chars: "\x1b[18;6~"                  }
+  - { key: F8,       mods: Alt,     chars: "\x1b[19;6~"                  }
+  - { key: F9,       mods: Alt,     chars: "\x1b[20;6~"                  }
+  - { key: F10,      mods: Alt,     chars: "\x1b[21;6~"                  }
+  - { key: F11,      mods: Alt,     chars: "\x1b[23;6~"                  }
+  - { key: F12,      mods: Alt,     chars: "\x1b[24;6~"                  }
+  - { key: F1,       mods: Super,   chars: "\x1b[1;3P"                   }
+  - { key: F2,       mods: Super,   chars: "\x1b[1;3Q"                   }
+  - { key: F3,       mods: Super,   chars: "\x1b[1;3R"                   }
+  - { key: F4,       mods: Super,   chars: "\x1b[1;3S"                   }
+  - { key: F5,       mods: Super,   chars: "\x1b[15;3~"                  }
+  - { key: F6,       mods: Super,   chars: "\x1b[17;3~"                  }
+  - { key: F7,       mods: Super,   chars: "\x1b[18;3~"                  }
+  - { key: F8,       mods: Super,   chars: "\x1b[19;3~"                  }
+  - { key: F9,       mods: Super,   chars: "\x1b[20;3~"                  }
+  - { key: F10,      mods: Super,   chars: "\x1b[21;3~"                  }
+  - { key: F11,      mods: Super,   chars: "\x1b[23;3~"                  }
+  - { key: F12,      mods: Super,   chars: "\x1b[24;3~"                  }
diff --git a/config/alacritty/default.nix b/config/alacritty/default.nix
new file mode 100644
index 000000000000..e9130245d568
--- /dev/null
+++ b/config/alacritty/default.nix
@@ -0,0 +1,3 @@
+{ configTools, alacritty }:
+
+configTools.addFlags alacritty "alacritty" ''--config-file ${./config.yml}''
diff --git a/config/firefox/beta/D6695.diff b/config/firefox/beta/D6695.diff
new file mode 100644
index 000000000000..f8a8deeb8581
--- /dev/null
+++ b/config/firefox/beta/D6695.diff
@@ -0,0 +1,281 @@
+diff --git a/toolkit/moz.build b/toolkit/moz.build
+index 109fb2c..0b871d9 100644
+--- a/toolkit/moz.build
++++ b/toolkit/moz.build
+@@ -72,3 +72,5 @@ with Files('mozapps/preferences/**'):
+ with Files('pluginproblem/**'):
+     BUG_COMPONENT = ('Core', 'Plug-ins')
+ 
++if CONFIG['ENABLE_TESTS']:
++    DIRS += ['tests/gtest']
+diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp
+index aafe82e..2d850fe 100644
+--- a/toolkit/xre/nsXREDirProvider.cpp
++++ b/toolkit/xre/nsXREDirProvider.cpp
+@@ -390,13 +390,6 @@ nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent,
+     nsCOMPtr<nsIFile> localDir;
+     rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false);
+     if (NS_SUCCEEDED(rv)) {
+-#if defined(XP_MACOSX)
+-      rv = localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla"));
+-#else
+-      rv = localDir->AppendNative(NS_LITERAL_CSTRING(".mozilla"));
+-#endif
+-    }
+-    if (NS_SUCCEEDED(rv)) {
+       localDir.swap(file);
+     }
+   }
+@@ -1238,7 +1231,8 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult,
+             nsDependentCString(hasVendor ? GetAppVendor() : GetAppName())))) {
+       return NS_ERROR_FAILURE;
+     }
+-  } else if (NS_FAILED(localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla")))) {
++  }
++  else if (NS_FAILED(localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla")))) {
+     return NS_ERROR_FAILURE;
+   }
+ 
+@@ -1367,6 +1361,9 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile,
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+   localDir = dirFileMac;
++
++  rv = localDir->AppendRelativeNativePath(nsDependentCString("Mozilla"));
++  NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_IOS)
+   nsAutoCString userDir;
+   if (GetUIKitDirectory(aLocal, userDir)) {
+@@ -1390,6 +1387,9 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile,
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+   rv = NS_NewLocalFile(path, true, getter_AddRefs(localDir));
++  NS_ENSURE_SUCCESS(rv, rv);
++  rv = localDir->AppendRelativeNativePath(nsDependentCString("Mozilla"));
++  NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_UNIX)
+   const char* homeDir = getenv("HOME");
+   if (!homeDir || !*homeDir) return NS_ERROR_FAILURE;
+@@ -1411,8 +1411,51 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile,
+         rv = localDir->AppendNative(NS_LITERAL_CSTRING(".cache"));
+     }
+   } else {
++    bool exists;
++    // check old config ~/.mozilla
+     rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
+                                getter_AddRefs(localDir));
++    NS_ENSURE_SUCCESS(rv, rv);
++    rv = localDir->AppendRelativeNativePath(nsDependentCString(".mozilla"));
++    NS_ENSURE_SUCCESS(rv, rv);
++    rv = localDir->Exists(&exists);
++    NS_ENSURE_SUCCESS(rv, rv);
++    // otherwise, use new config
++    if (!exists) {
++      const char* xdghomedir = getenv("XDG_DATA_HOME");
++      if (!xdghomedir || !*xdghomedir) {
++        rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
++                                   getter_AddRefs(localDir));
++        NS_ENSURE_SUCCESS(rv, rv);
++        rv = localDir->AppendRelativeNativePath(nsDependentCString(".local"));
++        NS_ENSURE_SUCCESS(rv, rv);
++        rv = localDir->Exists(&exists);
++        if (NS_SUCCEEDED(rv) && !exists) {
++          rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++          NS_ENSURE_SUCCESS(rv, rv);
++        }
++        rv = localDir->AppendRelativeNativePath(nsDependentCString("share"));
++        NS_ENSURE_SUCCESS(rv, rv);
++        rv = localDir->Exists(&exists);
++        if (NS_SUCCEEDED(rv) && !exists) {
++          rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++        }
++      }
++      else {
++        rv = NS_NewNativeLocalFile(nsDependentCString(xdghomedir), true,
++                               getter_AddRefs(localDir));
++      }
++      NS_ENSURE_SUCCESS(rv, rv);
++
++      rv = localDir->AppendRelativeNativePath(nsDependentCString("mozilla"));
++      NS_ENSURE_SUCCESS(rv, rv);
++      rv = localDir->Exists(&exists);
++      NS_ENSURE_SUCCESS(rv, rv);
++      if (NS_SUCCEEDED(rv) && !exists) {
++        rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
++        NS_ENSURE_SUCCESS(rv, rv);
++      }
++    }
+   }
+ #else
+ #error "Don't know how to get product dir on your platform"
+@@ -1523,20 +1566,12 @@ nsresult nsXREDirProvider::AppendSysUserExtensionPath(nsIFile* aFile) {
+ 
+ #if defined(XP_MACOSX) || defined(XP_WIN)
+ 
+-  static const char* const sXR = "Mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "Extensions";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+ #elif defined(XP_UNIX)
+ 
+-  static const char* const sXR = ".mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "extensions";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+@@ -1554,20 +1589,12 @@ nsresult nsXREDirProvider::AppendSysUserExtensionsDevPath(nsIFile* aFile) {
+ 
+ #if defined(XP_MACOSX) || defined(XP_WIN)
+ 
+-  static const char* const sXR = "Mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "SystemExtensionsDev";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+ #elif defined(XP_UNIX)
+ 
+-  static const char* const sXR = ".mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "systemextensionsdev";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+@@ -1625,9 +1652,6 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) {
+   NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_UNIX)
+   nsAutoCString folder;
+-  // Make it hidden (by starting with "."), except when local (the
+-  // profile is already under ~/.cache or XDG_CACHE_HOME).
+-  if (!aLocal) folder.Assign('.');
+ 
+   if (!profile.IsEmpty()) {
+     // Skip any leading path characters
+@@ -1647,8 +1671,12 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) {
+       folder.Append(vendor);
+       ToLowerCase(folder);
+ 
+-      rv = aFile->AppendNative(folder);
+-      NS_ENSURE_SUCCESS(rv, rv);
++      // Keep the 'mozilla' path for cache:
++      // Use ${XDG_DATA_HOME:-$HOME/.cache}/mozilla/firefox
++      if (aLocal) {
++        rv = aFile->AppendNative(folder);
++        NS_ENSURE_SUCCESS(rv, rv);
++      }
+ 
+       folder.Truncate();
+     }
+diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp
+index 90e4ec9..8b838ec 100644
+--- a/xpcom/io/nsAppFileLocationProvider.cpp
++++ b/xpcom/io/nsAppFileLocationProvider.cpp
+@@ -247,7 +247,7 @@ nsresult nsAppFileLocationProvider::CloneMozBinDirectory(nsIFile** aLocalFile) {
+ // GetProductDirectory - Gets the directory which contains the application data
+ // folder
+ //
+-// UNIX   : ~/.mozilla/
++// UNIX   : ~/.mozilla/ or ${XDG_DATA_HOME:-~/.local/share}/mozilla
+ // WIN    : <Application Data folder on user's machine>\Mozilla
+ // Mac    : :Documents:Mozilla:
+ //----------------------------------------------------------------------------------------
+@@ -291,19 +291,80 @@ nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
+     return rv;
+   }
+ #elif defined(XP_UNIX)
+-  rv = NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true,
++  const char* homeDir = PR_GetEnv("HOME");
++  /* check old config ~/.mozilla */
++  rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
+                              getter_AddRefs(localDir));
+   if (NS_FAILED(rv)) {
+     return rv;
+   }
++  rv = localDir->AppendRelativeNativePath(nsDependentCString(".mozilla"));
++  if (NS_FAILED(rv)) {
++    return rv;
++  }
++  rv = localDir->Exists(&exists);
++  if (NS_FAILED(rv)) {
++    return rv;
++  }
++  /* otherwise, use new config */
++  if (!exists) {
++    const char* xdghomedir = PR_GetEnv("XDG_DATA_HOME");
++    if (!xdghomedir || !*xdghomedir) {
++      /* XDG_DATA_HOME=$HOME/.local/share */
++      rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
++                                 getter_AddRefs(localDir));
++      if (NS_FAILED(rv)) {
++        return rv;
++      }
++      rv = localDir->AppendRelativeNativePath(nsDependentCString(".local"));
++      if (NS_FAILED(rv)) {
++        return rv;
++      }
++      rv = localDir->Exists(&exists);
++      if (NS_SUCCEEDED(rv) && !exists) {
++        rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++        if (NS_FAILED(rv)) {
++          return rv;
++        }
++      }
++      rv = localDir->AppendRelativeNativePath(nsDependentCString("share"));
++      if (NS_FAILED(rv)) {
++        return rv;
++      }
++      rv = localDir->Exists(&exists);
++      if (NS_SUCCEEDED(rv) && !exists) {
++        rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++      }
++    }
++    else {
++      rv = NS_NewNativeLocalFile(nsDependentCString(xdghomedir), true,
++                                 getter_AddRefs(localDir));
++    }
++    if (NS_FAILED(rv)) {
++      return rv;
++    }
++    rv = localDir->AppendRelativeNativePath(nsDependentCString("mozilla"));
++    if (NS_FAILED(rv)) {
++      return rv;
++    }
++  }
+ #else
+ #error dont_know_how_to_get_product_dir_on_your_platform
+ #endif
+ 
++#if !defined(XP_UNIX) || defined(XP_MACOSX)
++  // Since we have to check for legacy configuration, we have
++  // the complete path for Linux already, so this is not
++  // needed. If we stop checking for legacy at some point,
++  // then we can change this to not be protected by
++  // this clause.
+   rv = localDir->AppendRelativeNativePath(DEFAULT_PRODUCT_DIR);
++
+   if (NS_FAILED(rv)) {
+     return rv;
+   }
++#endif
++
+   rv = localDir->Exists(&exists);
+ 
+   if (NS_SUCCEEDED(rv) && !exists) {
+@@ -323,7 +384,7 @@ nsresult nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
+ // GetDefaultUserProfileRoot - Gets the directory which contains each user
+ // profile dir
+ //
+-// UNIX   : ~/.mozilla/
++// UNIX   : ~/.mozilla/ or ${XDG_DATA_HOME:-~/.local/share}/mozilla
+ // WIN    : <Application Data folder on user's machine>\Mozilla\Profiles
+ // Mac    : :Documents:Mozilla:Profiles:
+ //----------------------------------------------------------------------------------------
diff --git a/config/firefox/default.nix b/config/firefox/default.nix
new file mode 100644
index 000000000000..8b2cd7fca661
--- /dev/null
+++ b/config/firefox/default.nix
@@ -0,0 +1,6 @@
+{ configTools, firefox }:
+
+configTools.setEnv firefox "firefox" {
+  XDG_DATA_HOME = "$HOME/state";
+  XDG_CACHE_HOME = "$HOME/state/cache";
+}
diff --git a/config/firefox/module.nix b/config/firefox/module.nix
new file mode 100644
index 000000000000..3e729a23f1be
--- /dev/null
+++ b/config/firefox/module.nix
@@ -0,0 +1,11 @@
+{ pkgs, ... }:
+
+{
+  home.qyliss.dirs.state.activationScripts.profile = ''
+    install -o qyliss -g users -d mozilla{,/firefox{,/default}}
+    ln -sf ${./profiles.ini} mozilla/firefox/profiles.ini
+    ln -sf ${./user.js} mozilla/firefox/default/user.js
+  '';
+
+  environment.systemPackages = with pkgs.pkgsConfigured; [ firefox-nightly ];
+}
diff --git a/config/firefox/nightly/D6695.diff b/config/firefox/nightly/D6695.diff
new file mode 100644
index 000000000000..31a08471a8cc
--- /dev/null
+++ b/config/firefox/nightly/D6695.diff
@@ -0,0 +1,159 @@
+--- a/toolkit/xre/nsXREDirProvider.cpp
++++ b/toolkit/xre/nsXREDirProvider.cpp
+@@ -390,13 +390,6 @@ nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent,
+     nsCOMPtr<nsIFile> localDir;
+     rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false);
+     if (NS_SUCCEEDED(rv)) {
+-#if defined(XP_MACOSX)
+-      rv = localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla"));
+-#else
+-      rv = localDir->AppendNative(NS_LITERAL_CSTRING(".mozilla"));
+-#endif
+-    }
+-    if (NS_SUCCEEDED(rv)) {
+       localDir.swap(file);
+     }
+   }
+@@ -1249,8 +1242,6 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult,
+             nsDependentCString(hasVendor ? GetAppVendor() : GetAppName())))) {
+       return NS_ERROR_FAILURE;
+     }
+-  } else if (NS_FAILED(localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla")))) {
+-    return NS_ERROR_FAILURE;
+   }
+
+   if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("updates"))) ||
+@@ -1378,6 +1369,9 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile,
+   NS_ENSURE_SUCCESS(rv, rv);
+
+   localDir = dirFileMac;
++
++  rv = localDir->AppendRelativeNativePath(nsDependentCString("Mozilla"));
++  NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_IOS)
+   nsAutoCString userDir;
+   if (GetUIKitDirectory(aLocal, userDir)) {
+@@ -1401,6 +1395,9 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile,
+   NS_ENSURE_SUCCESS(rv, rv);
+
+   rv = NS_NewLocalFile(path, true, getter_AddRefs(localDir));
++  NS_ENSURE_SUCCESS(rv, rv);
++  rv = localDir->AppendRelativeNativePath(nsDependentCString("Mozilla"));
++  NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_UNIX)
+   const char* homeDir = getenv("HOME");
+   if (!homeDir || !*homeDir) return NS_ERROR_FAILURE;
+@@ -1422,8 +1419,51 @@ nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile,
+         rv = localDir->AppendNative(NS_LITERAL_CSTRING(".cache"));
+     }
+   } else {
++    bool exists;
++    /* check old config ~/.mozilla */
+     rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
+                                getter_AddRefs(localDir));
++    NS_ENSURE_SUCCESS(rv, rv);
++    rv = localDir->AppendRelativeNativePath(nsDependentCString(".mozilla"));
++    NS_ENSURE_SUCCESS(rv, rv);
++    rv = localDir->Exists(&exists);
++    NS_ENSURE_SUCCESS(rv, rv);
++    /* otherwise, use new config */
++    if (!exists) {
++      const char* xdghomedir = getenv("XDG_DATA_HOME");
++      if (!xdghomedir || !*xdghomedir) {
++        rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
++                                   getter_AddRefs(localDir));
++        NS_ENSURE_SUCCESS(rv, rv);
++        rv = localDir->AppendRelativeNativePath(nsDependentCString(".local"));
++        NS_ENSURE_SUCCESS(rv, rv);
++        rv = localDir->Exists(&exists);
++        if (NS_SUCCEEDED(rv) && !exists) {
++          rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++          NS_ENSURE_SUCCESS(rv, rv);
++        }
++        rv = localDir->AppendRelativeNativePath(nsDependentCString("share"));
++        NS_ENSURE_SUCCESS(rv, rv);
++        rv = localDir->Exists(&exists);
++        if (NS_SUCCEEDED(rv) && !exists) {
++          rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++        }
++      }
++      else {
++        rv = NS_NewNativeLocalFile(nsDependentCString(xdghomedir), true,
++                               getter_AddRefs(localDir));
++      }
++      NS_ENSURE_SUCCESS(rv, rv);
++
++      rv = localDir->AppendRelativeNativePath(nsDependentCString("mozilla"));
++      NS_ENSURE_SUCCESS(rv, rv);
++      rv = localDir->Exists(&exists);
++      NS_ENSURE_SUCCESS(rv, rv);
++      if (NS_SUCCEEDED(rv) && !exists) {
++        rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
++        NS_ENSURE_SUCCESS(rv, rv);
++      }
++    }
+   }
+ #else
+ #error "Don't know how to get product dir on your platform"
+@@ -1534,20 +1574,12 @@ nsresult nsXREDirProvider::AppendSysUserExtensionPath(nsIFile* aFile) {
+
+ #if defined(XP_MACOSX) || defined(XP_WIN)
+
+-  static const char* const sXR = "Mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "Extensions";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+
+ #elif defined(XP_UNIX)
+
+-  static const char* const sXR = ".mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "extensions";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+@@ -1565,20 +1597,12 @@ nsresult nsXREDirProvider::AppendSysUserExtensionsDevPath(nsIFile* aFile) {
+
+ #if defined(XP_MACOSX) || defined(XP_WIN)
+
+-  static const char* const sXR = "Mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "SystemExtensionsDev";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+
+ #elif defined(XP_UNIX)
+
+-  static const char* const sXR = ".mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "systemextensionsdev";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+@@ -1636,9 +1660,6 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) {
+   NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_UNIX)
+   nsAutoCString folder;
+-  // Make it hidden (by starting with "."), except when local (the
+-  // profile is already under ~/.cache or XDG_CACHE_HOME).
+-  if (!aLocal) folder.Assign('.');
+
+   if (!profile.IsEmpty()) {
+     // Skip any leading path characters
+@@ -1658,9 +1679,6 @@ nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) {
+       folder.Append(vendor);
+       ToLowerCase(folder);
+
+-      rv = aFile->AppendNative(folder);
+-      NS_ENSURE_SUCCESS(rv, rv);
+-
+       folder.Truncate();
+     }
+
diff --git a/config/firefox/overlay.nix b/config/firefox/overlay.nix
new file mode 100644
index 000000000000..74ce8760c53f
--- /dev/null
+++ b/config/firefox/overlay.nix
@@ -0,0 +1,18 @@
+self: super:
+
+{
+  firefoxPackages = with super.firefoxPackages;
+    super.firefoxPackages // {
+      firefox = firefox.overrideAttrs ({ patches ? [], ... }: {
+        patches = patches ++ [ release/D6695.diff ];
+      });
+
+      firefox-beta = firefox-beta.overrideAttrs ({ patches ? [], ... }: {
+        patches = patches ++ [ beta/D6695.diff ];
+      });
+
+      firefox-nightly = firefox-nightly.overrideAttrs ({ patches ? [], ... }: {
+        patches = patches ++ [ nightly/D6695.diff ];
+      });
+    };
+}
diff --git a/config/firefox/profiles.ini b/config/firefox/profiles.ini
new file mode 100644
index 000000000000..becf53354e76
--- /dev/null
+++ b/config/firefox/profiles.ini
@@ -0,0 +1,8 @@
+[General]
+StartWithLastProfile=1
+
+[Profile0]
+Name=default
+IsRelative=1
+Path=default
+Default=1
diff --git a/config/firefox/release/D6695.diff b/config/firefox/release/D6695.diff
new file mode 100644
index 000000000000..d15342ba50fa
--- /dev/null
+++ b/config/firefox/release/D6695.diff
@@ -0,0 +1,405 @@
+diff --git a/toolkit/moz.build b/toolkit/moz.build
+index 109fb2ce9..0b871d931 100644
+--- a/toolkit/moz.build
++++ b/toolkit/moz.build
+@@ -72,3 +72,5 @@ with Files('mozapps/preferences/**'):
+ with Files('pluginproblem/**'):
+     BUG_COMPONENT = ('Core', 'Plug-ins')
+ 
++if CONFIG['ENABLE_TESTS']:
++    DIRS += ['tests/gtest']
+diff --git a/toolkit/tests/gtest/TestXREAppDir.cpp b/toolkit/tests/gtest/TestXREAppDir.cpp
+new file mode 100644
+index 000000000..afa5f1b54
+--- /dev/null
++++ b/toolkit/tests/gtest/TestXREAppDir.cpp
+@@ -0,0 +1,94 @@
++/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
++/* vim:set ts=2 sw=2 sts=2 et cindent: */
++/* This Source Code Form is subject to the terms of the Mozilla Public
++ * License, v. 2.0. If a copy of the MPL was not distributed with this
++ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
++
++#include "nsXREDirProvider.h"
++#include "gtest/gtest.h"
++
++#if defined(XP_UNIX) && !defined(XP_MACOSX)
++
++#include <stdlib.h>
++#include <unistd.h>
++#include <sys/stat.h>
++
++// Remove @path and all its subdirs.
++static void
++cleanup(std::string path)
++{
++  nsresult rv;
++  nsCOMPtr<nsIFile> localDir;
++  rv = NS_NewNativeLocalFile(
++    nsDependentCString((char*)path.c_str()), true, getter_AddRefs(localDir));
++  EXPECT_EQ(NS_OK, rv);
++  rv = localDir->Remove(true);
++  EXPECT_EQ(NS_OK, rv);
++}
++
++// Create a temp dir and set HOME to it.
++// Upon successful completion, return the string with the path of the homedir.
++static std::string
++getNewHome()
++{
++  char tmpHomedir[] = "/tmp/mozilla-tmp.XXXXXX";
++  std::string homedir = mkdtemp(tmpHomedir);
++  EXPECT_EQ(0, setenv("HOME", (char*)homedir.c_str(), 1));
++  return homedir;
++}
++
++// Check if '$HOME/.mozilla' is used when it exists.
++TEST(toolkit_xre, LegacyAppUserDir)
++{
++  nsCOMPtr<nsIFile> localDir;
++  nsresult rv;
++  nsAutoCString cwd;
++  std::string homedir = getNewHome();
++  ASSERT_EQ(0, mkdir((char*)(homedir + "/.mozilla").c_str(), S_IRWXU));
++  rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(localDir));
++  ASSERT_EQ(NS_OK, rv);
++  localDir->GetNativePath(cwd);
++  std::string expectedAppDir = homedir + "/.mozilla/firefox";
++  std::string appDir = cwd.get();
++  ASSERT_EQ(expectedAppDir, appDir);
++  cleanup(homedir);
++}
++
++// Check if '$HOME/.local/share/mozilla' is used
++// if $HOME/.mozilla does not exist and the env
++// variable XDG_DATA_HOME is not set.
++TEST(toolkit_xre, XDGDefaultAppUserDir)
++{
++  nsCOMPtr<nsIFile> localDir;
++  nsresult rv;
++  nsAutoCString cwd;
++  std::string homedir = getNewHome();
++  ASSERT_EQ(0, unsetenv("XDG_DATA_HOME"));
++  rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(localDir));
++  ASSERT_EQ(NS_OK, rv);
++  localDir->GetNativePath(cwd);
++  std::string expectedAppDir = homedir + "/.local/share/mozilla/firefox";
++  std::string appDir = cwd.get();
++  ASSERT_EQ(expectedAppDir, appDir);
++  cleanup(homedir);
++}
++
++// Check if '$XDG_DATA_HOME/mozilla' is
++// used if '$HOME/.mozilla' does not exist
++// and the env variable XDG_DATA_HOME is set.
++TEST(toolkit_xre, XDGAppUserDir)
++{
++  nsCOMPtr<nsIFile> localDir;
++  nsresult rv;
++  nsAutoCString cwd;
++  std::string homedir = getNewHome();
++  ASSERT_EQ(0, setenv("XDG_DATA_HOME", (char*)homedir.c_str(), 1));
++  rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(localDir));
++  ASSERT_EQ(NS_OK, rv);
++  localDir->GetNativePath(cwd);
++  std::string expectedAppDir = homedir + "/mozilla/firefox";
++  std::string appDir = cwd.get();
++  ASSERT_EQ(expectedAppDir, appDir);
++  cleanup(homedir);
++}
++#endif
+diff --git a/toolkit/tests/gtest/moz.build b/toolkit/tests/gtest/moz.build
+new file mode 100644
+index 000000000..4c1a10181
+--- /dev/null
++++ b/toolkit/tests/gtest/moz.build
+@@ -0,0 +1,17 @@
++# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
++# vim: set filetype=python:
++# This Source Code Form is subject to the terms of the Mozilla Public
++# License, v. 2.0. If a copy of the MPL was not distributed with this
++# file, you can obtain one at http://mozilla.org/MPL/2.0/.
++
++Library('toolkit')
++
++UNIFIED_SOURCES = [
++    'TestXREAppDir.cpp',
++]
++
++LOCAL_INCLUDES += [
++  '/toolkit/xre'
++]
++
++FINAL_LIBRARY = 'xul-gtest'
+diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp
+index 62da8de9b..164d7792e 100644
+--- a/toolkit/xre/nsXREDirProvider.cpp
++++ b/toolkit/xre/nsXREDirProvider.cpp
+@@ -421,13 +421,6 @@ nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent,
+     nsCOMPtr<nsIFile> localDir;
+     rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false);
+     if (NS_SUCCEEDED(rv)) {
+-#if defined(XP_MACOSX)
+-      rv = localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla"));
+-#else
+-      rv = localDir->AppendNative(NS_LITERAL_CSTRING(".mozilla"));
+-#endif
+-    }
+-    if (NS_SUCCEEDED(rv)) {
+       localDir.swap(file);
+     }
+   }
+@@ -1381,7 +1374,8 @@ nsXREDirProvider::GetUpdateRootDir(nsIFile* *aResult)
+                                          GetAppName())))) {
+       return NS_ERROR_FAILURE;
+     }
+-  } else if (NS_FAILED(localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla")))) {
++  }
++  else if (NS_FAILED(localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla")))) {
+     return NS_ERROR_FAILURE;
+   }
+ 
+@@ -1562,6 +1556,9 @@ nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal)
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+   localDir = dirFileMac;
++
++  rv = localDir->AppendRelativeNativePath(nsDependentCString("Mozilla"));
++  NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_IOS)
+   nsAutoCString userDir;
+   if (GetUIKitDirectory(aLocal, userDir)) {
+@@ -1587,6 +1584,9 @@ nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal)
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+   rv = NS_NewLocalFile(path, true, getter_AddRefs(localDir));
++  NS_ENSURE_SUCCESS(rv, rv);
++  rv = localDir->AppendRelativeNativePath(nsDependentCString("Mozilla"));
++  NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_UNIX)
+   const char* homeDir = getenv("HOME");
+   if (!homeDir || !*homeDir)
+@@ -1609,8 +1609,51 @@ nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal)
+         rv = localDir->AppendNative(NS_LITERAL_CSTRING(".cache"));
+     }
+   } else {
++    bool exists;
++    // check old config ~/.mozilla
+     rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
+                                getter_AddRefs(localDir));
++    NS_ENSURE_SUCCESS(rv, rv);
++    rv = localDir->AppendRelativeNativePath(nsDependentCString(".mozilla"));
++    NS_ENSURE_SUCCESS(rv, rv);
++    rv = localDir->Exists(&exists);
++    NS_ENSURE_SUCCESS(rv, rv);
++    // otherwise, use new config
++    if (!exists) {
++      const char* xdghomedir = getenv("XDG_DATA_HOME");
++      if (!xdghomedir || !*xdghomedir) {
++        rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
++                                   getter_AddRefs(localDir));
++        NS_ENSURE_SUCCESS(rv, rv);
++        rv = localDir->AppendRelativeNativePath(nsDependentCString(".local"));
++        NS_ENSURE_SUCCESS(rv, rv);
++        rv = localDir->Exists(&exists);
++        if (NS_SUCCEEDED(rv) && !exists) {
++          rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++          NS_ENSURE_SUCCESS(rv, rv);
++        }
++        rv = localDir->AppendRelativeNativePath(nsDependentCString("share"));
++        NS_ENSURE_SUCCESS(rv, rv);
++        rv = localDir->Exists(&exists);
++        if (NS_SUCCEEDED(rv) && !exists) {
++          rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++        }
++      }
++      else {
++        rv = NS_NewNativeLocalFile(nsDependentCString(xdghomedir), true,
++                               getter_AddRefs(localDir));
++      }
++      NS_ENSURE_SUCCESS(rv, rv);
++
++      rv = localDir->AppendRelativeNativePath(nsDependentCString("mozilla"));
++      NS_ENSURE_SUCCESS(rv, rv);
++      rv = localDir->Exists(&exists);
++      NS_ENSURE_SUCCESS(rv, rv);
++      if (NS_SUCCEEDED(rv) && !exists) {
++        rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
++        NS_ENSURE_SUCCESS(rv, rv);
++      }
++    }
+   }
+ #else
+ #error "Don't know how to get product dir on your platform"
+@@ -1734,20 +1777,12 @@ nsXREDirProvider::AppendSysUserExtensionPath(nsIFile* aFile)
+ 
+ #if defined (XP_MACOSX) || defined(XP_WIN)
+ 
+-  static const char* const sXR = "Mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "Extensions";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+ #elif defined(XP_UNIX)
+ 
+-  static const char* const sXR = ".mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "extensions";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+@@ -1767,20 +1802,12 @@ nsXREDirProvider::AppendSysUserExtensionsDevPath(nsIFile* aFile)
+ 
+ #if defined (XP_MACOSX) || defined(XP_WIN)
+ 
+-  static const char* const sXR = "Mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "SystemExtensionsDev";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+ 
+ #elif defined(XP_UNIX)
+ 
+-  static const char* const sXR = ".mozilla";
+-  rv = aFile->AppendNative(nsDependentCString(sXR));
+-  NS_ENSURE_SUCCESS(rv, rv);
+-
+   static const char* const sExtensions = "systemextensionsdev";
+   rv = aFile->AppendNative(nsDependentCString(sExtensions));
+   NS_ENSURE_SUCCESS(rv, rv);
+@@ -1843,10 +1870,6 @@ nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal)
+   NS_ENSURE_SUCCESS(rv, rv);
+ #elif defined(XP_UNIX)
+   nsAutoCString folder;
+-  // Make it hidden (by starting with "."), except when local (the
+-  // profile is already under ~/.cache or XDG_CACHE_HOME).
+-  if (!aLocal)
+-    folder.Assign('.');
+ 
+   if (!profile.IsEmpty()) {
+     // Skip any leading path characters
+@@ -1869,8 +1892,12 @@ nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal)
+       folder.Append(vendor);
+       ToLowerCase(folder);
+ 
+-      rv = aFile->AppendNative(folder);
+-      NS_ENSURE_SUCCESS(rv, rv);
++      // Keep the 'mozilla' path for cache:
++      // Use ${XDG_DATA_HOME:-$HOME/.cache}/mozilla/firefox
++      if (aLocal) {
++        rv = aFile->AppendNative(folder);
++        NS_ENSURE_SUCCESS(rv, rv);
++      }
+ 
+       folder.Truncate();
+     }
+diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp
+index 45628ed7a..dfe517c77 100644
+--- a/xpcom/io/nsAppFileLocationProvider.cpp
++++ b/xpcom/io/nsAppFileLocationProvider.cpp
+@@ -252,7 +252,7 @@ nsAppFileLocationProvider::CloneMozBinDirectory(nsIFile** aLocalFile)
+ //----------------------------------------------------------------------------------------
+ // GetProductDirectory - Gets the directory which contains the application data folder
+ //
+-// UNIX   : ~/.mozilla/
++// UNIX   : ~/.mozilla/ or ${XDG_DATA_HOME:-~/.local/share}/mozilla
+ // WIN    : <Application Data folder on user's machine>\Mozilla
+ // Mac    : :Documents:Mozilla:
+ //----------------------------------------------------------------------------------------
+@@ -297,19 +297,80 @@ nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
+     return rv;
+   }
+ #elif defined(XP_UNIX)
+-  rv = NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true,
++  const char* homeDir = PR_GetEnv("HOME");
++  /* check old config ~/.mozilla */
++  rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
+                              getter_AddRefs(localDir));
+   if (NS_FAILED(rv)) {
+     return rv;
+   }
++  rv = localDir->AppendRelativeNativePath(nsDependentCString(".mozilla"));
++  if (NS_FAILED(rv)) {
++    return rv;
++  }
++  rv = localDir->Exists(&exists);
++  if (NS_FAILED(rv)) {
++    return rv;
++  }
++  /* otherwise, use new config */
++  if (!exists) {
++    const char* xdghomedir = PR_GetEnv("XDG_DATA_HOME");
++    if (!xdghomedir || !*xdghomedir) {
++      /* XDG_DATA_HOME=$HOME/.local/share */
++      rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true,
++                                 getter_AddRefs(localDir));
++      if (NS_FAILED(rv)) {
++        return rv;
++      }
++      rv = localDir->AppendRelativeNativePath(nsDependentCString(".local"));
++      if (NS_FAILED(rv)) {
++        return rv;
++      }
++      rv = localDir->Exists(&exists);
++      if (NS_SUCCEEDED(rv) && !exists) {
++        rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++        if (NS_FAILED(rv)) {
++          return rv;
++        }
++      }
++      rv = localDir->AppendRelativeNativePath(nsDependentCString("share"));
++      if (NS_FAILED(rv)) {
++        return rv;
++      }
++      rv = localDir->Exists(&exists);
++      if (NS_SUCCEEDED(rv) && !exists) {
++        rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
++      }
++    }
++    else {
++      rv = NS_NewNativeLocalFile(nsDependentCString(xdghomedir), true,
++                                 getter_AddRefs(localDir));
++    }
++    if (NS_FAILED(rv)) {
++      return rv;
++    }
++    rv = localDir->AppendRelativeNativePath(nsDependentCString("mozilla"));
++    if (NS_FAILED(rv)) {
++      return rv;
++    }
++  }
+ #else
+ #error dont_know_how_to_get_product_dir_on_your_platform
+ #endif
+ 
++#if !defined(XP_UNIX) || defined(XP_MACOSX)
++  // Since we have to check for legacy configuration, we have
++  // the complete path for Linux already, so this is not
++  // needed. If we stop checking for legacy at some point,
++  // then we can change this to not be protected by
++  // this clause.
+   rv = localDir->AppendRelativeNativePath(DEFAULT_PRODUCT_DIR);
++
+   if (NS_FAILED(rv)) {
+     return rv;
+   }
++#endif
++
+   rv = localDir->Exists(&exists);
+ 
+   if (NS_SUCCEEDED(rv) && !exists) {
+@@ -329,7 +390,7 @@ nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
+ //----------------------------------------------------------------------------------------
+ // GetDefaultUserProfileRoot - Gets the directory which contains each user profile dir
+ //
+-// UNIX   : ~/.mozilla/
++// UNIX   : ~/.mozilla/ or ${XDG_DATA_HOME:-~/.local/share}/mozilla
+ // WIN    : <Application Data folder on user's machine>\Mozilla\Profiles
+ // Mac    : :Documents:Mozilla:Profiles:
+ //----------------------------------------------------------------------------------------
diff --git a/config/firefox/user.js b/config/firefox/user.js
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/config/firefox/user.js
@@ -0,0 +1 @@
+
diff --git a/config/git/attributes b/config/git/attributes
new file mode 100644
index 000000000000..6f33cd4e46e9
--- /dev/null
+++ b/config/git/attributes
@@ -0,0 +1 @@
+*.plist diff=plist
diff --git a/config/git/config.nix b/config/git/config.nix
new file mode 100644
index 000000000000..88fa7220fe4d
--- /dev/null
+++ b/config/git/config.nix
@@ -0,0 +1,55 @@
+''
+[core]
+    attributesfile = ${./attributes}
+    excludesfile = ${./ignore}
+[user]
+    name = Alyssa Ross
+    email = hi@alyssa.is
+[alias]
+    ignore = "!gi() { curl -L -s https://www.gitignore.io/api/$@ ;}; gi"
+    unbranch = "!git branch --sort -HEAD --merged | sed 1d | xargs git branch -d"
+[push]
+    default = current
+[merge]
+    conflictstyle = diff3
+    tool = opendiff
+[color "branch"]
+    current = yellow reverse
+    local = yellow
+    remote = green
+[color "diff"]
+    meta = yellow bold
+    frag = magenta bold
+    old = red bold
+    new = green bold
+[color "status"]
+    added = yellow
+    changed = green
+    untracked = cyan
+[commit]
+    gpgsign = true
+    verbose = true
+[help]
+    autocorrect = 1
+[filter "lfs"]
+    smudge = git-lfs smudge -- %f
+    required = true
+    clean = git-lfs clean -- %f
+[diff]
+    compactionHeuristic = true
+    renameLimit = 0
+    wsErrorHighlight = all
+[credential]
+    helper = osxkeychain
+[hub]
+    protocol = ssh
+[branch]
+    autosetuprebase = always
+[diff "plist"]
+    textconv = /usr/bin/plutil -convert xml1 -o -
+[format]
+    pretty = format:%Cred%h %C(magenta)%G? %Cgreen(%ar)%Creset -%C(bold red)%d%Creset %s %C(bold blue)<%aN>%Creset
+[grep]
+    lineNumber = true
+    patternType = perl
+''
diff --git a/config/git/default.nix b/config/git/default.nix
new file mode 100644
index 000000000000..bc7d5bbb276c
--- /dev/null
+++ b/config/git/default.nix
@@ -0,0 +1,8 @@
+{ configTools, git }:
+
+configTools.xdgConfig {
+  package = git;
+  executable = "git";
+  path = "git/config";
+  text = import ./config.nix;
+}
diff --git a/config/git/ignore b/config/git/ignore
new file mode 100644
index 000000000000..412e6620e1af
--- /dev/null
+++ b/config/git/ignore
@@ -0,0 +1,22 @@
+# macOS
+.DS_Store
+
+# Kakoune
+*.kak.*
+
+# Sublime Text
+sublime-project
+sublime-workspace
+
+# IntelliJ IDEA
+.idea
+*.iml
+
+# Bundler
+gem_graph.png
+
+# Direnv
+.envrc
+
+# ctags
+/tags
diff --git a/config/gnupg/default.nix b/config/gnupg/default.nix
new file mode 100644
index 000000000000..a5a7b9b309c6
--- /dev/null
+++ b/config/gnupg/default.nix
@@ -0,0 +1,5 @@
+{ configTools, gnupg }:
+
+configTools.setEnv gnupg "gpg" {
+  GNUPGHOME = "$HOME/state/gnupg";
+}
diff --git a/config/gnupg/dirmngr.conf b/config/gnupg/dirmngr.conf
new file mode 100644
index 000000000000..9b7105671236
--- /dev/null
+++ b/config/gnupg/dirmngr.conf
@@ -0,0 +1 @@
+keyserver hkps://keyserver.ubuntu.com
diff --git a/config/gnupg/gpg-agent.conf.nix b/config/gnupg/gpg-agent.conf.nix
new file mode 100644
index 000000000000..36b2f196bfa4
--- /dev/null
+++ b/config/gnupg/gpg-agent.conf.nix
@@ -0,0 +1,9 @@
+{ stdenv, lib, pinentry_mac }:
+
+''
+${lib.optionalString stdenv.isDarwin ''
+  pinentry-program ${lib.concatStringsSep "/"
+    [ pinentry_mac "Applications" "pinentry-mac.app" "Contents" "MacOS"
+      "pinentry-mac" ]}
+''}
+''
diff --git a/config/gnupg/gpg.conf b/config/gnupg/gpg.conf
new file mode 100644
index 000000000000..ec69a24559bb
--- /dev/null
+++ b/config/gnupg/gpg.conf
@@ -0,0 +1,2 @@
+auto-key-retrieve
+ask-cert-level
diff --git a/config/gnupg/module.nix b/config/gnupg/module.nix
new file mode 100644
index 000000000000..d134443df70f
--- /dev/null
+++ b/config/gnupg/module.nix
@@ -0,0 +1,23 @@
+{ pkgs, lib, ... }:
+
+let
+  inherit (pkgs) callPackage writeText;
+  inherit (lib) concatStringsSep mapAttrsToList;
+
+  configs = {
+    "gpg.conf" = ./gpg.conf;
+    "gpg-agent.conf" = writeText "gpg-agent.conf"
+                         (callPackage ./gpg-agent.conf.nix {});
+    "dirmngr.conf" = ./dirmngr.conf;
+  };
+
+in
+  {
+    home.qyliss.dirs."state/gnupg".activationScripts.config = ''
+      ${concatStringsSep "\n" (mapAttrsToList (name: path: ''
+        ln -sf ${path} ${name}
+      '') configs)}
+    '';
+
+    environment.systemPackages = with pkgs.pkgsConfigured; [ gnupg ];
+  }
diff --git a/config/isync/default.nix b/config/isync/default.nix
new file mode 100644
index 000000000000..481fe84d9b82
--- /dev/null
+++ b/config/isync/default.nix
@@ -0,0 +1,7 @@
+{ configTools, writeText, isync, gnupg }:
+
+let
+  mbsyncrc = writeText "mbsyncrc" (import ./mbsyncrc.nix { inherit gnupg; });
+
+in
+  configTools.addFlags isync "mbsync" ''-c ${mbsyncrc}''
diff --git a/config/isync/imappass.gpg b/config/isync/imappass.gpg
new file mode 100644
index 000000000000..b2e85358794a
--- /dev/null
+++ b/config/isync/imappass.gpg
Binary files differdiff --git a/config/isync/mbsyncrc.nix b/config/isync/mbsyncrc.nix
new file mode 100644
index 000000000000..f714b97f0adf
--- /dev/null
+++ b/config/isync/mbsyncrc.nix
@@ -0,0 +1,26 @@
+{ gnupg }:
+
+''
+Create Both
+
+MaildirStore local
+  Path ~/mail/
+  Inbox ~/mail/INBOX
+  Subfolders Verbatim
+
+IMAPAccount fastmail
+  Host imap.fastmail.com
+  User alyssa@fastmail.com
+  PassCmd "${gnupg}/bin/gpg -d -q --no-tty ${./imappass.gpg}"
+  SSLType IMAPS
+  SSLVersions TLSv1.2
+
+IMAPStore fastmail-remote
+  Account fastmail
+
+Channel fastmail
+  Master :fastmail-remote:
+  Slave :local:
+  Patterns *
+  SyncState *
+''
diff --git a/config/kakoune/default.nix b/config/kakoune/default.nix
new file mode 100644
index 000000000000..aade2eca775f
--- /dev/null
+++ b/config/kakoune/default.nix
@@ -0,0 +1,12 @@
+{ configTools, kakoune, fzf, tmux }:
+
+let
+  kakrc = import ./kakrc.nix { inherit fzf tmux; };
+
+in
+  configTools.xdgConfig {
+    package = kakoune;
+    executable = "kak";
+    path = "kak/kakrc";
+    text = kakrc;
+  }
diff --git a/config/kakoune/kakrc.nix b/config/kakoune/kakrc.nix
new file mode 100644
index 000000000000..2168b37a6b26
--- /dev/null
+++ b/config/kakoune/kakrc.nix
@@ -0,0 +1,96 @@
+{ fzf, tmux }:
+
+''
+# Highlight any files whose names start with "zsh" as sh
+hook global BufCreate (.*/)?\.?zsh.* %{
+  set-option buffer filetype sh
+}
+
+# Highlight files ending in .conf as ini
+# (Will probably be close enough)
+hook global BufCreate .*\.conf %{
+  set-option buffer filetype ini
+}
+
+# Highlight files ending in .html.erb as html
+hook global BufCreate .*\.html.erb %{
+  set-option buffer filetype html
+}
+
+# Strip trailing whitespace on save
+hook global BufWritePre .*(?<!\.diff)$ %{
+  try %{execute-keys -draft %{%s\h+$<ret>d}}
+  echo
+}
+
+define-command -docstring "import from the system clipboard" clipboard-import %{
+  set-register dquote %sh{pbpaste}
+  echo -markup "{Information}imported system clipboard to register """
+}
+
+define-command -docstring "export to the system clipboard" clipboard-export %{
+  nop %sh{ printf "%s" "$kak_main_reg_dquote" | pbcopy }
+  echo -markup "{Information}exported register "" to system clipboard"
+}
+
+define-command -docstring "open a file using fzf" open %{
+  edit %sh{
+    git ls-files -oc --exclude-standard |
+      ${fzf}/bin/fzf-tmux -r 80 --reverse
+  }
+}
+alias global o open
+
+define-command -params 1.. -docstring "change file modes" chmod %{
+  nop %sh{ chmod $@ "$kak_buffile" }
+}
+
+declare-option str last_test
+define-command -docstring "intelligently run code" run %{
+  evaluate-commands -save-regs x %{
+    write
+    nop %sh{${tmux}/bin/tmux split-window -hl 80 "$kak_buffile; read"}
+  }
+}
+
+define-command mkdirp %{
+  nop %sh{mkdir -p "$(dirname "$kak_buffile")"}
+}
+
+define-command mkdirpw %{
+  mkdirp
+  write
+}
+
+map global normal <a-Y> :clipboard-import<ret>
+map global normal <a-y> :clipboard-export<ret>
+
+map global normal / /(?i)
+
+# Allow indenting from any position in the line, with the correct indentation.
+map global insert <tab> '<a-;><a-gt>'
+map global insert <s-tab> '<a-;><lt>'
+
+map -docstring "Remove Symbol hashrockets" global user \
+  <gt> 's:\S+<space>+=<gt><ret>;<a-?><space>+<ret>c:<esc>h<a-/>:<ret>d'
+
+map -docstring "Convert single quotes to double quotes" global user \
+  %{'} %{s'<ret>r"}
+
+map -docstring "Toggle line comments" global user \
+  %{#} %{:comment-line<ret>}
+
+declare-user-mode bookmarks
+map -docstring "Edit bookmarked file" global user b \
+  %{:enter-user-mode bookmarks<ret>}
+
+map -docstring "wiki" global bookmarks w %{:edit-wiki-index<ret>}
+
+set-option global indentwidth 2
+set-option global ui_options ncurses_assistant=none
+set-option global scrolloff 5,5
+set-option global autoreload yes
+
+add-highlighter global/ number-lines -hlcursor -separator "│"
+add-highlighter global/ show-matching
+''
diff --git a/config/less/default.nix b/config/less/default.nix
new file mode 100644
index 000000000000..56970cdd43aa
--- /dev/null
+++ b/config/less/default.nix
@@ -0,0 +1,5 @@
+{ configTools, less }:
+
+configTools.setEnv less "less" {
+  LESS = "cRS"; # use whole terminal, color, don't wrap
+}
diff --git a/config/msmtp/default.nix b/config/msmtp/default.nix
new file mode 100644
index 000000000000..9f99b5f3a51f
--- /dev/null
+++ b/config/msmtp/default.nix
@@ -0,0 +1,9 @@
+{ configTools, stdenv, writeText, gnupg, msmtp }:
+
+let
+  msmtprc = writeText "msmtprc" (import ./msmtprc.nix {
+    inherit stdenv gnupg;
+  });
+
+in
+  configTools.addFlags msmtp "msmtp" ''-C ${msmtprc}''
diff --git a/config/msmtp/msmtprc.nix b/config/msmtp/msmtprc.nix
new file mode 100644
index 000000000000..5320b27a470a
--- /dev/null
+++ b/config/msmtp/msmtprc.nix
@@ -0,0 +1,24 @@
+{ stdenv, gnupg }:
+
+''
+defaults
+auth on
+tls on
+tls_trust_file ${if stdenv.isDarwin
+                 then /etc/ssl/cert.pem
+                 else /etc/ssl/certs/ca-bundle.crt}
+
+account work
+host smtp.gmail.com
+port 587
+from alyssa.ross@freeagent.com
+user alyssa.ross@freeagent.com
+
+account personal
+host smtp.fastmail.com
+port 465
+tls_starttls off
+from hi@alyssa.is
+user alyssa@fastmail.com
+passwordeval ${gnupg}/bin/gpg --no-tty -q -d ${./smtppass.gpg}
+''
diff --git a/config/msmtp/smtppass.gpg b/config/msmtp/smtppass.gpg
new file mode 100644
index 000000000000..e60289a26e14
--- /dev/null
+++ b/config/msmtp/smtppass.gpg
Binary files differdiff --git a/config/neomutt/default.nix b/config/neomutt/default.nix
new file mode 100644
index 000000000000..dfd70e68ad71
--- /dev/null
+++ b/config/neomutt/default.nix
@@ -0,0 +1,7 @@
+{ configTools, writeText, gnupg, msmtp, neomutt }:
+
+let
+  muttrc = writeText "muttrc" (import ./muttrc.nix { inherit gnupg msmtp; });
+
+in
+  configTools.addFlags neomutt "neomutt" ''-F ${muttrc}''
diff --git a/config/neomutt/muttrc.nix b/config/neomutt/muttrc.nix
new file mode 100644
index 000000000000..adf5bb699a7d
--- /dev/null
+++ b/config/neomutt/muttrc.nix
@@ -0,0 +1,52 @@
+{ gnupg, msmtp }:
+
+''
+color index red default ~P
+
+set auto_tag = yes
+set beep = no
+set beep_new = yes
+set fast_reply = yes
+set folder = ~/mail
+set help = no
+set mark_old = no
+set pager = "less -+S"
+set quit = ask-yes
+set sort = threads
+set sort_browser = new
+unset prompt_after
+
+set spoolfile = +INBOX
+
+# set sendmail = "${msmtp}/bin/msmtp -a work"
+# set record = "=[Gmail]/Sent Mail"
+# set postponed = "=[Gmail]/Drafts"
+mailboxes =INBOX =Flats =Indirect =Roles =Services =Lists =Lists/GitHub =Lists/rust-users =Lists/libtool =Lists/Bugzilla =Lists/hacklab-members =Lists/edinburgh-ath =Drafts =Sent =Archive =Archive/Receipts =Trash =Spam
+set record = "=Sent"
+set trash = "=Archive"
+set postponed = "=Drafts"
+set sendmail = "${msmtp}/bin/msmtp -a personal"
+
+set pgp_use_gpg_agent = yes
+set crypt_autosign = yes
+set crypt_opportunistic_encrypt = yes
+set postpone_encrypt = yes
+
+# Required for postpone_encrypt to work
+set pgp_default_key = 757356D779BBB888773E415E736CCDF9EF51BD97
+
+set pgp_decode_command       = "${gnupg}/bin/gpg --status-fd=2 %?p?--pinentry-mode loopback --passphrase-fd 0? --no-verbose --quiet --batch --output - %f"
+set pgp_verify_command       = "${gnupg}/bin/gpg --status-fd=2 --no-verbose --quiet --batch --output - --verify %s %f"
+set pgp_decrypt_command      = "${gnupg}/bin/gpg --status-fd=2 %?p?--pinentry-mode loopback --passphrase-fd 0? --no-verbose --quiet --batch --output - --decrypt %f"
+set pgp_sign_command         = "${gnupg}/bin/gpg %?p?--pinentry-mode loopback --passphrase-fd 0? --no-verbose --batch --quiet --output - --armor --textmode %?a?--local-user %a? --detach-sign %f"
+set pgp_clearsign_command    = "${gnupg}/bin/gpg %?p?--pinentry-mode loopback --passphrase-fd 0? --no-verbose --batch --quiet --output - --armor --textmode %?a?--local-user %a? --clearsign %f"
+set pgp_encrypt_only_command = "pgpewrap ${gnupg}/bin/gpg --trust-model always --batch --quiet --no-verbose --output - --textmode --armor --encrypt -- --recipient %r -- %f"
+set pgp_encrypt_sign_command = "pgpewrap ${gnupg}/bin/gpg %?p?--pinentry-mode loopback --passphrase-fd 0? --trust-model always --batch --quiet --no-verbose --textmode --output - %?a?--local-user %a? --armor --sign --encrypt -- --recipient %r -- %f"
+set pgp_import_command       = "${gnupg}/bin/gpg --no-verbose --import %f"
+set pgp_export_command       = "${gnupg}/bin/gpg --no-verbose --armor --export %r"
+set pgp_verify_key_command   = "${gnupg}/bin/gpg --verbose --batch --fingerprint --check-sigs %r"
+set pgp_list_pubring_command = "${gnupg}/bin/gpg --no-verbose --batch --quiet --with-colons --with-fingerprint --with-fingerprint --list-keys %r"
+set pgp_list_secring_command = "${gnupg}/bin/gpg --no-verbose --batch --quiet --with-colons --with-fingerprint --with-fingerprint --list-secret-keys %r"
+set pgp_good_sign            = "^\\[GNUPG:\\] GOODSIG
+set pgp_decryption_okay      = "^\\[GNUPG:\\] DECRYPTION_OKAY"
+''
diff --git a/config/sway/config.nix b/config/sway/config.nix
new file mode 100644
index 000000000000..d1d6076ab25c
--- /dev/null
+++ b/config/sway/config.nix
@@ -0,0 +1,215 @@
+{ sway, rofi }:
+
+''
+### Variables
+#
+# Logo key. Use Mod1 for Alt.
+set $mod Mod4
+# Home row direction keys, like vim
+set $left h
+set $down j
+set $up k
+set $right l
+# Your preferred terminal emulator
+set $term alacritty
+# Your preferred application launcher
+# Note: it's recommended that you pass the final command to sway
+set $menu swaymsg exec ${rofi}/bin/rofi -show run
+
+### Output configuration
+#
+# Default wallpaper (more resolutions are available in ${sway}/share/backgrounds/sway/)
+output * bg ${sway}/share/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png fill
+#
+# Example configuration:
+#
+#   output HDMI-A-1 resolution 1920x1080 position 1920,0
+#
+# You can get the names of your outputs by running: swaymsg -t get_outputs
+
+### Idle configuration
+#
+# Example configuration:
+#
+#exec swayidle \
+#    timeout 300 'swaylock -c 000000' \
+#    timeout 600 'swaymsg "output * dpms off"' \
+#       resume 'swaymsg "output * dpms on"' \
+#    before-sleep 'swaylock -c 000000'
+#
+# This will lock your screen after 300 seconds of inactivity, then turn off
+# your displays after another 300 seconds, and turn your screens back on when
+# resumed. It will also lock your screen before your computer goes to sleep.
+
+### Input configuration
+#
+# Example configuration:
+#
+#   input "2:14:SynPS/2_Synaptics_TouchPad" {
+#       dwt enabled
+#       tap enabled
+#       natural_scroll enabled
+#       middle_emulation enabled
+#   }
+#
+# You can get the names of your inputs by running: swaymsg -t get_inputs
+# Read `man 5 sway-input` for more information about this section.
+
+### Key bindings
+#
+# Basics:
+#
+    # start a terminal
+    bindsym $mod+Return exec $term
+
+    # kill focused window
+    bindsym $mod+Shift+q kill
+
+    # start your launcher
+    bindsym $mod+d exec $menu
+
+    # Drag floating windows by holding down $mod and left mouse button.
+    # Resize them with right mouse button + $mod.
+    # Despite the name, also works for non-floating windows.
+    # Change normal to inverse to use left mouse button for resizing and right
+    # mouse button for dragging.
+    floating_modifier $mod normal
+
+    # reload the configuration file
+    bindsym $mod+Shift+c reload
+
+    # exit sway (logs you out of your Wayland session)
+    bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
+#
+# Moving around:
+#
+    # Move your focus around
+    bindsym $mod+$left focus left
+    bindsym $mod+$down focus down
+    bindsym $mod+$up focus up
+    bindsym $mod+$right focus right
+    # or use $mod+[up|down|left|right]
+    bindsym $mod+Left focus left
+    bindsym $mod+Down focus down
+    bindsym $mod+Up focus up
+    bindsym $mod+Right focus right
+
+    # _move_ the focused window with the same, but add Shift
+    bindsym $mod+Shift+$left move left
+    bindsym $mod+Shift+$down move down
+    bindsym $mod+Shift+$up move up
+    bindsym $mod+Shift+$right move right
+    # ditto, with arrow keys
+    bindsym $mod+Shift+Left move left
+    bindsym $mod+Shift+Down move down
+    bindsym $mod+Shift+Up move up
+    bindsym $mod+Shift+Right move right
+#
+# Workspaces:
+#
+    # switch to workspace
+    bindsym $mod+1 workspace 1
+    bindsym $mod+2 workspace 2
+    bindsym $mod+3 workspace 3
+    bindsym $mod+4 workspace 4
+    bindsym $mod+5 workspace 5
+    bindsym $mod+6 workspace 6
+    bindsym $mod+7 workspace 7
+    bindsym $mod+8 workspace 8
+    bindsym $mod+9 workspace 9
+    bindsym $mod+0 workspace 10
+    # move focused container to workspace
+    bindsym $mod+Shift+1 move container to workspace 1
+    bindsym $mod+Shift+2 move container to workspace 2
+    bindsym $mod+Shift+3 move container to workspace 3
+    bindsym $mod+Shift+4 move container to workspace 4
+    bindsym $mod+Shift+5 move container to workspace 5
+    bindsym $mod+Shift+6 move container to workspace 6
+    bindsym $mod+Shift+7 move container to workspace 7
+    bindsym $mod+Shift+8 move container to workspace 8
+    bindsym $mod+Shift+9 move container to workspace 9
+    bindsym $mod+Shift+0 move container to workspace 10
+    # Note: workspaces can have any name you want, not just numbers.
+    # We just use 1-10 as the default.
+#
+# Layout stuff:
+#
+    # You can "split" the current object of your focus with
+    # $mod+b or $mod+v, for horizontal and vertical splits
+    # respectively.
+    bindsym $mod+b splith
+    bindsym $mod+v splitv
+
+    # Switch the current container between different layout styles
+    bindsym $mod+s layout stacking
+    bindsym $mod+w layout tabbed
+    bindsym $mod+e layout toggle split
+
+    # Make the current focus fullscreen
+    bindsym $mod+f fullscreen
+
+    # Toggle the current focus between tiling and floating mode
+    bindsym $mod+Shift+space floating toggle
+
+    # Swap focus between the tiling area and the floating area
+    bindsym $mod+space focus mode_toggle
+
+    # move focus to the parent container
+    bindsym $mod+a focus parent
+#
+# Scratchpad:
+#
+    # Sway has a "scratchpad", which is a bag of holding for windows.
+    # You can send windows there and get them back later.
+
+    # Move the currently focused window to the scratchpad
+    bindsym $mod+Shift+minus move scratchpad
+
+    # Show the next scratchpad window or hide the focused scratchpad window.
+    # If there are multiple scratchpad windows, this command cycles through them.
+    bindsym $mod+minus scratchpad show
+#
+# Resizing containers:
+#
+mode "resize" {
+    # left will shrink the containers width
+    # right will grow the containers width
+    # up will shrink the containers height
+    # down will grow the containers height
+    bindsym $left resize shrink width 10px
+    bindsym $down resize grow height 10px
+    bindsym $up resize shrink height 10px
+    bindsym $right resize grow width 10px
+
+    # ditto, with arrow keys
+    bindsym Left resize shrink width 10px
+    bindsym Down resize grow height 10px
+    bindsym Up resize shrink height 10px
+    bindsym Right resize grow width 10px
+
+    # return to default mode
+    bindsym Return mode "default"
+    bindsym Escape mode "default"
+}
+bindsym $mod+r mode "resize"
+
+#
+# Status Bar:
+#
+# Read `man 5 sway-bar` for more information about this section.
+bar {
+    position top
+
+    # When the status_command prints a new line to stdout, swaybar updates.
+    # The default just shows the current date and time.
+    status_command while date +'%Y-%m-%d %l:%M:%S %p'; do sleep 1; done
+
+    colors {
+        statusline #ffffff
+        background #323232
+        inactive_workspace #32323200 #32323200 #5c5c5c
+    }
+}
+
+include ${sway}/etc/sway/config.d/*
+''
diff --git a/config/sway/default.nix b/config/sway/default.nix
new file mode 100644
index 000000000000..939613222328
--- /dev/null
+++ b/config/sway/default.nix
@@ -0,0 +1,7 @@
+{ configTools, writeText, sway, rofi }:
+
+let
+  config = import ./config.nix { inherit sway rofi; };
+
+in
+  configTools.addFlags sway "sway" ''-c ${writeText "sway-config" config}''
diff --git a/config/tmux/config.nix b/config/tmux/config.nix
new file mode 100644
index 000000000000..d62a472bc1d8
--- /dev/null
+++ b/config/tmux/config.nix
@@ -0,0 +1,90 @@
+{ stdenv, writeText, writeShellScriptBin
+, coreutils, extract_url, reattach-to-user-namespace, ruby, tmux, zsh }:
+
+''
+set -gw activity-action none
+set -g  allow-rename off
+set -g  base-index 1
+${if stdenv.isDarwin then ''
+  set -g default-command "${reattach-to-user-namespace}/bin/reattach-to-user-namespace -l ${zsh}/bin/zsh"
+'' else ''
+  set -g default-command "${zsh}/bin/zsh -l"
+''}
+set -gs default-terminal screen-256color
+set -s  escape-time 0
+set -gw mode-keys vi
+set -gw monitor-activity on
+set -g  mouse on
+set -gw pane-active-border-style fg=default,bright
+set -gw pane-base-index 1
+set -gw pane-border-format ' #{pane_current_command} '
+set -gw pane-border-status top
+set -g  prefix C-a
+set -g  renumber-windows on
+set -g  status-left ""
+set -g  status-position top
+set -g  status-right ""
+set -ga status-right " #{?client_prefix, #[bright]#{prefix}#[nobright] ,}"
+# set -ga status-right ' #(cd #{pane_current_path} && zsh -c "print -P %%~") '
+# set -ga status-right '#(head="$(git -C #{pane_current_path} rev-parse --abbrev-ref HEAD 2>/dev/null)" && echo " $head ")'
+# set -ga status-right ' #(pmset -g batt | grep -o \\d*%%) '
+set -ga status-right " %d-%b-%y %H:%M"
+set -g  status-right-length 200
+set -g  status-style bg=default,fg=default
+set -gw window-status-style fg=default
+set -gw window-status-activity-style fg=default,underscore
+set -gw window-status-bell-style fg=brightcyan
+set -gw window-status-current-format '#W'
+set -gw window-status-current-style bright
+set -gw window-status-format '#W'
+set -gw window-status-separator '  '
+
+bind -n   F1  select-window -t :1
+bind -n   F2  select-window -t :2
+bind -n   F3  select-window -t :3
+bind -n   F4  select-window -t :4
+bind -n   F5  select-window -t :5
+bind -n   F6  select-window -t :6
+bind -n   F7  select-window -t :7
+bind -n   F8  select-window -t :8
+bind -n   F9  select-window -t :9
+bind -n   F10 select-window -t :10
+bind -n   F11 select-window -t :11
+bind -n   F12 select-window -t :12
+bind -n S-F1  select-window -t :13
+bind -n S-F2  select-window -t :14
+bind -n S-F3  select-window -t :15
+bind -n S-F4  select-window -t :16
+bind -n S-F5  select-window -t :17
+bind -n S-F6  select-window -t :18
+bind -n S-F7  select-window -t :19
+bind -n S-F8  select-window -t :20
+
+bind -n C-l send-keys C-l \; run-shell "sleep 0.3" \; clear-history
+bind    C-l send-keys -R \; clear-history
+
+bind '"' split-window -v -c '#{pane_current_path}'
+bind  %  split-window -h -c '#{pane_current_path}'
+
+bind C-a send-prefix
+
+unbind C-b
+
+set-hook -g after-select-pane "refresh-client -S"
+set-hook -g alert-activity "refresh-client -S"
+
+bind u run ${writeShellScriptBin "tmux-url" ''
+set -ue
+buffer="$(mktemp -t tmux-buffer)"
+open=${writeText "tmux-open-url.rb" ''
+#!${ruby}/bin/ruby
+require "cgi"
+exec "/usr/bin/open", CGI.unescape(ARGV[0]), *ARGV[1..-1]
+''}
+${tmux}/bin/tmux capture-pane -J
+${tmux}/bin/tmux save-buffer "$buffer"
+${tmux}/bin/tmux split-window -l 10 -f "${coreutils}/bin/tac $buffer | BROWSER=\"$open\"i ${extract_url}/bin/extract_url"
+''}/bin/tmux-url
+
+# bind u run "~/.config/tmux/url.sh"
+''
diff --git a/config/tmux/default.nix b/config/tmux/default.nix
new file mode 100644
index 000000000000..af6723aa7349
--- /dev/null
+++ b/config/tmux/default.nix
@@ -0,0 +1,12 @@
+{ configTools, tmux
+, stdenv, writeText, writeShellScriptBin
+, coreutils, extract_url, reattach-to-user-namespace, ruby, zsh }:
+
+let
+  config = writeText "tmux.conf" (import ./config.nix {
+    inherit stdenv writeText writeShellScriptBin
+            coreutils extract_url reattach-to-user-namespace ruby tmux zsh;
+  });
+
+in
+  configTools.addFlags tmux "tmux" ''-f ${config}''
diff --git a/config/tools.nix b/config/tools.nix
new file mode 100644
index 000000000000..5e26bafdda17
--- /dev/null
+++ b/config/tools.nix
@@ -0,0 +1,40 @@
+{ lib, symlinkJoin, writeTextFile, writeShellScriptBin }:
+
+let
+  inherit (lib) concatStrings mapAttrsToList;
+
+in rec {
+
+  configure = pkg: wrapped:
+    (symlinkJoin {
+      name = "${pkg.name}-configured";
+      paths = [ wrapped pkg ];
+    }).overrideAttrs (oldAttrs: {
+      passthru = pkg.passthru or {};
+    }) // {
+      unconfigured = pkg;
+    };
+
+  addFlags = pkg: bin: flags:
+    configure pkg (writeShellScriptBin bin ''
+      exec ${pkg}/bin/${bin} ${flags} "$@"
+    '');
+
+  setEnv = pkg: bin: env:
+    configure pkg (writeShellScriptBin bin
+      ((concatStrings (mapAttrsToList
+        (name: value: "export ${name}=${value}\n") env)) + ''
+          exec ${pkg}/bin/${bin} "$@"
+        '')
+    );
+
+  xdgConfig = { package, executable, path, text }:
+    setEnv package executable {
+      XDG_CONFIG_HOME = writeTextFile {
+        name = "${package.name}-config";
+        destination = "/${path}";
+        inherit text;
+      };
+    };
+
+}
diff --git a/config/weechat/default.nix b/config/weechat/default.nix
new file mode 100644
index 000000000000..1c6f800d7685
--- /dev/null
+++ b/config/weechat/default.nix
@@ -0,0 +1,5 @@
+{ configTools, weechat }:
+
+configTools.setEnv weechat "weechat" {
+  WEECHAT_HOME = "$HOME/state/weechat";
+}
diff --git a/config/weechat/module.nix b/config/weechat/module.nix
new file mode 100644
index 000000000000..439fe175ae83
--- /dev/null
+++ b/config/weechat/module.nix
@@ -0,0 +1,12 @@
+{ pkgs, ... }:
+
+{
+  home.qyliss.dirs."state/weechat".activationScripts.git = ''
+    ${pkgs.git}/bin/git init -q
+    ${pkgs.git}/bin/git remote rm origin 2>/dev/null || true
+    ${pkgs.git}/bin/git remote add origin git@github.com:alyssais/weechat-config
+    chown -R qyliss .git
+  '';
+
+  environment.systemPackages = with pkgs.pkgsConfigured; [ weechat ];
+}
diff --git a/config/zsh/default.nix b/config/zsh/default.nix
new file mode 100644
index 000000000000..2c322ab6e928
--- /dev/null
+++ b/config/zsh/default.nix
@@ -0,0 +1,20 @@
+{ configTools, stdenv, lib, writeText, linkFarm, zsh
+, coreutils, zsh-nix-shell, zsh-syntax-highlighting
+, zsh-history-substring-search, zsh-autosuggestions
+}:
+
+let
+  inherit (lib) mapAttrsToList;
+
+  files = {
+    ".zshrc" = writeText "zshrc" (import ./zshrc.nix {
+      inherit stdenv lib coreutils zsh-nix-shell zsh-syntax-highlighting
+              zsh-history-substring-search zsh-autosuggestions;
+    });
+  };
+
+in
+  configTools.setEnv zsh "zsh" {
+    ZDOTDIR = linkFarm "zdotdir"
+                (mapAttrsToList (name: path: { inherit name path; }) files);
+  }
diff --git a/config/zsh/zshenv.nix b/config/zsh/zshenv.nix
new file mode 100644
index 000000000000..2ddac9a385b6
--- /dev/null
+++ b/config/zsh/zshenv.nix
@@ -0,0 +1,9 @@
+{ setEnvironment }:
+
+''
+unsetopt GLOBAL_RCS
+
+if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
+    . ${setEnvironment}
+fi
+''
diff --git a/config/zsh/zshrc.nix b/config/zsh/zshrc.nix
new file mode 100644
index 000000000000..e7553d63d873
--- /dev/null
+++ b/config/zsh/zshrc.nix
@@ -0,0 +1,195 @@
+{ stdenv, lib
+
+, coreutils
+, zsh-autosuggestions
+, zsh-history-substring-search
+, zsh-nix-shell
+, zsh-syntax-highlighting
+
+ }:
+
+let
+  options = {
+    # Completion
+    always_to_end = true;
+    auto_name_dirs = true;
+    complete_in_word = true;
+    list_packed = true;
+
+    # Expansion and Globbing
+    bad_pattern = true;
+    brace_ccl = true;
+    extended_glob = true;
+    equals = true;
+    glob_star_short = true;
+    magic_equal_subst = true;
+    rc_expand_param = true;
+    rematch_pcre = true;
+
+    # Input/Output
+    correct = true;
+    mail_warning = true;
+    rc_quotes = true;
+    rm_star_wait = true;
+    short_loops = true;
+
+    # History
+    share_history = true;
+    bang_hist = true;
+    hist_allow_clobber = true;
+    hist_beep = true;
+    hist_expire_dups_first = true;
+    hist_ignore_dups = true;
+    hist_ignore_space = true;
+    hist_no_store = true;
+    hist_reduce_blanks = true;
+
+    # Prompting
+    prompt_sp = true;
+
+    # Scripts and Functions
+    c_bases = true;
+    c_precedences = true;
+    octal_zeroes = true;
+  };
+
+  enabledOptions = lib.attrNames (lib.filterAttrs (_: lib.id) options);
+  disabledOptions = lib.attrNames (lib.filterAttrs (_: v: !v) options);
+
+in ''
+
+. ${<nixpkgs/nixos/modules/programs/zsh/zinputrc>}
+
+# Disable silver searcher numbers when piped or redirected.
+ag() {
+    if ! [ -t 1 ]; then
+        command ag --no-numbers "$@"
+    else
+        command ag "$@"
+    fi
+}
+
+fzf_ctrl_t() {
+    if git rev-parse >/dev/null 2>&1; then
+        git ls-files -co --exclude-standard
+    else
+        find . -type f | sed 's/^\.\///g'
+    fi
+}
+
+HISTFILE="$XDG_DATA_HOME/zsh/history"
+REPORTTIME=5
+SAVEHIST=9000
+ZSH_HIGHLIGHT_HIGHLIGHTERS=(main brackets)
+
+${lib.optionalString (enabledOptions == [])
+    "setopt ${lib.concatStringsSep " " enabledOptions}"}
+
+${lib.optionalString (disabledOptions == [])
+    "unsetopt ${lib.concatStringsSep " " enabledOptions}"}
+
+autoload -Uz colors && colors
+
+zstyle ':completion::complete:*' use-cache on
+zstyle ':completion::complete:*' cache-path "$XDG_CACHE_DIR/zsh/zcompcache"
+zstyle ':completion:*' auto-description '%d'
+zstyle ':completion:*' completer _expand _complete _ignored _match _correct \
+                                             _approximate _prefix
+zstyle ':completion:*' expand suffix
+zstyle ':completion:*' group-name '''
+zstyle ':completion:*' insert-unambiguous true
+zstyle ':completion:*' list-colors '''
+zstyle ':completion:*' list-suffixes true
+zstyle ':completion:*' matcher-list \
+    'm:{[:lower:]}={[:upper:]}' 'm:{[:lower:][:upper:]}={[:upper:][:lower:]}' \
+    'r:|[._-/]=* r:|=*' 'l:|=* r:|=*'
+zstyle ':completion:*' menu select=1
+zstyle ':completion:*' original false
+zstyle ':completion:*' preserve-prefix '//[^/]##/'
+zstyle ':completion:*' select-prompt \
+    %SScrolling active: current selection at %p%s
+zstyle ':completion:*' squeeze-slashes true
+zstyle ':completion:*' use-compctl true
+zstyle ':completion:*' verbose true
+
+autoload -U url-quote-magic
+zle -N self-insert url-quote-magic
+
+autoload -Uz compinit
+compinit -d "$XDG_CACHE_HOME/zsh/zcompdump"
+
+source ${zsh-nix-shell}/share/zsh-nix-shell/nix-shell.plugin.zsh
+source ${zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
+source ${zsh-history-substring-search}/share/zsh-history-substring-search/zsh-history-substring-search.zsh
+source ${zsh-autosuggestions}/share/zsh-autosuggestions/zsh-autosuggestions.zsh
+
+zmodload zsh/complist
+bindkey -M menuselect "^[[Z" reverse-menu-complete
+
+zmodload zsh/terminfo
+bindkey "^[[A" history-substring-search-up
+bindkey "^[[B" history-substring-search-down
+
+bindkey "^[[3~" delete-char
+bindkey "^[[F" end-of-line
+bindkey "^[[H" beginning-of-line
+bindkey "^[[1~" beginning-of-line
+bindkey "^[[4~" end-of-line
+bindkey "^[^[[C" forward-word
+bindkey "^[^[[D" backward-word
+
+if [ -n "$TMUX" ]; then
+    tmux-page-up() { tmux copy-mode -ue }
+    tmux-page-down() { tmux send-keys -X page-down 2>/dev/null }
+    zle -N tmux-page-up
+    zle -N tmux-page-down
+    bindkey "^[[5~" tmux-page-up
+    bindkey "^[[6~" tmux-page-down
+fi
+
+autoload -U edit-command-line
+zle -N edit-command-line
+bindkey "^x^e" edit-command-line
+
+if [[ -v TMUX ]]; then
+    _tmux_hook() {
+        local exit_status="$?"
+        tmux set-environment "last_exit_status_$(tmux display-message -p '#D')" "$exit_status"
+        tmux refresh-client -S
+    }
+
+    typeset -ag precmd_functions;
+    if [[ -z $${precmd_functions[(r)_tmux_hook]} ]]; then
+        precmd_functions+=_tmux_hook;
+    fi
+fi
+
+${lib.optionalString stdenv.isDarwin ''
+    # Load SSH passphrases from the Keychain.
+    # Use an explicit path because upstream OpenSSH
+    # doesn't have the keychain functionality.
+    (/usr/bin/ssh-add -A &) 2> /dev/null
+''}
+
+if command -v fzf-share >/dev/null; then
+    source "$(fzf-share)/key-bindings.zsh"
+fi
+
+# Aliases
+alias beep='printf "\a"'
+alias ls=${lib.escapeShellArg (if stdenv.isDarwin then
+                                 "/bin/ls -AFh"
+                               else
+                                 "${coreutils}/bin/ls -AF --si --color=auto")}
+alias tree='tree -aRF -I .git'
+alias vim=nvim
+
+for command in cargo curl dig find git mutt ri wget; do
+    alias "$command=noglob $command"
+done
+
+# Prompt
+nl=$'\n'
+PS1="%F{yellow}%(?..[exit %?]$nl)''${IN_NIX_SHELL+[nix-shell''${NIX_SHELL_PACKAGES:+($NIX_SHELL_PACKAGES)}] }%1(j.[%j job%2(j.s.)] .)%f%# "
+
+''
diff --git a/modules/home/default.nix b/modules/home/default.nix
new file mode 100644
index 000000000000..0ad1cb85b255
--- /dev/null
+++ b/modules/home/default.nix
@@ -0,0 +1,87 @@
+{ lib, config, ... }:
+
+let
+  inherit (lib) attrValues concatStringsSep mapAttrsToList mkOption
+                optionalString recursiveUpdate;
+  inherit (lib.types) bool loaOf nullOr str submodule;
+
+  dirOpts = { ... }: {
+    options = {
+      owner = mkOption {
+        default = null;
+        type = nullOr str;
+      };
+      group = mkOption {
+        default = "users";
+        type = str;
+      };
+      permissions = mkOption {
+        default = "0700";
+        type = str;
+      };
+      activationScripts = mkOption {
+        default = {};
+        type = loaOf str;
+      };
+    };
+  };
+
+  applyDirConfig = user: dir:
+    let
+      owner = if dir.owner == null
+              then user
+              else config.users.users.${dir.owner};
+    in
+      ''
+        chmod ${dir.permissions} .
+        chown ${owner.name}:${dir.group} .
+        ${concatStringsSep "\n" (attrValues dir.activationScripts)}
+      '';
+
+in
+  {
+    options = {
+      home = mkOption {
+        default = {};
+        type = loaOf (submodule (args: recursiveUpdate (dirOpts args) {
+          options = {
+            imperativeNix = mkOption {
+              default = false;
+              type = bool;
+            };
+            dirs = mkOption {
+              default = {};
+              type = loaOf (submodule dirOpts);
+            };
+          };
+        }));
+      };
+    };
+
+    config = {
+      system.activationScripts.home = {
+        deps = [];
+        text = concatStringsSep "\n" (mapAttrsToList
+          (key: home:
+            let
+              user = config.users.users.${key};
+
+            in ''
+              ${optionalString (!home.imperativeNix) ''
+                rm -rf ${user.home}/.nix-{defexpr,profile}
+              ''}
+
+              pushd ${user.home} >/dev/null
+              ${applyDirConfig user home}
+              ${concatStringsSep "\n" (mapAttrsToList (name: dir: ''
+                mkdir -p ${name}
+                pushd ${name} >/dev/null
+                ${applyDirConfig user dir}
+                popd >/dev/null
+              '') home.dirs)}
+              popd >/dev/null
+            ''
+        ) config.home);
+      };
+    };
+  }
diff --git a/modules/locale/default.nix b/modules/locale/default.nix
new file mode 100644
index 000000000000..eb92aeda76bd
--- /dev/null
+++ b/modules/locale/default.nix
@@ -0,0 +1,8 @@
+{ ... }:
+
+{
+  time.timeZone = "UTC";
+
+  i18n.defaultLocale = "eo.utf8";
+  environment.sessionVariables.LC_CTYPE = "en_GB.utf8";
+}
diff --git a/modules/nix/default.nix b/modules/nix/default.nix
new file mode 100644
index 000000000000..eb2f41c49697
--- /dev/null
+++ b/modules/nix/default.nix
@@ -0,0 +1,49 @@
+{ config, pkgs, options, lib, ... }:
+
+let
+  # Most of the standard Darwin-detection methods cause infinite recursion.
+  isDarwin = options.environment ? "darwinConfig";
+
+  # Copy entire nixlib tree to the store.
+  root =
+    let
+      # Just needed for runCommand and git.
+      bootstrapPkgs = import ../.. {};
+
+      # Remove .git before adding to the store, because it's likely to
+      # be large. Ideally, we would also remove files in .gitignore
+      # here too, but that would require either a builtin for running a
+      # shell command, or a gitignore parser written in Nix (eww).
+      workingTree = builtins.filterSource (path: type: baseNameOf path != ".git") ../..;
+    in
+      # Now, use a derivation to delete any gitignored files. Then, we
+      # can use the resulting Nix store path as root tree.
+      toString (bootstrapPkgs.runCommand "nixlib-root" {} ''
+        cp -R ${workingTree} "$out"
+        chmod -R u+w "$out"
+        ${bootstrapPkgs.git}/bin/git init "$out"
+        ${bootstrapPkgs.git}/bin/git -C "$out" clean -fX
+        rm -rf "$out/.git"
+      '');
+
+in {
+  nix.nixPath = [
+    "nixos-config=${root}/sys/${config.networking.hostName}.nix"
+    root
+  ];
+
+  nixpkgs.overlays =
+    let
+      inherit (builtins) attrNames readDir;
+      dir = ../../nixpkgs-overlays;
+      names = attrNames (readDir dir);
+    in
+      map (o: import "${root}/nixpkgs-overlays/${o}") names;
+
+  services = lib.optionalAttrs isDarwin
+    { nix-daemon.enable = true; };
+
+  nix.package = pkgs.nixUnstable;
+
+  nix.daemonNiceLevel = 2;
+}
diff --git a/modules/shell/default.nix b/modules/shell/default.nix
new file mode 100644
index 000000000000..cbf3a79f73e7
--- /dev/null
+++ b/modules/shell/default.nix
@@ -0,0 +1,45 @@
+{ pkgs, config, ... }:
+
+{
+  environment.systemPackages = with pkgs.pkgsConfigured; [
+    coreutils-prefixed
+    curl
+    fzf
+    gitSVN
+    gnused
+    gotop
+    httpie
+    jq
+    kakoune
+    less
+    lynx
+    moreutils
+    ncdu
+    neovim
+    nmap
+    openssh
+    pass
+    pv
+    ranger
+    silver-searcher
+    tmux
+    tree
+    units
+    unixtools.watch
+    wget
+    whois
+  ] ++ lib.optional stdenv.isDarwin pinentry_mac;
+
+  environment.shells = with pkgs.pkgsConfigured; [ zsh ];
+
+  environment.variables.EDITOR = "kak";
+  environment.variables.EMAIL = "hi@alyssa.is";
+
+  environment.etc.zshrc.text = ''
+    unsetopt GLOBAL_RCS
+
+    if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
+        . ${config.system.build.setEnvironment}
+    fi
+  '';
+}
diff --git a/modules/users/default.nix b/modules/users/default.nix
new file mode 100644
index 000000000000..7eda04536e2f
--- /dev/null
+++ b/modules/users/default.nix
@@ -0,0 +1,30 @@
+{ pkgs, lib, ... }:
+
+let
+  # These defaults should override the NixOS defaults,
+  # but still themselves be overridable at the default priority.
+  mkDefault = lib.mkOverride 999;
+
+in {
+  imports = [ ../home ];
+
+  users.mutableUsers = false;
+  users.users.qyliss = {
+    createHome = true;
+    home = mkDefault "/home";
+    uid = mkDefault 1000;
+    packages = with pkgs.pkgsConfigured; [
+      git
+      kakoune
+      tmux
+    ];
+    group = "users";
+    extraGroups = [ "wheel" "networkmanager" ];
+    shell = pkgs.pkgsConfigured.zsh;
+  };
+
+  home.qyliss.permissions = "0500";
+  home.qyliss.dirs.state = {
+    permissions = "0500";
+  };
+}
diff --git a/modules/workstation/default.nix b/modules/workstation/default.nix
new file mode 100644
index 000000000000..d9a4860cc372
--- /dev/null
+++ b/modules/workstation/default.nix
@@ -0,0 +1,11 @@
+{ lib, pkgs, ... }:
+
+{
+  imports = [
+    ../nix ../locale ../shell ../users
+    ./windowing ./fonts ./yubikey ./hardware ./networking
+    ./mail ../../config/weechat/module.nix ../../config/gnupg/module.nix
+  ];
+
+  environment.systemPackages = with pkgs; [ mosh ];
+}
diff --git a/modules/workstation/fonts/default.nix b/modules/workstation/fonts/default.nix
new file mode 100644
index 000000000000..9c59bb60189d
--- /dev/null
+++ b/modules/workstation/fonts/default.nix
@@ -0,0 +1,5 @@
+{ pkgs, ... }:
+
+{
+  fonts.fonts = with pkgs; [ fantasque-sans-mono ];
+}
diff --git a/modules/workstation/hardware/default.nix b/modules/workstation/hardware/default.nix
new file mode 100644
index 000000000000..a81508c7ed3b
--- /dev/null
+++ b/modules/workstation/hardware/default.nix
@@ -0,0 +1,6 @@
+{ ... }:
+
+{
+  i18n.consoleUseXkbConfig = true;
+  services.xserver.layout = "dvorak";
+}
diff --git a/modules/workstation/mail/default.nix b/modules/workstation/mail/default.nix
new file mode 100644
index 000000000000..9b14f2981ec8
--- /dev/null
+++ b/modules/workstation/mail/default.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+  imports = [ ./mutt ];
+
+  environment.systemPackages = with pkgs.pkgsConfigured; [ isync ];
+}
diff --git a/modules/workstation/mail/isync/default.nix b/modules/workstation/mail/isync/default.nix
new file mode 100644
index 000000000000..66343a0c3625
--- /dev/null
+++ b/modules/workstation/mail/isync/default.nix
@@ -0,0 +1,36 @@
+{ pkgs, options, ... }:
+
+let
+  inherit (pkgs.pkgsConfigured) isync;
+
+in {
+  config =
+    if options.environment ? "darwinConfig" then
+      {
+        launchd.user.agents.isync = {
+          serviceConfig.ProgramArguments = [ "${isync}/bin/mbsync" "Periodic" ];
+          serviceConfig.StartInterval = 300;
+          serviceConfig.RunAtLoad = true;
+        };
+      }
+    else
+      {
+        systemd.user.services.isync = {
+          serviceConfig = {
+            Type = "oneshot";
+            ExecStart = ''
+              ${isync}/bin/mbsync Periodic
+            '';
+          };
+        };
+
+        systemd.user.timers.isync = {
+          timerConfig = {
+            Unit = "isync.service";
+            OnCalendar = "*:0/5";
+            Persistent = "true";
+          };
+          wantedBy = [ "default.target" ];
+        };
+      };
+}
diff --git a/modules/workstation/mail/mutt/default.nix b/modules/workstation/mail/mutt/default.nix
new file mode 100644
index 000000000000..1aa8d2aa29d3
--- /dev/null
+++ b/modules/workstation/mail/mutt/default.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+  imports = [ ../../../shell ];
+
+  environment.systemPackages = with pkgs.pkgsConfigured; [ neomutt ];
+}
diff --git a/modules/workstation/networking/default.nix b/modules/workstation/networking/default.nix
new file mode 100644
index 000000000000..b813b33924a6
--- /dev/null
+++ b/modules/workstation/networking/default.nix
@@ -0,0 +1,36 @@
+{ pkgs, ... }:
+
+{
+  networking.networkmanager.enable = true;
+
+  # Plausible MAC randomization
+  networking.networkmanager.ethernet.macAddress = "random";
+  networking.networkmanager.wifi.macAddress = "random";
+  networking.networkmanager.extraConfig = ''
+    [connection-extra]
+    ethernet.generate-mac-address-mask=FE:FF:FF:00:00:00
+    wifi.generate-mac-address-mask=FE:FF:FF:00:00:00
+  '';
+
+  networking.nameservers = [ "::1" ];
+
+  networking.networkmanager.dispatcherScripts = [
+    {
+      source = pkgs.writeText "doh-stub" ''
+        if [ "$2" = up ]
+        then systemctl restart doh-stub.service
+        fi
+      '';
+      type = "basic";
+    }
+  ];
+
+  systemd.services.doh-stub = {
+    script = ''
+      exec ${pkgs.doh-proxy}/bin/doh-stub \
+          --level INFO \
+          --domain qyliss.net \
+          --remote-address 85.119.82.108
+    '';
+  };
+}
diff --git a/modules/workstation/windowing/default.nix b/modules/workstation/windowing/default.nix
new file mode 100644
index 000000000000..e435fd42b68e
--- /dev/null
+++ b/modules/workstation/windowing/default.nix
@@ -0,0 +1,12 @@
+{ pkgs, lib, ... }:
+
+{
+  imports = [ ../../../config/firefox/module.nix ];
+
+  environment.variables.XKB_DEFAULT_LAYOUT = "dvorak";
+  programs.sway-beta.enable = true;
+  programs.sway-beta.package = pkgs.pkgsConfigured.sway-beta;
+
+  environment.systemPackages = with pkgs.pkgsConfigured;
+    lib.optionals (!stdenv.isDarwin) [ alacritty ];
+}
diff --git a/modules/workstation/yubikey/default.nix b/modules/workstation/yubikey/default.nix
new file mode 100644
index 000000000000..7f314bd5cbee
--- /dev/null
+++ b/modules/workstation/yubikey/default.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+  services.pcscd.enable = true;
+
+  services.udev.packages = with pkgs; [ yubikey-personalization ];
+}
diff --git a/nixpkgs-overlays/zzzzzz-config/default.nix b/nixpkgs-overlays/zzzzzz-config/default.nix
new file mode 100644
index 000000000000..b3613ef154da
--- /dev/null
+++ b/nixpkgs-overlays/zzzzzz-config/default.nix
@@ -0,0 +1,63 @@
+# HACK: This directory is named zzzzzz-config.nix, because the config
+# overlay should always be applied last.
+
+self: super:
+
+let
+  defaultOverlays = map import [
+    ../../config/firefox/overlay.nix
+  ];
+
+  # Create another package set to avoid leaking configTools.
+  pkgsWithConfigTools = super.extend (self: super: {
+    configTools = super.callPackage ../../config/tools.nix { };
+  });
+
+  # Use callPackage from pkgsWithConfigTools so that configured packages
+  # don't depend on other configured packages by default.
+  inherit (pkgsWithConfigTools) callPackage;
+
+  inherit (super.lib) foldl;
+
+in
+  (foldl (a: e: a // e) {} (map (f: f self super) defaultOverlays)) // {
+    pkgsConfigured = pkgsWithConfigTools // rec {
+      alacritty = callPackage ../../config/alacritty { };
+
+      firefox = callPackage ../../config/firefox { };
+      firefox-nightly = callPackage ../../config/firefox { firefox = super.firefox-nightly; };
+      firefox-beta = callPackage ../../config/firefox { firefox = super.firefox-beta; };
+      firefox-esr-52 = callPackage ../../config/firefox { firefox = super.firefox-esr-52; };
+      firefox-esr-60 = callPackage ../../config/firefox { firefox = super.firefox-esr-60; };
+      firefox-esr = callPackage ../../config/firefox { firefox = super.firefox-esr; };
+
+      gitAndTools = super.gitAndTools // {
+        git = callPackage ../../config/git { inherit (super.gitAndTools) git; };
+        gitFull = callPackage ../../config/git { git = super.gitAndTools.gitFull; };
+        gitSVN = callPackage ../../config/git { git = super.gitAndTools.gitSVN; };
+      };
+      inherit (gitAndTools) git gitFull gitSVN;
+      gitMinimal = callPackage ../../config/git { git = super.gitMinimal; };
+
+      gnupg = callPackage ../../config/gnupg { };
+
+      isync = callPackage ../../config/isync { inherit gnupg; };
+
+      kakoune = callPackage ../../config/kakoune { };
+
+      less = callPackage ../../config/less { };
+
+      msmtp = callPackage ../../config/msmtp { inherit gnupg; };
+
+      neomutt = callPackage ../../config/neomutt { inherit gnupg msmtp; };
+
+      sway = callPackage ../../config/sway { };
+      sway-beta = callPackage ../../config/sway { sway = super.sway-beta; };
+
+      tmux = callPackage ../../config/tmux { };
+
+      weechat = callPackage ../../config/weechat { };
+
+      zsh = callPackage ../../config/zsh { };
+    };
+  }
diff --git a/sys/x220.nix b/sys/x220.nix
new file mode 100644
index 000000000000..a62de6d651a6
--- /dev/null
+++ b/sys/x220.nix
@@ -0,0 +1,49 @@
+{ pkgs, ... }:
+
+{
+  imports = [
+    ../modules/workstation
+    ../modules/nixos-hardware/lenovo/thinkpad/x220
+  ];
+
+  hardware.enableRedistributableFirmware = true;
+
+  boot.initrd.availableKernelModules =
+    [ "ehci_pci" "ahci" "usb_storage" "sd_mod" "sdhci_pci" ];
+
+  boot.kernelModules = [ "kvm-intel" ];
+  boot.kernelPackages = pkgs.linuxPackages_latest;
+  boot.loader.systemd-boot.enable = true;
+
+  boot.initrd.luks.devices."nixos-decrypted".device =
+    "/dev/disk/by-uuid/bb59aa96-f934-4900-a98b-a364b8dccefe";
+
+  fileSystems."/" = {
+    device = "/dev/disk/by-uuid/002e2225-6a03-4692-bac2-f57e5588ee49";
+    fsType = "ext4";
+  };
+
+  fileSystems."/boot" = {
+    device = "/dev/disk/by-uuid/5FED-1194";
+    fsType = "vfat";
+  };
+
+  swapDevices = [
+    {
+      device = "/dev/disk/by-partuuid/201f5472-f45e-49b7-88c9-7fd9458a30f7";
+      randomEncryption = true;
+    }
+  ];
+
+  nix.maxJobs = 4;
+  powerManagement.cpuFreqGovernor = "powersave";
+
+  networking.hostName = "x220";
+
+  system.stateVersion = "18.09";
+
+  users.users.qyliss.hashedPassword = "$5$rounds=1000000$tySRQ3rdqbPOduux$NcW7CoffEScpmOyS0Ga9gE5ZNLt8PT6.2Gvwn91vQn7";
+
+  hardware.opengl.enable = true;
+  hardware.opengl.extraPackages = with pkgs; [ libGL ];
+}