diff options
51 files changed, 698 insertions, 176 deletions
diff --git a/configs/alacritty.nix b/configs/alacritty.nix index 25fb2c3..1a8eb92 100644 --- a/configs/alacritty.nix +++ b/configs/alacritty.nix @@ -24,7 +24,7 @@ let colors.bright.magenta = "#fb53fb"; colors.bright.cyan = "#72fbfb"; colors.bright.white = "#fbfbfb"; - draw_bold_text_with_bright_colors = false; + colors.draw_bold_text_with_bright_colors = false; hints.enabled = [ { regex = "(ipfs:|ipns:|magnet:|mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)[^\\u0000-\\u001F\\u007F-\\u009F<>\"\\s{-}\\^⟨⟩`]+"; @@ -33,6 +33,7 @@ let action = "Select"; } ]; + mouse.hide_when_typing = true; scrolling.multiplier = 8; }; configs.root = lib.recursiveUpdate configs.default { @@ -51,18 +52,18 @@ let font.bold.family = "iosevka tv 2 Medium"; font.bold_italic.family = "iosevka tv 2 Medium"; font.size = 5; - key_bindings = [ + keyboard.bindings = [ { key = "Up"; mods = "Control"; action = "IncreaseFontSize"; } { key = "Down"; mods = "Control"; action = "DecreaseFontSize"; } { key = "Down"; mods = "Shift|Control"; action = "ResetFontSize"; } ]; }; - variants.x220 = { + variants.lodpi = { font.normal.family = "Clean"; font.bold.family = "Clean"; font.bold.style = "Regular"; font.size = 10; - key_bindings = let + keyboard.bindings = let font-size = arg: { program = "${pkgs.font-size-alacritty}/bin/font-size-alacritty"; args = [arg]; @@ -79,14 +80,17 @@ let fu = "hidpi"; leg = "hidpi"; ru = "hidpi"; - }.${config.krebs.build.host.name} or "x220"; + zoppo = "hidpi"; + }.${config.krebs.build.host.name} or "lodpi"; + + format = pkgs.formats.toml {}; in { environment.etc = lib.mapAttrs' - (name: config: lib.nameValuePair "alacritty/${name}.json" { - source = pkgs.writeJSON "alacritty-${name}.json" config; + (name: config: lib.nameValuePair "alacritty/${name}.toml" { + source = format.generate "alacritty-${name}.toml" config; }) configs; diff --git a/configs/bluetooth.nix b/configs/bluetooth.nix new file mode 100644 index 0000000..899dd2f --- /dev/null +++ b/configs/bluetooth.nix @@ -0,0 +1,7 @@ +{ pkgs, ... }: { + hardware.bluetooth.enable = true; + systemd.user.services.obex.enable = false; + environment.systemPackages = [ + pkgs.bluetuith + ]; +} diff --git a/configs/default.nix b/configs/default.nix index 5d74d96..fb8627d 100644 --- a/configs/default.nix +++ b/configs/default.nix @@ -11,7 +11,6 @@ imports = [ ./backup.nix ./bash - ./htop.nix ./nets/hkw.nix ./networkd.nix ./nginx @@ -19,6 +18,8 @@ ./pki ./ssh.nix ./sshd.nix + ./tmux.nix + ./variables.nix ./vim.nix ./xdg.nix { @@ -53,7 +54,7 @@ environment.profileRelativeEnvVars.PATH = lib.mkForce [ "/bin" ]; environment.systemPackages = with pkgs; [ - rxvt_unicode.terminfo + rxvt-unicode-unwrapped.terminfo ]; environment.shellAliases = lib.mkForce { diff --git a/configs/earlyoom.nix b/configs/earlyoom.nix new file mode 100644 index 0000000..1249c7a --- /dev/null +++ b/configs/earlyoom.nix @@ -0,0 +1,10 @@ +{ lib, ... }: { + services.earlyoom.enable = true; + systemd.services.earlyoom.environment.EARLYOOM_ARGS = lib.mkForce (toString [ + "-m 5" + "-s 10" + "-r 0" + "--prefer '(^|/)chromium$'" + ]); + +} diff --git a/configs/gitrepos.nix b/configs/gitrepos.nix index 0e61989..174e303 100644 --- a/configs/gitrepos.nix +++ b/configs/gitrepos.nix @@ -126,7 +126,7 @@ hc = {}; mime = {}; quipper = {}; - scanner = {}; + terminal-scanner = {}; wai-middleware-time = {}; web-routes-wai-custom = {}; xintmap = {}; diff --git a/configs/htop.nix b/configs/htop.nix deleted file mode 100644 index e60cc51..0000000 --- a/configs/htop.nix +++ /dev/null @@ -1,39 +0,0 @@ -{ pkgs, ... }: { - nixpkgs.config.packageOverrides = super: { - htop = pkgs.symlinkJoin { - name = "htop"; - paths = [ - (pkgs.writeDashBin "htop" '' - export HTOPRC=${pkgs.writeText "htoprc" '' - fields=0 48 17 18 38 39 40 2 46 47 49 1 - sort_key=46 - sort_direction=1 - hide_threads=0 - hide_kernel_threads=1 - hide_userland_threads=0 - shadow_other_users=1 - show_thread_names=1 - show_program_path=1 - highlight_base_name=1 - highlight_megabytes=1 - highlight_threads=1 - tree_view=1 - header_margin=0 - detailed_cpu_time=0 - cpu_count_from_zero=0 - update_process_names=0 - account_guest_in_cpu_meter=1 - color_scheme=0 - delay=15 - left_meters=LeftCPUs2 RightCPUs2 Memory Swap - left_meter_modes=1 1 1 1 - right_meters=Uptime Tasks LoadAverage Battery - right_meter_modes=2 2 2 2 - ''} - exec ${super.htop}/bin/htop "$@" - '') - super.htop - ]; - }; - }; -} diff --git a/configs/hw/AO753.nix b/configs/hw/AO753.nix index ea58c01..5e91564 100644 --- a/configs/hw/AO753.nix +++ b/configs/hw/AO753.nix @@ -41,7 +41,4 @@ ''; krebs.nixpkgs.allowUnfreePredicate = pkg: packageName pkg == "broadcom-sta"; - - tv.hw.screens.primary.width = 1366; - tv.hw.screens.primary.height = 768; } diff --git a/configs/hw/winmax2.nix b/configs/hw/winmax2.nix index 7b28466..8af5511 100644 --- a/configs/hw/winmax2.nix +++ b/configs/hw/winmax2.nix @@ -20,11 +20,10 @@ hardware.cpu.amd.updateMicrocode = true; hardware.enableRedistributableFirmware = true; - hardware.opengl.enable = true; - hardware.opengl.extraPackages = [ + hardware.graphics.enable = true; + hardware.graphics.extraPackages = [ pkgs.amdvlk - pkgs.rocm-opencl-icd - pkgs.rocm-opencl-runtime + pkgs.rocmPackages.clr ]; networking.wireless.enable = true; @@ -42,7 +41,4 @@ ''; tv.lidControl.enable = true; - - tv.hw.screens.primary.width = 2560; - tv.hw.screens.primary.height = 1600; } diff --git a/configs/hw/x220.nix b/configs/hw/x220.nix index 6993413..323be01 100644 --- a/configs/hw/x220.nix +++ b/configs/hw/x220.nix @@ -49,7 +49,7 @@ # Required for Centrino. hardware.enableRedistributableFirmware = true; - hardware.opengl.extraPackages = [ pkgs.vaapiIntel pkgs.vaapiVdpau ]; + hardware.graphics.extraPackages = [ pkgs.vaapiIntel pkgs.vaapiVdpau ]; hardware.trackpoint = { enable = true; @@ -64,6 +64,7 @@ services.tlp.enable = true; services.tlp.settings = { START_CHARGE_THRESH_BAT0 = 80; + WOL_DISABLE = false; }; @@ -82,7 +83,4 @@ services.xserver = { videoDriver = "intel"; }; - - tv.hw.screens.primary.width = lib.mkDefault 1366; - tv.hw.screens.primary.height = lib.mkDefault 768; } diff --git a/configs/man.nix b/configs/man.nix index c723138..8839476 100644 --- a/configs/man.nix +++ b/configs/man.nix @@ -7,7 +7,7 @@ #''; environment.systemPackages = [ pkgs.man-pages - pkgs.posix_man_pages + pkgs.man-pages-posix pkgs.xorg.xorgdocs ]; } diff --git a/configs/pulse.nix b/configs/pulse.nix index 17c203c..815c309 100644 --- a/configs/pulse.nix +++ b/configs/pulse.nix @@ -6,19 +6,19 @@ support32Bit = pkgs.stdenv.isx86_64 && - pkgs_i686.alsaLib != null && + pkgs_i686.alsa-lib != null && pkgs_i686.libpulseaudio != null; alsaConf = pkgs.writeText "asound.conf" '' ctl_type.pulse { - libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so; + libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so; ${lib.optionalString support32Bit - "libs.32Bit = ${pkgs_i686.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so;"} + "libs.32Bit = ${pkgs_i686.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so;"} } pcm_type.pulse { - libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so; + libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so; ${lib.optionalString support32Bit - "libs.32Bit = ${pkgs_i686.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so;"} + "libs.32Bit = ${pkgs_i686.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so;"} } ctl.!default { type pulse @@ -75,7 +75,7 @@ in ]; }; - hardware.pulseaudio = { + services.pulseaudio = { inherit support32Bit; }; diff --git a/configs/sshd.nix b/configs/sshd.nix index 281d498..d99ceb5 100644 --- a/configs/sshd.nix +++ b/configs/sshd.nix @@ -1,6 +1,7 @@ { config, lib, ... }: let cfg.host = config.krebs.build.host; nets = + lib.optional (cfg.host.nets?mycelium) cfg.host.nets.mycelium ++ lib.optional (cfg.host.nets?retiolum) cfg.host.nets.retiolum ++ lib.optional (cfg.host.nets?wiregrill) cfg.host.nets.wiregrill; in { diff --git a/configs/tmux.nix b/configs/tmux.nix new file mode 100644 index 0000000..a61b8c3 --- /dev/null +++ b/configs/tmux.nix @@ -0,0 +1,11 @@ +{ + programs.tmux.enable = true; + programs.tmux.baseIndex = 1; + programs.tmux.clock24 = true; + programs.tmux.escapeTime = 0; + programs.tmux.historyLimit = 10000; + programs.tmux.terminal = "tmux-direct"; + programs.tmux.extraConfig = '' + setw -g mouse on + ''; +} diff --git a/configs/variables.nix b/configs/variables.nix new file mode 100644 index 0000000..cdfaceb --- /dev/null +++ b/configs/variables.nix @@ -0,0 +1,19 @@ +{ config, lib, ... }: { + options.hrm.environment.variables = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + description = '' + This is a stricter version of `environment.variables`, using + `escapeShellArg` instead of `"` for quoting. + + Use this when you don't have the need to reference other variables or + inject code into `/nix/store/*-set-environment`. This is also useful + for variables that need be used in contexts that don't perform shell + initialization, like e.g. `systemd.services.*.environment`; + ''; + }; + config.environment.variables = + lib.mapAttrs + (_name: value: ''"${lib.escapeShellArg value}"'') + config.hrm.environment.variables; +} diff --git a/configs/vim.nix b/configs/vim.nix index cfe30eb..8f06084 100644 --- a/configs/vim.nix +++ b/configs/vim.nix @@ -130,6 +130,8 @@ au BufRead,BufNewFile /dev/shm/* set nobackup nowritebackup noswapfile + au TabClosed * tabp + cnoremap <C-A> <Home> noremap <C-c> :q<cr> diff --git a/configs/wiregrill.nix b/configs/wiregrill.nix index 55bb6f5..ee73061 100644 --- a/configs/wiregrill.nix +++ b/configs/wiregrill.nix @@ -8,8 +8,8 @@ in lib.mkIf cfg.enable { networking.wireguard.interfaces.wiregrill = { ips = - lib.optional (cfg.net.ip4 != null) cfg.net.ip4.addr ++ - lib.optional (cfg.net.ip6 != null) cfg.net.ip6.addr; + lib.optional (cfg.net.ip4 != null) (toCidrNotation cfg.net.ip4) ++ + lib.optional (cfg.net.ip6 != null) (toCidrNotation cfg.net.ip6); listenPort = 51820; privateKeyFile = "${config.krebs.secret.directory}/wiregrill.key"; allowedIPsAsRoutes = true; diff --git a/configs/xserver/default.nix b/configs/xserver/default.nix index e0b8d77..88c1afb 100644 --- a/configs/xserver/default.nix +++ b/configs/xserver/default.nix @@ -6,13 +6,11 @@ user = config.krebs.build.user; xmonad.pkg = pkgs.haskellPackages.xmonad-tv.overrideAttrs (_: { au = { - XMONAD_BUILD_SCREEN_WIDTH = 1920; XMONAD_BUILD_TERM_FONT_WIDTH = 10; XMONAD_BUILD_TERM_FONT = "xft:Input Mono:size=12:style=Regular"; XMONAD_BUILD_TERM_PADDING = 2; }; }.${config.krebs.build.host.name} or { - XMONAD_BUILD_SCREEN_WIDTH = 1366; XMONAD_BUILD_TERM_FONT_WIDTH = 6; XMONAD_BUILD_TERM_FONT = "-*-clean-*-*-*-*-*-*-*-*-*-*-iso10646-1"; XMONAD_BUILD_TERM_PADDING = 2; @@ -115,7 +113,7 @@ in { config.tv.slock.package pkgs.flameshot-once-tv pkgs.pulseaudio.out - pkgs.rxvt_unicode + pkgs.rxvt-unicode-unwrapped pkgs.xcalib "/run/wrappers" # for su ]; diff --git a/configs/xserver/urxvt.nix b/configs/xserver/urxvt.nix index c4e619d..d3c90fd 100644 --- a/configs/xserver/urxvt.nix +++ b/configs/xserver/urxvt.nix @@ -6,7 +6,7 @@ in { restartIfChanged = false; serviceConfig = { SyslogIdentifier = "urxvtd"; - ExecStart = "${pkgs.rxvt_unicode}/bin/urxvtd"; + ExecStart = "${pkgs.rxvt-unicode-unwrapped}/bin/urxvtd"; Restart = "always"; RestartSec = "2s"; StartLimitBurst = 0; diff --git a/configs/xsessions/urxvtd.nix b/configs/xsessions/urxvtd.nix index de16a63..80d4b3d 100644 --- a/configs/xsessions/urxvtd.nix +++ b/configs/xsessions/urxvtd.nix @@ -9,7 +9,7 @@ RXVT_SOCKET = "%t/urxvtd"; }; serviceConfig = { - ExecStart = "${pkgs.rxvt_unicode}/bin/urxvtd"; + ExecStart = "${pkgs.rxvt-unicode-unwrapped}/bin/urxvtd"; }; }; } diff --git a/lib/pure.nix b/lib/pure.nix index edf7292..ab4be9a 100644 --- a/lib/pure.nix +++ b/lib/pure.nix @@ -9,6 +9,14 @@ let }; in filterAttrsRecursive (name: _: !hasPrefix "_" name) eval.config; + evalOption = option: config: + (lib.evalModules { + modules = lib.singleton { + options.x = option; + config.x = config; + }; + }).config.x; + evalSource = import ./eval-source.nix; evalSubmodule = submodule: modules: let diff --git a/modules/backup.nix b/modules/backup.nix new file mode 100644 index 0000000..37f8e67 --- /dev/null +++ b/modules/backup.nix @@ -0,0 +1,252 @@ +{ config, lib, pkgs, ... }: +with import ../lib/pure.nix { inherit lib; }; +let + out = { + options.krebs.backup = api; + config = lib.mkIf cfg.enable imp; + }; + + cfg = config.krebs.backup; + + api = { + enable = mkEnableOption "krebs.backup" // { default = true; }; + plans = mkOption { + default = {}; + type = types.attrsOf (types.submodule ({ config, ... }: { + options = { + enable = mkEnableOption "krebs.backup.${config._module.args.name}" // { + default = true; + }; + method = mkOption { + type = types.enum ["pull" "push"]; + }; + name = mkOption { + type = types.str; + default = config._module.args.name; + defaultText = "‹name›"; + }; + src = mkOption { + type = types.krebs.file-location; + }; + dst = mkOption { + type = types.krebs.file-location; + }; + startAt = mkOption { + default = "hourly"; + type = with types; nullOr str; # TODO systemd.time(7)'s calendar event + }; + snapshots = mkOption { + default = { + hourly = { format = "%Y-%m-%dT%H"; retain = 4; }; + daily = { format = "%Y-%m-%d"; retain = 7; }; + weekly = { format = "%YW%W"; retain = 4; }; + monthly = { format = "%Y-%m"; retain = 12; }; + yearly = { format = "%Y"; }; + }; + type = types.attrsOf (types.submodule { + options = { + format = mkOption { + type = types.str; # TODO date's +FORMAT + }; + retain = mkOption { + type = types.nullOr types.int; + default = null; # null = retain all snapshots + }; + }; + }); + }; + timerConfig = mkOption { + type = with types; attrsOf str; + default = optionalAttrs (config.startAt != null) { + OnCalendar = config.startAt; + }; + }; + }; + })); + }; + }; + + imp = { + krebs.on-failure.plans = + listToAttrs (map (plan: nameValuePair "backup.${plan.name}" { + }) (filter (plan: build-host-is "pull" "dst" plan || + build-host-is "push" "src" plan) + enabled-plans)); + + systemd.services = + listToAttrs (map (plan: nameValuePair "backup.${plan.name}" { + # TODO if there is plan.user, then use its privkey + # TODO push destination users need a similar path + path = with pkgs; [ + coreutils + gnused + openssh + rsync + util-linux + ]; + restartIfChanged = false; + serviceConfig = rec { + ExecStart = start plan; + SyslogIdentifier = ExecStart.name; + Type = "oneshot"; + }; + }) (filter (plan: build-host-is "pull" "dst" plan || + build-host-is "push" "src" plan) + enabled-plans)); + + systemd.timers = + listToAttrs (map (plan: nameValuePair "backup.${plan.name}" { + wantedBy = [ "timers.target" ]; + timerConfig = plan.timerConfig; + }) (filter (plan: plan.timerConfig != {} && ( + build-host-is "pull" "dst" plan || + build-host-is "push" "src" plan)) + enabled-plans)); + + users.groups.backup.gid = genid "backup"; + users.users.root.openssh.authorizedKeys.keys = + map (plan: getAttr plan.method { + push = plan.src.host.ssh.pubkey; + pull = plan.dst.host.ssh.pubkey; + }) (filter (plan: build-host-is "pull" "src" plan || + build-host-is "push" "dst" plan) + enabled-plans); + }; + + enabled-plans = filter (getAttr "enable") (attrValues cfg.plans); + + build-host-is = method: side: plan: + plan.method == method && + config.krebs.build.host.name == plan.${side}.host.name; + + start = plan: let + login-name = "root"; + identity = local.host.ssh.privkey.path; + ssh = "ssh -i ${shell.escape identity}"; + local = getAttr plan.method { + push = plan.src // { rsync = src-rsync; }; + pull = plan.dst // { rsync = dst-rsync; }; + }; + remote = getAttr plan.method { + push = plan.dst // { rsync = dst-rsync; }; + pull = plan.src // { rsync = src-rsync; }; + }; + src-rsync = "rsync"; + dst-rsync = concatStringsSep " && " [ + "stat ${shell.escape plan.dst.path} >/dev/null" + "mkdir -m 0700 -p ${shell.escape plan.dst.path}/current" + "flock -n ${shell.escape plan.dst.path} rsync" + ]; + in pkgs.writeBash "backup.${plan.name}" '' + set -efu + start_date=$(date +%s) + ssh_target=${shell.escape login-name}@$(${fastest-address remote.host}) + ${getAttr plan.method { + push = '' + rsync_src=${shell.escape plan.src.path} + rsync_dst=$ssh_target:${shell.escape plan.dst.path} + echo >&2 "update snapshot current; $rsync_src -> $rsync_dst" + ''; + pull = '' + rsync_src=$ssh_target:${shell.escape plan.src.path} + rsync_dst=${shell.escape plan.dst.path} + echo >&2 "update snapshot current; $rsync_dst <- $rsync_src" + ''; + }} + # In `dst-rsync`'s `mkdir m 0700 -p` above, we care only about permission + # of the deepest directory: + # shellcheck disable=SC2174 + ${local.rsync} >&2 \ + -aAX --delete \ + --filter='dir-merge /.backup-filter' \ + --rsh=${shell.escape ssh} \ + --rsync-path=${shell.escape remote.rsync} \ + --link-dest=${shell.escape plan.dst.path}/current \ + "$rsync_src/" \ + "$rsync_dst/.partial" + + dst_exec() { + ${getAttr plan.method { + push = ''exec ${ssh} "$ssh_target" -T "exec$(printf ' %q' "$@")"''; + pull = ''exec "$@"''; + }} + } + dst_exec env \ + start_date="$start_date" \ + flock -n ${shell.escape plan.dst.path} \ + /bin/sh < ${toFile "backup.${plan.name}.take-snapshots" '' + set -efu + : $start_date + + dst_path=${shell.escape plan.dst.path} + + mv "$dst_path/current" "$dst_path/.previous" + mv "$dst_path/.partial" "$dst_path/current" + rm -fR "$dst_path/.previous" + echo >&2 + + snapshot() {( + : $ns $format $retain + name=$(date --date="@$start_date" +"$format") + if ! test -e "$dst_path/$ns/$name"; then + echo >&2 "create snapshot: $ns/$name" + mkdir -m 0700 -p "$dst_path/$ns" + rsync >&2 \ + -aAX --delete \ + --filter='dir-merge /.backup-filter' \ + --link-dest="$dst_path/current" \ + "$dst_path/current/" \ + "$dst_path/$ns/.partial.$name" + mv "$dst_path/$ns/.partial.$name" "$dst_path/$ns/$name" + echo >&2 + fi + case $retain in + ([0-9]*) + delete_from=$(($retain + 1)) + ls -r "$dst_path/$ns" \ + | sed -n "$delete_from,\$p" \ + | while read old_name; do + echo >&2 "delete snapshot: $ns/$old_name" + rm -fR "$dst_path/$ns/$old_name" + done + ;; + (ALL) + : + ;; + esac + )} + + ${concatStringsSep "\n" (mapAttrsToList (ns: { format, retain, ... }: + toString (map shell.escape [ + "ns=${ns}" + "format=${format}" + "retain=${if retain == null then "ALL" else toString retain}" + "snapshot" + ])) + plan.snapshots)} + ''} + ''; + + # XXX Is one ping enough to determine fastest address? + fastest-address = host: '' + { ${pkgs.fping}/bin/fping </dev/null -a -e \ + ${concatMapStringsSep " " shell.escape + (mapAttrsToList (_: net: head net.aliases) host.nets)} \ + | ${pkgs.gnused}/bin/sed -r 's/^(\S+) \(([0-9.]+) ms\)$/\2\t\1/' \ + | ${pkgs.coreutils}/bin/sort -n \ + | ${pkgs.coreutils}/bin/cut -f2 \ + | ${pkgs.coreutils}/bin/head -n 1 + } + ''; + +in out +# TODO ionice +# TODO mail on missing push +# TODO don't cancel plans on activation +# also, don't hang while deploying at: +# starting the following units: backup.wu-home-xu.push.service, backup.wu-home-xu.push.timer +# TODO make sure that secure hosts cannot backup to insecure ones +# TODO optionally only backup when src and dst are near enough :) +# TODO try using btrfs for snapshots (configurable) +# TODO warn if partial snapshots are found +# TODO warn if unknown stuff is found in dst path diff --git a/modules/ejabberd/default.nix b/modules/ejabberd/default.nix index 02c060d..9607741 100644 --- a/modules/ejabberd/default.nix +++ b/modules/ejabberd/default.nix @@ -131,6 +131,9 @@ in { systemd.services.ejabberd = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; + environment = { + HOME = cfg.stateDir; + |