neurosys

About

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.

Inspirations

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

Helpful Resources

Table of Contents QUOTE TOC_3

Host setup

Install NixOS

Followed: https://www.linode.com/docs/tools-reference/custom-kernels-distros/install-nixos-on-linode/

Add channels

Stable

NIXOS_VERSION=20.03

nix-channel --add "https://nixos.org/channels/nixos-${NIXOS_VERSION}" nixos
nix-channel --add "https://github.com/rycee/home-manager/archive/release-${NIXOS_VERSION}.tar.gz" home-manager
nix-channel --add "https://nixos.org/channels/nixpkgs-${NIXOS_VERSION}" nixpkgs

nix-channel update

Unstable


nix-channel --add https://github.com/rycee/home-manager/archive/master.tar.gz home-manager
nix-channel --add https://nixos.org/channels/nixos-unstable nixos
nix-channel --add "https://nixos.org/channels/nixpkgs-unstable" nixpkgs-unstable

nix-channel update

Deployment

Get the neurosys source

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

git clone --recursive git@github.com:dangirsh/neurosys.git

Sync to host

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

Rsync Script

HOST=$1
HOST_HOME=$2

rsync -Pav --rsync-path="sudo rsync" nixos/ $HOST:/etc/nixos/
rsync -Pav home/ $HOST:$HOST_HOME

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.

XMonad

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

Imports

import XMonad

import XMonad.Hooks.SetWMName
import XMonad.Hooks.DynamicProperty (dynamicTitle)
import XMonad.Hooks.EwmhDesktops
import XMonad.Hooks.ManageHelpers
import XMonad.Hooks.UrgencyHook

import XMonad.Layout.Grid
import XMonad.Layout.Fullscreen
import XMonad.Layout.Minimize
import XMonad.Actions.Minimize
import XMonad.Layout.NoBorders
import XMonad.Layout.NoFrillsDecoration (noFrillsDeco, shrinkText,
                                         inactiveBorderColor, inactiveColor, inactiveTextColor, activeBorderColor,
                                         activeColor, activeTextColor, urgentBorderColor, urgentTextColor, decoHeight)
import XMonad.Layout.Tabbed (simpleTabbed)
import XMonad.Layout.ResizableTile
import XMonad.Layout.MultiColumns

import XMonad.Actions.CycleWS (toggleWS)
import XMonad.Actions.CycleRecentWS (cycleRecentWS)
import qualified XMonad.StackSet as W

import XMonad.Prompt
import XMonad.Prompt.AppLauncher as AL
import XMonad.Util.Run

import Data.Monoid
import Data.Default (def)
import Data.Map as M (fromList,union, Map())
import Data.List (isPrefixOf)

Main

main :: IO ()
main = xmonad $
  withUrgencyHook NoUrgencyHook $
  ewmh $
  fullscreenSupport def {
    borderWidth = 1
  , focusedBorderColor = blue
  , terminal = "alacritty"
  , layoutHook = smartBorders $  -- no borders for sole windows
                 noFrillsDeco shrinkText topBarTheme $   -- visually mark the focused window with a top bar
                 minimize
                 (ResizableTall 1 (3/100) (1/2) []
                   ||| Mirror (ResizableTall 1 (3/100) (1/2) [])
                   ||| multiCol [1] 1 0.01 (-0.5)
                   ||| noBorders Full
                   ||| simpleTabbed
                   ||| Grid)

  , workspaces = map show $ [1..9] ++ [0 :: Int]
  , modMask = mod4Mask  -- super key as modifier
  , keys = \c -> myKeys c `M.union` keys def c
  , manageHook = myManageHook <+> manageZoomHook
  , handleEventHook = ewmhDesktopsEventHook <+> myHandleEventHook
  , startupHook = do
      -- http://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Hooks-SetWMName.html
      setWMName "LG3D"
      windows $ W.greedyView "1"
  }

