My neurosys encompasses configuration for:

These define a my computing environment, which strives to be

  • Effective: interactions with code/data should occur at the speed of thought.
  • Extensible: otherwise, it’s not really mine.
  • Long-lived: resist entropy/bitrot/bankruptcy until the end of the keyboard + screen era.
  • Minimal: less is more for the base configuration - Nix enables project-level dependency management.
  • Reproducible: enabled by the content-based hashes of Nix + Git. No fear of hitting un-recoverable states.

This literate org file, along with another for Doom, are the only two source files. The rest are generated via tangling.

If you are viewing this in a browser, consider opening the original org file in Emacs for the full experience. The HTML version is an imperfect rendering.

The name neurosys is used half-jokingly.


In no particular order, I found the following configurations especially helpful:

Helpful Resources

Table of Contents QUOTE TOC_3

Host setup

Install NixOS


Add channels




Get the neurosys source

Don’t forget to clone recursively, because of the submodules:

Sync to host

Run neurosys/deploy-to-host, which will tangle this file and rsync the results to a specified host.

Rsync Script

Doom Emacs

My Doom Emacs Configuration is tracked in the home folder as a git submodule.

Until projects like nix-doom-emacs are stable, I’m not yet tracking my Emacs packages / config in Nix. For now, I track known-good commits via submodules / straight.el, and tie them to external dependencies (all managed by Nix) in this repo. If you know a better way to do this, please let me know.

Emacs itself is tracked via the emacs-overlay, which is version pinned via niv in sources.json. See the nix delaration.


I use XMonad as a window manager and minimal desktop environment. I don’t run any additional desktop environment (e.g. XFCE). Instead, I have the interface to the few things I need configured here in Haskell, or elsewhere (Emacs).

I’ve don’t use any system trays / status bars / panels, since the Emacs modeline is enough for me. This choice reduces the complexity of the XMonad configuration, and avoids depending on xmobar/polybar.

Haskell Configuration




  1. Default



    binding command
    mod-shift-return Launch terminal
    mod-p Launch dmenu rofi
    mod-shift-p Launch gmrun
    mod-shift-c Close the focused window
    mod-space Rotate through the available layout algorithms
    mod-shift-space Reset the layouts on the current workspace to default
    mod-n Resize viewed windows to the correct size
    mod-tab Move focus to the next window
    mod-shift-tab Move focus to the previous window
    mod-j Move focus to the next window
    mod-k Move focus to the previous window
    mod-m Move focus to the master window
    mod-return Swap the focused window and the master window
    mod-shift-j Swap the focused window with the next window
    mod-shift-k Swap the focused window with the previous window
    mod-h Shrink the master area
    mod-l Expand the master area
    mod-t Push window back into tiling
    mod-comma Increment the number of windows in the master area
    mod-period Deincrement the number of windows in the master area
    mod-shift-q Quit xmonad
    mod-q Restart xmonad
    mod-shift-slash Run xmessage with a summary of the default keybindings (useful for beginners)
    mod-[1..9] Switch to workspace N
    mod-shift-[1..9] Move client to workspace N
    mod-{w,e,r} Switch to physical/Xinerama screens 1, 2, or 3
    mod-shift-{w,e,r} Move client to screen 1, 2, or 3
    mod-button1 Set the window to floating mode and move by dragging
    mod-button2 Raise the window to the top of the stack
    mod-button3 Set the window to floating mode and resize by dragging
  2. Custom

    1. Rebooting / Restarting

    2. Add Workspace 0

    3. Launcher / Window Switcher

      I currently use rofi to run programs or switch between open windows. It’s simple, fast, and supports fuzzy search.

    4. Running Emacs

    5. Lock Screen

    6. Horizontal Resizing

      An obvious missing default…

    7. Window Minimization / Restoration

    8. Fullscreen

    9. Workspace Swapping

      Using mod+comma quickly swap between workspaces is very handy.

    10. Changing number of master windows

      Some layouts, like ResizableTall, have a “master” area, with 1 window initially assigned there. These commands enable incrementing or decrementing that number.

      They are bound by default to mod+, and mod+., but mod+, is much more useful for Workspace Swapping. Here I add Shift to the defaults.

    11. Easier Kill Binding

      • I find the default mod+shift+c binding to be clumbsy for killing windows.
      • mod+q is easier / more natural.
      • The default mod+q for killing XMonad is something I’ve never needed.
    12. Volume Control

      I don’t run a desktop environment, so the volume buttons on my keyboard don’t do anything by default.

    13. Screenshots

    14. Keyboard

    15. Arandr

      I have and symlinked to whatever the current xrandr scripts are for my desk / laptop.

    16. Cycle Recent Workspace

    17. Multiple Monitors

      The functionality here is a primary reason for choosing XMonad: a natural, keyboard-driven way for coordinating workspaces across multiple monitors. I’m genuinely curious to know if others have found something on-par/better elsewhere. Please contact me if you do.

      • Bind mod-{w, e, r} to switch focus between monitors.
      • Bind mod-shift-{w, e, r} to move workspaces between monitors.
      1. TODO Try XMonad.Actions.PhysicalScreens to order mod-{w, e, r} based on physical screen layout


