Skip to content
Permalink
34a0ff7967
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time

Hi there! That’s my dotfiles.

This repository contains configuration for three hosts:

  • omicron — my main laptop which runs NixOS
  • pie — my home server RPi running NixOS (see pie.org)
  • AlexeyShmalko — my work laptop running Ubuntu (ugh, corporate policies) with home-manager on top

Most of config files are generated by org-babel from org files in this repository (yes, including this very same README.org). That’s literate programming applied to dotfiles.

This file contains NixOS configuration for omicron, and common home-manager configuration between omicron and AlexeyShmalko. (I try to keep repetition to minimum, but some is inevitable.)

work.org file contains home-manager configuration specific for AlexeyShmalko.

pie.org is a separate NixOS config for pie host.

For Emacs configuration, see emacs.org.

To generate actual nix files, you can open this file in Emacs, and execute M-x org-babel-tangle. Or from command line with the following command.

emacs README.org --batch -f org-babel-tangle

Note that you need to patch org-babel to correctly generate configs. See Patch ob-tangle in Emacs config.

I keep generated files in sync with org files (so this repo is a valid Nix Flake), but they are not worth looking at—you’ll have much better time reading this doc instead.

Pieces not (yet) covered in org files are:

  • awesome wm configuration at .config/awesome/
  • scripts at bin/

Top-level

Flake

This repository is nix flakes–compatible.

The following goes to flake.nix file.