Keybindings

  1. Default

    From https://xmonad.org/manpage.html#default-keyboard-bindings

    source

    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

    myKeys :: XConfig t -> M.Map (KeyMask, KeySym) (X ())
    myKeys XConfig {modMask = m, terminal = term} = M.fromList $ [
    1. Rebooting / Restarting

        ((m .|. shiftMask .|. mod1Mask, xK_r), spawn "reboot")
      , ((m .|. shiftMask .|. mod1Mask, xK_i), spawn "xmonad --recompile && xmonad --restart")
    2. Add Workspace 0

      , ((m, xK_0), windows $ W.greedyView "0")
      , ((m .|. shiftMask, xK_0), windows $ W.shift "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.

      , ((m, xK_p), spawn "rofi -show drun -modi drun -show-icons -matching fuzzy -sort")
      , ((m .|. shiftMask, xK_p), spawn "GDK_SCALE=2 rofi -show drun -modi drun -show-icons -matching fuzzy -sort")
      , ((m, xK_b), spawn "rofi -show window -show-icons -matching fuzzy -sort")
      -- Like M-y for helm-show-kill-ring in Emacs
      , ((m, xK_y), spawn "rofi -modi \"clipboard:greenclip print\" -show clipboard -run-command '{cmd}'")
      -- Text espander
      
      , ((m .|. shiftMask .|. mod1Mask, xK_j), spawn "texpander.sh")
    4. Running Emacs

      , ((m, xK_n), spawn "emacsclient -c")
      , ((m .|. shiftMask .|. mod1Mask, xK_n), spawn "EMACS_NON_WORK_MODE=1 ~/scripts/run_emacs.sh")
      , ((m .|. shiftMask, xK_n), spawn "~/scripts/run_emacs.sh")
    5. Lock Screen

      , ((m .|. shiftMask .|. mod1Mask, xK_o), spawn "xtrlock -b")
    6. Horizontal Resizing

      An obvious missing default…

      , ((m .|. shiftMask, xK_h), sendMessage MirrorShrink)
      , ((m .|. shiftMask, xK_l), sendMessage MirrorExpand)
    7. Window Minimization / Restoration

      , ((m, xK_m), withFocused minimizeWindow)
      , ((m .|. shiftMask, xK_m), withLastMinimized maximizeWindowAndFocus)
    8. Fullscreen

      , ((m .|. shiftMask, xK_f), withFocused $ \f -> windows =<< appEndo `fmap` runQuery doFullFloat f)
    9. Workspace Swapping

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

      , ((m, xK_comma), toggleWS)
    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.

      , ((m .|. shiftMask, xK_comma), sendMessage (IncMasterN 1))
      , ((m .|. shiftMask, xK_period), sendMessage (IncMasterN (-1)))
    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.
      , ((m, xK_q), kill)
    12. Volume Control

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

      , ((m .|. shiftMask, xK_Up), spawn "amixer -c 0 -q set Master 2dB+ unmute")
      , ((m .|. shiftMask, xK_Down),spawn "amixer -c 0 -q set Master 2dB- unmute")
    13. Screenshots

      ,((0, xK_Print), myScreenshot)
      ,((m, xK_Print), myScreenshotClipboard)
    14. Keyboard

      , ((m .|. shiftMask, xK_i), spawn "setxkbmap -option 'ctrl:nocaps' && xset r rate 160 80")
    15. Arandr

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

      , ((m, xK_s), spawn "/home/dan/.screenlayout/main.sh && feh --bg-fill --no-xinerama --randomize ~/Media/images/wallpaper/* &" )
      , ((m .|. shiftMask, xK_s), spawn "/home/dan/.screenlayout/laptop.sh && feh --bg-fill --no-xinerama --randomize ~/Media/images/wallpaper/*" )
    16. Cycle Recent Workspace

      , ((m .|. shiftMask, xK_Tab), cycleRecentWS [xK_Super_L] xK_Tab xK_BackSpace)
    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.
      ] ++
      [((m .|. nilOrShift, key), screenWorkspace sc
              >>= flip whenJust (windows . f))
           | (key, sc) <- zip [xK_w, xK_e, xK_r] [0..]
           , (f, nilOrShift) <- [(W.view, 0), (W.shift, shiftMask)]]
      1. TODO Try XMonad.Actions.PhysicalScreens to order mod-{w, e, r} based on physical screen layout

Asthetics

red     = "#dc322f"
blue    = "#268bd2"
yellow  = "#b58900"
inactive  = "#002b36"
active      = blue

topBarTheme = def
    { inactiveBorderColor   = inactive
    , inactiveColor         = inactive
    , inactiveTextColor     = inactive
    , activeBorderColor     = active
    , activeColor           = active
    , activeTextColor       = active
    , urgentBorderColor     = red
    , urgentTextColor       = yellow
    , decoHeight            = 10
    }


myShellPrompt = def
       { font              = "xft:Hack:pixelsize=30"
       , promptBorderWidth = 1
       , position          = Top
       , height            = 42
       , defaultText       = []
       }

Float certain apps

manageZoomHook =
  composeAll $
    [ (className =? zoomClassName) <&&> shouldFloat <$> title --> doFloat,
      (className =? zoomClassName) <&&> shouldSink <$> title --> doSink
    ]
  where
    zoomClassName = "zoom"
    tileTitles =
      [ "Zoom - Free Account", -- main window
        "Zoom - Licensed Account", -- main window
        "Zoom", -- meeting window on creation
        "Zoom Meeting" -- meeting window shortly after creation
      ]
    shouldFloat title = title `notElem` tileTitles
    shouldSink title = title `elem` tileTitles
    doSink = (ask >>= doF . W.sink) <+> doF W.swapDown

myHandleEventHook =
  mconcat
    [ dynamicTitle manageZoomHook,
      handleEventHook defaultConfig
    ]


myManageHook = composeAll [ appName =? "Open Files" --> doFloat,
                            className =? "Zenity" --> doFloat]

Screenshot

myScreenshot = do
  -- init takes care of the trailing newline character returned by date
  date <- init <$> runProcessWithInput "date" ["+%Y-%m-%d-%H:%M:%S"] []
  AL.launchApp myShellPrompt { defaultText = "~/screenshots/" ++ date ++ ".png"} "maim -s"
myScreenshotClipboard :: X ()
myScreenshotClipboard = spawn  "maim -s | xclip -selection clipboard -t image/png"

Start Script

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

#!/bin/bash

# Identify the home of our gtkrc file, important for setting styles of
# gtk-based applications
export GTK2_RC_FILES="$HOME/.gtkrc-2.0"

# enable for hidpi displays
export GDK_SCALE=1

# sudo timedatectl set-timezone Europe/Berlin
#sudo timedatectl set-timezone America/New_York
sudo timedatectl set-timezone America/Los_Angeles

# Ensure Zoom output volume stays constant
/home/dan/scripts/fuck_zoom.sh 100 &
$HOME/scripts/configure_laptop_keeboard.sh

# Clipboard manager (used with rofi)
greenclip daemon &

# set trackball rate
xinput --set-prop "Primax Kensington Eagle Trackball" "libinput Accel Speed" 1 || true

# Wallpaper
feh --bg-fill --no-xinerama --randomize ~/Media/images/wallpaper/*
# xsetroot -solid black

# Config in ~/.config/redshift/redshift.conf
redshift &

picom -b

eval $(ssh-agent)

syncthing serve &

# Now, finally, start xmonad
exec xmonad

Desktop Entry Pointing to Start Script

file:/usr/share/xsessions/xmonad.desktop

[Desktop Entry]
Name=XMonad
Comment=Lightweight tiling window manager
Exec=/home/dan/.xmonad/start-xmonad
Icon=xmonad.png
Type=XSession

Nixos Config

System-level Config

{ config, pkgs, ... }:
let
  sources = import ./nix/sources.nix;
  # ghcide-nix = import sources."ghcide-nix" { };
in {
  imports =
    [ ./hardware-configuration.nix
      ./settings.nix
      "${builtins.fetchTarball https://github.com/rycee/home-manager/archive/release-20.03.tar.gz}/nixos"
    ];

  system.stateVersion = "20.03";

  nixpkgs.config = {
    # Allow unfree, which is required for some drivers.
    allowUnfree = true;
  };

Nix

nix = {
  useSandbox = true;
  autoOptimiseStore = true;
  maxJobs = 3; # should be 1 per CPU logical core
  binaryCaches = [
    "https://cache.nixos.org/"
    "https://ghcide-nix.cachix.org"
    "https://hercules-ci.cachix.org"
    "https://iohk.cachix.org"
    "https://nix-tools.cachix.org"
  ];
  binaryCachePublicKeys = [
    "ghcide-nix.cachix.org-1:ibAY5FD+XWLzbLr8fxK6n8fL9zZe7jS+gYeyxyWYK5c="
    "hercules-ci.cachix.org-1:ZZeDl9Va+xe9j+KqdzoBZMFJHVQ42Uu/c/1/KMC5Lw0="
    "iohk.cachix.org-1:DpRUyj7h7V830dp/i6Nti+NEO2/nhblbov/8MW7Rqoo="
    "nix-tools.cachix.org-1:ebBEBZLogLxcCvipq2MTvuHlP7ZRdkazFSQsbs0Px1A="
  ];
  gc = {
    automatic = true;
    dates = "23:00";
    options = "--delete-older-than 30d";
  };
};

Timezone

time.timeZone = "America/Los_Angeles";

Boot

boot = {
  cleanTmpDir = true;

  loader = {
    timeout = 1; # Timeout (in seconds) until loader boots the default menu item.
    grub = {
      enable = true;
      version = 2;
      device = "nodev";
      copyKernels = true;
      fsIdentifier = "provided";
      extraConfig = "serial; terminal_input serial; terminal_output serial";
    };
    systemd-boot.enable = false;
    efi.canTouchEfiVariables = false;

  };
};

Networking


networking.useDHCP = false;
networking.usePredictableInterfaceNames = false;
networking.interfaces.eth0.useDHCP = true;
networking.firewall.enable = false;
networking.firewall.allowPing = true;
# networking.networkmanager.enable = true;
networking.hostName = "nixos-dev";

networking.interfaces.eth0.tempAddress = "disabled";

Services


services = {

  xserver = {
    enable = true;
    layout = "us";

    windowManager.xmonad = {
      enable = true;
      enableContribAndExtras = true;
      extraPackages = haskellPackges: [
        haskellPackges.xmonad-contrib
        haskellPackges.xmonad-extras
        haskellPackges.xmonad
      ];
    };

    displayManager = {
      defaultSession = "none+xmonad";
      lightdm.enable = true;
    };
    desktopManager.xterm.enable = false;
  };
  1. Syncthing

    
    # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/networking/syncthing.nix
    syncthing = {
      enable = true;
      openDefaultPorts = true;
      user = "${config.settings.username}";
      configDir = "/home/${config.settings.username}/.config/syncthing";
      dataDir = "/home/${config.settings.username}/.local/share/syncthing";
      declarative = {
        devices = {
          nixos-dev.id = "EEMRJQE-TBONTUL-UBGJ6FT-AAUS25K-COP3VHE-WERN7IN-PTNZ63Z-GZZX2AY";
          x1carbon9.id = "EIZV5LR-F3JILKF-7MD5UMZ-KYRW37L-RVJ2WI4-7LQD7VC-U5BSEBD-YMGQPQ3";
          pixel6-pro.id = "DISECZT-3ILXZ2S-B7BTYBI-R6KKLH2-YYIDZGD-4LP2OEF-NAURN57-ZPR6XAD";
        };
        folders = {
          sync = rec {
            id = "at23u-zmxto";
            devices = [ "nixos-dev" "x1carbon9" "pixel6-pro"];
            path = "/bkp/Sync";
            watch = false;
            rescanInterval = 3600 * 1;
            type = "receiveonly"; # sendreceive
            enable = true;
            versioning.type = "simple";
            versioning.params.keep = "5";
          };
          media = rec {
            id = "media";
            devices = [ "nixos-dev"  "x1carbon9" "pixel6-pro"];
            path = "/bkp/Media";
            watch = false;
            rescanInterval = 3600 * 6;
            type = "receiveonly"; # sendreceive
            enable = true;
            versioning.type = "simple";
            versioning.params.keep = "5";
          };
          work = rec {
            id = "d7svv-zjsz2";
            devices = [ "nixos-dev" "x1carbon9" "pixel6-pro"];
            path = "/bkp/Work";
            watch = false;
            rescanInterval = 3600 * 6;
            type = "receiveonly"; # sendreceive
            enable = true;
            versioning.type = "simple";
            versioning.params.keep = "5";
          };
        };
      };
    };
  2. Tarsnap

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

    tarsnap.enable = true;
    tarsnap.keyfile = "/bkp/Sync/keys/nixos-dev-tarsnap.key";
    tarsnap.archives = {
      main = {
        keyfile = "/bkp/Sync/keys/nixos-dev-tarsnap.key";
        directories = ["/bkp/Sync" "/bkp/Work" "/bkp/Media"];
      };
    };
  3. End

    };
    # virtualisation.docker.enable = true;

Packages

environment.systemPackages = with pkgs; [
  coreutils binutils
  curl wget
  zip unzip
  git
  killall
  syncthing-cli
  sshfs
  mtr # traceroute
  sysstat
  htop
];

Fonts


fonts = {
  enableFontDir = true;
  enableGhostscriptFonts = true;
  fonts = with pkgs; [
    corefonts
    hack-font
  ];
};

User Definition


security.sudo.wheelNeedsPassword = false;

users.mutableUsers = false;

users.extraUsers.${config.settings.username} = {
  isNormalUser = true;
  uid = 1000;
  createHome = true;
  home = "/home/${config.settings.username}";
  description = "${config.settings.name}";
  extraGroups = [
    "audio"
    "networkmanager"
    "systemd-journal"
    "vboxusers"
    "video"
    "wheel"
  ];
};

home-manager.users.dan = import ./home.nix ;

SSH


services.openssh = {
  enable = true;
  forwardX11 = true;
  permitRootLogin = "without-password";
  passwordAuthentication = false;
};

users.users.${config.settings.username}.openssh.authorizedKeys.keys = [
  "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+yJ5sv7iO9PBuozfmitR0JJfqDsJ7w+rlryq5CwdatO3tkRdR5dMYdFTFCeHbmeakPTC/uys08fziEUXh3DL206jDKQEMBoMGXNowZHyYzr25nIogHbveqeNTgP8jsTw5uBaJu8LFzHHey4Sw9WlRrvIqguUT5jB3omZh8yDWcxTrTJlTsN2TM3HILvirfVwBkD2uNTDdd5LplbZhx6x87VCs6ZNYhBjJ4CPcO4zTQuEdyyxUHEgtMkYgrS4Jb/Kl6Tleftlh55E74SZ3XXnw3lWdH9ra8ewH265iqNr/RwysagnalslBZDLl8yJcrMsCVi4tPrZZc4vaeCsIWK4X dan@x1carbon"
];

programs.ssh.startAgent = true;

X2Go Client


# programs.x2goserver.enable = true;

End

}

User-level Config

{ config, pkgs, ... }:

let
  homeDir = builtins.getEnv "HOME";
  syncDir = builtins.toPath("${homeDir}/Sync");
  sources = import ./nix/sources.nix;
  nixos20_03 = import sources."nixpkgs-20.03" { };
  emacs-overlay = import (import ./nix/sources.nix)."emacs-overlay";
in {
  imports = [
    ./settings.nix
  ];

  home.stateVersion = "20.03";

  nixpkgs.config = {
    allowUnfree = true;
    packageOverrides = pkgs: { stable = nixos20_03; };
  };

  nixpkgs.overlays = [ emacs-overlay ];

Environment Variables

home.sessionVariables = {
  EDITOR = "emacsclient --create-frame --alternate-editor emacs";
  PASSWORD_STORE_DIR = "${syncDir}/.password-store";
  GNUPGHOME = "${syncDir}/.gnupg/";
  # GTK2_RC_FILES="${homeDir}/.gtkrc-2.0";
  # https://github.com/xmonad/xmonad/issues/126
  _JAVA_AWT_WM_NONREPARENTING = "1";
};

# gtk = {
#   enable = true;
#   iconTheme = {
#     name = "Adwaita";
#     package = pkgs.gnome3.adwaita-icon-theme;
#   };
#   theme = {
#     name = "Adwaita-dark";
#     package = pkgs.gnome3.gnome_themes_standard;
#   };
# };

xdg.enable = true;

Packages


home.packages = with pkgs; [
  rofi
  gnupg

  (pass.withExtensions (exts: [
    exts.pass-otp
    exts.pass-genphrase
  ]))

  xtrlock-pam  # screen locking
  maim  # screenshots
  rofi-pass  # interface to password manager
  xclip  # programmatic access to clipbaord
  arandr  # gui for xrandr (monitor layout)

  # direnv

  # Upstream failing :(
  # julia_13

  ## Doom dependencies

  (ripgrep.override {withPCRE2 = true;})
  gnutls              # for TLS connectivity

  ## Optional dependencies
  fd                  # faster projectile indexing
  imagemagick         # for image-dired
  pinentry_emacs

  ## Module dependencies
  # :tools lookup & :lang org +roam
  sqlite
  # :lang latex & :lang org (latex previews)
  texlive.combined.scheme-tetex

  firefox-beta-bin
];

Programs

programs = {
  1. Home Manager

    # Let Home Manager install and manage itself.
    home-manager.enable = true;
  2. Emacs

    emacs = {
      enable = true;
      # Compile with imagemagick support so I can resize images.
      package = pkgs.emacsGit.override { inherit (pkgs) imagemagick; };
    };
  3. Bash

    bash = {
      enable = true;
      historyFile = "${syncDir}/.config/bash/.bash_history";
      # FIXME: Document and reduce these
      shellOptions = [
        "autocd" "cdspell" "dirspell" "globstar" # bash >= 4
        "cmdhist" "nocaseglob" "histappend" "extglob"];
      # TODO: Test this
      # https://github.com/akermu/emacs-libvterm#directory-tracking-and-prompt-tracking
      initExtra = [
        ''
        vterm_prompt_end(){
          vterm_printf \"51;A$(whoami)@$(hostname):$(pwd)\"
        }
        PS1=$PS1'\\[$(vterm_prompt_end)\\]'
        ''
      ]
    };
  4. Git

    git = {
      enable = true;
      userName = "${config.settings.name}";
      userEmail = "${config.settings.email}";
    };
  5. Direnv

    # direnv.enable = true;
  6. SSH

    ssh = {
      enable = true;
    
      controlMaster  = "auto";
      controlPath    = "/tmp/ssh-%u-%r@%h:%p";
      controlPersist = "1800";
    
      forwardAgent = true;
      serverAliveInterval = 60;
    
      hashKnownHosts = true;
      userKnownHostsFile = "${homeDir}/.ssh/known_hosts";
    
      matchBlocks = {
        droplet = {
          hostname = "45.55.5.197";
          identityFile = "${homeDir}/.ssh/id_rsa";
          user = "dgirsh";
        };
        dangirsh = {
          host = "dangirsh.org";
          hostname = "ssh.phx.nearlyfreespeech.net";
          identityFile = "${homeDir}/.ssh/id_rsa";
          user = "dangirsh_dangirsh";
        };
        nixos-dev = {
          hostname = "45.79.58.229";
          identityFile = "${homeDir}/.ssh/id_rsa";
          user = "dan";
        };
      };
    };
  7. End

    };

Services

services = {
  emacs.enable = true;

  # redshift = {
  #   enable = true;
  #   latitude = "33";
  #   longitude = "-97";
  #   temperature.day = 6500;
  #   temperature.night = 3000;
  # };

  # https://www.reddit.com/r/emacsporn/comments/euf7m8/doomoutrunelectric_theme_xmonad_nixos/
  # https://github.com/willbush/system/blob/371cfa9933f24bca585a3c6c952c41c864d97aa0/nixos/home.nix#L178
  # compton = {
  #     enable = true;
  #     fade = true;
  #     backend = "xrender";
  #     fadeDelta = 1;
  #     # I only want transparency for a couple of applications.
  #     opacityRule = [
  #       "90:class_g ?= 'emacs' && focused"
  #       "75:class_g ?= 'emacs' && !focused"
  #       "90:class_g ?= 'alacritty' && focused"
  #       "75:class_g ?= 'alacritty' && !focused"
  #     ];
  #   };

  # lorri.enable = true;
};

End

}

TODO To test [0/5]

TODO To add [0/1]

  • keyboard config: currently requires xset & setxbmap
    • unified messenger (if it exists) for dealing with the madness of:
      • sms
      • signal
      • fb
      • whatsapp
      • keybase
      • slack
      • irc
      • riot
    • To try
    • Otherwise:
      • Browser tabs for everything sans signal
      • Signal-destop
      • maybe slack desktop
      • caprine for fb is nice on ubuntu

Global Constants

{config, pkgs, lib, ...}:

with lib;

{
  options = {
    settings = {
      name = mkOption {
        default = "Dan Girshovich";
        type = with types; uniq str;
      };
      username = mkOption {
        default = "dan";
        type = with types; uniq str;
      };
      email = mkOption {
        default = "dan.girsh@gmail.com";
        type = with types; uniq str;
      };
    };
  };
}

Hardware-specific Config

# Do not modify this file!  It was generated by ‘nixos-generate-config’
# and may be overwritten by future invocations.  Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, ... }:

{
  imports =
    [ <nixpkgs/nixos/modules/profiles/qemu-guest.nix>
    ];

  boot.initrd.availableKernelModules = [ "virtio_pci" "ahci" "sd_mod" ];
  boot.initrd.kernelModules = [ ];
  boot.kernelModules = [ ];
  boot.extraModulePackages = [ ];

  nix.maxJobs = lib.mkDefault 1;

Disk Mounts

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

  fileSystems."/" =
    { device = "/dev/disk/by-uuid/bf38bdde-34dd-4d57-9bfe-07de465f0f29";
      fsType = "ext4";
    };

  # Linode Volume "bkp". Targetted by syncthing.
  fileSystems."/bkp" =
    { device = "/dev/disk/by-id/scsi-0Linode_Volume_bkp";
      fsType = "ext4";
    };

  swapDevices =
    [ { device = "/dev/disk/by-uuid/7596d600-d2c6-4d77-b138-7f595283af00"; }
    ];
}

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": "https://github.com/nix-community/emacs-overlay/archive/0feda8b31b52f3ea008555dfe79dba3989d3e585.tar.gz",
        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
    },
    "ghcide-nix": {
        "branch": "master",
        "description": "Nix installation for ghcide",
        "homepage": "https://github.com/digital-asset/ghcide",
        "owner": "cachix",
        "repo": "ghcide-nix",
        "rev": "f940ec611cc6914693874ee5e024eba921cab19e",
        "sha256": "0vri0rivdzjvxrh6lzlwwkh8kzxsn82jp1c2w5rqzhp87y6g2k8z",
        "type": "tarball",
        "url": "https://github.com/cachix/ghcide-nix/archive/f940ec611cc6914693874ee5e024eba921cab19e.tar.gz",
        "url_template": "https://github.com/<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": "https://github.com/NixOS/nixpkgs",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "7829e5791ba1f6e6dbddbb9b43dda72024dd2bd1",
        "sha256": "0hs9swpz0kibjc8l3nx4m10kig1fcjiyy35qy2zgzm0a33pj114w",
        "type": "tarball",
        "url": "https://github.com/NixOS/nixpkgs/archive/7829e5791ba1f6e6dbddbb9b43dda72024dd2bd1.tar.gz",
        "url_template": "https://github.com/<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
    then
        if hasThisAsNixpkgsPath
        then import (builtins_fetchTarball { inherit (sources_nixpkgs) url sha256; }) {}
        else import <nixpkgs> {}
    else
        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:
      let
        inherit (builtins) lessThan nixVersion fetchTarball;
      in
        if lessThan nixVersion "1.12" then
          fetchTarball { inherit url; }
        else
          fetchTarball attrs;

  # fetchurl version that is compatible between all the versions of Nix
  builtins_fetchurl =
      { url, sha256 }@attrs:
      let
        inherit (builtins) lessThan nixVersion fetchurl;
      in
        if lessThan nixVersion "1.12" then
          fetchurl { inherit url; }
        else
          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"
  else
    if builtins.hasAttr "url" spec && builtins.hasAttr "sha256" spec
    then
      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.

Examples:

  • 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:

export GDK_SCALE=2

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:

systemctl --user daemon-reload
systemctl --user restart keybase.gui.service

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: https://orgmode.org/manual/Noweb-Reference-Syntax.html. See “This feature can also be used for management of indentation in exported code snippets”.

Autoloads