about summary refs log tree commit diff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/config/labernix-pkgs.nix7
-rw-r--r--modules/git/default.nix72
-rw-r--r--modules/i3/conky.nix115
-rw-r--r--modules/i3/default.nix129
-rw-r--r--modules/i3/i3.conf131
-rw-r--r--modules/i3/workspace.nix107
-rw-r--r--modules/module-list.nix7
-rw-r--r--modules/services/postfix/default.nix1
-rw-r--r--modules/services/postfix/restrictions.nix53
-rw-r--r--modules/slim/default.nix43
-rw-r--r--modules/vlock/default.nix51
-rw-r--r--modules/vlock/message.cat18
-rw-r--r--modules/vlock/message.colmap18
-rw-r--r--modules/zsh/default.nix106
14 files changed, 795 insertions, 63 deletions
diff --git a/modules/config/labernix-pkgs.nix b/modules/config/labernix-pkgs.nix
deleted file mode 100644
index c1f6d807..00000000
--- a/modules/config/labernix-pkgs.nix
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  nixpkgs.config.packageOverrides = pkgs: {
-    labernix = import ../../pkgs {
-      inherit pkgs;
-    };
-  };
-}
diff --git a/modules/git/default.nix b/modules/git/default.nix
new file mode 100644
index 00000000..65aefb45
--- /dev/null
+++ b/modules/git/default.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  genConf = attrs: let
+    escStr = s: "\"${escape [ "\"" "\\" ] s}\"";
+    mkVal = v: if isBool v && v  then "true"
+          else if isBool v && !v then "false"
+          else escStr (toString v);
+    mkLine = key: val: "${key} = ${mkVal val}";
+
+    filterNull = filterAttrs (_: v: !(isNull v));
+
+    mkSection = sect: subsect: vals: ''
+      [${sect}${optionalString (subsect != null) " ${escStr subsect}"}]
+      ${concatStringsSep "\n" (mapAttrsToList mkLine (filterNull vals))}
+    '';
+
+    mkConf = sect: content: let
+      subs = filterAttrs (_: isAttrs) content;
+      nonSubs = filterAttrs (_: s: !isAttrs s) content;
+      hasPlain = (attrNames nonSubs) != [];
+      plainSects = singleton (mkSection sect null nonSubs);
+    in mapAttrsToList (mkSection sect) subs ++ optional hasPlain plainSects;
+
+    text = concatStringsSep "\n" (flatten (mapAttrsToList mkConf attrs));
+  in pkgs.writeText "gitconfig" text;
+
+  gitPatched = overrideDerivation pkgs.gitFull (git: {
+    makeFlags = let
+      oldFlags = git.makeFlags or [];
+      newVal = "ETC_GITCONFIG=${config.vuizvui.git.config}";
+    in if isList oldFlags
+       then oldFlags ++ [ newVal ]
+       else "${oldFlags} ${newVal}";
+  });
+in {
+  options.vuizvui.git = {
+    enable = mkEnableOption "Git";
+
+    config = mkOption {
+      description = "System-wide default config for Git";
+
+      type = let
+        superType = types.attrsOf types.unspecified;
+      in mkOptionType {
+        name = "attribute set of either plain values or "
+             + "attribute sets of values (if it is a subsection)";
+        inherit (superType) check merge;
+        inherit (superType) getSubOptions getSubModules substSubModules;
+      };
+
+      default = {};
+      example = {
+        color.ui = "auto";
+        merge.tool = "vimdiff";
+        guitool.foobar.noconsole = true;
+      };
+
+      apply = genConf;
+    };
+  };
+
+  config = mkIf config.vuizvui.git.enable {
+    environment.systemPackages = [
+      gitPatched
+      pkgs.gitAndTools.git-remote-hg
+      pkgs.gitAndTools.hub
+    ];
+  };
+}
diff --git a/modules/i3/conky.nix b/modules/i3/conky.nix
new file mode 100644
index 00000000..77a63ea5
--- /dev/null
+++ b/modules/i3/conky.nix
@@ -0,0 +1,115 @@
+{ pkgs ? import <nixpkgs> {}, timeout ? 300 }:
+
+with pkgs.lib;
+
+let
+  baseConfig = pkgs.writeText "conkyrc" ''
+    cpu_avg_samples 2
+    net_avg_samples 2
+    no_buffers yes
+    out_to_console yes
+    out_to_ncurses no
+    out_to_stderr no
+    out_to_x no
+    extra_newline no
+    update_interval 1.0
+    uppercase no
+    use_spacer none
+    pad_percents 3
+    use_spacer left
+    TEXT
+  '';
+
+  optexpr = name: expr: "\${${name}_disabled:-\\\${${name} ${expr}\\}}";
+  cexpr = name: args: "${optexpr name (concatStringsSep " " args)}";
+
+  mkNetInfo = iface: let
+    upspeed = cexpr "upspeed" [ iface ];
+    downspeed = cexpr "downspeed" [ iface ];
+  in "${upspeed} ${downspeed}";
+
+  mkDiskFree = path: let
+    used = cexpr "fs_used" [ path ];
+    size = cexpr "fs_size" [ path ];
+  in "${used}/${size}";
+
+  gpuTemp = "${cexpr "hwmon" [ "0" "temp" "1" ]}C";
+
+  weather = (cexpr "weather" [
+    "http://weather.noaa.gov/pub/data/observations/metar/stations/"
+    "EDMA"
+    "temperature"
+  ]) + "C";
+
+  mkConky = args: let
+    time = cexpr "time" [ "%a %b %d %T %Z %Y" ];
+    text = concatStringsSep " | " (args ++ singleton time);
+    conky = pkgs.conky.override {
+      weatherMetar = true;
+    };
+  in pkgs.writeScript "conky-run.sh" ''
+    #!${pkgs.stdenv.shell}
+    PATH="${pkgs.coreutils}/bin"
+
+    cpuload() {
+      for i in $(seq 1 $(nproc))
+      do
+        [ $i -eq 1 ] || echo -n ' '
+        echo -n "\''${cpu cpu$i}%"
+      done
+    }
+
+    cputemp_collect() {
+      for i in /sys/bus/platform/devices/coretemp.?/hwmon/hwmon?/temp?_input
+      do
+        [ -e "$i" ] || continue
+        echo "$i" | ${pkgs.gnused}/bin/sed -re \
+          's/^.*hwmon([0-9]+)[^0-9]*([0-9]+).*$/''${hwmon \1 temp \2}/'
+      done
+    }
+
+    cputemp() {
+      echo $(cputemp_collect)
+    }
+
+    tries=0
+    while ! raw_netinfo="$(${
+      "${pkgs.iproute}/sbin/ip route get 8.8.8.8 2> /dev/null"
+    })"; do
+      if [ $tries -ge ${toString timeout} ]; then
+        upspeed_disabled=N/A
+        downspeed_disabled=N/A
+        break
+      fi
+      echo "Waiting for primary network interface to become available..."
+      tries=$(($tries + 1))
+      sleep 1
+    done
+
+    primary_netdev="$(echo "$raw_netinfo" | \
+      ${pkgs.gnused}/bin/sed -nre 's/^.*dev *([^ ]+).*$/\1/p')"
+
+    ${conky}/bin/conky -c "${baseConfig}" -t "${text}"
+  '';
+
+in {
+  left = mkConky [
+    "CPU: $(cpuload) - ${cexpr "cpu" [ "cpu0" ]}%"
+    "MEM: \\$mem/\\$memmax - \\$memperc%"
+    "SWAP: \\$swap/\\$swapmax \\$swapperc%"
+  ];
+
+  right = mkConky [
+    "NET: ${mkNetInfo "$primary_netdev"}"
+    "DF: ${mkDiskFree "/"}"
+    "LAVG: \\$loadavg"
+    "TEMP - CPU: $(cputemp) - GPU: ${gpuTemp} - OUTSIDE: ${weather}"
+  ];
+
+  single = mkConky [
+    "CPU: $(cpuload) - ${cexpr "cpu" [ "cpu0" ]}%"
+    "MEM: \\$mem/\\$memmax - \\$memperc%"
+    "NET: ${mkNetInfo "$primary_netdev"}"
+    "TEMP - CPU: $(cputemp) - OUTSIDE: ${weather}"
+  ];
+}
diff --git a/modules/i3/default.nix b/modules/i3/default.nix
new file mode 100644
index 00000000..bca88cca
--- /dev/null
+++ b/modules/i3/default.nix
@@ -0,0 +1,129 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  inherit (config.services.xserver) xrandrHeads;
+
+  # The symbols if you press shift and a number key.
+  wsNumberSymbols = [
+    "exclam" "at" "numbersign" "dollar" "percent"
+    "asciicircum" "ampersand" "asterisk" "parenleft" "parenright"
+  ];
+
+  wsCount = length wsNumberSymbols;
+
+  headCount = length xrandrHeads;
+  wsPerHead = wsCount / headCount;
+  excessWs = wsCount - (headCount * wsPerHead);
+  headModifier = if config.vuizvui.i3.reverseHeads then reverseList else id;
+  getHeadAt = elemAt (headModifier xrandrHeads);
+
+  mkDefaultWorkspace = number: numberSymbol: {
+    name = toString number;
+    value = mkDefault {
+      label = null;
+      labelPrefix = "${toString number}: ";
+      keys.switchTo = "$mod+${if number == 10 then "0" else toString number}";
+      keys.moveTo = "$mod+Shift+${numberSymbol}";
+      head = if headCount == 0 then null
+             else getHeadAt ((number - (excessWs + 1)) / wsPerHead);
+    };
+  };
+
+  wsCfgList = mapAttrsToList (_: getAttr "config") config.vuizvui.i3.workspaces;
+  wsConfig = concatStrings wsCfgList;
+  defaultWorkspaces = listToAttrs (imap mkDefaultWorkspace wsNumberSymbols);
+
+  conky = import ./conky.nix {
+    inherit pkgs;
+    timeout = config.vuizvui.i3.networkTimeout;
+  };
+
+  mkBar = output: statusCmd: singleton ''
+    bar {
+      ${optionalString (output != null) "output ${output}"}
+      ${optionalString (statusCmd != null) "status_command ${statusCmd}"}
+      colors {
+        focused_workspace  #5c5cff #e5e5e5
+        active_workspace   #ffffff #0000ee
+        inactive_workspace #00cdcd #0000ee
+        urgent_workspace   #ffff00 #cd0000
+      }
+    }
+  '';
+
+  barConfig = let
+    barHeads = headModifier xrandrHeads;
+    bars = if headCount == 0 then mkBar null conky.single
+      else if headCount == 1 then mkBar (head barHeads) conky.single
+      else let inner = take (length barHeads - 2) (tail barHeads);
+           in mkBar (head barHeads) conky.left
+           ++ map (flip mkBar null) inner
+           ++ mkBar (last barHeads) conky.right;
+  in concatStrings (headModifier bars);
+
+in
+{
+  options.vuizvui.i3 = {
+    enable = mkEnableOption "i3";
+
+    workspaces = mkOption {
+      type = types.attrsOf (types.submodule (import ./workspace.nix));
+      default = listToAttrs (imap mkDefaultWorkspace wsNumberSymbols);
+      description = ''
+        Workspace to monitor assignment.
+
+        Workspaces are by default assigned starting from the leftmost monitor
+        being workspace 1 and the rightmost monitor being workspace 10. The
+        workspaces are divided by the number of available heads, so if you have
+        a dual head system, you'll end up having workspace 1 to 5 on the left
+        monitor and 6 to 10 on the right.
+      '';
+    };
+
+    reverseHeads = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Reverse the order of the heads, so if enabled and you have two heads,
+        you'll end up having workspaces 1 to 5 on the right head and 6 to 10 on
+        the left head.
+      '';
+    };
+
+    networkTimeout = mkOption {
+      type = types.int;
+      default = 300;
+      description = ''
+        Maximum number of seconds to wait for network device detection.
+      '';
+    };
+  };
+
+  config.vuizvui.i3.workspaces = defaultWorkspaces;
+
+  config.services.xserver.windowManager = mkIf config.vuizvui.i3.enable {
+    default = "i3";
+
+    i3.enable = true;
+    i3.configFile = pkgs.substituteAll {
+      name = "i3.conf";
+      src = ./i3.conf;
+
+      inherit (pkgs) dmenu xterm pvolctrl;
+      inherit (pkgs.xorg) xsetroot;
+      inherit wsConfig barConfig;
+
+      lockall = pkgs.writeScript "lockvt.sh" ''
+        #!${pkgs.stdenv.shell}
+        "${pkgs.socat}/bin/socat" - UNIX-CONNECT:/run/console-lock.sock \
+          < /dev/null
+      '';
+
+      postInstall = ''
+        ${pkgs.i3}/bin/i3 -c "$target" -C
+      '';
+    };
+  };
+}
diff --git a/modules/i3/i3.conf b/modules/i3/i3.conf
new file mode 100644
index 00000000..cd14c425
--- /dev/null
+++ b/modules/i3/i3.conf
@@ -0,0 +1,131 @@
+# default modifier key
+set $mod Mod4
+
+# we want to have a VT-style font :-)
+font -dosemu-vga-medium-r-normal--17-160-75-75-c-80-ibm-cp866
+
+# Use Mouse+$mod to drag floating windows to their wanted position
+floating_modifier $mod
+
+# reasonable defaults!
+default_orientation horizontal
+workspace_layout tabbed
+popup_during_fullscreen ignore
+
+# start a terminal
+bindsym $mod+Shift+Return exec --no-startup-id @xterm@/bin/xterm
+
+# kill focused window
+bindsym $mod+Shift+C kill
+
+# start dmenu (a program launcher)
+bindsym $mod+p exec --no-startup-id @dmenu@/bin/dmenu_run
+
+# start lock screen
+bindsym $mod+Shift+Escape exec --no-startup-id @lockall@
+
+# set background
+exec @xsetroot@/bin/xsetroot -solid black
+
+# audio controls
+bindsym XF86AudioLowerVolume exec @pvolctrl@/bin/pvolctrl -10
+bindsym XF86AudioRaiseVolume exec @pvolctrl@/bin/pvolctrl 10
+bindsym XF86AudioMute exec @pvolctrl@/bin/pvolctrl 0
+
+# change/move focus
+bindsym $mod+Shift+Left move left
+bindsym $mod+Shift+H move left
+bindsym $mod+Shift+Down move down
+bindsym $mod+Shift+T move down
+bindsym $mod+Shift+Up move up
+bindsym $mod+Shift+N move up
+bindsym $mod+Shift+Right move right
+bindsym $mod+Shift+S move right
+
+bindsym $mod+Left focus left
+bindsym $mod+h focus left
+bindsym $mod+Down focus down
+bindsym $mod+t focus down
+bindsym $mod+Up focus up
+bindsym $mod+n focus up
+bindsym $mod+Right focus right
+bindsym $mod+s focus right
+
+# split in horizontal orientation
+bindsym $mod+i split h
+
+# split in vertical orientation
+bindsym $mod+d split v
+
+# enter fullscreen mode for the focused container
+bindsym $mod+f fullscreen
+
+# change container layout (stacked, tabbed, default)
+bindsym $mod+apostrophe layout stacking
+bindsym $mod+comma layout tabbed
+bindsym $mod+period layout default
+
+# toggle tiling / floating
+bindsym $mod+Shift+space floating toggle
+
+# change focus between tiling / floating windows
+bindsym $mod+space focus mode_toggle
+
+# focus the parent container
+bindsym $mod+a focus parent
+
+# focus the child container
+bindsym $mod+semicolon focus child
+
+# reload the configuration file
+bindsym $mod+Shift+L reload
+# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
+bindsym $mod+Shift+R restart
+# exit i3 (logs you out of your X session)
+bindsym $mod+Shift+Q exit
+
+# resize window (you can also use the mouse for that)
+mode "resize" {
+    # These bindings trigger as soon as you enter the resize mode
+
+    # They resize the border in the direction you pressed, e.g.
+    # when pressing left, the window is resized so that it has
+    # more space on its left
+
+    bindsym Left resize shrink left 10 px or 10 ppt
+    bindsym h resize shrink left 10 px or 10 ppt
+    bindsym Down resize shrink down 10 px or 10 ppt
+    bindsym t resize shrink down 10 px or 10 ppt
+    bindsym Up resize shrink up 10 px or 10 ppt
+    bindsym n resize shrink up 10 px or 10 ppt
+    bindsym Right resize shrink right 10 px or 10 ppt
+    bindsym s resize shrink right 10 px or 10 ppt
+
+    bindsym Shift+Left resize grow left 10 px or 10 ppt
+    bindsym Shift+H resize grow left 10 px or 10 ppt
+    bindsym Shift+Down resize grow down 10 px or 10 ppt
+    bindsym Shift+T resize grow down 10 px or 10 ppt
+    bindsym Shift+Up resize grow up 10 px or 10 ppt
+    bindsym Shift+N resize grow up 10 px or 10 ppt
+    bindsym Shift+Right resize grow right 10 px or 10 ppt
+    bindsym Shift+S resize grow right 10 px or 10 ppt
+
+    # back to normal: Enter or Escape
+    bindsym Return mode "default"
+    bindsym Escape mode "default"
+}
+
+bindsym $mod+r mode "resize"
+
+# workspace configuration
+@wsConfig@
+
+# ratmenu should be as unintrusive as possible
+for_window [class="^ratmenu$"] floating enable
+for_window [class="^ratmenu$"] border none
+
+# various app cruft
+for_window [class="^Dia$"] floating enable
+
+# bar configuration
+@barConfig@
diff --git a/modules/i3/workspace.nix b/modules/i3/workspace.nix
new file mode 100644
index 00000000..403ba57d
--- /dev/null
+++ b/modules/i3/workspace.nix
@@ -0,0 +1,107 @@
+{ name, lib, config, ... }:
+
+with lib;
+
+let
+  finalLabel =
+    if config.label == null then name
+    else config.labelPrefix + config.label;
+
+  mkDoc = anchor: "http://i3wm.org/docs/userguide.html#${anchor}";
+in
+{
+  options = {
+    labelPrefix = mkOption {
+      type = types.str;
+      default = "";
+      example = "666: ";
+      description = ''
+        The value that will be put in front of the <option>label</option>.
+        So if you have a label called <replaceable>bar</replaceable> and a
+        <option>labelPrefix</option> called <replaceable>foo</replaceable> the
+        label for the workspace will be <replaceable>foobar</replaceable>.
+      '';
+    };
+
+    label = mkOption {
+      type = types.nullOr types.str;
+      default = name;
+      description = ''
+        The label of this workspace, which is its name by default. If the value
+        is <replaceable>null</replaceable>, the resulting label of the workspace
+        is just its name and no <option>labelPrefix</option> is applied.
+      '';
+    };
+
+    assign = mkOption {
+      type = types.listOf types.attrs;
+      default = [];
+      example = [
+        { class = "^Chromium(?:-browser)?\$"; }
+        { instance = "^gajim\$"; }
+      ];
+      description = let
+        anchor = "_automatically_putting_clients_on_specific_workspaces";
+      in ''
+        Assign windows to this specific workspace using the attribute names
+        described by <link xlink:href="${mkDoc anchor}"/>.
+      '';
+    };
+
+    head = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The XRandR head this workspace will be assigned to.
+      '';
+    };
+
+    keys = let
+      commonDesc = ''
+        The <replaceable>$mod</replaceable> placeholder represents the default
+        modifier key. Details about the syntax of key combinations can be found
+        at <link xlink:href="${mkDoc "keybindings"}"/>.
+      '';
+    in {
+      switchTo = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "$mod+1";
+        description = ''
+          Key combination to switch to this workspace.
+        '' + commonDesc;
+      };
+
+      moveTo = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "$mod+Shift+exclam";
+        description = ''
+          Key combination to move a container to this workspace.
+        '' + commonDesc;
+      };
+    };
+
+    config = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Raw configuration options for this workspace.
+      '';
+    };
+  };
+
+  config.config = let
+    mkAssign = mapAttrsToList (criteria: value: "${criteria}=\"${value}\"");
+    mkSym = sym: rest: optionalString (sym != null) "bindsym ${sym} ${rest}";
+  in ''
+    ${optionalString (config.head != null) ''
+    workspace "${finalLabel}" output ${config.head}
+    ''}
+    ${mkSym config.keys.switchTo "workspace \"${finalLabel}\""}
+    ${mkSym config.keys.moveTo "move workspace \"${finalLabel}\""}
+    ${concatMapStrings (assign: ''
+    assign [${concatStringsSep " " (mkAssign assign)}] ${finalLabel}
+    '') config.assign}
+  '';
+}
diff --git a/modules/module-list.nix b/modules/module-list.nix
index 5137c672..916e0efe 100644
--- a/modules/module-list.nix
+++ b/modules/module-list.nix
@@ -1,4 +1,7 @@
 [
-  ./config/labernix-pkgs.nix
-  ./services/postfix
+  ./git
+  ./i3
+  ./slim
+  ./vlock
+  ./zsh
 ]