#
# This file is auto-generated from "README.org"
#
{
  description = "rasendubi's packages and NixOS/home-manager configurations";

  inputs = {
    nixpkgs = {
      type = "github";
      owner = "NixOS";
      repo = "nixpkgs-channels";
      ref = "nixpkgs-unstable";
    };

    <<flake-inputs>>
  };

  outputs = { self, ... }@inputs:
    let
      # Flakes are evaluated hermetically, thus are unable to access
      # host environment (including looking up current system).
      #
      # That's why flakes must explicitly export sets for each system
      # supported.
      systems = ["x86_64-linux" "aarch64-linux"];

      # genAttrs applies f to all elements of a list of strings, and
      # returns an attrset { name -> result }
      #
      # Useful for generating sets for all systems or hosts.
      genAttrs = list: f: inputs.nixpkgs.lib.genAttrs list f;

      # Generate pkgs set for each system. This takes into account my
      # nixpkgs config (allowUnfree) and my overlays.
      pkgsBySystem =
        let mkPkgs = system: import inputs.nixpkgs {
              inherit system;
              overlays = self.overlays.${system};
              config = { allowUnfree = true; };
            };
        in genAttrs systems mkPkgs;

      # genHosts takes an attrset { name -> options } and calls mkHost
      # with options+name. The result is accumulated into an attrset
      # { name -> result }.
      #
      # Used in NixOS and Home Manager configurations.
      genHosts = hosts: mkHost:
        genAttrs (builtins.attrNames hosts) (name: mkHost ({ inherit name; } // hosts.${name}));

      # merges a list of attrsets into a single attrset
      mergeSections = inputs.nixpkgs.lib.foldr inputs.nixpkgs.lib.mergeAttrs {};

    in mergeSections [
      <<flake-outputs-nixos>>
      <<flake-outputs-home-manager>>
      <<flake-outputs-packages>>
      <<flake-outputs-overlays>>
    ];
}

Nix flakes are still an experimental feature, so you need the following in NixOS configuration to enable it.

{
  nix = {
    package = pkgs.nixFlakes;
    extraOptions = ''
      experimental-features = nix-command flakes
    '';
  };
}

For non-NixOS system, install nixFlakes and put the following into ~/.config/nix/nix.conf.

experimental-features = nix-command flakes

NixOS

Expose NixOS configurations.

(let
  nixosHosts = {
    omicron = { system = "x86_64-linux";  config = ./nixos-config.nix; };

    # pie uses a separate config as it is very different
    # from other hosts.
    pie =     { system = "aarch64-linux"; config = ./pie.nix; };
  };

  mkNixosConfiguration = { name, system, config }:
    let pkgs = pkgsBySystem.${system};
    in inputs.nixpkgs.lib.nixosSystem {
      inherit system;
      modules = [
        { nixpkgs = { inherit pkgs; }; }
        (import config)
      ];
      specialArgs = { inherit name inputs; };
    };

in {
  nixosConfigurations = genHosts nixosHosts mkNixosConfiguration;
})

Home manager

Add home-manager to flake inputs.

home-manager = {
  type = "github";
  owner = "rycee";
  repo = "home-manager";
  ref = "bqv-flakes";
  inputs.nixpkgs.follows = "nixpkgs";
};

Expose home-manager configurations.

(let
  homeManagerHosts = {
    AlexeyShmalko = {
      system = "x86_64-linux";
      config = ./work.nix;
      username = "rasen";
      homeDirectory = "/home/rasen";
    };
  };

  mkHomeManagerConfiguration = { system, name, config, username, homeDirectory }:
    let pkgs = pkgsBySystem.${system};
    in inputs.home-manager.lib.homeManagerConfiguration {
      inherit system pkgs username homeDirectory;
      configuration = { ... }: {
        nixpkgs.config.allowUnfree = true;
        nixpkgs.overlays = self.overlays.${system};
        nixpkgs.config.firefox.enableTridactylNative = true;
        imports = [
          self.lib.home-manager-common

          (import config)
        ];
      };
    };

in {
  # Re-export common home-manager configuration to be reused between
  # NixOS module and standalone home-manager config.
  lib.home-manager-common = { lib, pkgs, config, ... }: {
    imports = [
      <<home-manager-section>>
    ];
  };
  homeManagerConfigurations = genHosts homeManagerHosts mkHomeManagerConfiguration;
})

Integrate home-manager module into NixOS.

{
  imports = [inputs.home-manager.nixosModules.home-manager];
  home-manager = {
    useUserPackages = true;
    useGlobalPkgs = true;
    users.rasen = inputs.self.lib.home-manager-common;
  };
}

Packages

Generate packages set for each supported system.

(let
  mkPackages = system:
    let
      pkgs = pkgsBySystem.${system};
    in
      mergeSections [
        <<flake-packages>>
      ];

in {
  packages = genAttrs systems mkPackages;
})

Overlays

Generate overlays for all supported systems.

(let
  mkOverlays = system: [
    # mix-in all local packages, so they are available as pkgs.${packages-name}
    (final: prev: self.packages.${system})

    <<flake-overlays>>
  ];
in {
  overlays = genAttrs systems mkOverlays;
})

<<flake-overlays>> are defined elsewhere.

NixOS

General

I’m a NixOS user. What’s cool about it is that I can describe all my system configuration in one file (almost). I can execute a single command and have a system with the same software, system settings, etc.

An outline of configuration looks like this:

#
# This file is auto-generated from "README.org"
#
{ name, config, pkgs, lib, inputs, ... }:
let
  machine-config = lib.getAttr name {
    omicron = [
      <<machine-omicron>>
    ];
  };

in
{
  imports = [
    {
      nixpkgs.config.allowUnfree = true;

      # The NixOS release to be compatible with for stateful data such as databases.
      system.stateVersion = "19.09";
    }

    <<nixos-section>>
  ] ++ machine-config;
}

This <<nixos-section>> is replaced by other parts of this doc.

Re-expose nixpkgs

{
  # for compatibility with nix-shell, nix-build, etc.
  environment.etc.nixpkgs.source = inputs.nixpkgs;
  nix.nixPath = ["nixpkgs=/etc/nixpkgs"];

  # register self and nixpkgs as flakes for quick access
  nix.registry = {
    self.flake = inputs.self;

    nixpkgs = {
      from = { id = "nixpkgs"; type = "indirect"; };
      flake = inputs.nixpkgs;
    };
  };
}

Same but for Home Manager–managed host.

{
  home.file."nixpkgs".source = inputs.nixpkgs;
  systemd.user.sessionVariables.NIX_PATH = lib.mkForce "nixpkgs=$HOME/nixpkgs\${NIX_PATH:+:}$NIX_PATH";

  xdg.configFile."nix/registry.json".text = builtins.toJSON {
    version = 2;
    flakes = [
      {
        from = { id = "self"; type = "indirect"; };
        to = ({
          type = "path";
          path = inputs.self.outPath;
        } // lib.filterAttrs
          (n: v: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash")
          inputs.self);
      }
      {
        from = { id = "nixpkgs"; type = "indirect"; };
        to = ({
          type = "path";
          path = inputs.nixpkgs.outPath;
        } // lib.filterAttrs
          (n: v: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash")
          inputs.nixpkgs);
      }
    ];
  };
}

Users

I’m the only user of the system:

{
  users.extraUsers.rasen = {
    isNormalUser = true;
    uid = 1000;
    extraGroups = [ "users" "wheel" "input" ];
    initialPassword = "HelloWorld";
  };
  nix.trustedUsers = ["rasen"];
}

initialPassword is used only first time when user is created. It must be changed as soon as possible with passwd.

Machines

I currently have only one machine.

omicron

This is my small Dell XPS 13.

{
  imports = [
    (import "${inputs.nixos-hardware}/dell/xps/13-9360")
    inputs.nixpkgs.nixosModules.notDetected
  ];

  boot.initrd.availableKernelModules = [ "xhci_pci" "nvme" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ];
  boot.kernelModules = [ "kvm-intel" ];
  boot.extraModulePackages = [ ];

  nix.maxJobs = lib.mkDefault 4;

  # powerManagement.cpuFreqGovernor = "powersave";

  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;
}

inputs.nixos-hardware comes from the following flake input.

nixos-hardware = {
  type = "github";
  owner = "NixOS";
  repo = "nixos-hardware";
  flake = false;
};

LVM on LUKS setup for disk encryption.

{
  boot.initrd.luks.devices = {
    root = {
      device = "/dev/disk/by-uuid/8b591c68-48cb-49f0-b4b5-2cdf14d583dc";
      preLVM = true;
    };
  };
  fileSystems."/boot" = {
    device = "/dev/disk/by-uuid/BA72-5382";
    fsType = "vfat";
  };
  fileSystems."/" = {
    device = "/dev/disk/by-uuid/434a4977-ea2c-44c0-b363-e7cf6e947f00";
    fsType = "ext4";
    options = [ "noatime" "nodiratime" "discard" ];
  };
  fileSystems."/home" = {
    device = "/dev/disk/by-uuid/8bfa73e5-c2f1-424e-9f5c-efb97090caf9";
    fsType = "ext4";
    options = [ "noatime" "nodiratime" "discard" ];
  };
  swapDevices = [
    { device = "/dev/disk/by-uuid/26a19f99-4f3a-4bd5-b2ed-359bed344b1e"; }
  ];
}

Clickpad:

{
  services.xserver.libinput = {
    enable = true;
    accelSpeed = "0.7";
  };
}

Bluetooth

I have a bluetooth headset, so this enables bluetooth audio in NixOS.

{
  hardware.bluetooth.enable = true;
  hardware.pulseaudio = {
    enable = true;

    # NixOS allows either a lightweight build (default) or full build
    # of PulseAudio to be installed.  Only the full build has
    # Bluetooth support, so it must be selected here.
    package = pkgs.pulseaudioFull;
  };
}

NTFS

Install ntfs-3g to mount ntfs volumes in read-write mode.

{
  environment.systemPackages = [
    pkgs.ntfs3g
  ];
}

Local packages

As a responsible NixOS user, I refuse to install software blindly with sudo make install. That’s why I must write my own nix-expressions.

Sandbox

Build all packages in sandbox:

{
  nix.useSandbox = true;
}

Naga

This is an integration package for Razer Naga Chroma and my awesome wm setup.

{
  naga = pkgs.callPackage ./naga { };
}

Go install it right away:

{
  home.packages = [ pkgs.naga ];
}

Custom Input font

I like the following settings more than defaults. I also need a custom four-style family because Emacs confuses regular/medium weight otherwise. Use link specified in requireFile to download the font.

./images/20200409192721-screenshot.png

{
  # note it's a new attribute and does not override old one
  input-mono = (pkgs.input-fonts.overrideAttrs (old: {
    src = pkgs.requireFile {
      name = "Input-Font.zip";
      url = "https://input.fontbureau.com/download/index.html?customize&fontSelection=fourStyleFamily&regular=InputMonoNarrow-Regular&italic=InputMonoNarrow-Italic&bold=InputMonoNarrow-Bold&boldItalic=InputMonoNarrow-BoldItalic&a=0&g=0&i=topserif&l=serifs_round&zero=0&asterisk=height&braces=straight&preset=default&line-height=1.2&email=";
      sha256 = "0nn41w2b6jvsbr3r4lfy4p8w2ssjmgdjzd1pbj7p0vmawjpvx2w8";
    };
    outputHash = "1w2i660dg04nyc6fc6r6sd3pw53h8dh8yx4iy6ccpii9gwjl9val";
  }));
}

Online banking

My bank uses a two-part websigner. The first part is a browser extension (does not require additional setup) and the second part is a native host application companion (it is installed here).

Pack native messaging host:

(let
  websigner =
    { stdenv
    , fetchurl
    , autoPatchelfHook
    , gtk2
    , glib
    , pcsclite
    }:
    stdenv.mkDerivation {
      pname = "procreditbank-websigner";
      version = "2020-01-20";

      src = fetchurl {
        url = "https://ibank.procreditbank.com.ua/websigner-linux.bin";
        sha256 = "1bm88jg7nhgrmc0q5hv35hgv4nc0d15ihl0acrhf6x5f7wv4pszv";
      };

      nativeBuildInputs = [ autoPatchelfHook ];

      buildInputs = [ gtk2 glib pcsclite ];

      unpackCmd = ''
        sh $src --extract
      '';

      dontConfigure = true;

      dontBuild = true;

      installPhase = ''
        mkdir -p $out/bin
        mkdir -p $out/lib/websigner/hosts/firefox
        mkdir -p $out/lib/websigner/hosts/chromium

        install -m 555 x86_64-linux/npwebsigner.so $out/lib/websigner
        install -m 777 x86_64-linux/nmwebsigner $out/lib/websigner

        sed "s|PLUGIN_PATH|$out/lib/websigner/nmwebsigner|" com.bifit.websigner-mozilla.json > $out/lib/websigner/hosts/firefox/com.bifit.websigner.json
        sed "s|PLUGIN_PATH|$out/lib/websigner/nmwebsigner|" com.bifit.websigner-chrome.json > $out/lib/websigner/hosts/chromium/com.bifit.websigner.json

        mkdir -p $out/lib/mozilla/native-messaging-hosts
        ln -s $out/lib/websigner/hosts/firefox/*.json $out/lib/mozilla/native-messaging-hosts
      '';
    };
in {
  procreditbank-websigner = pkgs.callPackage websigner { };
})

Override Firefox to use websigner.

(final: prev: {
  firefox = prev.firefox.override {
    extraNativeMessagingHosts = [ final.procreditbank-websigner ];
  };
})

Emacs

I use emacs-27 from emacs-overlay.

emacs-overlay = {
  type = "github";
  owner = "nix-community";
  repo = "emacs-overlay";
};

Use overlay (goes to flake-overlays section).

inputs.emacs-overlay.overlay

Expose Emacs with my packages as a top-level package.

(let
  emacs-base = pkgs.emacsGit;
  # emacs = pkgs.emacsUnstable;
  # emacs = pkgs.emacs.override {
  #   # Build emacs with proper imagemagick support.
  #   # See https://github.com/NixOS/nixpkgs/issues/70631#issuecomment-570085306
  #   imagemagick = pkgs.imagemagickBig;
  # };
  emacs-packages = (epkgs:
    (with epkgs.melpaPackages; [

      aggressive-indent
      atomic-chrome
      avy
      beacon
      blacken
      cider
      clojure-mode
      cmake-mode
      color-identifiers-mode
      company
      company-box
      company-lsp
      company-org-roam
      counsel
      counsel-projectile
      diff-hl
      diminish
      direnv
      dockerfile-mode
      doom-modeline
      dtrt-indent
      edit-indirect
      el-patch
      elpy
      epresent
      evil
      evil-collection
      evil-magit
      evil-numbers
      evil-org
      evil-surround
      evil-swap-keys
      fish-mode
      flycheck
      flycheck-inline
      flycheck-jest
      flycheck-rust
      forth-mode
      gcmh
      general
      gitconfig-mode
      go-mode
      google-translate
      graphviz-dot-mode
      groovy-mode
      haskell-mode
      imenu-list
      ivy
      ivy-bibtex
      jinja2-mode
      js2-mode
      json-mode
      ledger-mode
      lispyville
      lsp-haskell
      lsp-mode
      lsp-ui
      lua-mode
      magit
      markdown-mode
      mbsync
      modus-operandi-theme
      monokai-theme
      nix-mode
      nix-sandbox
      notmuch
      org-cliplink
      org-download
      org-drill
      org-ref
      org-roam
      org-roam-bibtex
      org-super-agenda
      paren-face
      php-mode
      pip-requirements
      plantuml-mode
      prettier-js
      projectile
      protobuf-mode
      psc-ide
      purescript-mode
      py-autopep8
      racer
      restclient
      rjsx-mode
      rust-mode
      smex
      spaceline
      terraform-mode
      tide
      typescript-mode
      use-package
      visual-fill-column
      vue-mode
      w3m
      web-mode
      wgrep
      which-key
      whitespace-cleanup-mode
      writegood-mode
      yaml-mode
      yasnippet

    ]) ++
    [
      epkgs.orgPackages.org-plus-contrib
      epkgs.elpaPackages.adaptive-wrap
      epkgs.exwm

      pkgs.ycmd
      pkgs.notmuch
      pkgs.w3m
      pkgs.imagemagick
      pkgs.shellcheck

      (pkgs.python3.withPackages (pypkgs: [
        pypkgs.autopep8
        pypkgs.black
        pypkgs.flake8
        pypkgs.mypy
        pypkgs.pylint
        pypkgs.virtualenv
      ]))

      (pkgs.aspellWithDicts (dicts: with dicts; [en en-computers en-science ru uk]))

      # latex for displaying fragments in org-mode
      (pkgs.texlive.combine {
        inherit (pkgs.texlive) scheme-small dvipng dvisvgm mhchem ;
      })
    ]
  );

  emacs-final = (pkgs.emacsPackagesGen emacs-base).emacsWithPackages emacs-packages;

 in {
   my-emacs = emacs-final // {
     base = emacs-base;
     packages = emacs-packages;
   };
 })

Install Emacs with Home manager

{
  programs.emacs = {
    enable = true;
    package = pkgs.my-emacs.base;
    extraPackages = pkgs.my-emacs.packages;
  };
  services.emacs.enable = true;

  # fonts used by emacs
  home.packages = [
    pkgs.input-mono
    pkgs.libertine
  ];
}

For the main emacs configuration, check emacs.org file.

Services

VPN

{
  services.openvpn.servers.nano-vpn = {
    config = ''
      config /root/openvpn/nano-vpn.ovpn
    '';
  };
}

NetworkManager

{
  networking = {
    hostName = name;

    networkmanager.enable = true;

    # disable wpa_supplicant
    wireless.enable = false;
  };

  users.extraUsers.rasen.extraGroups = [ "networkmanager" ];
}

Install network manager applet for user.

{
  home.packages = [pkgs.networkmanagerapplet];
}

Avahi

{
  services.avahi = {
    enable = true;
    interfaces = [];
    openFirewall = false;
  };
}

PulseAudio

Use pulseaudio (multiple sound sinks, skype calls).

Also, Pulseaudio is a requirement for Firefox Quantum.

{
  hardware.pulseaudio = {
    enable = true;
    support32Bit = true;
  };

}

pavucontrol is PulseAudio Volume Control—a nice utility for controlling pulseaudio settings.

{
  home.packages = [ pkgs.pavucontrol ];
}

Locate

Update locate database daily.

{
  services.locate = {
    enable = true;
    localuser = "rasen";
  };
}

SSH

{
  services.openssh = {
    enable = true;
    passwordAuthentication = false;
  };
}

Mosh

Mosh (mobile shell) is a cool addition to ssh.

{
  programs.mosh.enable = true;
}

Gitolite

{
  services.gitolite = {
    enable = true;
    user = "git";
    adminPubkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHH15uiQw3jBbrdlcRb8wOr8KVltuwbHP/JOFAzXFO1l/4QxnKs6Nno939ugULM7Lu0Vx5g6FreuCOa2NMWk5rcjIwOzjrZnHZ7aoAVnE7H9scuz8NGnrWdc1Oq0hmcDxdZrdKdB6CPG/diGWNZy77nLvz5JcX1kPLZENPeApCERwR5SvLecA4Es5JORHz9ssEcf8I7VFpAebfQYDu+VZZvEu03P2+5SXv8+5zjiuxM7qxzqRmv0U8eftii9xgVNC7FaoRBhhM7yKkpbnqX7IeSU3WeVcw4+d1d8b9wD/sFOyGc1xAcvafLaGdgeCQGU729DupRRJokpw6bBRQGH29 rasen@omicron";
  };
}

dnsmasq

Use dnsmasq as a DNS cache.

{
  services.dnsmasq = {
    enable = true;

    # These are used in addition to resolv.conf
    servers = [
      "8.8.8.8"
      "8.8.4.4"
    ];

    extraConfig = ''
      listen-address=127.0.0.1
      cache-size=1000

      no-negcache
    '';
  };
}

Syncthing

I use Syncthing to sync my org-mode files to my phone.

{
  services.syncthing = {
    enable = true;
    user = "rasen";
    dataDir = "/home/rasen/.config/syncthing";
    configDir = "/home/rasen/.config/syncthing";
    openDefaultPorts = true;
  };
}

Firewall

Enable firewall. This blocks all ports (for ingress traffic) and pings.

{
  networking.firewall = {
    enable = true;
    allowPing = false;

    connectionTrackingModules = [];
    autoLoadConntrackHelpers = false;
  };
}

Development

{
  virtualisation.docker.enable = true;
}

Backup

I use borg for backups.

(let
  commonOptions = {
    repo = "borg@10.13.0.3:.";
    encryption.mode = "keyfile-blake2";
    encryption.passCommand = "cat /root/secrets/borg";
    compression = "auto,lzma,9";
    doInit = false;
    environment = { BORG_RSH = "ssh -i /root/.ssh/borg"; };
    # UTC timestamp
    dateFormat = "-u +%Y-%m-%dT%H:%M:%S";
  };
in {
  services.borgbackup.jobs."all" = commonOptions // {
    archiveBaseName = "${config.networking.hostName}";
    paths = [
      "/var/lib/gitolite/"
      "/home/rasen/backup/"
      "/home/rasen/.ssh/"
      "/home/rasen/.gnupg/"
      "/home/rasen/.password-store/"
      "/home/rasen/dotfiles/"
      "/home/rasen/org/"

      # Mail
      "/home/rasen/Mail/"
      "/home/rasen/.mbsync/"
    ];
    exclude = [
      # Scanning notmuch takes too much time and doesn't make much
      # sense as it is easily replicable
      "/home/rasen/Mail/.notmuch"
    ];
  };

  # Start backup on boot if missed one while laptop was off
  systemd.timers.borgbackup-job-all.timerConfig = {
    Persistent = true;
  };

  # Require VPN connection for repo to be reachable
  systemd.services.borgbackup-job-all = {
    requires = ["openvpn-nano-vpn.service"];
  };
})

ADB

I need to access my Android device.

{
  services.udev.packages = [ pkgs.android-udev-rules ];
  programs.adb.enable = true;
  users.users.rasen.extraGroups = ["adbusers"];
}

fwupd

fwupd is a service that allows applications to update firmware.

{
  services.fwupd.enable = true;
}

Execute the following command to update firmware.

fwupdmgr get-updates

lorri + direnv

{
  services.lorri.enable = true;
  programs.direnv.enable = true;
}

Mail setup

{
  # Store mails in ~/Mail
  accounts.email.maildirBasePath = "Mail";

  # Use mbsync to fetch email. Configuration is constructed manually
  # to keep my current email layout.
  programs.mbsync = {
    enable = true;
    extraConfig = lib.mkBefore ''
      MaildirStore local
      Path ~/Mail/
      Inbox ~/Mail/INBOX
      SubFolders Verbatim
    '';
  };

  # Notmuch for email browsing, tagging, and searching.
  programs.notmuch = {
    enable = true;
    new.ignore = [
      ".mbsyncstate"
      ".mbsyncstate.lock"
      ".mbsyncstate.new"
      ".mbsyncstate.journal"
      ".uidvalidity"
      "dovecot-uidlist"
      "dovecot-keywords"
      "dovecot.index"
      "dovecot.index.log"
      "dovecot.index.log.2"
      "dovecot.index.cache"
      "/^archive/"
    ];
  };

  # msmtp for sending mail
  programs.msmtp.enable = true;

  # My Maildir layout predates home-manager configuration, so I do not
  # use mbsync config generation from home-manager, to keep layout
  # compatible.
  imports =
    let
      emails = [
        { name = "gmail";   email = "rasen.dubi@gmail.com";    path = "Personal"; primary = true; }
        { name = "ps";      email = "ashmalko@doctoright.org"; path = "protocolstandard"; }
        { name = "egoless"; email = "me@egoless.tech";         path = "egoless"; }
      ];
      mkGmailBox = { name, email, path, ... }@all: {
        accounts.email.accounts.${name} = {
          realName = "Alexey Shmalko";
          address = email;
          flavor = "gmail.com";

          passwordCommand = "pass imap.gmail.com/${email}";
          maildir.path = path;

          msmtp.enable = true;
          notmuch.enable = true;
        } // (removeAttrs all ["name" "email" "path"]);

        programs.mbsync.extraConfig = ''
          IMAPAccount ${name}
          Host imap.gmail.com
          User ${email}
          PassCmd "pass imap.gmail.com/${email}"
          SSLType IMAPS
          CertificateFile /etc/ssl/certs/ca-certificates.crt

          IMAPStore ${name}-remote
          Account ${name}

          Channel sync-${name}-all
          Master :${name}-remote:"[Gmail]/All Mail"
          Slave :local:${path}/all
          Create Both
          SyncState *

          Channel sync-${name}-spam
          Master :${name}-remote:"[Gmail]/Spam"
          Slave :local:${path}/spam
          Create Both
          SyncState *

          Channel sync-${name}-sent
          Master :${name}-remote:"[Gmail]/Sent Mail"
          Slave :local:${path}/sent
          Create Both
          SyncState *

          Group sync-${name}
          Channel sync-${name}-all
          Channel sync-${name}-spam
          Channel sync-${name}-sent
        '';
      };
    in map mkGmailBox emails;
}

Environment

General

I definitely use X server:

{
  services.xserver.enable = true;
}

Use English as my only supported locale:

{
  i18n.supportedLocales = [ "en_US.UTF-8/UTF-8" ];
}

Setup timezone:

{
  time.timeZone = "Europe/Kiev";
}

Login manager / display manager

{
  services.xserver.displayManager.lightdm.enable = true;
}

Window manager (EXWM)

I recently switched to use EXWM as my window manager.

NixOS has an EXWM module, but my feeling is that it’s too limiting.

{
  services.xserver.windowManager.session = lib.singleton {
    name = "exwm";
    start = ''
      exec ${pkgs.my-emacs}/bin/emacsclient -a "" -c
    '';
  };
  services.xserver.displayManager.defaultSession = "none+exwm";
}

Without a window compositor, I have experienced window tearing (especially in Firefox)—with picom everything runs smoothly.

{
  services.picom.enable = true;
}

Awesome WM (backup)

Keep awesome wm as a backup window manager.

{
  services.xserver.windowManager.awesome = {
    enable = true;
    luaModules = [ pkgs.luaPackages.luafilesystem pkgs.luaPackages.cjson ];
  };
  services.xserver.displayManager.defaultSession = lib.mkDefault "none+awesome";
}

Disabling xterm makes awesome wm a default choice in slim:

{
  services.xserver.desktopManager.xterm.enable = false;
}

These packages are used by my awesome wm setup:

{
  home.packages = [
    pkgs.wmname
    pkgs.xclip
    pkgs.escrotum
    pkgs.xorg.xkbcomp
  ];
}

Link awesome wm configuration in place.

{
  xdg.configFile."awesome".source = ./.config/awesome;
}

Keyboard

Layouts

I use English and Ukrainian layouts. I also use Russian symbols, but they are on the third level.

{
  services.xserver.layout = "us,ua";
  services.xserver.xkbVariant = "workman,";

  # Use same config for linux console
  console.useXkbConfig = true;
}

Map left Caps Lock to Ctrl, and left Ctrl to switch between layout. (Shift-Ctrl triggers Caps Lock function.)

I toggle between them with either Caps Lock, or Menu key—I have two different keyboards, and one doesn’t have Menu when Caps Lock is too far on the second. I never use Caps Lock–the feature, so it’s nice to have Caps LED indicate alternate layouts.

{
  services.xserver.xkbOptions = "grp:lctrl_toggle,grp_led:caps,ctrl:nocaps";
}
{
  home.keyboard = {
    layout = "us,ua";
    variant = "workman,";
  };
}

Use a slightly customized Workman keyboard layout (more keys on 3rd level).

{

  xsession.initExtra = ''
    xkbcomp ${./Xkeymap} $DISPLAY
  '';
}

Layout indicator

I use built-in awesome layout indicator. See .config/awesome/rc.lua for more details.

Redshift

Redshift adjusts the color temperature of the screen according to the position of the sun.

Blue light blocks melatonin (sleep harmone) secretion, so you feel less sleepy when you stare at computer screen. Redshift blocks some blue light (making screen more red), which should improve melatonin secretion and restore sleepiness (which is a good thing).

{
  services.redshift = {
    enable = true;
  };
  location.provider = "geoclue2";
}

Screen brightness

xbacklight stopped working recently. acpilight is a drop-in replacement.

{
  hardware.acpilight.enable = true;
  environment.systemPackages = [
    pkgs.acpilight
  ];
  users.extraUsers.rasen.extraGroups = [ "video" ];
}

For Home Manager–managed hosts.

{
  home.packages = [pkgs.acpilight];
}

Look and Feel

Fonts

I’m not a font guru, so I just stuffed a bunch of random fonts in here.

{
  fonts = {
    fontconfig.enable = true;
    enableFontDir = true;
    enableGhostscriptFonts = false;

    fonts = with pkgs; [
      pkgs.inconsolata
      pkgs.dejavu_fonts
      pkgs.source-code-pro
      pkgs.ubuntu_font_family
      pkgs.unifont
      pkgs.powerline-fonts
      pkgs.terminus_font
    ];
  };
}

For home-manager.

{
  fonts.fontconfig.enable = true;
  home.packages = [
    pkgs.inconsolata
    pkgs.dejavu_fonts
    pkgs.source-code-pro
    pkgs.ubuntu_font_family
    pkgs.unifont
    pkgs.powerline-fonts
    pkgs.terminus_font
  ];
}

Hi-DPI

These are for omicron-only.

{
  xresources.properties = {
    "Xft.dpi" = 276;
    "Xcursor.size" = 64;
  };
}
{
  console.packages = [
    pkgs.terminus_font
  ];
  console.font = "ter-132n";
}
{
  services.xserver.dpi = 276;
}

Applications

Here go applications (almost) every normal user needs.

GPG

{
  programs.gnupg.agent = {
    enable = true;
    enableSSHSupport = true;
    pinentryFlavor = "qt";
  };

  ## is it no longer needed?
  #
  # systemd.user.sockets.gpg-agent-ssh = {
  #   wantedBy = [ "sockets.target" ];
  #   listenStreams = [ "%t/gnupg/S.gpg-agent.ssh" ];
  #   socketConfig = {
  #     FileDescriptorName = "ssh";
  #     Service = "gpg-agent.service";
  #     SocketMode = "0600";
  #     DirectoryMode = "0700";
  #   };
  # };

  services.pcscd.enable = true;
}

Yubikey

{
  environment.systemPackages = [
    pkgs.yubikey-manager
    pkgs.yubikey-personalization
    pkgs.yubikey-personalization-gui
  ];

  services.udev.packages = [
    pkgs.yubikey-personalization
    pkgs.libu2f-host
  ];
}

password-store

Install password-store along with one-time password extension.

{
  home.packages = [
    (pkgs.pass.withExtensions (exts: [ exts.pass-otp ]))
  ];
}

Install browserpass firefox extension backend.

{
  programs.browserpass = {
    enable = true;
    browsers = ["firefox" "chrome"];
  };
}

KDE apps

I don’t use full KDE but some apps are definitely nice.

{
  home.packages = [
    pkgs.gwenview
    pkgs.dolphin
    pkgs.kdeFrameworks.kfilemetadata
    pkgs.filelight
    pkgs.shared_mime_info
  ];
}

KDE apps might have issues with mime types without this:

{
  environment.pathsToLink = [ "/share" ];
}

Browsers

Firefox is default; Chrome for backup.

{
  home.packages = [
    pkgs.firefox
    pkgs.google-chrome
  ];
}

Zathura

Zathura is a cool document viewer with Vim-like bindings.

{
  programs.zathura = {
    enable = true;
    options = {
      incremental-search = true;
    };

    # Swap j/k (for Workman layout)
    extraConfig = ''
      map j scroll up
      map k scroll down
    '';
  };
}

Screen locking

Slock

Slock is a simple X display locker and should probably not crash as xscreensaver does.

Slock tries to disable OOM killer (so the locker is not killed when memory is low) and this requires a suid flag for executable. Otherwise, you get the following message:

slock: unable to disable OOM killer. Make sure to suid or sgid slock.
{
  programs.slock.enable = true;
}

xss-lock

xss-lock is a small utility to plug a screen locker into screen saver extension for X. This automatically activates selected screensaver after a period of user inactivity, or when system goes to sleep.

{
  home.packages = [
    pkgs.xss-lock
  ];
}

User applications

{
  home.packages = [
    pkgs.google-play-music-desktop-player
    pkgs.tdesktop # Telegram

    pkgs.mplayer
    pkgs.smplayer
  ];
}

Development

Editors

I’m a seasoned Vim user, but I’ve switched to emacs.

{
  environment.systemPackages = [
    (pkgs.vim_configurable.override { python3 = true; })
    pkgs.neovim
  ];
}

For Home Manager–managed hosts.

{
  home.packages = [
    (pkgs.vim_configurable.override { python3 = true; })
    pkgs.neovim
  ];
}

rxvt-unicode

I use urxvt as my terminal emulator.

{
  programs.urxvt = {
    enable = true;
    iso14755 = false;

    fonts = [
      "-*-terminus-medium-r-normal-*-32-*-*-*-*-*-iso10646-1"
    ];

    scroll = {
      bar.enable = false;
      lines = 65535;
      scrollOnOutput = false;
      scrollOnKeystroke = true;
    };
    extraConfig = {
      "loginShell" = "true";
      "urgentOnBell" = "true";
      "secondaryScroll" = "true";

      # Molokai color theme
      "background" = "#101010";
      "foreground" = "#d0d0d0";
      "color0" = "#101010";
      "color1" = "#960050";
      "color2" = "#66aa11";
      "color3" = "#c47f2c";
      "color4" = "#30309b";
      "color5" = "#7e40a5";
      "color6" = "#3579a8";
      "color7" = "#9999aa";
      "color8" = "#303030";
      "color9" = "#ff0090";
      "color10" = "#80ff00";
      "color11" = "#ffba68";
      "color12" = "#5f5fee";
      "color13" = "#bb88dd";
      "color14" = "#4eb4fa";
      "color15" = "#d0d0d0";
    };
  };
}

Urxvt gets its setting from .Xresources file. If you ever want to reload it on-the-fly, type the following (or press C-c C-c if you’re reading this document in emacs now):

xrdb ~/.Xresources

Font

I use Terminus font.

{
  fonts = {
    fonts = [
      pkgs.powerline-fonts
      pkgs.terminus_font
    ];
  };
}

shell

fish is a cool shell, I use it as my default for day-to-day work.

{
  programs.fish.enable = true;
  users.defaultUserShell = pkgs.fish;
}

For home-manager:

{
  programs.fish = {
    enable = true;
    shellAliases = {
      g = "git";
      rm = "rm -r";
      ec = "emacsclient";
    };
    functions = {
      # old stuff
      screencast = ''
        function screencast
            # key-mon --meta --nodecorated --theme=big-letters --key-timeout=0.05 &
            ffmpeg -probesize 3000000000 -f x11grab -framerate 25 -s 3840x3960 -i :0.0 -vcodec libx264 -threads 2 -preset ultrafast -crf 0 ~/tmp/record/record-(date +"%FT%T%:z").mkv
            # killall -r key-mon
        end
      '';
      reencode = ''
        function reencode
            ffmpeg -i file:$argv[1] -c:v libx264 -crf 0 -preset veryslow file:(basename $argv[1] .mkv).crf-0.min.mkv
        end
      '';
    };
  };

  # manage other shells as well
  programs.bash.enable = true;
}

Vi key bindings

{
  programs.fish.functions.fish_user_key_bindings = ''
    function fish_user_key_bindings
        fish_vi_key_bindings

        bind -s j up-or-search
        bind -s k down-or-search
        bind -s -M visual j up-line
        bind -s -M visual k down-line

        bind -s '.' repeat-jump
    end
  '';
}

git

home-manager-section

{
  programs.git = {
    enable = true;
    package = pkgs.gitAndTools.gitFull;

    userName = "Alexey Shmalko";
    userEmail = "rasen.dubi@gmail.com";

    signing = {
      key = "EB3066C3";
      signByDefault = true;
    };

    extraConfig = {
      sendemail = {
        smtpencryption = "ssl";
        smtpserver = "smtp.gmail.com";
        smtpuser = "rasen.dubi@gmail.com";
        smtpserverport = 465;
      };

      color.ui = true;
      core.editor = "vim";
      push.default = "simple";
      pull.rebase = true;
      rebase.autostash = true;
      rerere.enabled = true;
      advice.detachedHead = false;
    };
  };
}

I have LOTS of aliases:

{
  programs.git.aliases = {
    cl    = "clone";
    gh-cl = "gh-clone";
    cr    = "cr-fix";
    p     = "push";
    pl    = "pull";
    f     = "fetch";
    fa    = "fetch --all";
    a     = "add";
    ap    = "add -p";
    d     = "diff";
    dl    = "diff HEAD~ HEAD";
    ds    = "diff --staged";
    l     = "log --show-signature";
    l1    = "log -1";
    lp    = "log -p";
    c     = "commit";
    ca    = "commit --amend";
    co    = "checkout";
    cb    = "checkout -b";
    cm    = "checkout origin/master";
    de    = "checkout --detach";
    fco   = "fetch-checkout";
    br    = "branch";
    s     = "status";
    re    = "reset --hard";
    r     = "rebase";
    rc    = "rebase --continue";
    ri    = "rebase -i";
    m     = "merge";
    t     = "tag";
    su    = "submodule update --init --recursive";
    bi    = "bisect";
  };
}

Always push to github with ssh keys instead of login/password.

{
  programs.git.extraConfig = {
    url."git@github.com:".pushInsteadOf = "https://github.com";
  };
}

Also, install git for the rest of the system.

{
  environment.systemPackages = [
    pkgs.git
  ];
}

tmux

{
  environment.systemPackages = [
    pkgs.tmux
  ];
}

For home-manager.

{
  programs.tmux = {
    enable = true;
    keyMode = "vi";
    # Use C-a as prefix
    shortcut = "a";
    # To make vim work properly
    terminal = "screen-256color";

    # start numbering from 1
    baseIndex = 1;
    # Allows for faster key repetition
    escapeTime = 0;
    historyLimit = 10000;

    reverseSplit = true;

    clock24 = true;

    extraConfig = ''
      bind-key S-left swap-window -t -1
      bind-key S-right swap-window -t +1

      bind h select-pane -L
      bind k select-pane -D
      bind j select-pane -U
      bind l select-pane -R

      bind r source-file ~/.tmux.conf \; display-message "Config reloaded..."

      set-window-option -g automatic-rename
    '';
  };
}

Other terminal goodies

{
  environment.systemPackages = [
    pkgs.wget
    pkgs.htop
    pkgs.psmisc
    pkgs.zip
    pkgs.unzip
    pkgs.unrar
    pkgs.bind
    pkgs.file
    pkgs.which
    pkgs.utillinuxCurses
    pkgs.ripgrep

    pkgs.patchelf

    pkgs.python3
  ];
  # environment.variables.NPM_CONFIG_PREFIX = "$HOME/.npm-global";
  # environment.variables.PATH = "$HOME/.npm-global/bin:$PATH";
}

Man pages

This install a number of default man pages for the linux/posix system.

{
  documentation = {
    man.enable = true;
    dev.enable = true;
  };

  environment.systemPackages = [
    pkgs.man-pages
    pkgs.stdman
    pkgs.posix_man_pages
    pkgs.stdmanpages
  ];
}

Other configs

{
  home.file = {
    ".vim".source = ./.vim;
    ".nvim".source = ./.vim;
    ".nethackrc".source = ./.nethackrc;
  };

  programs.fish.shellInit = ''
    set -x PATH ${./bin} $PATH
  '';
}