Float certain apps


Start Script

This is currently only used by when on non-NixOS systems (e.g. Ubuntu).

Desktop Entry Pointing to Start Script


[Desktop Entry]
Comment=Lightweight tiling window manager

Nixos Config

System-level Config






  1. Syncthing

  2. Tarsnap

    nixpkgs/tarsnap.nix at master · NixOS/nixpkgs · GitHub

  3. End



User Definition


X2Go Client


User-level Config

Environment Variables



  1. Home Manager

  2. Emacs

  3. Bash

  4. Git

  5. Direnv

  6. SSH

  7. End



TODO To test [0/5]

  • [ ] xtrlock-pam
  • [ ] maim
  • [ ] rofi-pass (see Floating Emacs popup for quick commands)
  • [ ] xclip
  • [ ] arandr

TODO To add [0/1]

  • keyboard config: currently requires xset & setxbmap
  • [ ] Messenging
    • unified messenger (if it exists) for dealing with the madness of:
      • sms
      • signal
      • fb
      • whatsapp
      • keybase
      • slack
      • irc
      • riot
    • To try
      • [X] pidgin - just bad. no hidpi, things like signal don’t sync with mobile state, clunky ui.
    • Otherwise:
      • Browser tabs for everything sans signal
      • Signal-destop
      • maybe slack desktop
      • caprine for fb is nice on ubuntu

Global Constants

Hardware-specific Config

Disk Mounts

From Chapter 8. File Systems: “Mount points are created automatically if they don’t already exist.”

Version Pinning

