about summary refs log tree commit diff
path: root/home/profiles
diff options
context:
space:
mode:
Diffstat (limited to 'home/profiles')
-rw-r--r--home/profiles/alacritty.nix51
-rw-r--r--home/profiles/dev.nix49
-rw-r--r--home/profiles/emacs.nix13
-rw-r--r--home/profiles/firefox.nix59
-rw-r--r--home/profiles/git.nix73
-rw-r--r--home/profiles/gtk.nix35
-rw-r--r--home/profiles/home.nix69
-rw-r--r--home/profiles/mako.nix25
-rw-r--r--home/profiles/nas.nix67
-rw-r--r--home/profiles/ssh.nix18
-rw-r--r--home/profiles/sway.nix172
-rw-r--r--home/profiles/tmux.nix20
-rw-r--r--home/profiles/waybar.nix155
-rw-r--r--home/profiles/wofi.nix13
-rw-r--r--home/profiles/workstation.nix113
-rw-r--r--home/profiles/ytdlp.nix37
-rw-r--r--home/profiles/yubikey.nix28
-rw-r--r--home/profiles/zsh/completion-style.zsh42
-rw-r--r--home/profiles/zsh/default.nix51
-rw-r--r--home/profiles/zsh/new-go-project.zsh19
-rw-r--r--home/profiles/zsh/options.zsh27
-rw-r--r--home/profiles/zsh/prompt.zsh17
-rw-r--r--home/profiles/zsh/tmux.zsh9
23 files changed, 1162 insertions, 0 deletions
diff --git a/home/profiles/alacritty.nix b/home/profiles/alacritty.nix
new file mode 100644
index 0000000..2789f12
--- /dev/null
+++ b/home/profiles/alacritty.nix
@@ -0,0 +1,51 @@
+{
+  programs.alacritty = {
+    enable = true;
+    settings = {
+      env = {
+        TERM = "xterm-256color";
+      };
+
+      live_config_reload = true;
+      draw_bold_text_with_bright_colors = true;
+      selection = { save_to_clipboard = true; };
+
+      colors = {
+        primary = {
+          background = "#000000";
+          foreground = "#D3D7CF";
+        };
+
+        normal = {
+          black = "#2E3436";
+          red = "#CC0000";
+          green = "#4E9A06";
+          yellow = "#C4A000";
+          blue = "#3465A4";
+          magenta = "#EF33C0";
+          cyan = "#04B5B8";
+          white = "#D3D7CF";
+        };
+
+        bright = {
+          black = "#555753";
+          red = "#EF2929";
+          green = "#8AE234";
+          yellow = "#FCE94F";
+          blue = "#729FCF";
+          magenta = "#EE38DA";
+          cyan = "#34E2E2";
+          white = "#EEEEEC";
+        };
+      };
+      font = {
+        size = 13;
+        normal.family = "Source Code Pro";
+      };
+      cursor = {
+        style.blinking = "Never";
+        unfocused_hollow = true;
+      };
+    };
+  };
+}
diff --git a/home/profiles/dev.nix b/home/profiles/dev.nix
new file mode 100644
index 0000000..c1323d6
--- /dev/null
+++ b/home/profiles/dev.nix
@@ -0,0 +1,49 @@
+{ pkgs, config, ... }:
+let
+  ruststable = (pkgs.rust-bin.stable.latest.default.override {
+    extensions = [
+      "rust-src"
+      "rust-analyzer-preview"
+      "rust-analysis"
+      "rustfmt-preview"
+    ];
+  });
+  pythonEnv = pkgs.python3.withPackages (p: with p; [
+    black
+    isort
+    pylsp-mypy
+    requests
+    types-requests
+    pip
+    ipython
+  ]);
+in
+{
+  programs.go = {
+    enable = true;
+    goPath = ".local/share/pkg.go";
+    goBin = ".local/bin.go";
+    goPrivate = [ "git.fcuny.net" "golang.fcuny.net" ];
+    package = pkgs.go_1_18;
+  };
+
+  home.packages = with pkgs; [
+    go-tools
+    pythonEnv
+    google-cloud-sdk
+    ruststable
+  ];
+
+  home.sessionPath = [
+    config.home.sessionVariables.GOBIN
+    "$CARGO_HOME/bin"
+  ];
+
+  home.sessionVariables = with config.xdg; {
+    IPYTHONDIR = "${cacheHome}/ipython";
+    PIP_LOG = "${cacheHome}/pip/pip.log";
+    PYLINTHOME = "${cacheHome}/pylint";
+    PYTHON_EGG_CACHE = "${cacheHome}/python-eggs";
+    CARGO_HOME = "${dataHome}/cargo";
+  };
+}
diff --git a/home/profiles/emacs.nix b/home/profiles/emacs.nix
new file mode 100644
index 0000000..17c751d
--- /dev/null
+++ b/home/profiles/emacs.nix
@@ -0,0 +1,13 @@
+{ lib, config, pkgs, ... }:
+{
+  home.packages = with pkgs; [
+    emacsPgtk
+    # see https://github.com/hlissner/doom-emacs/issues/4138
+    (aspellWithDicts (dicts: with dicts; [ en en-computers en-science ]))
+  ];
+
+  home.sessionVariables = {
+    EDITOR = "emacsclient -a=";
+    VISUAL = "emacsclient -a=";
+  };
+}
diff --git a/home/profiles/firefox.nix b/home/profiles/firefox.nix
new file mode 100644
index 0000000..e74aaea
--- /dev/null
+++ b/home/profiles/firefox.nix
@@ -0,0 +1,59 @@
+{ pkgs, lib, config, ... }:
+{
+  programs.firefox = {
+    enable = true;
+    profiles = {
+      default = {
+        extensions = with pkgs.nur.repos.rycee.firefox-addons; [
+          pkgs.nur.repos.rycee.firefox-addons."1password-x-password-manager"
+          consent-o-matic
+          refined-github
+          sponsorblock
+          ublock-origin
+        ];
+        settings = {
+          ## nix handle updates
+          "app.update.auto" = false;
+          "browser.bookmarks.showMobileBookmarks" = false;
+          "browser.compactmode.show" = true;
+          "browser.contentblocking.category" = "strict";
+          "browser.search.countryCode" = "US";
+          "browser.search.defaultenginename" = "DuckDuckGo";
+          "browser.search.isUS" = true;
+          "browser.search.region" = "US";
+          "browser.search.selectedEngine" = "DuckDuckGo";
+          ## don't check if it's the default browser
+          "browser.shell.checkDefaultBrowser" = false;
+          "browser.startup.homepage" = "https://duckduckgo.com";
+          "browser.urlbar.placeholderName" = "DuckDuckGo";
+          ## keep this with ff 96
+          "media.ffmpeg.vaapi.enabled" = true;
+          ## remove on ff 96
+          "media.ffvpx.enabled" = false;
+          ## remove on ff 96
+          "media.navigator.mediadatadecoder_vpx_enabled" = true;
+          "media.peerconnection.enabled" = true;
+          ## keep this with ff 96
+          "media.rdd-ffmpeg.enabled" = true;
+          ## remove on ff 96
+          "media.rdd-vpx.enabled" = false;
+          ## Block third-party cookies
+          "network.cookie.cookieBehavior" = 1;
+          "privacy.donottrackheader.enabled" = true;
+          "privacy.trackingprotection.enabled" = true;
+          "privacy.trackingprotection.socialtracking.enabled" = true;
+
+          "font.name.monospace.x-western" = "Source Code Pro";
+          "font.name.sans-serif.x-western" = "DejaVu Sans";
+          "font.name.serif.x-western" = "DejaVu Serif";
+        };
+
+        userChrome = ''
+          #TabsToolbar {
+          visibility: collapse;
+          }
+        '';
+      };
+    };
+  };
+}
diff --git a/home/profiles/git.nix b/home/profiles/git.nix
new file mode 100644
index 0000000..de2cbf6
--- /dev/null
+++ b/home/profiles/git.nix
@@ -0,0 +1,73 @@
+{ self, lib, pkgs, config, ... }:
+let
+  sshPub = builtins.fromTOML (
+    builtins.readFile "${self}/configs/ssh-pubkeys.toml"
+  );
+in
+{
+  home.file.".ssh/allowed_signers".text = lib.concatMapStrings (x: "franck@fcuny.net ${x}\n") (with sshPub; [ aptos work git ykey-laptop ]);
+
+  programs.git = {
+    enable = true;
+    userName = "Franck Cuny";
+    userEmail = "franck@fcuny.net";
+
+    signing = {
+      key = "key::${sshPub.ykey-laptop}";
+      signByDefault = true;
+    };
+
+    extraConfig = {
+      core.whitespace = "trailing-space,space-before-tab";
+      color.ui = "true";
+
+      diff.age.textconv = "${pkgs.age}/bin/age --identity ${config.home.homeDirectory}/.age/key.txt --decrypt";
+
+      gpg.format = "ssh";
+      gpg.ssh.allowedSignersFile = "~/.ssh/allowed_signers";
+
+      # abort if the remote branch does not match the local one
+      push.default = "simple";
+
+      init.defaultBranch = "main";
+
+      pull.rebase = true;
+      rebase = {
+        # Automatically create a temporary stash entry before the
+        # operation begins, and apply it after the operation ends.
+        autoStash = true;
+        # Print a warning if some commits are removed
+        missingCommitsCheck = "warn";
+      };
+
+      branch.autosetuprebase = "remote";
+      branch.sort = "authordate";
+
+      commit.template = "${config.xdg.dataHome}/git/commit.template";
+    };
+
+    ignores = [
+      "*~"
+      ".direnv"
+    ];
+  };
+
+  xdg.dataFile."git/commit.template".source = pkgs.writeText "commit.template" ''
+
+    # (If applied, this commit will...) <subject>
+
+    # Explain why this change is being made
+
+    # --- COMMIT END ---
+    # Remember to
+    #    Use the imperative mood, present tense: `change' not `changed' nor `changes'
+    #    Do not end the subject line with a period
+    #    Use the body to explain what and why vs. how
+    #    Can use multiple lines with "-" for bullet points in body
+'';
+
+  home.packages = with pkgs; [
+    tools.git-blame-stats
+    gitAndTools.pre-commit
+  ];
+}
diff --git a/home/profiles/gtk.nix b/home/profiles/gtk.nix
new file mode 100644
index 0000000..a3fff9d
--- /dev/null
+++ b/home/profiles/gtk.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+{
+  gtk = {
+    enable = true;
+
+    iconTheme = {
+      name = "Papirus-Dark";
+      package = pkgs.papirus-icon-theme;
+    };
+
+    theme = {
+      name = "palenight";
+      package = pkgs.palenight-theme;
+    };
+
+    cursorTheme = {
+      name = "Numix-Cursor";
+      package = pkgs.numix-cursor-theme;
+    };
+
+    gtk3.extraConfig = {
+      Settings = ''
+        gtk-application-prefer-dark-theme=1
+      '';
+    };
+
+    gtk4.extraConfig = {
+      Settings = ''
+        gtk-application-prefer-dark-theme=1
+      '';
+    };
+  };
+
+  home.sessionVariables.GTK_THEME = "palenight";
+}
diff --git a/home/profiles/home.nix b/home/profiles/home.nix
new file mode 100644
index 0000000..1aa90cf
--- /dev/null
+++ b/home/profiles/home.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+{
+  imports = [
+    ./git.nix
+    ./ssh.nix
+    ./zsh
+  ];
+
+  home.packages = with pkgs; [
+    dive # explore layers in docker images
+    jq
+    ripgrep
+    util-linux
+    xdg-utils
+
+    age
+    rage
+    age-plugin-yubikey
+
+    # tools inside the tools directory
+    tools.git-blame-stats
+    tools.git-broom
+    tools.ipconverter
+    tools.seqstat
+
+    # tools from external repositories
+    # x509-info
+    # gh-ssh-keys
+    # masked-emails
+  ];
+
+  programs.direnv = {
+    enable = true;
+    nix-direnv.enable = true;
+    config = {
+      global.disable_stdin = true;
+      global.strict_env = true;
+    };
+  };
+
+  xdg = {
+    enable = true;
+    # File types
+    mime.enable = true;
+    # File associatons
+    mimeApps = { enable = true; };
+    # User directories
+    userDirs = {
+      enable = true;
+      createDirectories = true;
+      desktop = "${config.home.homeDirectory}/documents";
+      documents = "${config.home.homeDirectory}/documents";
+      download = "${config.home.homeDirectory}/downloads";
+      music = "${config.home.homeDirectory}/media/music";
+      pictures = "${config.home.homeDirectory}/media/pictures";
+      publicShare = "${config.home.homeDirectory}/documents/public";
+      templates = "${config.home.homeDirectory}/documents/templates";
+      videos = "${config.home.homeDirectory}/media/videos";
+    };
+  };
+
+  home.sessionVariables = with config.xdg; {
+    LESS = "-FRSXM";
+    LESSCHARSET = "utf-8";
+    LESSHISTFILE = "${dataHome}/less/history";
+    LESSKEY = "${configHome}/less/lesskey";
+    PAGER = "less";
+  };
+}
diff --git a/home/profiles/mako.nix b/home/profiles/mako.nix
new file mode 100644
index 0000000..d4e54fe
--- /dev/null
+++ b/home/profiles/mako.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+{
+  home.packages = [
+    pkgs.libnotify
+  ];
+
+  # All the options are documented via `man 5 mako`
+  services.mako = {
+    enable = true;
+    font = "Source Code Pro";
+    backgroundColor = "#000021DD";
+    textColor = "#FFFFFFFF";
+    borderSize = 0;
+    borderRadius = 15;
+    icons = true;
+    iconPath = "${pkgs.moka-icon-theme}/share/icons/Moka";
+    markup = true;
+    actions = true;
+    defaultTimeout = 3000;
+    padding = "20";
+    height = 200;
+    width = 500;
+    layer = "overlay";
+  };
+}
diff --git a/home/profiles/nas.nix b/home/profiles/nas.nix
new file mode 100644
index 0000000..c1e5ca9
--- /dev/null
+++ b/home/profiles/nas.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+let
+  bc-to-beet = pkgs.writeShellApplication {
+    name = "bc-to-beet";
+    runtimeInputs = [ pkgs.beets ];
+    text = ''
+      ALBUM_PATH="$1"
+      ALBUM_NAME=$(basename "$ALBUM_PATH")
+
+      mkdir -p ~/import
+      rm -rf ~/import/tmp-bc
+      unzip -d ~/import/tmp-bc ~/import/album.zip
+      beet import ~/import/tmp-bc
+      rm -rf ~/import/tmp-bc
+      rm -rf ~/import/album.zip
+    '';
+  };
+in
+{
+  imports = [
+    ./ytdlp.nix
+  ];
+
+  home.packages = with pkgs; [
+    bc-to-beet
+    flac
+    abcde
+    (pkgs.writers.writeDashBin "rip-flac" ''
+      cd ~/import
+      ${pkgs.abcde}/bin/abcde -Vx -G -a "cddb,read,encode,tag,move,clean" -o flac
+    '')
+  ];
+
+  programs.beets = {
+    enable = true;
+    settings = {
+      directory = cfg.musicDirectory;
+      plugins =
+        "fromfilename discogs duplicates fetchart embedart badfiles lastgenre scrub";
+      paths = {
+        default = "$albumartist/$album%aunique{}/$track $title";
+        singleton = "Singles/$artist/$title";
+        comp = "Compilations/$album%aunique{}/$track - $title";
+        "albumtype:soundtrack" = "Soundtracks/$album ($year)/$track $title";
+      };
+      import = {
+        copy = true;
+        move = true;
+      };
+      va_name = "Various Artists";
+      embedart = { ifempty = true; };
+
+      lastgenre = {
+        auto = false;
+        canonical = true;
+        fallback = "unknown";
+        force = true;
+        prefer_specific = true;
+      };
+
+      fetchart = {
+        cautious = true;
+        sources = "filesystem coverart itunes amazon lastfm wikipedia";
+      };
+    };
+  };
+}
diff --git a/home/profiles/ssh.nix b/home/profiles/ssh.nix
new file mode 100644
index 0000000..576f451
--- /dev/null
+++ b/home/profiles/ssh.nix
@@ -0,0 +1,18 @@
+{ ... }:
+{
+  programs.ssh = {
+    enable = true;
+    forwardAgent = true;
+    serverAliveInterval = 60;
+    controlMaster = "auto";
+    controlPersist = "30m";
+    matchBlocks = {
+      "github.com" = {
+        hostname = "github.com";
+        user = "git";
+        forwardAgent = false;
+        extraOptions = { preferredAuthentications = "publickey"; };
+      };
+    };
+  };
+}
diff --git a/home/profiles/sway.nix b/home/profiles/sway.nix
new file mode 100644
index 0000000..0665556
--- /dev/null
+++ b/home/profiles/sway.nix
@@ -0,0 +1,172 @@
+{ config, lib, pkgs, ... }:
+let
+  modifier = "Mod4"; # `Super` key
+in
+{
+  imports = [
+    ./gtk.nix
+    ./mako.nix
+    ./waybar.nix
+    ./wofi.nix
+  ];
+
+  home.packages = with pkgs; [
+    wlogout
+    brightnessctl
+    pulseaudio
+    grim
+    slurp
+    polkit_gnome
+    xsettingsd
+    swaylock
+    swayidle
+    wl-clipboard
+  ];
+
+  home.sessionVariables = {
+    MOZ_ENABLE_WAYLAND = "1";
+    XDG_CURRENT_DESKTOP = "sway";
+    XDG_SESSION_TYPE = "wayland";
+  };
+
+  programs = {
+    zsh.loginExtra = ''
+      if [ $(ps ax | grep "[s]sh-agent" | wc -l) -eq 0 ] ; then
+        eval $(ssh-agent -s) > /dev/null
+      fi
+      if [ "$(tty)" = "/dev/tty1" ]; then
+        exec sway &> /dev/null
+      fi
+    '';
+
+    zsh.profileExtra = ''
+      if [ $(ps ax | grep "[s]sh-agent" | wc -l) -eq 0 ] ; then
+        eval $(ssh-agent -s) > /dev/null
+      fi
+      if [ "$(tty)" = "/dev/tty1" ]; then
+        exec sway &> /dev/null
+      fi
+    '';
+  };
+
+  wayland.windowManager.sway = {
+    enable = true;
+    # in order to import some variables (e.g. PATH) so that all the
+    # units that will be started have all the required environment
+    # variables
+    extraSessionCommands = "systemctl --user import-environment";
+    # this will start sway-session.target and run
+    # dbus-update-activation-environment
+    systemdIntegration = true;
+    config = {
+      # FIXME: this should be a variable
+      terminal = "alacritty";
+      modifier = modifier;
+      menu = ''${pkgs.wofi}/bin/wofi -S drun -p "app:" -L 10'';
+      bars = [ ];
+      fonts = {
+        names = [ "Source Code Pro" ];
+        size = 10.0;
+      };
+      keybindings = lib.mkOptionDefault {
+        # control the volume
+        "XF86AudioRaiseVolume" =
+          "exec ${pkgs.pulseaudio}/bin/pactl set-sink-volume @DEFAULT_SINK@ +5%";
+        "XF86AudioLowerVolume" =
+          "exec ${pkgs.pulseaudio}/bin/pactl set-sink-volume @DEFAULT_SINK@ -5%";
+        "XF86AudioMute" =
+          "exec ${pkgs.pulseaudio}/bin/pactl set-sink-mute @DEFAULT_SINK@ toggle";
+        "XF86AudioMicMute" =
+          "exec ${pkgs.pulseaudio}/bin/pactl set-source-mute @DEFAULT_SOURCE@ toggle";
+
+        # control brightness
+        "XF86MonBrightnessDown" =
+          "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-";
+        "XF86MonBrightnessUp" =
+          "exec ${pkgs.brightnessctl}/bin/brightnessctl set +5%";
+
+        # logout
+        "${modifier}+Escape" = "exec ${pkgs.wlogout}/bin/wlogout";
+
+        # screenshot
+        "${modifier}+s" =
+          "exec ${pkgs.grim}/bin/grim $(xdg-user-dir DOCUMENTS)/screenshots/$(date +'%Y-%m-%d-%H%M%S_screenshot.png')";
+        "${modifier}+Shift+s" =
+          "exec ${pkgs.slurp}/bin/slurp | ${pkgs.grim}/bin/grim -g - $(xdg-user-dir DOCUMENTS)/screenshots/$(date +'%Y-%m-%d-%H%M%S_screenshot.png')";
+
+        # File Manager
+        "${modifier}+p" = "exec ${pkgs.pcmanfm}/bin/pcmanfm";
+      };
+
+      # use `swaymsg -t get_tree' to get the title/name/ID of the applications
+      window = {
+        commands = [
+          {
+            criteria.class = ".blueman-manager-wrapped";
+            command = "floating enable";
+          }
+          {
+            criteria.class = "Pavucontrol";
+            command = "floating enable";
+          }
+        ];
+      };
+
+      input = {
+        "*" = {
+          "xkb_layout" = "us,fr";
+          # map capslock to ctrl, and switch layout using shift+caps
+          "xkb_options" = "ctrl:nocaps,grp:shift_caps_toggle";
+        };
+      };
+
+      assigns = {
+        "1" = [{ app_id = "emacs"; }];
+        "2" = [{ app_id = "Alacritty"; }];
+        "3" = [{ app_id = "firefox"; }];
+        "4" = [{ app_id = "org.gnome.Fractal"; }];
+      };
+
+      output = {
+        "*" = {
+          scale = "1.5";
+          bg = "#2E3440 solid_color";
+        };
+        # This is for aptos
+        "eDP-1" = { scale = "1.3"; };
+      };
+    };
+  };
+
+  xdg.configFile."swaylock/config".source = pkgs.writeText "config" ''
+    color=2E3440
+    daemonize
+    indicator-caps-lock
+    hide-keyboard-layout
+  '';
+
+  services.swayidle = {
+    enable = true;
+    events = [
+      {
+        event = "before-sleep";
+        command = "${pkgs.swaylock}/bin/swaylock -fF";
+      }
+      {
+        event = "lock";
+        command = "${pkgs.swaylock}/bin/swaylock -fF";
+      }
+    ];
+    timeouts = [
+      {
+        timeout = 300;
+        command = "${pkgs.sway}/bin/swaymsg \"output * dpms off\"";
+        resumeCommand = "${pkgs.sway}/bin/swaymsg \"output * dpms on\"";
+      }
+      {
+        timeout = 310;
+        command = "${pkgs.systemd}/bin/loginctl lock-session";
+      }
+    ];
+  };
+}
diff --git a/home/profiles/tmux.nix b/home/profiles/tmux.nix
new file mode 100644
index 0000000..22f8683
--- /dev/null
+++ b/home/profiles/tmux.nix
@@ -0,0 +1,20 @@
+{ ... }:
+{
+  programs.tmux = {
+    enable = true;
+
+    terminal = "xterm-256color";
+    escapeTime = 0;
+    aggressiveResize = true;
+    baseIndex = 1;
+    shortcut = "z";
+    clock24 = true;
+    historyLimit = 50000; # Bigger buffer
+
+    extraConfig = ''
+      setw -g mouse on
+
+      set-option -g renumber-windows on
+    '';
+  };
+}
diff --git a/home/profiles/waybar.nix b/home/profiles/waybar.nix
new file mode 100644
index 0000000..e6f6c3d
--- /dev/null
+++ b/home/profiles/waybar.nix
@@ -0,0 +1,155 @@
+{ config, lib, pkgs, ... }:
+let
+  # waybar-systemd = pkgs.writeShellApplication {
+  #   name = "waybar-systemd.sh";
+  #   runtimeInputs = [ ];
+  #   text = ''
+  #     failed_user="$(systemctl --plain --no-legend --user list-units --state=failed | awk '{ print $1 }')"
+  #     failed_system="$(systemctl --plain --no-legend list-units --state=failed | awk '{ print $1 }')"
+
+  #     failed_systemd_count="$(echo -n "$failed_system" | grep -c '^')"
+  #     failed_user_count="$(echo -n "$failed_user" | grep -c '^')"
+
+  #     text=$(( failed_systemd_count + failed_user_count ))
+
+  #     if [ "$text" -eq 0 ]; then
+  #         printf '{"text": ""}\n'
+  #     else
+  #         tooltip=""
+
+  #         [ -n "$failed_system" ] && tooltip="Failed system services:\n\n${failed_system}\n\n${tooltip}"
+  #         [ -n "$failed_user" ]   && tooltip="Failed user services:\n\n${failed_user}\n\n${tooltip}"
+
+  #         tooltip="$(printf  "%s" "$tooltip" | perl -pe 's/\n/\\n/g' | perl -pe 's/(?:\\n)+$//')"
+
+  #         printf '{"text": "%s", "tooltip": "%s" }\n' "$text" "$tooltip"
+  #     fi
+  #   '';
+  # };
+in
+{
+  programs.waybar = {
+    enable = true;
+    systemd.enable = true;
+
+    settings = [{
+      layer = "bottom";
+      height = 25;
+      position = "top";
+      margin-top = 0;
+      margin-left = 0;
+      margin-right = 0;
+      margin-bottom = 0;
+      modules-left = [ "sway/workspaces" "sway/mode" ];
+      modules-right = [ "pulseaudio" "network" "battery" "clock" "tray" ];
+      "sway/workspaces" = {
+        format = "{name}";
+        disable-scroll = true;
+      };
+      "sway/mode" = { format = "{}"; };
+      tray = { spacing = 10; };
+      clock = { format = "{: %a %b %d %R}"; };
+      battery = {
+        states = {
+          warning = 30;
+          critical = 15;
+        };
+        format = "ac:{capacity}%";
+        tooltip = true;
+        tooltip-format = "{timeTo} ({capacity}%)";
+      };
+      # "custom/systemd" = {
+      #   exec = "${waybarSystemd}/bin/waybar-systemd";
+      #   return-type = "json";
+      #   interval = 10;
+      # };
+      pulseaudio = {
+        format = "vol:{volume}%";
+        format-bluetooth = "bt:{volume}%";
+        format-bluetooth-muted = "bt:{volume}%";
+        format-muted = "vol:{volume}%";
+        on-click = "pavucontrol";
+      };
+      "network" = {
+        format-wifi = "{essid}:{signalStrength}%";
+        format-ethernet = "{ipaddr}/{cidr}";
+        format-linked = "{ifname} (No IP)";
+        format-disconnected = "network unavailable";
+        format-alt = "{ifname}: {ipaddr}/{cidr}";
+        tooltip = false;
+      };
+    }];
+  };
+
+  programs.waybar.style = pkgs.writeText "style.css" ''
+    * {
+      border-radius: 0;
+      border: none;
+      margin: 0;
+      min-height: 0;
+      padding: 0;
+      font-family: Source Code Pro;
+      font-size: 15px;
+    }
+    window#waybar {
+      background-color: #282A36;
+      color: #eee;
+    }
+    #workspaces button {
+      padding: 0 3px;
+      background-color: transparent;
+      color: #eee;
+    }
+    #workspaces button.focused {
+      background-color: #285577;
+      border: 1px solid #4c7899;
+    }
+    #clock,
+    #battery,
+    #network,
+    #pulseaudio,
+    #tray,
+    #mode {
+      padding-left: 10px;
+      padding-right: 10px;
+    }
+    #mode {
+      /* No styles */
+    }
+    #tray {
+      /* No styles */
+    }
+    #clock {
+      /* No styles */
+    }
+    #battery {
+      animation-timing-function: linear;
+      animation-iteration-count: infinite;
+      animation-direction: alternate;
+    }
+    #battery.discharging {
+      color: #90a1ad;
+    }
+    #battery.charging {
+      color: #fffff8;
+    }
+    #battery.warning {
+      border-bottom: 2px solid #ff9e21;
+    }
+    #battery.critical {
+      border-bottom: 2px solid #ff3121;
+    }
+    #network {
+      /* No styles */
+    }
+    #network.disconnected {
+      color: orange;
+    }
+    #pulseaudio {
+      /* No styles */
+    }
+    #pulseaudio.muted {
+      color: #90a1ad;
+    }
+  '';
+}
diff --git a/home/profiles/wofi.nix b/home/profiles/wofi.nix
new file mode 100644
index 0000000..7140650
--- /dev/null
+++ b/home/profiles/wofi.nix
@@ -0,0 +1,13 @@
+{ config, lib, pkgs, ... }: {
+
+  home.packages = with pkgs; [ wofi ];
+
+  xdg.configFile."wofi/config".source = pkgs.writeText "config" ''
+    allow_images=true
+    image_size=25px
+    drun-display_generic=true
+    dynamic_lines=true
+    insensitive=true
+    run-cache_file=/dev/null
+  '';
+}
diff --git a/home/profiles/workstation.nix b/home/profiles/workstation.nix
new file mode 100644
index 0000000..acbbccb
--- /dev/null
+++ b/home/profiles/workstation.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ... }:
+let
+  restic-nas = pkgs.writeShellApplication
+    {
+      name = "restic-nas";
+      runtimeInputs = [ pkgs.restic pkgs.tailscale pkgs.jq ];
+      text = ''
+        NAS=$(tailscale status --json | jq -r '.Peer | map(select(.HostName == "tahoe"))[0].TailscaleIPs[0]')
+
+        RESTIC_REPOSITORY="sftp:''${NAS}:/$(hostname)"
+        export RESTIC_REPOSITORY
+        export RESTIC_PASSWORD_FILE=/run/agenix/restic/repo-users
+
+        sudo -E restic -o sftp.command="ssh backup@''${NAS} -i /run/agenix/restic/ssh-key -s sftp" "$@"
+      '';
+    };
+
+  album-to-nas = pkgs.writeShellApplication {
+    name = "album-to-nas";
+    runtimeInputs = [ pkgs.jq pkgs.tailscale ];
+    text = ''
+      ALBUM_PATH="$1"
+
+      NAS=$(tailscale status --json | jq -r '.Peer | map(select(.HostName == "tahoe"))[0].TailscaleIPs[0]')
+
+      scp "$ALBUM_PATH" "$NAS:~/import/album.zip"
+      ssh "$NAS" bc-to-beet ~/import/album.zip
+    '';
+  };
+in
+{
+  imports = [
+    ./alacritty.nix
+    ./dev.nix
+    ./emacs.nix
+    ./firefox.nix
+    ./tmux.nix
+    ./yubikey.nix
+    ./ytdlp.nix
+  ];
+
+  home.packages = with pkgs; [
+    # media
+    gnome3.eog
+    gnome3.evince
+    sublime-music
+    vlc
+    yt-dlp
+
+    passage
+    tree
+
+    # scanning
+    tesseract
+    imagemagick
+    exiftool
+    sane-airscan
+
+    transmission-remote-gtk
+
+    # custom tools
+    album-to-nas
+    restic-nas
+
+    # tools from external repositories
+    # x509-info
+    # gh-ssh-keys
+    # masked-emails
+  ];
+
+  programs.feh.enable = true;
+  programs.mpv = {
+    enable = true;
+    config = {
+      sub-auto = "fuzzy";
+      vo = "gpu";
+      hwdec = "auto-safe";
+      gpu-context = "wayland";
+      audio-display = "no";
+      cache-pause = "no";
+      cache = "yes";
+      mute = "no";
+      osc = "yes";
+      screenshot-directory = "~/documents/screenshots/mpv-screenshots/";
+      screenshot-format = "png";
+    };
+    scripts = lib.attrVals [ "sponsorblock" ] pkgs.mpvScripts;
+  };
+
+  services.gammastep = {
+    enable = true;
+    #TODO: this needs to come from locale.nix
+    latitude = 37.8715;
+    longitude = -122.273;
+    temperature = {
+      day = 5000;
+      night = 3700;
+    };
+  };
+
+  home.sessionVariables = {
+    PASSAGE_DIR = "${config.xdg.dataHome}/passage/store";
+    PASSAGE_IDENTITIES_FILE = "${config.xdg.dataHome}/passage/identities";
+    # for now I have to default to rage, as the version of age is
+    # not recent enough to work with keys generated by
+    # age-plugin-yubikey
+    PASSAGE_AGE = "${pkgs.rage}/bin/rage";
+  };
+
+
+  # enable bluetooth
+  services.blueman-applet.enable = true;
+}
diff --git a/home/profiles/ytdlp.nix b/home/profiles/ytdlp.nix
new file mode 100644
index 0000000..ce2e32c
--- /dev/null
+++ b/home/profiles/ytdlp.nix
@@ -0,0 +1,37 @@
+{ pkgs, ... }:
+{
+  home.packages = with pkgs; [
+    yt-dlp
+  ];
+
+  xdg.configFile."yt-dlp/config".source = pkgs.writeText "config" ''
+    # Preferred formats:
+    # 1. 1080p, combined, mp4 (for some non-youtube sites).
+    # 2. 1080p, combined, any format (in case mp4 is not available).
+    # 3. 1080p, best video + best audio (only available with separate video and audio on youtube).
+    # 4. >30fps (any resolution), best video + best audio (only available with separate video and audio on youtube).
+    # 5. 720p, pre-joined, because it is available on youtube.
+    # 6. <720p, best video + best audio (480p and some other lower resolutions are only available with separate video and audio on youtube).
+    # 7. When all else fails, take whatever youtube-dl thinks is the best (mainly for non-YT websites).
+    --format="best[height=1080][ext=mp4]/best[height=1080]/bestvideo[height=1080][ext=mp4]+bestaudio[ext=m4a]/bestvideo[fps>30][ext=mp4]+bestaudio[ext=m4a]/best[height=720][ext=mp4]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best"
+
+    --continue
+
+    --sub-langs all
+    --write-subs
+
+    --convert-subs=srt
+
+    --restrict-filenames
+    --output="$HOME/media/videos/%(uploader)s/%(playlist)s/%(upload_date)s-%(title)s.%(ext)s"
+    --merge-output-format mkv
+
+    --embed-metadata
+    --embed-chapters
+    --embed-info-json
+    # create chapter entries to mark sponsor segments
+    --sponsorblock-mark all
+
+    --yes-playlist
+  '';
+}
diff --git a/home/profiles/yubikey.nix b/home/profiles/yubikey.nix
new file mode 100644
index 0000000..b18ce5d
--- /dev/null
+++ b/home/profiles/yubikey.nix
@@ -0,0 +1,28 @@
+{ pkgs, lib, config, ... }:
+{
+  home.packages = with pkgs; [ yubikey-manager yubikey-touch-detector ];
+
+  systemd.user.sockets.yubikey-touch-detector = {
+    Unit.Description = "Unix socket activation for YubiKey touch detector service";
+    Socket = {
+      ListenStream = "%t/yubikey-touch-detector.socket";
+      RemoveOnStop = true;
+    };
+    Install.WantedBy = [ "sockets.target" ];
+  };
+
+  systemd.user.services.yubikey-touch-detector = {
+    Unit = {
+      Description = "Detects when your YubiKey is waiting for a touch";
+      Requires = "yubikey-touch-detector.socket";
+    };
+    Service = {
+      ExecStart = "${pkgs.yubikey-touch-detector}/bin/yubikey-touch-detector --libnotify";
+      EnvironmentFile = "-%E/yubikey-touch-detector/service.conf";
+    };
+    Install = {
+      Also = "yubikey-touch-detector.socket";
+      WantedBy = [ "default.target" ];
+    };
+  };
+}
diff --git a/home/profiles/zsh/completion-style.zsh b/home/profiles/zsh/completion-style.zsh
new file mode 100644
index 0000000..79a4e68
--- /dev/null
+++ b/home/profiles/zsh/completion-style.zsh
@@ -0,0 +1,42 @@
+# 'ctrl-x r' will complete the 12 last modified (mtime) files/directories
+zle -C newest-files menu-complete _generic
+# Use "*newest-files" so that it matches both "newest-files" and
+# "load-completion-and-newest-files".
+zstyle ':completion:*newest-files:*' completer _files
+zstyle ':completion:*newest-files:*' file-patterns '*(omN[1,12])'
+zstyle ':completion:*newest-files:*' menu select yes
+zstyle ':completion:*newest-files:*' sort false
+zstyle ':completion:*newest-files:*' matcher-list 'b:=*' # important
+
+# colors for zsh file name completion
+zmodload zsh/complist
+zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
+
+# Show a prompt on selection
+zstyle ':completion:*' select-prompt '%SScrolling active: current selection at %p%s'
+
+# Use arrow keys in completion list
+zstyle ':completion:*' menu select
+
+# Group results by category
+zstyle ':completion:*' group-name ''
+
+# Keep directories and files separated
+zstyle ':completion:*' list-dirs-first true
+
+# match uppercase from lowercase
+zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
+
+# Filename suffixes to ignore during completion (except after rm command)
+zstyle ':completion:*:*:(^rm):*:*files' ignored-patterns '*?.old'
+
+# command for process lists, the local web server details and host completion
+# on processes completion complete all user processes
+zstyle ':completion:*:processes' command 'ps -au$USER'
+
+# Completion formatting and messages
+zstyle ':completion:*' verbose yes
+zstyle ':completion:*:descriptions' format '%B%d%b'
+zstyle ':completion:*:messages' format '%d'
+zstyle ':completion:*:warnings' format 'No matches for: %d'
+zstyle ':completion:*:corrections' format '%B%d (errors: %e)%b'
diff --git a/home/profiles/zsh/default.nix b/home/profiles/zsh/default.nix
new file mode 100644
index 0000000..38450ab
--- /dev/null
+++ b/home/profiles/zsh/default.nix
@@ -0,0 +1,51 @@
+{ config, pkgs, lib, ... }:
+{
+  home.packages = with pkgs; [ zsh-completions ];
+
+  programs.zsh = {
+    enable = true;
+    dotDir = ".config/zsh";
+
+    enableCompletion = true;
+    enableAutosuggestions = true;
+
+    history = {
+      size = 500000;
+      save = 500000;
+      extended = false;
+      ignoreSpace = true;
+      ignoreDups = true;
+      share = false;
+      # see
+      # https://github.com/nix-community/home-manager/blob/32a7da69dc53c9eb5ad0675eb7fdc58f7fe35272/modules/programs/zsh.nix#L537
+      path = "${config.xdg.dataHome}/zsh/zsh_history";
+    };
+
+    localVariables = {
+      # Print timing statistics for everything which takes longer than 5 seconds of
+      # user + system time.
+      REPORTTIME = 5;
+    };
+
+    shellAliases = {
+      ll = "ls -l --color=auto";
+      lt = "ls -ltrh --color=auto";
+      la = "ls -ltrha --color=auto";
+      pkgsearch = "nix search nixpkgs";
+      hms = "home-manager switch --flake .";
+      nr = "sudo nixos-rebuild switch --flake .";
+      flup = "nix flake update --commit-lock-file";
+      dhcp-leasese = "xdg-open http://192.168.6.1:8067/";
+    };
+
+    defaultKeymap = "emacs";
+
+    initExtra = lib.concatMapStrings builtins.readFile [
+      ./completion-style.zsh
+      ./options.zsh
+      ./prompt.zsh
+      ./tmux.zsh
+      ./new-go-project.zsh
+    ];
+  };
+}
diff --git a/home/profiles/zsh/new-go-project.zsh b/home/profiles/zsh/new-go-project.zsh
new file mode 100644
index 0000000..0b96a34
--- /dev/null
+++ b/home/profiles/zsh/new-go-project.zsh
@@ -0,0 +1,19 @@
+new-go-project() {
+  local project_name=$1
+
+  echo "> creating ${project_name}"
+  cd ~/workspace/
+  mkdir $project_name
+  cd $project_name
+
+  echo "> initializing the git repository"
+  git init .
+
+  echo "> setting the default template for go projects"
+  nix flake init -t ~/workspace/world/templates#go
+  direnv allow
+
+  echo "> creating initial commit, touch your yubikey"
+  git add .
+  git commit -m 'initial commit'
+}
diff --git a/home/profiles/zsh/options.zsh b/home/profiles/zsh/options.zsh
new file mode 100644
index 0000000..6d39bc1
--- /dev/null
+++ b/home/profiles/zsh/options.zsh
@@ -0,0 +1,27 @@
+# Show an error when a globbing expansion doesn't find any match
+setopt nomatch
+
+# List on ambiguous completion and Insert first match immediately
+setopt autolist menucomplete
+
+# Use pushd when cd-ing around
+setopt autopushd pushdminus pushdsilent
+
+# Use single quotes in string without the weird escape tricks
+setopt rcquotes
+
+# Single word commands can resume an existing job
+setopt autoresume
+
+# Append commands to history as they are exectuted
+setopt inc_append_history_time
+
+# Remove useless whitespace from commands
+setopt hist_reduce_blanks
+
+# Those options aren't wanted
+unsetopt beep extendedglob notify
+
+# word select works like in bash
+autoload -U select-word-style
+select-word-style bash
diff --git a/home/profiles/zsh/prompt.zsh b/home/profiles/zsh/prompt.zsh
new file mode 100644
index 0000000..8a3efa9
--- /dev/null
+++ b/home/profiles/zsh/prompt.zsh
@@ -0,0 +1,17 @@
+setopt prompt_subst
+
+autoload -Uz vcs_info
+
+# display the name of the branch
+zstyle ':vcs_info:git*' formats " [%b]"
+zstyle ':vcs_info:*' enable git
+
+precmd () { vcs_info }
+PROMPT='%m%f:%F{green}%~%f%F{yellow}$vcs_info_msg_0_ %F{reset}'
+
+# For tramp (emacs).
+if [ "$TERM" = "dumb" ]; then
+  unset PROMPT
+  PS1='$ '
+  unsetopt zle
+fi
diff --git a/home/profiles/zsh/tmux.zsh b/home/profiles/zsh/tmux.zsh
new file mode 100644
index 0000000..97944f5
--- /dev/null
+++ b/home/profiles/zsh/tmux.zsh
@@ -0,0 +1,9 @@
+# If we're not in an ssh connection, and tmux is installed, and we're
+# not already in a tmux session, attach to the session named
+# 'default', and if the session does not exist, start one named
+# 'default'
+if [ -z "$SSH_CONNECTION" ]; then
+  if command -v tmux &> /dev/null && [ -z "$TMUX" ]; then
+    tmux attach -t default || tmux new -s default
+  fi
+fi