diff --git a/modules/services/postfix/default.nix b/modules/services/postfix/default.nix
deleted file mode 100644
index 4103a41b..00000000
--- a/modules/services/postfix/default.nix
+++ /dev/null
@@ -1 +0,0 @@
-import ./restrictions.nix # TODO: Dummy for now, implement me!
diff --git a/modules/services/postfix/restrictions.nix b/modules/services/postfix/restrictions.nix
deleted file mode 100644
index fbb47f10..00000000
--- a/modules/services/postfix/restrictions.nix
+++ /dev/null
@@ -1,53 +0,0 @@
-{ config, lib, ... }:
-
-with lib;
-
-let
-  mkRestriction = name: specificDescription: {
-    option.${name} = mkOption {
-      default = null;
-      type = types.nullOr types.list;
-      description = ''
-        A list of restrictions to apply or <option>null</option> to use the
-        built-in default value from Postfix.
-        ${specificDescription}
-      '';
-    };
-    config = let
-      cfg = config.labernix.postfix.restrictions.${name};
-    in mkIf (cfg != null) ''
-      smtpd_${name}_restrictions = ${concatStringsSep ", " cfg}
-    '';
-  };
-  restrictions = mapAttrsToList mkRestriction {
-    client = mkRestriction ''
-      SMTP server access restrictions in the context of a client SMTP connection
-      request.
-    '';
-    data = mkRestriction ''
-      Access restrictions that the Postfix SMTP server applies in the context of
-      the SMTP DATA command.
-    '';
-    end_of_data = mkRestriction ''
-      Access restrictions that the Postfix SMTP server applies in the context of
-      the SMTP END-OF-DATA command.
-    '';
-    etrn = mkRestriction ''
-      SMTP server access restrictions in the context of a client ETRN request.
-    '';
-    helo = mkRestriction ''
-      Restrictions that the Postfix SMTP server applies in the context of the
-      SMTP HELO command.
-    '';
-    recipient = mkRestriction ''
-      Access restrictions that the Postfix SMTP server applies in the context of
-      the RCPT TO command.
-    '';
-    sender = mkRestriction ''
-      Restrictions that the Postfix SMTP server applies in the context of the
-      MAIL FROM command.
-    '';
-  };
-in {
-  options.labernix.postfix.restrictions = mapAttrs mkRestriction restrictions;
-}
diff --git a/modules/slim/default.nix b/modules/slim/default.nix
new file mode 100644
index 00000000..ca3ce8c6
--- /dev/null
+++ b/modules/slim/default.nix
@@ -0,0 +1,43 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.vuizvui.slim;
+  randrHeads = config.services.xserver.xrandrHeads;
+in {
+  options.vuizvui.slim.enable = mkEnableOption "Vuizvui SLiM";
+
+  config.services.xserver.displayManager.slim = mkIf cfg.enable {
+    enable = true;
+    theme = pkgs.stdenv.mkDerivation {
+      name = "nixos-theme-vuizvui";
+      src = pkgs.slimThemes.nixosSlim;
+      phases = [ "unpackPhase" "patchPhase" "installPhase" ];
+      patchPhase = let
+        headFactor = if randrHeads == [] then 1 else lib.length randrHeads;
+        centerLeft = 100 / (headFactor * 2);
+      in ''
+        ${pkgs.imagemagick}/bin/mogrify \
+          -fill '#080010' -draw 'color 0,0 reset' \
+          share/slim/themes/nixos-slim-testing/background.png
+        ${pkgs.imagemagick}/bin/mogrify \
+          -negate -region 100x110+0+0 -negate -fill white -colorize 20% \
+          share/slim/themes/nixos-slim-testing/panel.png
+        sed -i \
+          -e 's/^\([a-z_]\+_x[^0-9]*\)[0-9]\+%/\1${toString centerLeft}%/' \
+          share/slim/themes/nixos-slim-testing/slim.theme
+        cat >> share/slim/themes/nixos-slim-testing/slim.theme <<EOF
+        session_x      ${toString centerLeft}%
+        msg_color      #ffffff
+        username_color #ffffff
+        password_color #ffffff
+        input_color    #ffffff
+        EOF
+      '';
+      installPhase = ''
+        cp -R share/slim/themes/nixos-slim-testing "$out"
+      '';
+    };
+  };
+}
diff --git a/modules/vlock/default.nix b/modules/vlock/default.nix
new file mode 100644
index 00000000..11e9a475
--- /dev/null
+++ b/modules/vlock/default.nix
@@ -0,0 +1,51 @@
+{ pkgs, config, lib, ... }:
+
+let
+  messageFile = pkgs.runCommand "message.cat" {} ''
+    echo -en '\e[H\e[2J\e[?25l' > "$out"
+    "${pkgs.aacolorize}/bin/aacolorize" \
+      "${./message.cat}" "${./message.colmap}" \
+      >> "$out"
+  '';
+
+  esc = "\\\\033";
+  unlockCSI = "${esc}[16;39H${esc}[?25h${esc}[K";
+
+  vlock = lib.overrideDerivation pkgs.vlock (o: {
+    postPatch = (o.postPatch or "") + ''
+      echo -n '"' > src/message.h
+      sed -e ':nl;N;$!bnl;s/[\\"]/\\&/g;s/\n/\\n/g' "${messageFile}" \
+        >> src/message.h
+      sed -i -e '$s/$/"/' src/message.h
+      sed -i -e 's!getenv("VLOCK_MESSAGE")!\n#include "message.h"\n!' \
+        src/vlock-main.c
+      sed -i -re 's/(fprintf[^"]*")(.*user)/\1${unlockCSI}\2/' \
+        src/auth-pam.c
+    '';
+  });
+in {
+  options.vuizvui.vlock.enable = lib.mkEnableOption "console lock";
+
+  config.systemd.sockets.vlock = {
+    description = "Console Lock Socket";
+    wantedBy = [ "sockets.target" ];
+    socketConfig.ListenStream = "/run/console-lock.sock";
+    socketConfig.Accept = true;
+  };
+
+  config.systemd.services."vlock@" = lib.mkIf config.vuizvui.vlock.enable {
+    description = "Lock All Consoles";
+    serviceConfig.Type = "oneshot";
+
+    #environment.USER = "%i"; XXX
+    environment.USER = "aszlig";
+
+    script = ''
+      retval=0
+      oldvt="$("${pkgs.kbd}/bin/fgconsole")"
+      "${vlock}/bin/vlock" -asn || retval=$?
+      if [ $retval -ne 0 ]; then "${pkgs.kbd}/bin/chvt" "$oldvt"; fi
+      exit $retval
+    '';
+  };
+}
diff --git a/modules/vlock/message.cat b/modules/vlock/message.cat
new file mode 100644
index 00000000..f079e829
--- /dev/null
+++ b/modules/vlock/message.cat
@@ -0,0 +1,18 @@
+
+                .
+                |
+          -_    |     .           .-.  .-. ..      ,.--., ,===.
+            `-_ |     |           '||\.||' `' ,  , ||  || ;___
+    -_         >:_    |    _-      ||`\||  || `\/' ||  ||     ;
+      `-_   _-'   `-_ | _-'       .'   `|  ;' /'`\ ``=='' ,==='
+         >:'         `:'
+      _-' |           |    _-   ..              ..             ..
+    -'    |           | _-'     ||              ||             ||
+         .|.         _:<        ||  ,---. .---. ||,-. .--.  .--||
+      _-' | `-_   _-'   `-_     ||  ||"|| ||''' |.,'' |"/'  |,";|
+    -'    |    `:<         `-   ||_ ||_|| ||__  |,\\. ||__  ||_,|
+          |     | `-_           `--'`---' `---' '' `' `---' `---'
+          '     |    `-
+                |                     press ENTER to unlock
+                `
+
diff --git a/modules/vlock/message.colmap b/modules/vlock/message.colmap
new file mode 100644
index 00000000..d7e42fb6
--- /dev/null
+++ b/modules/vlock/message.colmap
@@ -0,0 +1,18 @@
+
+                c
+                c
+          cc    c     b           WWW  WWW WW      BccccB cBBBc
+            ccc c     b           WWWWWWWW WW W  W Bc  cB cccc
+    bb         ccc    b    bb      WWWWWW  WW WWWW Bc  cB     c
+      bbb   bbb   ccc b bbb       WW   WW  WW WWWW BcBBcB cBBBc
+         bbb         cbb
+      bbb c           b    cc   rr              rr             rr
+    bb    c           b ccc     rr              rr             rr
+         ccb         ccc        rr  rrrrr rrrrr rrrrr rrrr  rrrrr
+      ccc c bbb   ccc   ccc     rr  rrRrr rrRRR rrrrr rRrr  rrRrr
+    cc    c    bbb         cc   rrr rrrrr rrrr  rrrrr rrrr  rrrrr
+          c     b bbb           rrrrrrrrr rrrrr rr rr rrrrr rrrrr
+          c     b    bb
+                b                     ppppp PPPPP pp pppppp
+                b
+
diff --git a/modules/zsh/default.nix b/modules/zsh/default.nix
new file mode 100644
index 00000000..4d21e33a
--- /dev/null
+++ b/modules/zsh/default.nix
@@ -0,0 +1,106 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+  options.vuizvui.zsh = {
+    enable = mkEnableOption "zsh";
+  };
+
+  config = mkIf config.vuizvui.zsh.enable {
+    environment.shellInit = ''
+      export EDITOR="vim"
+      export EMAIL="aszlig@redmoonstudios.org"
+    '';
+
+    programs.zsh.enable = true;
+
+    programs.zsh.shellAliases.t = "task";
+
+    programs.zsh.interactiveShellInit = mkAfter ''
+      export HISTFILE=~/.histfile
+      export HISTSIZE=100000
+      export SAVEHIST=100000
+
+      unsetopt SHARE_HISTORY
+
+      setopt extendedglob
+      setopt extendedhistory
+      setopt globcomplete
+      setopt histnostore
+      setopt histreduceblanks
+      setopt correct
+      setopt dvorak
+      setopt interactivecomments
+      setopt autopushd
+      setopt autocd
+      setopt beep
+
+      bindkey -v
+      if [[ "$TERM" = xterm ]]; then
+        bindkey -v '\e[H' vi-beginning-of-line
+        bindkey -v '\e[F' vi-end-of-line
+
+        function set-title() {
+          echo -en "\e]2;$2\a"
+        }
+
+        function reset-title() {
+          echo -en "\e]2;''${(%):-%~}\a"
+        }
+
+        autoload -Uz add-zsh-hook
+        add-zsh-hook preexec set-title
+        add-zsh-hook precmd reset-title
+      else
+        bindkey -v '\e[1~' vi-beginning-of-line
+        bindkey -v '\e[4~' vi-end-of-line
+      fi
+
+      bindkey -a '/' history-incremental-pattern-search-backward
+      bindkey -a '?' history-incremental-pattern-search-forward
+      bindkey '\e[A' up-line-or-history
+      bindkey '\e[B' down-line-or-history
+
+      zstyle ':completion:*' completer _expand _complete _ignored _approximate
+      zstyle ':completion:*' expand prefix suffix
+      zstyle ':completion:*' group-name '''
+      zstyle ':completion:*' insert-unambiguous true
+      zstyle ':completion:*' list-colors '''
+      zstyle ':completion:*' list-prompt \
+        %SAt %p: Hit TAB for more, or the character to insert%s
+      zstyle ':completion:*' list-suffixes true
+      zstyle ':completion:*' matcher-list ''' \
+        'm:{[:lower:]}={[:upper:]}' \
+        'm:{[:lower:][:upper:]}={[:upper:][:lower:]}' \
+        'l:|=* r:|=*' \
+        'r:|[._-]=** r:|=**'
+      zstyle ':completion:*' max-errors 2 numeric
+      zstyle ':completion:*' menu select=long
+      zstyle ':completion:*' original true
+      zstyle ':completion:*' preserve-prefix '//[^/]##/'
+      zstyle ':completion:*' prompt \
+        'Hm, did you mistype something? There are %e errors in the completion.'
+      zstyle ':completion:*' select-prompt \
+        %SScrolling active: current selection at %p%s
+      zstyle ':completion:*' use-compctl false
+      zstyle ':completion:*' verbose true
+
+      autoload -Uz compinit
+      compinit
+
+      autoload -Uz zmv
+    '';
+
+    programs.zsh.promptInit = ''
+      autoload -Uz prompt_special_chars
+
+      () {
+          local p_machine='%(!..%B%F{red}%n%b%F{blue}@)%b%F{red}%m'
+          local p_path='%B%F{blue}[%F{cyan}%~%B%F{blue}]'
+          local p_exitcode='%F{green}%?%(!.%F{cyan}>.%b%F{green}>)%b%f '
+          PROMPT="$p_machine$p_path$p_exitcode"
+      }
+    '';
+  };
+}