about summary refs log tree commit diff
path: root/modules/shell
diff options
context:
space:
mode:
Diffstat (limited to 'modules/shell')
-rw-r--r--modules/shell/cargo/default.nix7
-rw-r--r--modules/shell/cmake/default.nix5
-rw-r--r--modules/shell/default.nix67
-rw-r--r--modules/shell/git/attributes2
-rw-r--r--modules/shell/git/config.in81
-rw-r--r--modules/shell/git/default.nix32
-rw-r--r--modules/shell/git/diff/mozlz4.in4
-rw-r--r--modules/shell/git/ignore31
-rw-r--r--modules/shell/kakoune/default.nix7
-rw-r--r--modules/shell/kakoune/kakrc86
-rw-r--r--modules/shell/less/default.nix8
-rw-r--r--modules/shell/lynx/default.nix16
-rw-r--r--modules/shell/lynx/lynx.cfg17
-rw-r--r--modules/shell/lynx/lynx.lss105
-rw-r--r--modules/shell/pass/default.nix11
-rw-r--r--modules/shell/screen/default.nix7
-rw-r--r--modules/shell/tmux/config.nix90
-rw-r--r--modules/shell/tmux/default.nix8
-rw-r--r--modules/shell/units/default.nix9
-rw-r--r--modules/shell/zsh/default.nix18
-rw-r--r--modules/shell/zsh/zshrc.nix226
21 files changed, 837 insertions, 0 deletions
diff --git a/modules/shell/cargo/default.nix b/modules/shell/cargo/default.nix
new file mode 100644
index 000000000000..523d2f992a78
--- /dev/null
+++ b/modules/shell/cargo/default.nix
@@ -0,0 +1,7 @@
+{ ... }:
+
+{
+  environment.extraInit = ''
+    export CARGO_HOME="$HOME/state/cargo"
+  '';
+}
diff --git a/modules/shell/cmake/default.nix b/modules/shell/cmake/default.nix
new file mode 100644
index 000000000000..055d82cd368d
--- /dev/null
+++ b/modules/shell/cmake/default.nix
@@ -0,0 +1,5 @@
+{ ... }:
+
+{
+  environment.variables.CMAKE_EXPORT_COMPILE_COMMANDS = "1";
+}
diff --git a/modules/shell/default.nix b/modules/shell/default.nix
new file mode 100644
index 000000000000..2f161f472b14
--- /dev/null
+++ b/modules/shell/default.nix
@@ -0,0 +1,67 @@
+{ pkgs, ... }:
+
+{
+  imports = [
+    ./cargo ./cmake ./git ./kakoune ./less ./lynx ./pass ./screen
+    ./tmux ./units ./zsh
+  ];
+
+  environment.systemPackages = with pkgs; [
+    (aspellWithDicts (dicts: with dicts; [ en en-computers en-science eo ]))
+    bind.dnsutils
+    binutils
+    coreutils-prefixed
+    curl
+    direnv
+    execline
+    file
+    finger_bsd
+    ffsend
+    fzf
+    gdb
+    gnused
+    gotop
+    htop
+    httpie
+    jq
+    libarchive
+    lsof
+    moreutils
+    ncdu
+    neovim
+    nix-diff
+    nix-output-monitor
+    nix-top
+    nmap
+    openssh
+    pciutils
+    pv
+    ranger
+    restic
+    silver-searcher
+    socat
+    traceroute
+    tree
+    unixtools.watch
+    unzip
+    wget
+    whois
+
+    (busybox.override {
+      enableMinimal = true;
+      extraConfig = ''
+        CONFIG_PSTREE y
+      '';
+    })
+  ] ++ lib.optional stdenv.isDarwin pinentry_mac;
+
+  environment.variables.CLICOLOR = "1";
+  environment.variables.EDITOR = "kak";
+  environment.variables.EMAIL = "hi@alyssa.is";
+  environment.variables.GOPATH = "$XDG_DATA_HOME/go";
+
+  security.sudo.package = pkgs.sudo.override { withInsults = true; };
+  security.sudo.extraConfig = ''
+    Defaults insults
+  '';
+}
diff --git a/modules/shell/git/attributes b/modules/shell/git/attributes
new file mode 100644
index 000000000000..57ea092a232a
--- /dev/null
+++ b/modules/shell/git/attributes
@@ -0,0 +1,2 @@
+*.json diff=json
+*.json.mozlz4 diff=mozlz4
diff --git a/modules/shell/git/config.in b/modules/shell/git/config.in
new file mode 100644
index 000000000000..138ab59124ce
--- /dev/null
+++ b/modules/shell/git/config.in
@@ -0,0 +1,81 @@
+[absorb]
+    maxStack = 100
+[alias]
+    ignore = "!gi() { curl -L -s https://www.gitignore.io/api/$@ ;}; gi"
+    unbranch = "!git branch --sort -HEAD --merged | sed 1d | xargs git branch -d"
+[am]
+    messageid = true
+    threeWay = true
+[branch]
+    autosetuprebase = always
+[color "branch"]
+    current = yellow reverse
+    local = yellow
+    remote = green
+[color "diff"]
+    frag = magenta bold
+    meta = yellow bold
+    new = green bold
+    old = red bold
+[color "status"]
+    added = yellow
+    changed = green
+    untracked = cyan
+[commit]
+    gpgsign = true
+    verbose = true
+[core]
+    attributesfile = @attributesfile@
+    excludesfile = @ignorefile@
+[credential]
+    helper = osxkeychain
+[diff]
+    algorithm = histogram
+    compactionHeuristic = true
+    mnemonicPrefix = true
+    renameLimit = 0
+    renames = copy
+    submodule = diff
+    wsErrorHighlight = all
+[diff "mozlz4"]
+    textconv = @mozlz4_textconv@
+[fetch]
+    parallel = 0
+[filter "lfs"]
+    clean = git-lfs clean -- %f
+    required = true
+    smudge = git-lfs smudge -- %f
+[github]
+    user = alyssais
+[grep]
+    fallbackToNoIndex = true
+    lineNumber = true
+    patternType = perl
+[help]
+    autocorrect = 1
+[hub]
+    protocol = ssh
+[init]
+    defaultBranch = main
+[merge]
+    conflictstyle = diff3
+    tool = opendiff
+[pull]
+    rebase = true
+[push]
+    default = upstream
+[rebase]
+    autoSquash = true
+    autoStash = true
+    rescheduleFailedExec = true
+    stat = true
+[rerere]
+    enable = true
+[sendemail]
+    assume8bitEncoding = UTF-8
+    confirm = always
+    envelopeSender = auto
+[stash]
+    showPatch = true
+[submodule]
+    fetchJobs = 0
diff --git a/modules/shell/git/default.nix b/modules/shell/git/default.nix
new file mode 100644
index 000000000000..94a89b316275
--- /dev/null
+++ b/modules/shell/git/default.nix
@@ -0,0 +1,32 @@
+{ pkgs, ... }:
+
+let
+  mozlz4_textconv = pkgs.substituteAll {
+    src = diff/mozlz4.in;
+    isExecutable = true;
+
+    inherit (pkgs) execline mozlz4a;
+  };
+
+  config = pkgs.substituteAll {
+    src = ./config.in;
+
+    inherit mozlz4_textconv;
+    python = pkgs.python3;
+    attributesfile = ./attributes;
+    ignorefile = ./ignore;
+    preferLocalBuild = true;
+  };
+
+in
+{
+  imports = [ ../../xdg ];
+
+  environment.systemPackages = with pkgs; with gitAndTools; [
+    (git.override { svnSupport = true; sendEmailSupport = true; })
+    git-absorb
+    git-remote-hg
+  ];
+
+  users.users.qyliss.xdg.config.paths."git/config" = config;
+}
diff --git a/modules/shell/git/diff/mozlz4.in b/modules/shell/git/diff/mozlz4.in
new file mode 100644
index 000000000000..4377e69526c7
--- /dev/null
+++ b/modules/shell/git/diff/mozlz4.in
@@ -0,0 +1,4 @@
+#! @execline@/bin/execlineb -S1
+
+pipeline { @mozlz4a@/bin/mozlz4a -d $1 /dev/stdout }
+@python@/bin/python -m json.tool
diff --git a/modules/shell/git/ignore b/modules/shell/git/ignore
new file mode 100644
index 000000000000..c5887a0fb636
--- /dev/null
+++ b/modules/shell/git/ignore
@@ -0,0 +1,31 @@
+# macOS
+.DS_Store
+
+# Kakoune
+*.kak.*
+
+# Sublime Text
+sublime-project
+sublime-workspace
+
+# IntelliJ IDEA
+.idea
+*.iml
+
+# Bundler
+gem_graph.png
+
+# Direnv
+.envrc
+
+# ctags
+/tags
+
+# Emacs
+*~
+\#*\#
+.\#*
+
+# clangd
+compile_commands.json
+.cache/clangd
diff --git a/modules/shell/kakoune/default.nix b/modules/shell/kakoune/default.nix
new file mode 100644
index 000000000000..8831d628f2b8
--- /dev/null
+++ b/modules/shell/kakoune/default.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+  environment.systemPackages = with pkgs; [ kakoune ];
+
+  users.users.qyliss.xdg.config.paths."kak/kakrc" = pkgs.copyPathToStore ./kakrc;
+}
diff --git a/modules/shell/kakoune/kakrc b/modules/shell/kakoune/kakrc
new file mode 100644
index 000000000000..33b6535ac2a9
--- /dev/null
+++ b/modules/shell/kakoune/kakrc
@@ -0,0 +1,86 @@
+# 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)(?<!\.patch)$ %{
+  try %{execute-keys -draft %{%s\h+$<ret>d}}
+  echo
+}
+
+hook global WinSetOption filetype=mail %{
+  set-option buffer autowrap_column 72
+  autowrap-enable
+}
+
+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 | choose}
+}
+alias global o open
+
+define-command -params 1.. -docstring "change file modes" chmod %{
+  nop %sh{ chmod $@ "$kak_buffile" }
+}
+
+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/modules/shell/less/default.nix b/modules/shell/less/default.nix
new file mode 100644
index 000000000000..0803cfaf2755
--- /dev/null
+++ b/modules/shell/less/default.nix
@@ -0,0 +1,8 @@
+{ pkgs, ... }:
+
+{
+  environment.systemPackages = with pkgs; [ less ];
+
+  # Use whole terminal, use smart case in search, color, don't wrap.
+  environment.variables.LESS = "ciRS";
+}
diff --git a/modules/shell/lynx/default.nix b/modules/shell/lynx/default.nix
new file mode 100644
index 000000000000..0e1262b07a7d
--- /dev/null
+++ b/modules/shell/lynx/default.nix
@@ -0,0 +1,16 @@
+{ pkgs, ... }:
+
+{
+  environment.extraInit = ''
+    export LYNX_CFG="/etc/xdg/nixos/per-user/$USER/lynx/lynx.cfg"
+    export LYNX_LSS="/etc/xdg/nixos/per-user/$USER/lynx/lynx.lss"
+  '';
+
+  environment.systemPackages = with pkgs; [ lynx ];
+
+  users.users.qyliss.xdg.config.paths."lynx/lynx.cfg" =
+    pkgs.copyPathToStore ./lynx.cfg;
+
+  users.users.qyliss.xdg.config.paths."lynx/lynx.lss" =
+    pkgs.copyPathToStore ./lynx.lss;
+}
diff --git a/modules/shell/lynx/lynx.cfg b/modules/shell/lynx/lynx.cfg
new file mode 100644
index 000000000000..3359b95ce9af
--- /dev/null
+++ b/modules/shell/lynx/lynx.cfg
@@ -0,0 +1,17 @@
+STARTFILE:https://search.weho.st/
+DEFAULT_INDEX_FILE:https://search.weho.st/
+SAVE_SPACE:/tmp
+URL_DOMAIN_PREFIXES:
+URL_DOMAIN_SUFFIXES:
+SHOW_CURSOR:TRUE
+UNDERLINE_LINKS:TRUE
+SOURCE_CACHE:MEMORY
+FORCE_SSL_COOKIES_SECURE:TRUE
+SYSTEM_MAIL:neomutt
+VI_KEYS_ALWAYS_ON:TRUE
+DEFAULT_KEYPAD_MODE:LINKS_AND_FIELDS_ARE_NUMBERED
+MAKE_LINKS_FOR_ALL_IMAGES:TRUE
+XLOADIMAGE_COMMAND:imv %s &
+EXTERNAL:mailto:neomutt %s:TRUE:TRUE
+TEXTFIELDS_NEED_ACTIVATION:TRUE
+CHARACTER_SET:utf-8
diff --git a/modules/shell/lynx/lynx.lss b/modules/shell/lynx/lynx.lss
new file mode 100644
index 000000000000..cf0d3265e00c
--- /dev/null
+++ b/modules/shell/lynx/lynx.lss
@@ -0,0 +1,105 @@
+# Normal type styles correspond to HTML tags.
+#
+# The next line (beginning with "em") means:  use bold if mono, otherwise
+# brightblue on <defaultbackground>
+em:		bold:			brightblue
+strong:		bold:			brightred
+b:		bold:			red
+i:		bold:			brightblue
+a:		bold:			green
+img:		dim:			brown
+fig:		normal:			gray
+caption:	reverse:		brown
+hr:		normal:			yellow
+blockquote:	normal:			brightblue
+ul:		normal:			brown
+address:	normal:			magenta
+title:		normal:			magenta
+tt:		dim:			brightmagenta:	black
+h1:		bold:			yellow:		blue
+label:		normal:			magenta
+q:		normal:			yellow:		magenta
+small:		dim:			default
+big:		bold:			yellow
+sup:		bold:			yellow
+sub:		dim:			gray
+li:		normal:			magenta
+code:		normal:			cyan
+cite:		normal:			cyan
+
+table:		normal:			brightcyan
+tr:		bold:			brown
+td:		normal:			default
+br:		normal:			default
+
+# Special styles - not corresponding directly to HTML tags
+#	alert	- status bar, when message begins "Alert".
+#	alink	- active link
+#	normal	- default attributes
+#	status	- status bar
+#	whereis	- whereis search target
+#
+#normal:normal:default:blue
+alink:		reverse:		yellow:		black
+status:		reverse:		yellow:		blue
+alert:		bold:			yellow:		red
+whereis:	reverse+underline:	magenta:	cyan
+# currently not used
+#value:normal:green
+
+menu.bg:	normal:			black:		lightgray
+menu.frame:	normal:			black:		lightgray
+menu.entry:	normal:			lightgray:	black
+menu.n:		normal:			red:		gray
+menu.active:	normal:			yellow:		black
+menu.sb:	normal:			brightred:	lightgray
+
+forwbackw.arrow:reverse
+hot.paste:	normal:			brightred:	gray
+
+# Styles with classes - <ul class=red> etc.
+ul.red:		underline:		brightred
+ul.blue:	bold:			brightblue
+li.red:		reverse:		red:		yellow
+li.blue:	bold:			blue
+strong.a:	bold:			black:		red
+em.a:		reverse:		black:		blue
+strong.b:	bold:			white:		red
+em.b:		reverse:		white:		blue
+strong.debug:	reverse:		green
+font.letter:	normal:			white:		blue
+input.submit:	normal:			cyan
+tr.baone:	bold:			yellow
+tr.batwo:	bold:			green
+tr.bathree:	bold:			red
+#
+# Special handling for link.
+link:		normal:			white
+link.green:	bold:			brightgreen
+link.red:	bold:			black:		red
+link.blue:	bold:			white:		blue
+link.toc:	bold:			black:		white
+# Special cases for link - the rel or title is appended after the class.
+# <link rel=next class=red href="1">
+link.red.next:	bold:			red
+link.red.prev:	bold:			yellow:		red
+link.blue.prev:	bold:			yellow:		blue
+link.blue.next:	bold:			blue
+link.green.toc:	bold:			white:		green
+#
+# Define styles that will be used when syntax highlighting is requested
+# (commandline option -prettysrc).
+span.htmlsrc_comment:normal:		white
+span.htmlsrc_tag:normal:		white
+#If you don't like that the tag name and attribute name are displayed
+#in different colors, comment the following line.
+span.htmlsrc_attrib:normal:		cyan
+span.htmlsrc_attrval:normal:		magenta
+span.htmlsrc_abracket:normal:		white
+span.htmlsrc_entity:normal:		white
+##span.htmlsrc_href:
+##span.htmlsrc_entire:
+span.htmlsrc_badseq:normal:		red
+span.htmlsrc_badtag:normal:		red
+span.htmlsrc_badattr:normal:		red
+span.htmlsrc_sgmlspecial:normal:	yellow
diff --git a/modules/shell/pass/default.nix b/modules/shell/pass/default.nix
new file mode 100644
index 000000000000..0e4dff5eb928
--- /dev/null
+++ b/modules/shell/pass/default.nix
@@ -0,0 +1,11 @@
+{ pkgs, ... }:
+
+{
+  environment.systemPackages = [
+    (pkgs.pass.withExtensions (es: with es; [ pass-otp ]))
+  ];
+
+  environment.extraInit = ''
+    export PASSWORD_STORE_DIR="$HOME/state/pass"
+  '';
+}
diff --git a/modules/shell/screen/default.nix b/modules/shell/screen/default.nix
new file mode 100644
index 000000000000..e2ee4d793890
--- /dev/null
+++ b/modules/shell/screen/default.nix
@@ -0,0 +1,7 @@
+{ ... }:
+
+{
+  environment.extraInit = ''
+    export SCREENDIR="$XDG_RUNTIME_DIR/screen"
+  '';
+}
diff --git a/modules/shell/tmux/config.nix b/modules/shell/tmux/config.nix
new file mode 100644
index 000000000000..d62a472bc1d8
--- /dev/null
+++ b/modules/shell/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/modules/shell/tmux/default.nix b/modules/shell/tmux/default.nix
new file mode 100644
index 000000000000..e1abecae0664
--- /dev/null
+++ b/modules/shell/tmux/default.nix
@@ -0,0 +1,8 @@
+{ pkgs, ... }:
+
+{
+  users.users.qyliss.xdg.config.paths."tmux/tmux.conf" =
+    with pkgs; writeText "tmux.conf" (callPackage ./config.nix { });
+
+  environment.systemPackages = with pkgs; [ tmux ];
+}
diff --git a/modules/shell/units/default.nix b/modules/shell/units/default.nix
new file mode 100644
index 000000000000..6add834711fb
--- /dev/null
+++ b/modules/shell/units/default.nix
@@ -0,0 +1,9 @@
+{ config, pkgs, ... }:
+
+{
+  environment.systemPackages = with pkgs; [ units ];
+
+  systemd.tmpfiles.rules = [
+    "d ${config.users.users.qyliss.home}/state/units 0700 qyliss qyliss"
+  ];
+}
diff --git a/modules/shell/zsh/default.nix b/modules/shell/zsh/default.nix
new file mode 100644
index 000000000000..95d7b38cf6ca
--- /dev/null
+++ b/modules/shell/zsh/default.nix
@@ -0,0 +1,18 @@
+{ pkgs, config, ... }:
+
+{
+  environment.extraInit = ''
+    export ZDOTDIR="/etc/xdg/nixos/per-user/$USER/zsh"
+  '';
+
+  programs.zsh.enable = true;
+
+  systemd.tmpfiles.rules = [
+    "d ${config.users.users.qyliss.home}/state/zsh 0700 qyliss qyliss"
+  ];
+
+  users.users.qyliss.shell = pkgs.zsh;
+
+  users.users.qyliss.xdg.config.paths."zsh/.zshrc" =
+    with pkgs; writeText "zshrc" (callPackage ./zshrc.nix {});
+}
diff --git a/modules/shell/zsh/zshrc.nix b/modules/shell/zsh/zshrc.nix
new file mode 100644
index 000000000000..a5cd68b480be
--- /dev/null
+++ b/modules/shell/zsh/zshrc.nix
@@ -0,0 +1,226 @@
+{ stdenv, lib
+, any-nix-shell
+, direnv
+, zsh-autosuggestions
+, zsh-history-substring-search
+, 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 = false;
+    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=~/state/zsh/history
+HISTSIZE=10000
+REPORTTIME=5
+SAVEHIST=9000
+ZSH_HIGHLIGHT_HIGHLIGHTERS=(main brackets)
+
+${lib.optionalString (enabledOptions != [])
+    "setopt ${lib.concatStringsSep " " enabledOptions}"}
+
+${lib.optionalString (disabledOptions != [])
+    "unsetopt ${lib.concatStringsSep " " disabledOptions}"}
+
+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"
+
+eval "$(${any-nix-shell}/bin/any-nix-shell zsh --info-right)"
+eval "$(${direnv}/bin/direnv hook 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 df='df -h'
+alias gdb='gdb -q'
+alias ip='ip -c=auto'
+alias ls=${lib.escapeShellArg (if stdenv.isDarwin then
+                                 "ls -AFh"
+                               else
+                                 "ls -AF --si --color=auto")}
+alias tree='tree -aRF -I .git'
+alias units='units -H $XDG_DATA_HOME/units/history'
+alias vim=nvim
+
+for command in cargo curl dig find git mutt ri wget; do
+    alias "$command=noglob $command"
+done
+
+# Prompt
+PS1="%F{yellow}%1(j.&%j .)%f%# "
+
+if tput hs
+then
+    _tsl="$(tput tsl)"
+    _fsl="$(tput fsl)"
+    set_title() {
+        echo -n "$_tsl$1$_fsl"
+    }
+else
+    set_title() {}
+fi
+
+preexec() {
+    show_exit=1
+
+    set_title "$([ -n "$SSH_TTY" ] && hostname && printf ": ")$TTY: $2"
+}
+
+precmd() {
+    local ex="$?"
+    if [[ -n "$show_exit" && "$ex" -ne 0 ]]
+    then echo -e "\r\e[33m[exit $ex]\e[0m"
+    fi
+    unset show_exit
+
+    set_title "$TTY: $ZSH_NAME"
+}
+
+''