These are generated via niv.

    "emacs-overlay": {
        "branch": "master",
        "description": "Bleeding edge emacs overlay [maintainer=@adisbladis] ",
        "homepage": "",
        "owner": "nix-community",
        "repo": "emacs-overlay",
        "rev": "0feda8b31b52f3ea008555dfe79dba3989d3e585",
        "sha256": "1ijr9pl0czzbgj35vj8kq4xvcana6w24ljcmzriz7cyxln4pgvln",
        "type": "tarball",
        "url": "",
        "url_template": "<owner>/<repo>/archive/<rev>.tar.gz"
    "ghcide-nix": {
        "branch": "master",
        "description": "Nix installation for ghcide",
        "homepage": "",
        "owner": "cachix",
        "repo": "ghcide-nix",
        "rev": "f940ec611cc6914693874ee5e024eba921cab19e",
        "sha256": "0vri0rivdzjvxrh6lzlwwkh8kzxsn82jp1c2w5rqzhp87y6g2k8z",
        "type": "tarball",
        "url": "",
        "url_template": "<owner>/<repo>/archive/<rev>.tar.gz"
    "nixpkgs-20.03": {
        "branch": "release-20.03",
        "description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to",
        "homepage": "",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "7829e5791ba1f6e6dbddbb9b43dda72024dd2bd1",
        "sha256": "0hs9swpz0kibjc8l3nx4m10kig1fcjiyy35qy2zgzm0a33pj114w",
        "type": "tarball",
        "url": "",
        "url_template": "<owner>/<repo>/archive/<rev>.tar.gz"
# This file has been generated by Niv.

# A record, from name to path, of the third-party packages
with rec
  pkgs =
    if hasNixpkgsPath
        if hasThisAsNixpkgsPath
        then import (builtins_fetchTarball { inherit (sources_nixpkgs) url sha256; }) {}
        else import <nixpkgs> {}
        import (builtins_fetchTarball { inherit (sources_nixpkgs) url sha256; }) {};

  sources_nixpkgs =
    if builtins.hasAttr "nixpkgs" sources
    then sources.nixpkgs
    else abort
        Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
        add a package called "nixpkgs" to your sources.json.

  # fetchTarball version that is compatible between all the versions of Nix
  builtins_fetchTarball =
      { url, sha256 }@attrs:
        inherit (builtins) lessThan nixVersion fetchTarball;
        if lessThan nixVersion "1.12" then
          fetchTarball { inherit url; }
          fetchTarball attrs;

  # fetchurl version that is compatible between all the versions of Nix
  builtins_fetchurl =
      { url, sha256 }@attrs:
        inherit (builtins) lessThan nixVersion fetchurl;
        if lessThan nixVersion "1.12" then
          fetchurl { inherit url; }
          fetchurl attrs;

  # A wrapper around pkgs.fetchzip that has inspectable arguments,
  # annoyingly this means we have to specify them
  fetchzip = { url, sha256 }@attrs: pkgs.fetchzip attrs;

  # A wrapper around pkgs.fetchurl that has inspectable arguments,
  # annoyingly this means we have to specify them
  fetchurl = { url, sha256 }@attrs: pkgs.fetchurl attrs;

  hasNixpkgsPath = (builtins.tryEval <nixpkgs>).success;
  hasThisAsNixpkgsPath =
    (builtins.tryEval <nixpkgs>).success && <nixpkgs> == ./.;

  sources = builtins.fromJSON (builtins.readFile ./sources.json);

  mapAttrs = builtins.mapAttrs or
    (f: set: with builtins;
      listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)));

  # borrowed from nixpkgs
  functionArgs = f: f.__functionArgs or (builtins.functionArgs f);
  callFunctionWith = autoArgs: f: args:
    let auto = builtins.intersectAttrs (functionArgs f) autoArgs;
    in f (auto // args);

  getFetcher = spec:
    let fetcherName =
      if builtins.hasAttr "type" spec
      then builtins.getAttr "type" spec
      else "builtin-tarball";
    in builtins.getAttr fetcherName {
      "tarball" = fetchzip;
      "builtin-tarball" = builtins_fetchTarball;
      "file" = fetchurl;
      "builtin-url" = builtins_fetchurl;
# NOTE: spec must _not_ have an "outPath" attribute
mapAttrs (_: spec:
  if builtins.hasAttr "outPath" spec
  then abort
    "The values in sources.json should not have an 'outPath' attribute"
    if builtins.hasAttr "url" spec && builtins.hasAttr "sha256" spec
      spec //
      { outPath = callFunctionWith spec (getFetcher spec) { }; }
    else spec
  ) sources

Future Work

TODO [#A] Floating Emacs popup for quick commands

It’s usually most convenient to use/implement functions in Emacs than other tools. This was a huge draw towards EXWM, and I got used to that workflow.

For example, generating / getting passwords stored in pass has a nice Emacs interface. Currently, this often means switching from Firefox to Emacs to run a command, then back to Firefox. In this situation, a common solution seems to be eschewing Emacs for things like rofi (e.g. rofi-pass).

However, if I could popup a fast, floating Emacs window (similar to Rofi), I’d be able to leverage Elisp for most tasks like this. This might fully obviate Rofi and the XMonad prompt.

The floating window would default to a counsel-M-x fuzzy search over all incremental commands, and be bound to s-x in XMonad.


  • screenshotting
  • pass interface
  • redshift / brightness controls
  • (maybe) finding windows / launching programs
  • clipboard management (helm kill ring)

As a small added benefit, I’d have the full Emacs bindings available in the input area.

TODO [#B] HiDPI autotoggle

I’m occasionally lucky enough to plug into a 4k screen, which is not yet a seamless experience in linux.

For now I manually do the following on HiDPI screens:

and change layout.css.devPixelsPerPx in Firefox about:config from -1 to 2.

tweak font size in Firefox and Emacs.

Keybase Tweak

Per keybase/client#5797 increase font size in client, edit the ExecStart line to have --force-device-scale-factor=2. Then, reload with:

TODO [#B] Consistent copy/paste bindings

In EXWM, I used the simulation keys to have consistent Emacs copy/paste bindings everywhere. Need a solution for XMonad. Setting the GTK bindings to “emacs” didn’t seem to affect for Firefox, which is (by far) my primary non-Emacs window.

TODO [#B] Consolidate XMonad and Emacs window management

Ideally, XMonad could manage all windows, including Emacs windows.

Possible solutions:

  • frames-only-mode
  • frame-mode

These seemed to cause more problems than they fixed for me.

TODO [#C] Fix indentation in tangled files

Even with org-src-preserve-indentation set to t, files like home.nix aren’t properly indented. This is because they are split across several org blocks, and in each on I use the nix-mode auto-indent to fixup the indentation within the block. This removes the leading whitespace, so each block is tangled without leading indentation. Thankfully, Nix doesn’t care, so this is purely for the aesthetics of the generated files.

Quickfix is to auto-indent the tangled files, then detangle.

Using noweb references seems to be the best option: See “This feature can also be used for management of indentation in exported code snippets”.