initial refactoring

This commit is contained in:
Noah Masur
2025-01-20 22:35:40 -05:00
parent a4b5e05f8f
commit c7933f8502
209 changed files with 5998 additions and 5308 deletions

View File

@ -1,9 +0,0 @@
{ ... }:
{
# AWS settings require this
permitRootLogin = "prohibit-password";
# Make sure disk size is large enough
# https://github.com/nix-community/nixos-generators/issues/150
amazonImage.sizeMB = 16 * 1024;
}

View File

@ -1,41 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
_1password = {
enable = lib.mkEnableOption {
description = "Enable 1Password.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config._1password.enable) {
unfreePackages = [
"1password"
"_1password-gui"
"1password-cli"
];
home-manager.users.${config.user} = {
home.packages = [
pkgs._1password-cli
] ++ (if pkgs.stdenv.isLinux then [ pkgs._1password-gui ] else [ ]);
};
# https://1password.community/discussion/135462/firefox-extension-does-not-connect-to-linux-app
# On Mac, does not apply: https://1password.community/discussion/142794/app-and-browser-integration
# However, the button doesn't work either:
# https://1password.community/discussion/140735/extending-support-for-trusted-web-browsers
environment.etc."1password/custom_allowed_browsers".text = ''
${
config.home-manager.users.${config.user}.programs.firefox.package
}/Applications/Firefox.app/Contents/MacOS/firefox
firefox
'';
};
}

View File

@ -1,107 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
alacritty = {
enable = lib.mkEnableOption {
description = "Enable Alacritty.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.alacritty.enable) {
home-manager.users.${config.user} = {
xsession.windowManager.i3.config.terminal = "alacritty";
programs.rofi.terminal = "${pkgs.alacritty}/bin/alacritty";
programs.alacritty = {
enable = true;
settings = {
window = {
dimensions = {
columns = 85;
lines = 30;
};
padding = {
x = 20;
y = 20;
};
opacity = 1.0;
};
scrolling.history = 10000;
font = {
size = 14.0;
};
key_bindings = [
# Used for word completion in fish_user_key_bindings
{
key = "Return";
mods = "Shift";
chars = "\\x1F";
}
# Used for searching nixpkgs in fish_user_key_bindings
{
key = "N";
mods = "Control|Shift";
chars = "\\x11F";
}
{
key = "H";
mods = "Control|Shift";
mode = "~Vi";
action = "ToggleViMode";
}
{
key = "Return";
mode = "Vi";
action = "ToggleViMode";
}
# Used to enable $ keybind in Vi mode
{
key = 5; # Scancode for key4
mods = "Shift";
mode = "Vi|~Search";
action = "Last";
}
];
colors = {
primary = {
background = config.theme.colors.base00;
foreground = config.theme.colors.base05;
};
cursor = {
text = "#1d2021";
cursor = config.theme.colors.base05;
};
normal = {
black = "#1d2021";
red = config.theme.colors.base08;
green = config.theme.colors.base0B;
yellow = config.theme.colors.base0A;
blue = config.theme.colors.base0D;
magenta = config.theme.colors.base0E;
cyan = config.theme.colors.base0C;
white = config.theme.colors.base05;
};
bright = {
black = config.theme.colors.base03;
red = config.theme.colors.base09;
green = config.theme.colors.base01;
yellow = config.theme.colors.base02;
blue = config.theme.colors.base04;
magenta = config.theme.colors.base06;
cyan = config.theme.colors.base0F;
white = config.theme.colors.base07;
};
};
draw_bold_text_with_bright_colors = false;
};
};
};
};
}

View File

@ -1,17 +0,0 @@
{ ... }:
{
imports = [
./1password.nix
./alacritty.nix
./discord.nix
./firefox.nix
./kitty.nix
./media.nix
./obsidian.nix
./qbittorrent.nix
./slack.nix
./wezterm.nix
./yt-dlp.nix
];
}

View File

@ -1,34 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
discord = {
enable = lib.mkEnableOption {
description = "Enable Discord.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.discord.enable) {
unfreePackages = [ "discord" ];
environment.systemPackages = [ pkgs.discord ];
home-manager.users.${config.user} = {
xdg.configFile."discord/settings.json".text = ''
{
"BACKGROUND_COLOR": "#202225",
"IS_MAXIMIZED": false,
"IS_MINIMIZED": false,
"OPEN_ON_STARTUP": false,
"MINIMIZE_TO_TRAY": false,
"SKIP_HOST_UPDATE": true
}
'';
};
};
}

View File

@ -1,203 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
firefox = {
enable = lib.mkEnableOption {
description = "Enable Firefox.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.firefox.enable) {
unfreePackages = [
(lib.mkIf config._1password.enable "onepassword-password-manager")
"okta-browser-plugin"
];
home-manager.users.${config.user} = {
programs.firefox = {
enable = true;
package = if pkgs.stdenv.isDarwin then pkgs.firefox-bin else pkgs.firefox;
profiles.default = {
id = 0;
name = "default";
isDefault = true;
# https://nur.nix-community.org/repos/rycee/
extensions = with pkgs.nur.repos.rycee.firefox-addons; [
(lib.mkIf config._1password.enable onepassword-password-manager)
darkreader
don-t-fuck-with-paste
facebook-container
markdownload
multi-account-containers
okta-browser-plugin
reddit-enhancement-suite
return-youtube-dislikes
sponsorblock
ublock-origin
ublacklist
vimium
];
settings = {
"app.update.auto" = false;
"browser.aboutConfig.showWarning" = false;
"browser.warnOnQuit" = false;
"browser.quitShortcut.disabled" = if pkgs.stdenv.isLinux then true else false;
"browser.theme.dark-private-windows" = true;
"browser.toolbars.bookmarks.visibility" = false;
"browser.startup.page" = 3; # Restore previous session
"browser.newtabpage.enabled" = false; # Make new tabs blank
"trailhead.firstrun.didSeeAboutWelcome" = true; # Disable welcome splash
"dom.forms.autocomplete.formautofill" = false; # Disable autofill
"extensions.formautofill.creditCards.enabled" = false; # Disable credit cards
"dom.payments.defaults.saveAddress" = false; # Disable address save
"general.autoScroll" = true; # Drag middle-mouse to scroll
"services.sync.prefs.sync.general.autoScroll" = false; # Prevent disabling autoscroll
"extensions.pocket.enabled" = false;
"toolkit.legacyUserProfileCustomizations.stylesheets" = true; # Allow userChrome.css
"layout.css.color-mix.enabled" = true;
"ui.systemUsesDarkTheme" = if config.theme.dark == true then 1 else 0;
"media.ffmpeg.vaapi.enabled" = true; # Enable hardware video acceleration
"cookiebanners.ui.desktop.enabled" = true; # Reject cookie popups
"devtools.command-button-screenshot.enabled" = true; # Scrolling screenshot of entire page
"svg.context-properties.content.enabled" = true; # Sidebery styling
"browser.tabs.hoverPreview.enabled" = false; # Disable tab previews
"browser.tabs.hoverPreview.showThumbnails" = false; # Disable tab previews
};
userChrome = ''
:root {
--focus-outline-color: ${config.theme.colors.base04} !important;
--toolbar-color: ${config.theme.colors.base07} !important;
--tab-min-height: 30px !important;
}
/* Background of tab bar */
.toolbar-items {
background-color: ${config.theme.colors.base00} !important;
}
/* Extra tab bar sides on macOS */
.titlebar-spacer {
background-color: ${config.theme.colors.base00} !important;
}
.titlebar-buttonbox-container {
background-color: ${config.theme.colors.base00} !important;
}
#tabbrowser-tabs {
border-inline-start: 0 !important;
}
/* Private Browsing indicator on macOS */
#private-browsing-indicator-with-label {
background-color: ${config.theme.colors.base00} !important;
margin-inline: 0 !important;
padding-inline: 7px;
}
/* Tabs themselves */
.tabbrowser-tab .tab-stack {
border-radius: 5px 5px 0 0;
overflow: hidden;
background-color: ${config.theme.colors.base00};
color: ${config.theme.colors.base06} !important;
}
.tab-content {
border-bottom: 2px solid color-mix(in srgb, var(--identity-tab-color) 40%, transparent);
border-radius: 5px 5px 0 0;
background-color: ${config.theme.colors.base00};
color: ${config.theme.colors.base06} !important;
}
.tab-content[selected] {
border-bottom: 2px solid color-mix(in srgb, var(--identity-tab-color) 25%, transparent);
background-color: ${config.theme.colors.base01} !important;
color: ${config.theme.colors.base07} !important;
}
/* Below tab bar */
#nav-bar {
background: ${config.theme.colors.base01} !important;
}
/* URL bar in nav bar */
#urlbar[focused=true] {
color: ${config.theme.colors.base07} !important;
background: ${config.theme.colors.base02} !important;
caret-color: ${config.theme.colors.base05} !important;
}
#urlbar:not([focused=true]) {
color: ${config.theme.colors.base04} !important;
background: ${config.theme.colors.base02} !important;
}
#urlbar ::-moz-selection {
color: ${config.theme.colors.base07} !important;
background: ${config.theme.colors.base02} !important;
}
#urlbar-input-container {
border: 1px solid ${config.theme.colors.base01} !important;
}
#urlbar-background {
background: ${config.theme.colors.base01} !important;
}
/* Text in URL bar */
#urlbar-input, #urlbar-scheme, .searchbar-textbox {
color: ${config.theme.colors.base07} !important;
}
'';
userContent = ''
@-moz-document url-prefix(about:blank) {
* {
background-color:${config.theme.colors.base01} !important;
}
}
'';
extraConfig = "";
};
};
# Mimic nixpkgs package environment for read-only profiles.ini management
# From: https://github.com/booxter/home-manager/commit/dd1602e306fec366280f5953c5e1b553e3d9672a
home.sessionVariables = {
MOZ_LEGACY_PROFILES = 1;
MOZ_ALLOW_DOWNGRADE = 1;
};
# launchd.user.envVariables = config.home-manager.users.${config.user}.home.sessionVariables;
xdg.mimeApps = {
associations.added = {
"text/html" = [ "firefox.desktop" ];
};
defaultApplications = {
"text/html" = [ "firefox.desktop" ];
};
associations.removed = {
"text/html" = [ "wine-extension-htm.desktop" ];
};
};
xsession.windowManager.i3.config.keybindings = lib.mkIf pkgs.stdenv.isLinux {
"${
config.home-manager.users.${config.user}.xsession.windowManager.i3.config.modifier
}+Shift+b" = "exec ${
# Don't name the script `firefox` or it will affect grep
builtins.toString (
pkgs.writeShellScript "focus-ff.sh" ''
count=$(ps aux | grep -c firefox)
if [ "$count" -eq 1 ]; then
i3-msg "exec --no-startup-id firefox"
sleep 0.5
fi
i3-msg "[class=firefox] focus"
''
)
}";
};
};
};
}

View File

@ -1,118 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
kitty = {
enable = lib.mkEnableOption {
description = "Enable Kitty.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.kitty.enable) {
# Set the Rofi-Systemd terminal for viewing logs
# Using optionalAttrs because only available in NixOS
environment =
{ }
// lib.attrsets.optionalAttrs (builtins.hasAttr "sessionVariables" config.environment) {
sessionVariables.ROFI_SYSTEMD_TERM = lib.mkDefault "${pkgs.kitty}/bin/kitty";
};
home-manager.users.${config.user} = {
# Set the i3 terminal
xsession.windowManager.i3.config.terminal = lib.mkIf pkgs.stdenv.isLinux "kitty";
# Set the Rofi terminal for running programs
programs.rofi.terminal = lib.mkIf pkgs.stdenv.isLinux (lib.mkDefault "${pkgs.kitty}/bin/kitty");
# Display images in the terminal
programs.fish.interactiveShellInit = # fish
''
if test "$TERM" = "xterm-kitty"
alias icat="kitty +kitten icat"
alias ssh="kitty +kitten ssh"
end
'';
programs.kitty = {
enable = true;
environment = { };
extraConfig = "";
font.size = 14;
keybindings = {
# Use shift+enter to complete text suggestions in fish
"shift+enter" = "send_text all \\x1F";
# Easy fullscreen toggle (for macOS)
"super+f" = "toggle_fullscreen";
};
settings = {
# Colors (adapted from: https://github.com/kdrag0n/base16-kitty/blob/master/templates/default-256.mustache)
background = config.theme.colors.base00;
foreground = config.theme.colors.base05;
selection_background = config.theme.colors.base05;
selection_foreground = config.theme.colors.base00;
url_color = config.theme.colors.base04;
cursor = config.theme.colors.base05;
active_border_color = config.theme.colors.base03;
inactive_border_color = config.theme.colors.base01;
active_tab_background = config.theme.colors.base00;
active_tab_foreground = config.theme.colors.base05;
inactive_tab_background = config.theme.colors.base01;
inactive_tab_foreground = config.theme.colors.base04;
tab_bar_background = config.theme.colors.base01;
# normal
color0 = config.theme.colors.base00;
color1 = config.theme.colors.base08;
color2 = config.theme.colors.base0B;
color3 = config.theme.colors.base0A;
color4 = config.theme.colors.base0D;
color5 = config.theme.colors.base0E;
color6 = config.theme.colors.base0C;
color7 = config.theme.colors.base05;
# bright
color8 = config.theme.colors.base03;
color9 = config.theme.colors.base08;
color10 = config.theme.colors.base0B;
color11 = config.theme.colors.base0A;
color12 = config.theme.colors.base0D;
color13 = config.theme.colors.base0E;
color14 = config.theme.colors.base0C;
color15 = config.theme.colors.base07;
# extended base16 colors
color16 = config.theme.colors.base09;
color17 = config.theme.colors.base0F;
color18 = config.theme.colors.base01;
color19 = config.theme.colors.base02;
color20 = config.theme.colors.base04;
color21 = config.theme.colors.base06;
# Scrollback
scrollback_lines = 10000;
scrollback_pager_history_size = 300; # MB
# Window
window_padding_width = 6;
tab_bar_edge = "top";
tab_bar_style = "slant";
# Disable audio
enable_audio_bell = false;
};
};
};
};
}

View File

@ -1,67 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
media = {
enable = lib.mkEnableOption {
description = "Enable media programs.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.media.enable) {
home-manager.users.${config.user} = {
home.packages = with pkgs; [
nsxiv # Image viewer
mupdf # PDF viewer
zathura # PDF viewer
];
# Video player
programs.mpv = {
enable = true;
bindings = { };
config = {
image-display-duration = 2; # For cycling through images
hwdec = "auto-safe"; # Attempt to use GPU decoding for video
};
scripts = [
# Automatically load playlist entries before and after current file
pkgs.mpvScripts.autoload
# Delete current file after quitting
pkgs.mpvScripts.mpv-delete-file
];
};
# Set default programs for opening PDFs and other media
xdg.mimeApps = {
associations.added = {
"application/pdf" = [ "pwmt.zathura-cb.desktop" ];
"image/jpeg" = [ "nsxiv.desktop" ];
"image/png" = [ "nsxiv.desktop" ];
"image/*" = [ "nsxiv.desktop" ];
};
associations.removed = {
"application/pdf" = [
"mupdf.desktop"
"wine-extension-pdf.desktop"
];
};
defaultApplications = {
"application/pdf" = [ "pwmt.zathura-cb.desktop" ];
"image/jpeg" = [ "nsxiv.desktop" ];
"image/png" = [ "nsxiv.desktop" ];
"image/*" = [ "nsxiv.desktop" ];
};
};
};
};
}

View File

@ -1,28 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
obsidian = {
enable = lib.mkEnableOption {
description = "Enable Obsidian.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.obsidian.enable) {
unfreePackages = [ "obsidian" ];
home-manager.users.${config.user} = {
home.packages = with pkgs; [ obsidian ];
};
# Broken on 2023-12-11
# https://forum.obsidian.md/t/electron-25-is-now-eol-please-upgrade-to-a-newer-version/72878/8
insecurePackages = [ "electron-25.9.0" ];
};
}

View File

@ -1,25 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
qbittorrent = {
enable = lib.mkEnableOption {
description = "Enable qBittorrent.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.qbittorrent.enable) {
home-manager.users.${config.user} = {
home.packages = with pkgs; [ qbittorrent ];
};
};
}

View File

@ -1,26 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
slack = {
enable = lib.mkEnableOption {
description = "Enable Slack.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.slack.enable) {
unfreePackages = [ "slack" ];
home-manager.users.${config.user} = {
home.packages = with pkgs; [ slack ];
};
};
# Theme string: #1D2122,#665C54,#8EC07C,#BDAE93
}

View File

@ -1,286 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
wezterm = {
enable = lib.mkEnableOption {
description = "Enable WezTerm terminal.";
default = false;
};
};
};
config =
let
font = config.home-manager.users.${config.user}.programs.kitty.font.name;
in
lib.mkIf (config.gui.enable && config.wezterm.enable) {
# Set the Rofi-Systemd terminal for viewing logs
# Using optionalAttrs because only available in NixOS
environment =
{ }
// lib.attrsets.optionalAttrs (builtins.hasAttr "sessionVariables" config.environment) {
sessionVariables.ROFI_SYSTEMD_TERM = "${pkgs.wezterm}/bin/wezterm";
};
terminal = "${pkgs.wezterm}/bin/wezterm";
terminalLaunchCommand = "${config.terminal} start --";
home-manager.users.${config.user} = {
# Set the i3 terminal
xsession.windowManager.i3.config.terminal = lib.mkIf pkgs.stdenv.isLinux "wezterm";
# Display images in the terminal
programs.fish.shellAliases = {
icat = lib.mkForce "wezterm imgcat";
};
programs.wezterm = {
enable = true;
colorSchemes = {
myTheme = {
background = config.theme.colors.base00;
foreground = config.theme.colors.base05;
cursor_bg = config.theme.colors.base05;
cursor_fg = config.theme.colors.base00;
cursor_border = config.theme.colors.base05;
selection_bg = config.theme.colors.base05;
selection_fg = config.theme.colors.base00;
scrollbar_thumb = config.theme.colors.base03;
ansi = [
config.theme.colors.base01 # black
config.theme.colors.base0F # maroon
config.theme.colors.base0B # green
config.theme.colors.base0A # olive
config.theme.colors.base0D # navy
config.theme.colors.base0E # purple
config.theme.colors.base0C # teal
config.theme.colors.base06 # silver
];
brights = [
config.theme.colors.base03 # grey
config.theme.colors.base08 # red
config.theme.colors.base0B # lime
config.theme.colors.base0A # yellow
config.theme.colors.base0D # blue
config.theme.colors.base0E # fuchsia
config.theme.colors.base0C # aqua
config.theme.colors.base07 # white
];
compose_cursor = config.theme.colors.base09; # orange
copy_mode_active_highlight_bg = {
Color = config.theme.colors.base03;
};
copy_mode_active_highlight_fg = {
Color = config.theme.colors.base07;
};
copy_mode_inactive_highlight_bg = {
Color = config.theme.colors.base02;
};
copy_mode_inactive_highlight_fg = {
Color = config.theme.colors.base06;
};
quick_select_label_bg = {
Color = config.theme.colors.base02;
};
quick_select_label_fg = {
Color = config.theme.colors.base06;
};
quick_select_match_bg = {
Color = config.theme.colors.base03;
};
quick_select_match_fg = {
Color = config.theme.colors.base07;
};
};
};
extraConfig = ''
local wezterm = require("wezterm")
local config = wezterm.config_builder()
config.check_for_updates = false
config.color_scheme = "myTheme"
-- Scrollback
config.scrollback_lines = 10000
-- Window
config.window_padding = {
left = 10,
right = 10,
top = 10,
bottom = 12,
}
config.font = wezterm.font('${font}', { weight = 'Bold'})
config.font_size = ${if pkgs.stdenv.isLinux then "14.0" else "18.0"}
-- Fix color blocks instead of text
config.front_end = "WebGpu"
-- Tab Bar
config.hide_tab_bar_if_only_one_tab = true
config.window_frame = {
font = wezterm.font('${font}', { weight = 'Bold'}),
font_size = ${if pkgs.stdenv.isLinux then "12.0" else "16.0"},
}
config.colors = {
tab_bar = {
active_tab = {
bg_color = '${config.theme.colors.base00}',
fg_color = '${config.theme.colors.base04}',
},
},
}
-- Disable audio
config.audible_bell = "Disabled"
config.initial_rows = 80
config.initial_cols = 200
config.unix_domains = {
{
name = 'unix',
},
}
-- This causes `wezterm` to act as though it was started as
-- `wezterm connect unix` by default, connecting to the unix
-- domain on startup.
-- If you prefer to connect manually, leave out this line.
config.default_gui_startup_args = { 'connect', 'unix' }
config.leader = {
key = 'a',
mods = 'CTRL',
timeout_milliseconds = 2000,
}
config.keys = {
-- Attach to muxer
{
key = 'a',
mods = 'LEADER',
action = wezterm.action.AttachDomain 'unix',
},
-- Detach from muxer
{
key = 'd',
mods = 'LEADER',
action = wezterm.action.DetachDomain { DomainName = 'unix' },
},
-- sends completion string for fish autosuggestions
{
key = 'Enter',
mods = 'SHIFT',
action = wezterm.action.SendString '\x1F'
},
-- ctrl-shift-h was "hide"
{
key = 'H',
mods = 'SHIFT|CTRL',
action = wezterm.action.DisableDefaultAssignment
},
-- alt-enter was "fullscreen"
{
key = 'Enter',
mods = 'ALT',
action = wezterm.action.DisableDefaultAssignment
},
-- make super-f "fullscreen"
{
key = 'f',
mods = 'SUPER',
action = wezterm.action.ToggleFullScreen
},
-- super-t open new tab in new dir
{
key = 't',
mods = ${if pkgs.stdenv.isDarwin then "'SUPER'" else "'ALT'"},
action = wezterm.action.SpawnCommandInNewTab {
cwd = wezterm.home_dir,
},
},
-- shift-super-t open new tab in same dir
{
key = 't',
mods = 'SUPER|SHIFT',
action = wezterm.action.SpawnTab 'CurrentPaneDomain'
},
-- project switcher
{
key = 'P',
mods = 'SUPER',
action = wezterm.action_callback(function(window, pane)
local choices = {}
wezterm.log_info "working?"
function scandir(directory)
local i, t, popen = 0, {}, io.popen
local pfile = popen('${pkgs.fd}/bin/fd --search-path "'..directory..'" --type directory --exact-depth 2 | ${pkgs.proximity-sort}/bin/proximity-sort "'..os.getenv("HOME").."/dev/work"..'"')
for filename in pfile:lines() do
i = i + 1
t[i] = filename
end
pfile:close()
return t
end
for _, v in pairs(scandir(os.getenv("HOME").."/dev")) do
table.insert(choices, { label = v })
end
window:perform_action(
wezterm.action.InputSelector {
action = wezterm.action_callback(function(window, pane, id, label)
if not id and not label then
wezterm.log_info "cancelled"
else
window:perform_action(
wezterm.action.SpawnCommandInNewTab {
cwd = label,
},
pane
)
end
end),
fuzzy = true,
title = "Select Project",
choices = choices,
},
pane
)
end),
},
}
-- print the workspace name at the upper right
wezterm.on("update-right-status", function(window, pane)
window:set_right_status(window:active_workspace())
end)
-- load plugin
local workspace_switcher = wezterm.plugin.require("https://github.com/MLFlexer/smart_workspace_switcher.wezterm")
-- set path to zoxide
workspace_switcher.zoxide_path = "${pkgs.zoxide}/bin/zoxide"
-- keymaps
table.insert(config.keys, { key = "s", mods = "CTRL|SHIFT", action = workspace_switcher.switch_workspace() })
-- table.insert(config.keys, { key = "t", mods = "CTRL|SHIFT", action = wezterm.action.ShowLauncherArgs({ flags = "FUZZY|WORKSPACES" }) })
table.insert(config.keys, { key = "[", mods = "CTRL|SHIFT", action = wezterm.action.SwitchWorkspaceRelative(1) })
table.insert(config.keys, { key = "]", mods = "CTRL|SHIFT", action = wezterm.action.SwitchWorkspaceRelative(-1) })
return config
'';
};
};
};
}

View File

@ -1,38 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
yt-dlp = {
enable = lib.mkEnableOption {
description = "Enable YouTube downloader.";
default = false;
};
};
};
config = lib.mkIf (config.yt-dlp.enable) {
home-manager.users.${config.user} = {
programs.yt-dlp = {
enable = true;
extraConfig = "";
settings = {
no-continue = true; # Always re-download each fragment
no-overwrites = true; # Don't overwrite existing files
download-archive = "archive.log"; # Log of archives
embed-metadata = true;
embed-thumbnail = true;
embed-subs = true;
sub-langs = "en.*";
concurrent-fragments = 4; # Parallel download chunks
};
};
programs.fish.shellAbbrs.yt = "yt-dlp";
};
};
}

View File

@ -1,220 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options.mail.aerc.enable = lib.mkEnableOption "Aerc email.";
config = lib.mkIf config.mail.aerc.enable {
home-manager.users.${config.user} = {
home.packages = with pkgs; [
w3m # Render HTML
dante # Socksify for rendering HTML
];
programs.aerc = {
enable = true;
extraBinds = {
# Binds are of the form <key sequence> = <command to run>
# To use '=' in a key sequence, substitute it with "Eq": "<Ctrl+Eq>"
# If you wish to bind #, you can wrap the key sequence in quotes: "#" = quit
global = {
"<C-p>" = ":prev-tab<Enter>";
"<C-n>" = ":next-tab <Enter>";
"<C-t>" = ":term<Enter>";
"?" = ":help keys<Enter>";
};
messages = {
q = ":quit<Enter>";
j = ":next <Enter>";
"<Down>" = ":next<Enter>";
"<C-d>" = ":next 50%<Enter>";
"<C-f>" = ":next 100%<Enter>";
"<PgDn>" = ":next 100%<Enter>";
k = ":prev <Enter>";
"<Up>" = ":prev<Enter>";
"<C-u>" = ":prev 50%<Enter>";
"<C-b>" = ":prev 100%<Enter>";
"<PgUp>" = ":prev 100%<Enter>";
g = ":select 0 <Enter>";
G = ":select -1<Enter>";
J = ":next-folder <Enter>";
K = ":prev-folder<Enter>";
H = ":collapse-folder<Enter>";
L = ":expand-folder<Enter>";
v = ":mark -t<Enter>";
V = ":mark -v<Enter>";
T = ":toggle-threads<Enter>";
"<Enter>" = ":view<Enter>";
d = ":prompt 'Really delete this message?' 'delete-message'<Enter>";
D = ":move Trash<Enter>";
A = ":archive flat<Enter>";
C = ":compose<Enter>";
rr = ":reply -a<Enter>";
rq = ":reply -aq<Enter>";
Rr = ":reply<Enter>";
Rq = ":reply -q<Enter>";
c = ":cf<space>";
"$" = ":term<space>";
"!" = ":term<space>";
"|" = ":pipe<space>";
"/" = ":search<space>-a<space>";
"\\" = ":filter <space>";
n = ":next-result<Enter>";
N = ":prev-result<Enter>";
"<Esc>" = ":clear<Enter>";
};
"messages:folder=Drafts" = {
"<Enter>" = ":recall<Enter>";
};
view = {
"/" = ":toggle-key-passthrough <Enter> /";
q = ":close<Enter>";
O = ":open<Enter>";
S = ":save<space>";
"|" = ":pipe<space>";
D = ":move Trash<Enter>";
A = ":archive flat<Enter>";
"<C-l>" = ":open-link <space>";
f = ":forward <Enter>";
rr = ":reply -a<Enter>";
rq = ":reply -aq<Enter>";
Rr = ":reply<Enter>";
Rq = ":reply -q<Enter>";
H = ":toggle-headers<Enter>";
"<C-k>" = ":prev-part<Enter>";
"<C-j>" = ":next-part<Enter>";
J = ":next <Enter>";
K = ":prev<Enter>";
};
"view::passthrough" = {
"$noinherit" = "true";
"$ex" = "<C-x>";
"<Esc>" = ":toggle-key-passthrough<Enter>";
};
compose = {
# Keybindings used when the embedded terminal is not selected in the compose
# view
"$noinherit" = "true";
"$ex" = "<C-x>";
"<C-k>" = ":prev-field<Enter>";
"<C-j>" = ":next-field<Enter>";
"<A-p>" = ":switch-account -p<Enter>";
"<A-n>" = ":switch-account -n<Enter>";
"<tab>" = ":next-field<Enter>";
"<C-p>" = ":prev-tab<Enter>";
"<C-n>" = ":next-tab<Enter>";
};
"compose::editor" = {
# Keybindings used when the embedded terminal is selected in the compose view
"$noinherit" = "true";
"$ex" = "<C-x>";
"<C-k>" = ":prev-field<Enter>";
"<C-j>" = ":next-field<Enter>";
"<C-p>" = ":prev-tab<Enter>";
"<C-n>" = ":next-tab<Enter>";
};
"compose::review" = {
# Keybindings used when reviewing a message to be sent
y = ":send <Enter>";
n = ":abort<Enter>";
p = ":postpone<Enter>";
q = ":choose -o d discard abort -o p postpone postpone<Enter>";
e = ":edit<Enter>";
a = ":attach<space>";
d = ":detach<space>";
};
terminal = {
"$noinherit" = "true";
"$ex" = "<C-x>";
"<C-p>" = ":prev-tab<Enter>";
"<C-n>" = ":next-tab<Enter>";
};
};
extraConfig = {
general = {
unsafe-accounts-conf = true;
# log-file = "~/.cache/aerc.log";
# log-level = "debug";
};
viewer = {
pager = "${pkgs.less}/bin/less -R";
};
filters = {
"text/plain" = "${pkgs.aerc}/libexec/aerc/filters/colorize";
"text/calendar" = "${pkgs.gawk}/bin/awk -f ${pkgs.aerc}/libexec/aerc/filters/calendar";
"text/html" =
"${pkgs.aerc}/libexec/aerc/filters/html | ${pkgs.aerc}/libexec/aerc/filters/colorize"; # Requires w3m, dante
# "text/*" =
# ''${pkgs.bat}/bin/bat -fP --file-name="$AERC_FILENAME "'';
"message/delivery-status" = "${pkgs.aerc}/libexec/aerc/filters/colorize";
"message/rfc822" = "${pkgs.aerc}/libexec/aerc/filters/colorize";
"application/x-sh" = "${pkgs.bat}/bin/bat -fP -l sh";
"application/pdf" = "${pkgs.zathura}/bin/zathura -";
"audio/*" = "${pkgs.mpv}/bin/mpv -";
"image/*" = "${pkgs.feh}/bin/feh -";
};
};
};
accounts.email.accounts.home.aerc = {
enable = true;
extraAccounts = {
check-mail = "5m";
check-mail-cmd = "${pkgs.isync}/bin/mbsync -a";
check-mail-timeout = "15s";
};
};
xdg.desktopEntries.aerc = lib.mkIf (pkgs.stdenv.isLinux && config.gui.enable) {
name = "aerc";
exec = "${config.terminalLaunchCommand} aerc %u";
};
xsession.windowManager.i3.config.keybindings = lib.mkIf pkgs.stdenv.isLinux {
"${config.home-manager.users.${config.user}.xsession.windowManager.i3.config.modifier}+Shift+e" =
"exec ${
# Don't name the script `aerc` or it will affect grep
builtins.toString (
pkgs.writeShellScript "focus-mail.sh" ''
count=$(ps aux | grep -c aerc)
if [ "$count" -eq 1 ]; then
i3-msg "exec --no-startup-id ${config.terminal} start --class aerc -- aerc"
sleep 0.25
fi
i3-msg "[class=aerc] focus"
''
)
}";
};
programs.fish.shellAbbrs = {
ae = "aerc";
};
};
};
}

View File

@ -1,139 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
imports = [
./himalaya.nix
./aerc.nix
./system.nix
];
options = {
mail.enable = lib.mkEnableOption "Mail service.";
mail.user = lib.mkOption {
type = lib.types.str;
description = "User name for the email address.";
default = config.user;
};
mail.server = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Server name for the email address.";
};
mail.imapHost = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Server host for IMAP (reading mail).";
};
mail.smtpHost = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Server host for SMTP (sending mail).";
};
};
config = lib.mkIf config.mail.enable {
home-manager.users.${config.user} = {
programs.mbsync = {
enable = true;
};
# Automatically check for mail and keep files synced locally
services.mbsync = lib.mkIf pkgs.stdenv.isLinux {
enable = true;
frequency = "*:0/5";
postExec = "${pkgs.notmuch}/bin/notmuch new";
};
# Used to watch for new mail and trigger sync
services.imapnotify.enable = pkgs.stdenv.isLinux;
# Allows sending email from CLI/sendmail
programs.msmtp.enable = true;
# Better local mail search
programs.notmuch = {
enable = true;
new.ignore = [
".mbsyncstate.lock"
".mbsyncstate.journal"
".mbsyncstate.new"
];
};
accounts.email = {
# Where email files are stored
maildirBasePath = "${config.homePath}/mail";
accounts = {
home =
let
address = "${config.mail.user}@${config.mail.server}";
in
{
userName = address;
realName = config.fullName;
primary = true;
inherit address;
aliases = map (user: "${user}@${config.mail.server}") [
"me"
"hey"
"admin"
];
# Options for contact completion
alot = { };
imap = {
host = config.mail.imapHost;
port = 993;
tls.enable = true;
};
# Watch for mail and run notifications or sync
imapnotify = {
enable = true;
boxes = [ "Inbox" ];
onNotify = "${pkgs.isync}/bin/mbsync -a";
onNotifyPost =
lib.mkIf config.home-manager.users.${config.user}.services.dunst.enable
"${pkgs.libnotify}/bin/notify-send 'New mail arrived'";
};
# Name of the directory in maildir for this account
maildir = {
path = "main";
};
# Bi-directional syncing options for local files
mbsync = {
enable = true;
create = "both";
expunge = "both";
remove = "both";
patterns = [ "*" ];
extraConfig.channel = {
CopyArrivalDate = "yes"; # Sync time of original message
};
};
# Enable indexing
notmuch.enable = true;
# Used to login and send and receive emails
passwordCommand = "${pkgs.age}/bin/age --decrypt --identity ~/.ssh/id_ed25519 ${pkgs.writeText "mailpass.age" (builtins.readFile ../../../private/mailpass.age)}";
smtp = {
host = config.mail.smtpHost;
port = 465;
tls.enable = true;
};
};
};
};
};
};
}

View File

@ -1,26 +0,0 @@
{ config, lib, ... }:
{
options.mail.himalaya.enable = lib.mkEnableOption "Himalaya email.";
config = lib.mkIf config.mail.himalaya.enable {
home-manager.users.${config.user} = {
programs.himalaya = {
enable = true;
};
accounts.email.accounts.home.himalaya = {
enable = true;
settings = {
downloads-dir = config.userDirs.download;
smtp-insecure = true;
};
};
programs.fish.shellAbbrs = {
hi = "himalaya";
};
};
};
}

View File

@ -1,35 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf (config.mail.enable || config.server) {
home-manager.users.${config.user} = {
programs.msmtp.enable = true;
# The system user for sending automatic notifications
accounts.email.accounts.system =
let
address = "system@${config.mail.server}";
in
{
userName = address;
realName = "NixOS System";
primary = !config.mail.enable; # Only primary if mail not enabled
inherit address;
passwordCommand = "${pkgs.age}/bin/age --decrypt --identity ${config.identityFile} ${pkgs.writeText "mailpass-system.age" (builtins.readFile ../../../private/mailpass-system.age)}";
msmtp.enable = true;
smtp = {
host = config.mail.smtpHost;
port = 465;
tls.enable = true;
};
};
};
};
}

View File

@ -1,164 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options.kubernetes.enable = lib.mkEnableOption "Kubernetes tools.";
config = lib.mkIf config.kubernetes.enable {
home-manager.users.${config.user} = {
home.packages = with pkgs; [
kubectl # Basic Kubernetes queries
kubernetes-helm # Helm CLI
fluxcd # Bootstrap clusters with Flux
kustomize # Kustomize CLI (for Flux)
];
programs.fish.shellAbbrs = {
k = "kubectl";
pods = "kubectl get pods -A";
nodes = "kubectl get nodes";
deploys = "kubectl get deployments -A";
dash = "kube-dashboard";
ks = "k9s";
};
# Terminal Kubernetes UI
programs.k9s = {
enable = true;
settings = {
k9s = {
ui = {
enableMouse = true;
headless = true;
logoless = true;
crumbsless = false;
skin = "main";
};
};
};
skins = {
main = {
k9s = {
body = {
fgColor = config.theme.colors.base06;
bgColor = "default";
logoColor = config.theme.colors.base02; # *blue ?
};
# Search bar
prompt = {
fgColor = config.theme.colors.base06;
bgColor = "default";
suggestColor = config.theme.colors.base03;
};
# Header left side
info = {
fgColor = config.theme.colors.base04;
sectionColor = config.theme.colors.base05;
};
dialog = {
fgColor = config.theme.colors.base06;
bgColor = "default";
buttonFgColor = config.theme.colors.base06;
buttonBgColor = config.theme.colors.base0E;
buttonFocusFgColor = config.theme.colors.base07;
buttonFocusBgColor = config.theme.colors.base02; # *cyan
labelFgColor = config.theme.colors.base09;
fieldFgColor = config.theme.colors.base06;
};
frame = {
border = {
fgColor = config.theme.colors.base01;
focusColor = config.theme.colors.base06;
};
menu = {
fgColor = config.theme.colors.base06;
keyColor = config.theme.colors.base0E; # *magenta
numKeyColor = config.theme.colors.base0E; # *magenta
};
crumbs = {
fgColor = config.theme.colors.base06;
bgColor = config.theme.colors.base01;
activeColor = config.theme.colors.base03;
};
status = {
newColor = config.theme.colors.base04; # *cyan
modifyColor = config.theme.colors.base0D; # *blue
addColor = config.theme.colors.base0B; # *green
errorColor = config.theme.colors.base08; # *red
highlightColor = config.theme.colors.base09; # *orange
killColor = config.theme.colors.base03; # *comment
completedColor = config.theme.colors.base03; # *comment
};
title = {
fgColor = config.theme.colors.base06;
bgColor = "default";
highlightColor = config.theme.colors.base09; # *orange
counterColor = config.theme.colors.base0D; # *blue
filterColor = config.theme.colors.base0E; # *magenta
};
};
views = {
charts = {
bgColor = "default";
defaultDialColors = [
config.theme.colors.base0D
config.theme.colors.base08
];
# - *blue
# - *red
defaultChartColors = [
config.theme.colors.base0D
config.theme.colors.base08
];
# - *blue
# - *red
};
table = {
# List of resources
fgColor = config.theme.colors.base06;
bgColor = "default";
# Row selection
cursorFgColor = config.theme.colors.base07;
cursorBgColor = config.theme.colors.base01;
# Header row
header = {
fgColor = config.theme.colors.base0D;
bgColor = "default";
sorterColor = config.theme.colors.base0A; # *selection
};
};
xray = {
fgColor = config.theme.colors.base06;
bgColor = "default";
cursorColor = config.theme.colors.base06;
graphicColor = config.theme.colors.base0D;
showIcons = false;
};
yaml = {
keyColor = config.theme.colors.base0D;
colonColor = config.theme.colors.base04;
fgColor = config.theme.colors.base03;
};
logs = {
fgColor = config.theme.colors.base06;
bgColor = "default";
indicator = {
fgColor = config.theme.colors.base06;
bgColor = "default";
};
};
};
};
};
};
};
};
};
}

View File

@ -17,10 +17,14 @@
};
home.packages = with pkgs; [
cargo
rustc
clippy
gcc
rustc
cargo
cargo-watch
clippy
rustfmt
pkg-config
openssl
];
};
};

View File

@ -1,39 +0,0 @@
{ config, lib, ... }:
{
# Shell history sync
options.atuin.enable = lib.mkEnableOption "Atuin";
config = {
home-manager.users.${config.user} = lib.mkIf config.atuin.enable {
programs.atuin = {
enable = true;
flags = [
"--disable-up-arrow"
"--disable-ctrl-r"
];
settings = {
auto_sync = true;
update_check = false;
sync_address = "https://api.atuin.sh";
search_mode = "fuzzy";
filter_mode = "host"; # global, host, session, directory
search_mode_shell_up_key_binding = "fuzzy";
filter_mode_shell_up_key_binding = "session";
style = "compact"; # or auto,full
show_help = true;
history_filter = [ ];
secrets_filter = true;
enter_accept = false;
keymap_mode = "vim-normal";
};
};
};
# Give root user the same setup
home-manager.users.root.programs.atuin = config.home-manager.users.${config.user}.programs.atuin;
};
}

View File

@ -1,22 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
# Convenience utilities from charm.sh
options.charm.enable = lib.mkEnableOption "Charm utilities.";
config.home-manager.users.${config.user} = lib.mkIf config.charm.enable {
home.packages = with pkgs; [
glow # Markdown previews
skate # Key-value store
charm # Manage account and filesystem
pop # Send emails from a TUI
];
};
}

View File

@ -1,18 +0,0 @@
{ ... }:
{
imports = [
./atuin.nix
./bash
./charm.nix
./direnv.nix
./fish
./fzf.nix
./git.nix
./github.nix
./jujutsu.nix
./nixpkgs.nix
./starship.nix
./utilities.nix
./work.nix
];
}

View File

@ -1,36 +0,0 @@
{ config, ... }:
{
# Enables quickly entering Nix shells when changing directories
home-manager.users.${config.user}.programs.direnv = {
enable = true;
nix-direnv.enable = true;
config = {
whitelist = {
prefix = [ config.dotfilesPath ];
};
};
};
# programs.direnv.direnvrcExtra = ''
# layout_postgres() {
# export PGDATA="$(direnv_layout_dir)/postgres"
# export PGHOST="$PGDATA"
#
# if [[ ! -d "PGDATA" ]]; then
# initdb
# cat >> "$PGDATA/postgres.conf" <<- EOF
# listen_addresses = '''
# unix_socket_directories = '$PGHOST'
# EOF
# echo "CREATE DATABASE $USER;" | postgres --single -E postgres
# fi
# }
# '';
# Prevent garbage collection
nix.extraOptions = ''
keep-outputs = true
keep-derivations = true
'';
}

View File

@ -1,154 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
users.users.${config.user}.shell = pkgs.fish;
programs.fish.enable = true; # Needed for LightDM to remember username
home-manager.users.${config.user} = {
# Packages used in abbreviations and aliases
home.packages = with pkgs; [ curl ];
programs.fish = {
enable = true;
shellAliases = {
# Version of bash which works much better on the terminal
bash = "${pkgs.bashInteractive}/bin/bash";
# Use eza (exa) instead of ls for fancier output
ls = "${pkgs.eza}/bin/eza --group";
# Move files to XDG trash on the commandline
trash = lib.mkIf pkgs.stdenv.isLinux "${pkgs.trash-cli}/bin/trash-put";
};
functions = {
commandline-git-commits = {
description = "Insert commit into commandline";
body = builtins.readFile ./functions/commandline-git-commits.fish;
};
copy = {
description = "Copy file contents into clipboard";
body = "cat $argv | pbcopy"; # Need to fix for non-macOS
};
edit = {
description = "Open a file in Vim";
body = builtins.readFile ./functions/edit.fish;
};
envs = {
description = "Evaluate a bash-like environment variables file";
body = ''set -gx (cat $argv | tr "=" " " | string split ' ')'';
};
fcd = {
description = "Jump to directory";
argumentNames = "directory";
body = builtins.readFile ./functions/fcd.fish;
};
fish_user_key_bindings = {
body = builtins.readFile ./functions/fish_user_key_bindings.fish;
};
ip = {
body = builtins.readFile ./functions/ip.fish;
};
json = {
description = "Tidy up JSON using jq";
body = "pbpaste | jq '.' | pbcopy"; # Need to fix for non-macOS
};
note = {
description = "Edit or create a note";
argumentNames = "filename";
body = builtins.readFile ./functions/note.fish;
};
recent = {
description = "Open a recent file in Vim";
body = builtins.readFile ./functions/recent.fish;
};
search-and-edit = {
description = "Search and open the relevant file in Vim";
body = builtins.readFile ./functions/search-and-edit.fish;
};
syncnotes = {
description = "Full git commit on notes";
body = builtins.readFile ./functions/syncnotes.fish;
};
_which = {
description = "Identify the path to a program in the shell";
body = "command --search (string sub --start=2 $argv)";
};
};
interactiveShellInit = ''
fish_vi_key_bindings
bind yy fish_clipboard_copy
bind Y fish_clipboard_copy
bind -M visual y fish_clipboard_copy
bind -M default p fish_clipboard_paste
set -g fish_vi_force_cursor
set -g fish_cursor_default block
set -g fish_cursor_insert line
set -g fish_cursor_visual block
set -g fish_cursor_replace_one underscore
'';
loginShellInit = "";
shellAbbrs = {
# Directory aliases
l = "ls -lh";
lh = "ls -lh";
ll = "ls -alhF";
la = "ls -a";
c = "cd";
"-" = "cd -";
mkd = "mkdir -pv";
# Convert a program into its full path
"=" = {
position = "anywhere";
regex = "=\\w+";
function = "_which";
};
# System
s = "sudo";
sc = "systemctl";
scs = "systemctl status";
sca = "systemctl cat";
m = "make";
t = "trash";
# Vim (overwritten by Neovim)
v = "vim";
vl = "vim -c 'normal! `0'";
# Notes
sn = "syncnotes";
# Fun CLI Tools
weather = "curl wttr.in/$WEATHER_CITY";
moon = "curl wttr.in/Moon";
# Cheat Sheets
ssl = "openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr";
fingerprint = "ssh-keyscan myhost.com | ssh-keygen -lf -";
publickey = "ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub";
forloop = "for i in (seq 1 100)";
# Docker
dc = "$DOTS/bin/docker_cleanup";
dr = "docker run --rm -it";
db = "docker build . -t";
};
shellInit = "";
};
home.sessionVariables.fish_greeting = "";
programs.starship.enableFishIntegration = true;
programs.zoxide.enableFishIntegration = true;
programs.fzf.enableFishIntegration = true;
};
}

View File

@ -1,56 +0,0 @@
{ config, pkgs, ... }:
{
# FZF is a fuzzy-finder for the terminal
home-manager.users.${config.user} = {
programs.fzf.enable = true;
programs.fish = {
functions = {
projects = {
description = "Jump to a project";
body = ''
set projdir ( \
fd \
--search-path $HOME/dev \
--type directory \
--exact-depth 2 \
| ${pkgs.proximity-sort}/bin/proximity-sort $PWD \
| sed 's/\\/$//' \
| fzf --tiebreak=index \
)
and cd $projdir
and commandline -f execute
'';
};
};
shellAbbrs = {
lsf = "ls -lh | fzf";
};
};
# Global fzf configuration
home.sessionVariables =
let
fzfCommand = "fd --type file";
in
{
FZF_DEFAULT_COMMAND = fzfCommand;
FZF_CTRL_T_COMMAND = fzfCommand;
FZF_DEFAULT_OPTS = "-m --height 50% --border";
};
home.packages = [
(pkgs.writeShellApplication {
name = "jqr";
runtimeInputs = [
pkgs.jq
pkgs.fzf
];
text = builtins.readFile ./bash/scripts/jqr.sh;
})
];
};
}

View File

@ -1,195 +0,0 @@
{
config,
pkgs,
lib,
...
}:
let
home-packages = config.home-manager.users.${config.user}.home.packages;
in
{
options = {
gitName = lib.mkOption {
type = lib.types.str;
description = "Name to use for git commits";
};
gitEmail = lib.mkOption {
type = lib.types.str;
description = "Email to use for git commits";
};
};
config = {
home-manager.users.root.programs.git = {
enable = true;
extraConfig.safe.directory = config.dotfilesPath;
};
home-manager.users.${config.user} = {
programs.git = {
enable = true;
userName = config.gitName;
userEmail = config.gitEmail;
extraConfig = {
core.pager = "${pkgs.git}/share/git/contrib/diff-highlight/diff-highlight | less -F";
interactive.difffilter = "${pkgs.git}/share/git/contrib/diff-highlight/diff-highlight";
pager = {
branch = "false";
};
safe = {
directory = config.dotfilesPath;
};
pull = {
ff = "only";
};
push = {
autoSetupRemote = "true";
};
init = {
defaultBranch = "master";
};
rebase = {
autosquash = "true";
};
gpg = {
format = "ssh";
ssh.allowedSignersFile = "~/.config/git/allowed-signers";
};
# commit.gpgsign = true;
# tag.gpgsign = true;
};
ignores = [
".direnv/**"
"result"
];
includes = [
{
path = "~/.config/git/personal";
condition = "gitdir:~/dev/personal/";
}
];
};
# Personal git config
# TODO: fix with variables
xdg.configFile."git/personal".text = ''
[user]
name = "${config.fullName}"
email = "7386960+nmasur@users.noreply.github.com"
signingkey = ~/.ssh/id_ed25519
[commit]
gpgsign = true
[tag]
gpgsign = true
'';
xdg.configFile."git/allowed-signers".text = ''
7386960+nmasur@users.noreply.github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB+AbmjGEwITk5CK9y7+Rg27Fokgj9QEjgc9wST6MA3s
'';
programs.fish.shellAbbrs = {
g = "git";
gs = "git status";
gd = "git diff";
gds = "git diff --staged";
gdp = "git diff HEAD^";
ga = "git add";
gaa = "git add -A";
gac = "git commit -am";
gc = "git commit -m";
gca = "git commit --amend --no-edit";
gcae = "git commit --amend";
gu = "git pull";
gp = "git push";
gl = "git log --graph --decorate --oneline -20";
gll = "git log --graph --decorate --oneline";
gco = "git checkout";
gcom = ''git switch (git symbolic-ref refs/remotes/origin/HEAD | cut -d"/" -f4)'';
gcob = "git switch -c";
gb = "git branch";
gpd = "git push origin -d";
gbd = "git branch -d";
gbD = "git branch -D";
gdd = {
position = "anywhere";
setCursor = true;
expansion = "BRANCH=% git push origin -d $BRANCH and git branch -d $BRANCH";
};
gr = "git reset";
grh = "git reset --hard";
gm = "git merge";
gcp = "git cherry-pick";
cdg = "cd (git rev-parse --show-toplevel)";
};
# Required for fish commands
home.packages = with pkgs; [
fish
fzf
bat
];
programs.fish.functions =
lib.mkIf (builtins.elem pkgs.fzf home-packages && builtins.elem pkgs.bat home-packages)
{
git = {
body = builtins.readFile ./fish/functions/git.fish;
};
git-add-fuzzy = {
body = builtins.readFile ./fish/functions/git-add-fuzzy.fish;
};
git-fuzzy-branch = {
argumentNames = "header";
body = builtins.readFile ./fish/functions/git-fuzzy-branch.fish;
};
git-checkout-fuzzy = {
body = ''
set branch (git-fuzzy-branch "checkout branch...")
and git checkout $branch
'';
};
git-delete-fuzzy = {
body = ''
set branch (git-fuzzy-branch "delete branch...")
and git branch -d $branch
'';
};
git-force-delete-fuzzy = {
body = ''
set branch (git-fuzzy-branch "force delete branch...")
and git branch -D $branch
'';
};
git-delete-both = {
argumentNames = "branch";
body = ''
git push origin -d $branch
git branch -d $branch
'';
};
git-merge-fuzzy = {
body = ''
set branch (git-fuzzy-branch "merge from...")
and git merge $branch
'';
};
git-show-fuzzy = {
body = builtins.readFile ./fish/functions/git-show-fuzzy.fish;
};
git-commits = {
body = builtins.readFile ./fish/functions/git-commits.fish;
};
git-history = {
body = builtins.readFile ./fish/functions/git-history.fish;
};
uncommitted = {
description = "Find uncommitted git repos";
body = builtins.readFile ./fish/functions/uncommitted.fish;
};
};
};
};
}

View File

@ -1,89 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
unfreePackages = [ "gh-copilot" ];
home-manager.users.${config.user} = {
programs.gh = lib.mkIf config.home-manager.users.${config.user}.programs.git.enable {
enable = true;
gitCredentialHelper.enable = true;
settings.git_protocol = "https";
extensions = [
pkgs.gh-collaborators
pkgs.gh-dash
pkgs.gh-copilot
];
};
programs.fish = lib.mkIf config.home-manager.users.${config.user}.programs.gh.enable {
shellAbbrs = {
ghr = "gh repo view -w";
gha = "gh run list | head -1 | awk '{ print \\$\\(NF-2\\) }' | xargs gh run view";
grw = "gh run watch";
grf = "gh run view --log-failed";
grl = "gh run view --log";
ghpr = "gh pr create && sleep 3 && gh run watch";
# https://github.com/cli/cli/discussions/4067
prs = "gh search prs --state=open --review-requested=@me";
};
functions = {
repos = {
description = "Clone GitHub repositories";
argumentNames = "organization";
body = ''
set directory (gh-repos $organization)
and cd $directory
'';
};
};
};
home.packages = [
(pkgs.writeShellScriptBin "gh-repos" ''
case $1 in
t2) organization="take-two" ;;
d2c) organization="take-two-t2gp" ;;
t2gp) organization="take-two-t2gp" ;;
pd) organization="private-division" ;;
dots) organization="playdots" ;;
*) organization="nmasur" ;;
esac
selected=$(gh repo list "$organization" \
--limit 1000 \
--no-archived \
--json=name,description,isPrivate,updatedAt,primaryLanguage \
| jq -r '.[] | .name + "," + if .description == "" then "-" else .description |= gsub(","; " ") | .description end + "," + .updatedAt + "," + .primaryLanguage.name' \
| (echo "REPO,DESCRIPTION,UPDATED,LANGUAGE"; cat -) \
| column -s , -t \
| fzf \
--header-lines=1 \
--layout=reverse \
--height=100% \
--bind "ctrl-o:execute:gh repo view -w ''${organization}/{1}" \
--bind "shift-up:preview-half-page-up" \
--bind "shift-down:preview-half-page-down" \
--preview "GH_FORCE_TTY=49% gh repo view ''${organization}/{1} | glow -" \
--preview-window up
)
[ -n "''${selected}" ] && {
directory="$HOME/dev/work"
if [ $organization = "nmasur" ]; then directory="$HOME/dev/personal"; fi
repo=$(echo "''${selected}" | awk '{print $1}')
repo_full="''${organization}/''${repo}"
if [ ! -d "''${directory}/''${repo}" ]; then
gh repo clone "$repo_full" "''${directory}/''${repo}"
fi
echo "''${directory}/''${repo}"
}
'')
];
};
}

View File

@ -1,18 +0,0 @@
{ config, ... }:
{
config = {
home-manager.users.${config.user}.programs.jujutsu = {
enable = true;
# https://github.com/martinvonz/jj/blob/main/docs/config.md
settings = {
user = {
name = config.home-manager.users.${config.user}.programs.git.userName;
email = config.home-manager.users.${config.user}.programs.git.userEmail;
};
};
};
};
}

View File

@ -1,133 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
home-manager.users.${config.user} = {
programs.fish = {
shellAbbrs = {
n = "nix";
ns = "nix-shell -p";
nsf = "nix-shell --run fish -p";
nsr = "nix-shell-run";
nps = "nix repl --expr 'import <nixpkgs>{}'";
nixo = "man configuration.nix";
nixh = "man home-configuration.nix";
nr = {
function = "rebuild-nixos";
};
nro = {
function = "rebuild-nixos-offline";
};
hm = {
function = "rebuild-home";
};
};
functions = {
nix-shell-run = {
body = ''
set program $argv[1]
if test (count $argv) -ge 2
commandline -r "nix run nixpkgs#$program -- $argv[2..-1]"
else
commandline -r "nix run nixpkgs#$program"
end
commandline -f execute
'';
};
nix-fzf = {
body = ''
commandline -i (nix-instantiate --eval --json \
-E 'builtins.attrNames (import <nixpkgs> {})' \
| jq '.[]' -r | fzf)
commandline -f repaint
'';
};
rebuild-nixos = {
body = ''
git -C ${config.dotfilesPath} add --intent-to-add --all
echo "doas nixos-rebuild switch --flake ${config.dotfilesPath}#${config.networking.hostName}"
'';
};
rebuild-nixos-offline = {
body = ''
git -C ${config.dotfilesPath} add --intent-to-add --all
echo "doas nixos-rebuild switch --option substitute false --flake ${config.dotfilesPath}#${config.networking.hostName}"
'';
};
rebuild-home = {
body = ''
git -C ${config.dotfilesPath} add --intent-to-add --all
echo "${pkgs.home-manager}/bin/home-manager switch --flake ${config.dotfilesPath}#${config.networking.hostName}";
'';
};
};
};
# Provides "command-not-found" options
programs.nix-index = {
enable = true;
enableFishIntegration = true;
};
# Create nix-index if doesn't exist
home.activation.createNixIndex =
let
cacheDir = "${config.homePath}/.cache/nix-index";
in
lib.mkIf config.home-manager.users.${config.user}.programs.nix-index.enable (
config.home-manager.users.${config.user}.lib.dag.entryAfter [ "writeBoundary" ] ''
if [ ! -d ${cacheDir} ]; then
$DRY_RUN_CMD ${pkgs.nix-index}/bin/nix-index -f ${pkgs.path}
fi
''
);
# Set automatic generation cleanup for home-manager
nix.gc = {
automatic = config.nix.gc.automatic;
options = config.nix.gc.options;
};
};
nix = {
# Set channel to flake packages, used for nix-shell commands
nixPath = [ "nixpkgs=${pkgs.path}" ];
# For security, only allow specific users
settings.allowed-users = [
"@wheel"
config.user
];
# Enable features in Nix commands
extraOptions = ''
experimental-features = nix-command flakes
warn-dirty = false
'';
gc = {
automatic = true;
options = "--delete-older-than 10d";
};
settings = {
# Add community Cachix to binary cache
# Don't use with macOS because blocked by corporate firewall
builders-use-substitutes = true;
substituters = lib.mkIf (!pkgs.stdenv.isDarwin) [ "https://nix-community.cachix.org" ];
trusted-public-keys = lib.mkIf (!pkgs.stdenv.isDarwin) [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
# Scans and hard links identical files in the store
# Not working with macOS: https://github.com/NixOS/nix/issues/7273
auto-optimise-store = lib.mkIf (!pkgs.stdenv.isDarwin) true;
};
};
}

View File

@ -1,72 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
home-manager.users.${config.user}.programs.starship = {
enable = true;
settings = {
add_newline = false; # Don't print new line at the start of the prompt
format = lib.concatStrings [
"$directory"
"$git_branch"
"$git_commit"
"$git_status"
"$hostname"
"$cmd_duration"
"$character"
];
right_format = "$nix_shell";
character = {
success_symbol = "[](bold green)";
error_symbol = "[](bold red)";
vicmd_symbol = "[](bold green)";
};
cmd_duration = {
min_time = 5000;
show_notifications = if pkgs.stdenv.isLinux then false else true;
min_time_to_notify = 30000;
format = "[$duration]($style) ";
};
directory = {
truncate_to_repo = true;
truncation_length = 100;
};
git_branch = {
format = "[$symbol$branch]($style)";
};
git_commit = {
format = "( @ [$hash]($style) )";
only_detached = false;
};
git_status = {
format = "([$all_status$ahead_behind]($style) )";
conflicted = "=";
ahead = "";
behind = "";
diverged = "";
untracked = "";
stashed = "";
modified = "";
staged = "+";
renamed = "»";
deleted = "";
style = "red";
};
hostname = {
ssh_only = true;
format = "on [$hostname](bold red) ";
};
nix_shell = {
format = "[$symbol $name]($style)";
symbol = "";
};
python = {
format = "[\${version}\\(\${virtualenv}\\)]($style)";
};
};
};
}

View File

@ -1,77 +0,0 @@
{ config, pkgs, ... }:
let
ignorePatterns = ''
!.env*
!.github/
!.gitignore
!*.tfvars
.terraform/
.target/
/Library/'';
in
{
config = {
home-manager.users.${config.user} = {
home.packages = with pkgs; [
age # Encryption
bc # Calculator
delta # Fancy diffs
difftastic # Other fancy diffs
dig # DNS lookup
fd # find
htop # Show system processes
killall # Force quit
inetutils # Includes telnet, whois
jless # JSON viewer
jo # JSON output
jq # JSON manipulation
lf # File viewer
osc # Clipboard over SSH
qrencode # Generate qr codes
rsync # Copy folders
# ren # Rename files
# rep # Replace text in files
ripgrep # grep
sd # sed
spacer # Output lines in terminal
tealdeer # Cheatsheets
tree # View directory hierarchy
vimv-rs # Batch rename files
unzip # Extract zips
dua # File sizes (du)
du-dust # Disk usage tree (ncdu)
duf # Basic disk information (df)
];
programs.zoxide.enable = true; # Shortcut jump command
home.file = {
".rgignore".text = ignorePatterns;
".digrc".text = "+noall +answer"; # Cleaner dig commands
};
xdg.configFile."fd/ignore".text = ignorePatterns;
programs.bat = {
enable = true; # cat replacement
config = {
theme = config.theme.colors.batTheme;
pager = "less -R"; # Don't auto-exit if one screen
};
};
programs.fish.functions = {
ping = {
description = "Improved ping";
argumentNames = "target";
body = "${pkgs.prettyping}/bin/prettyping --nolegend $target";
};
};
};
};
}

View File

@ -1,59 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
home-manager.users.${config.user} = lib.mkIf pkgs.stdenv.isDarwin {
home.packages =
let
ldap_scheme = "ldaps";
magic_prefix = "take";
ldap_port = 3269;
jq_parse = pkgs.writeShellScriptBin "ljq" ''
jq --slurp \
--raw-input 'split("\n\n")|map(split("\n")|map(select(.[0:1]!="#" and length>0)) |select(length > 0)|map(capture("^(?<key>[^:]*:?): *(?<value>.*)") |if .key[-1:.key|length] == ":" then .key=.key[0:-1]|.value=(.value|@base64d) else . end)| group_by(.key) | map({key:.[0].key,value:(if .|length > 1 then [.[].value] else .[].value end)}) | from_entries)' | jq -r 'del(.[].thumbnailPhoto)'
'';
ldap_script = pkgs.writeShellScriptBin "ldap" ''
if ! [ "$LDAP_HOST" ]; then
echo "No LDAP_HOST specified!"
exit 1
fi
SEARCH_FILTER="$@"
ldapsearch -LLL \
-B -o ldif-wrap=no \
-E pr=5000/prompt \
-H "${ldap_scheme}://''${LDAP_HOST}:${builtins.toString ldap_port}" \
-D "${pkgs.lib.toUpper magic_prefix}2\\${pkgs.lib.toLower config.user}" \
-w "$(${pkgs._1password-cli}/bin/op item get T2 --fields label=password --reveal)" \
-b "dc=''${LDAP_HOST//./,dc=}" \
-s "sub" -x "(cn=''${SEARCH_FILTER})" \
| ${jq_parse}/bin/ljq
'';
ldapm_script = pkgs.writeShellScriptBin "ldapm" ''
if ! [ "$LDAP_HOST" ]; then
echo "No LDAP_HOST specified!"
exit 1
fi
${ldap_script}/bin/ldap "$@" | jq '[ .[].memberOf] | add'
'';
ldapg_script = pkgs.writeShellScriptBin "ldapg" ''
if ! [ "$LDAP_HOST" ]; then
echo "No LDAP_HOST specified!"
exit 1
fi
${ldap_script}/bin/ldap "$@" | jq '[ .[].member] | add'
'';
in
[
ldap_script
ldapm_script
ldapg_script
jq_parse
];
};
}

View File

@ -1,656 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
# MacOS-specific settings for Alacritty
home-manager.users.${config.user} = lib.mkIf pkgs.stdenv.isDarwin {
programs.alacritty.settings = {
font.size = lib.mkForce 20.0;
shell.program = "${pkgs.fish}/bin/fish";
window.startup_mode = "SimpleFullScreen";
key_bindings = [
{
key = "F";
mods = "Super";
action = "ToggleSimpleFullscreen";
}
{
key = "F";
mods = "Control|Shift";
action = "SearchForward";
}
{
key = "L";
mods = "Super";
chars = "\\x1F";
}
# Some Mac key combinations need to be manually added
{
key = "Return";
mods = "Shift";
chars = "\\x1b[13;2u";
}
{
key = "Return";
mods = "Control";
chars = "\\x1b[13;5u";
}
{
key = "Return";
mods = "Control|Shift";
chars = "\\x1b[13;6u";
}
{
key = "N";
mods = "Command";
action = "SpawnNewInstance";
}
{
key = "PageUp";
action = "ScrollPageUp";
}
{
key = "PageUp";
mods = "Alt";
chars = "\\x1B[5~";
}
{
key = "PageUp";
mods = "Shift";
chars = "\\x1B[5~";
}
{
key = "PageDown";
action = "ScrollPageDown";
}
{
key = "PageDown";
mods = "Alt";
chars = "\\x1B[6~";
}
{
key = "PageDown";
mods = "Shift";
chars = "\\x1B[6~";
}
{
key = "Home";
action = "ScrollToTop";
}
{
key = "Home";
mods = "Alt";
chars = "\\x1BOH";
}
{
key = "Home";
mods = "Shift";
chars = "\\x1B[H";
}
{
key = "End";
action = "ScrollToBottom";
}
{
key = "End";
mods = "Alt";
chars = "\\x1BOF";
}
{
key = "End";
mods = "Shift";
chars = "\\x1B[F";
}
{
key = "Tab";
mods = "Alt";
chars = "\\x1B ";
}
{
key = "Tab";
mods = "Alt|Shift";
chars = "\\x1Bx1B[Z";
}
# {
# key = "Back";
# chars = "\\x7F";
# }
{
key = "Back";
mods = "Alt";
chars = "\\x1Bx7F";
}
{
key = "Back";
mods = "Alt|Shift";
chars = "\\x1Bx08";
}
{
key = "Space";
mods = "Control";
chars = "\\x00";
}
{
key = "Space";
mods = "Alt";
chars = "\\x20";
}
{
key = "Left";
mods = "Alt";
chars = "\\x1Bb";
}
{
key = "Left";
mods = "Alt|Shift";
chars = "\\x1Bx1B[D";
}
{
key = "Right";
mods = "Alt";
chars = "\\x1Bf";
}
{
key = "Right";
mods = "Alt|Shift";
chars = "\\x1Bx1B[C";
}
{
key = "Down";
mods = "Alt";
chars = "\\x1Bx1B[B";
}
{
key = "Down";
mods = "Alt|Shift";
chars = "\\x1Bx1B[B";
}
{
key = "Up";
mods = "Alt";
chars = "\\x1Bx1B[A";
}
{
key = "Up";
mods = "Alt|Shift";
chars = "\\x1Bx1B[A";
}
{
key = "A";
mods = "Alt";
chars = "\\x1Ba";
}
{
key = "A";
mods = "Alt|Shift";
chars = "\\x1BA";
}
{
key = "B";
mods = "Alt";
chars = "\\x1Bb";
}
{
key = "B";
mods = "Alt|Shift";
chars = "\\x1BB";
}
{
key = "C";
mods = "Alt";
chars = "\\x1Bc";
}
{
key = "C";
mods = "Alt|Shift";
chars = "\\x1BC";
}
{
key = "D";
mods = "Alt";
chars = "\\x1Bd";
}
{
key = "D";
mods = "Alt|Shift";
chars = "\\x1BD";
}
{
key = "E";
mods = "Alt";
chars = "\\x1Be";
}
{
key = "E";
mods = "Alt|Shift";
chars = "\\x1BE";
}
{
key = "F";
mods = "Alt";
chars = "\\x1Bf";
}
{
key = "F";
mods = "Alt|Shift";
chars = "\\x1BF";
}
{
key = "G";
mods = "Alt";
chars = "\\x1Bg";
}
{
key = "G";
mods = "Alt|Shift";
chars = "\\x1BG";
}
{
key = "H";
mods = "Alt";
chars = "\\x1Bh";
}
{
key = "H";
mods = "Alt|Shift";
chars = "\\x1BH";
}
{
key = "I";
mods = "Alt";
chars = "\\x1Bi";
}
{
key = "I";
mods = "Alt|Shift";
chars = "\\x1BI";
}
{
key = "J";
mods = "Alt";
chars = "\\x1Bj";
}
{
key = "J";
mods = "Alt|Shift";
chars = "\\x1BJ";
}
{
key = "K";
mods = "Alt";
chars = "\\x1Bk";
}
{
key = "K";
mods = "Alt|Shift";
chars = "\\x1BK";
}
{
key = "L";
mods = "Alt";
chars = "\\x1Bl";
}
{
key = "L";
mods = "Alt|Shift";
chars = "\\x1BL";
}
{
key = "M";
mods = "Alt";
chars = "\\x1Bm";
}
{
key = "M";
mods = "Alt|Shift";
chars = "\\x1BM";
}
{
key = "N";
mods = "Alt";
chars = "\\x1Bn";
}
{
key = "N";
mods = "Alt|Shift";
chars = "\\x1BN";
}
{
key = "O";
mods = "Alt";
chars = "\\x1Bo";
}
{
key = "O";
mods = "Alt|Shift";
chars = "\\x1BO";
}
{
key = "P";
mods = "Alt";
chars = "\\x1Bp";
}
{
key = "P";
mods = "Alt|Shift";
chars = "\\x1BP";
}
{
key = "Q";
mods = "Alt";
chars = "\\x1Bq";
}
{
key = "Q";
mods = "Alt|Shift";
chars = "\\x1BQ";
}
{
key = "R";
mods = "Alt";
chars = "\\x1Br";
}
{
key = "R";
mods = "Alt|Shift";
chars = "\\x1BR";
}
{
key = "S";
mods = "Alt";
chars = "\\x1Bs";
}
{
key = "S";
mods = "Alt|Shift";
chars = "\\x1BS";
}
{
key = "T";
mods = "Alt";
chars = "\\x1Bt";
}
{
key = "T";
mods = "Alt|Shift";
chars = "\\x1BT";
}
{
key = "U";
mods = "Alt";
chars = "\\x1Bu";
}
{
key = "U";
mods = "Alt|Shift";
chars = "\\x1BU";
}
{
key = "V";
mods = "Alt";
chars = "\\x1Bv";
}
{
key = "V";
mods = "Alt|Shift";
chars = "\\x1BV";
}
{
key = "W";
mods = "Alt";
chars = "\\x1Bw";
}
{
key = "W";
mods = "Alt|Shift";
chars = "\\x1BW";
}
{
key = "X";
mods = "Alt";
chars = "\\x1Bx";
}
{
key = "X";
mods = "Alt|Shift";
chars = "\\x1BX";
}
{
key = "Y";
mods = "Alt";
chars = "\\x1By";
}
{
key = "Y";
mods = "Alt|Shift";
chars = "\\x1BY";
}
{
key = "Z";
mods = "Alt";
chars = "\\x1Bz";
}
{
key = "Z";
mods = "Alt|Shift";
chars = "\\x1BZ";
}
{
key = "Key1";
mods = "Alt";
chars = "\\x1B1";
}
{
key = "Key1";
mods = "Alt|Shift";
chars = "\\x1B!";
}
{
key = "Key2";
mods = "Alt";
chars = "\\x1B2";
}
{
key = "Key2";
mods = "Alt|Shift";
chars = "\\x1B#";
}
{
key = "Key3";
mods = "Alt";
chars = "\\x1B3";
}
{
key = "Key3";
mods = "Alt|Shift";
chars = "\\x1B#";
}
{
key = "Key4";
mods = "Alt";
chars = "\\x1B4";
}
{
key = "Key4";
mods = "Alt|Shift";
chars = "\\x1B$";
}
{
key = "Key5";
mods = "Alt";
chars = "\\x1B5";
}
{
key = "Key5";
mods = "Alt|Shift";
chars = "\\x1B%";
}
{
key = "Key6";
mods = "Alt";
chars = "\\x1B6";
}
{
key = "Key6";
mods = "Alt|Shift";
chars = "\\x1B^";
}
{
key = "Key7";
mods = "Alt";
chars = "\\x1B7";
}
{
key = "Key7";
mods = "Alt|Shift";
chars = "\\x1B&";
}
{
key = "Key8";
mods = "Alt";
chars = "\\x1B8";
}
{
key = "Key8";
mods = "Alt|Shift";
chars = "\\x1B*";
}
{
key = "Key9";
mods = "Alt";
chars = "\\x1B9";
}
{
key = "Key9";
mods = "Alt|Shift";
chars = "\\x1B(";
}
{
key = "Key0";
mods = "Alt";
chars = "\\x1B0";
}
{
key = "Key0";
mods = "Alt|Shift";
chars = "\\x1B)";
}
{
key = "Minus";
mods = "Alt";
chars = "\\x1B-";
}
{
key = "Minus";
mods = "Alt|Shift";
chars = "\\x1B_";
}
{
key = "Equals";
mods = "Alt";
chars = "\\x1B=";
}
{
key = "Equals";
mods = "Alt|Shift";
chars = "\\x1B+";
}
{
key = "LBracket";
mods = "Alt";
chars = "\\x1B[";
}
{
key = "LBracket";
mods = "Alt|Shift";
chars = "\\x1B{";
}
{
key = "RBracket";
mods = "Alt";
chars = "\\x1B]";
}
{
key = "RBracket";
mods = "Alt|Shift";
chars = "\\x1B}";
}
{
key = "Backslash";
mods = "Alt";
chars = "\\x1B\\\\";
}
{
key = "Backslash";
mods = "Alt|Shift";
chars = "\\x1B|";
}
{
key = "Semicolon";
mods = "Alt";
chars = "\\x1B;";
}
{
key = "Semicolon";
mods = "Alt|Shift";
chars = "\\x1B =";
}
{
key = "Apostrophe";
mods = "Alt";
chars = "\\x1B'";
}
{
key = "Apostrophe";
mods = "Alt|Shift";
chars = ''\x1B"'';
}
{
key = "Comma";
mods = "Alt";
chars = "\\x1B;";
}
{
key = "Comma";
mods = "Alt|Shift";
chars = "\\x1B<";
}
{
key = "Period";
mods = "Alt";
chars = "\\x1B.";
}
{
key = "Period";
mods = "Alt|Shift";
chars = "\\x1B>";
}
{
key = "Slash";
mods = "Alt";
chars = "\\x1B/";
}
{
key = "Slash";
mods = "Alt|Shift";
chars = "\\x1B?";
}
{
key = "Grave";
mods = "Alt";
chars = "\\x1B`";
}
{
key = "Grave";
mods = "Alt|Shift";
chars = "\\x1B~";
}
];
};
};
}

View File

@ -1,18 +0,0 @@
{ ... }:
{
imports = [
./alacritty.nix
./fish.nix
./fonts.nix
./hammerspoon.nix
./homebrew.nix
./kitty.nix
./networking.nix
./nixpkgs.nix
./system.nix
./tmux.nix
./user.nix
./utilities.nix
];
}

View File

@ -1,10 +0,0 @@
{ pkgs, ... }:
{
config = {
# MacOS-specific settings for Fish
programs.fish.useBabelfish = true;
programs.fish.babelfishPackage = pkgs.babelfish;
};
}

View File

@ -1,43 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
# Hammerspoon - MacOS custom automation scripting
config = lib.mkIf pkgs.stdenv.isDarwin {
home-manager.users.${config.user} = {
xdg.configFile."hammerspoon/init.lua".source = ./hammerspoon/init.lua;
xdg.configFile."hammerspoon/Spoons/ControlEscape.spoon".source = ./hammerspoon/Spoons/ControlEscape.spoon;
xdg.configFile."hammerspoon/Spoons/DismissAlerts.spoon".source = ./hammerspoon/Spoons/DismissAlerts.spoon;
xdg.configFile."hammerspoon/Spoons/Launcher.spoon/init.lua".source = pkgs.substituteAll {
src = ./hammerspoon/Spoons/Launcher.spoon/init.lua;
firefox = "${pkgs.firefox-bin}/Applications/Firefox.app";
discord = "${pkgs.discord}/Applications/Discord.app";
wezterm = "${pkgs.wezterm}/Applications/WezTerm.app";
obsidian = "${pkgs.obsidian}/Applications/Obsidian.app";
slack = "${pkgs.slack}/Applications/Slack.app";
};
xdg.configFile."hammerspoon/Spoons/MoveWindow.spoon".source = ./hammerspoon/Spoons/MoveWindow.spoon;
home.activation.reloadHammerspoon =
config.home-manager.users.${config.user}.lib.dag.entryAfter [ "writeBoundary" ]
''
$DRY_RUN_CMD /Applications/Hammerspoon.app/Contents/Frameworks/hs/hs -c "hs.reload()"
$DRY_RUN_CMD sleep 1
$DRY_RUN_CMD /Applications/Hammerspoon.app/Contents/Frameworks/hs/hs -c "hs.console.clearConsole()"
'';
};
homebrew.casks = [ "hammerspoon" ];
system.activationScripts.postUserActivation.text = ''
defaults write org.hammerspoon.Hammerspoon MJConfigFile "${config.homePath}/.config/hammerspoon/init.lua"
sudo killall Dock
'';
};
}

View File

@ -1,53 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
# Homebrew - Mac-specific packages that aren't in Nix
config = lib.mkIf pkgs.stdenv.isDarwin {
# Requires Homebrew to be installed
system.activationScripts.preUserActivation.text = ''
if ! xcode-select --version 2>/dev/null; then
$DRY_RUN_CMD xcode-select --install
fi
if ! /opt/homebrew/bin/brew --version 2>/dev/null; then
$DRY_RUN_CMD /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
'';
# Add homebrew paths to CLI path
home-manager.users.${config.user}.home.sessionPath = [ "/opt/homebrew/bin/" ];
homebrew = {
enable = true;
onActivation = {
autoUpdate = false; # Don't update during rebuild
cleanup = "zap"; # Uninstall all programs not declared
upgrade = true;
};
global = {
brewfile = true; # Run brew bundle from anywhere
lockfiles = false; # Don't save lockfile (since running from anywhere)
};
brews = [
"trash" # Delete files and folders to trash instead of rm
];
casks = [
"1password" # 1Password will not launch from Nix on macOS
# "gitify" # Git notifications in menu bar (downgrade manually from 4.6.1)
"keybase" # GUI on Nix not available for macOS
# "logitech-g-hub" # Mouse and keyboard management
"logitune" # Logitech webcam firmware
"meetingbar" # Show meetings in menu bar
"scroll-reverser" # Different scroll style for mouse vs. trackpad
"notunes" # Don't launch Apple Music with the play button
"steam" # Not packaged for Nixon macOS
"epic-games" # Not packaged for Nix
];
};
};
}

View File

@ -1,22 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
# MacOS-specific settings for Kitty
home-manager.users.${config.user} = lib.mkIf pkgs.stdenv.isDarwin {
programs.kitty = {
font.size = lib.mkForce 20;
settings = {
shell = "/run/current-system/sw/bin/fish";
macos_traditional_fullscreen = true;
macos_quit_when_last_window_closed = true;
disable_ligatures = "always";
macos_option_as_alt = true;
};
};
};
}

View File

@ -1,17 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
# config = lib.mkIf pkgs.stdenv.isDarwin {
# networking = {
# computerName = config.networking.hostName;
# # Adjust if necessary
# # hostName = "";
# };
# };
# }
}

View File

@ -1,42 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
home-manager.users.${config.user} = lib.mkIf pkgs.stdenv.isDarwin {
programs.fish = {
shellAbbrs = {
nr = {
function = lib.mkForce "rebuild-darwin";
};
nro = {
function = lib.mkForce "rebuild-darwin-offline";
};
};
functions = {
rebuild-darwin = {
body = ''
git -C ${config.dotfilesPath} add --intent-to-add --all
echo "darwin-rebuild switch --flake ${config.dotfilesPath}#lookingglass"
'';
};
rebuild-darwin-offline = {
body = ''
git -C ${config.dotfilesPath} add --intent-to-add --all
echo "darwin-rebuild switch --option substitute false --flake ${config.dotfilesPath}#lookingglass"
'';
};
rebuild-home = lib.mkForce {
body = ''
git -C ${config.dotfilesPath} add --intent-to-add --all
echo "${pkgs.home-manager}/bin/home-manager switch --flake ${config.dotfilesPath}#lookingglass";
'';
};
};
};
};
}

View File

@ -1,222 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
terminal = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Path to executable for terminal emulator program.";
default = null;
};
terminalLaunchCommand = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Command for using the terminal to launch a new window with a program.";
default = null;
};
};
config = lib.mkIf pkgs.stdenv.isDarwin {
services.nix-daemon.enable = true;
# This setting only applies to Darwin, different on NixOS
nix.gc.interval = {
Hour = 12;
Minute = 15;
Day = 1;
};
environment.shells = [ pkgs.fish ];
security.pam.enableSudoTouchIdAuth = true;
system = {
stateVersion = 5;
keyboard = {
remapCapsLockToControl = true;
enableKeyMapping = true; # Allows for skhd
};
defaults = {
NSGlobalDomain = {
# Set to dark mode
AppleInterfaceStyle = "Dark";
# Don't change from dark to light automatically
# AppleInterfaceSwitchesAutomatically = false;
# Enable full keyboard access for all controls (e.g. enable Tab in modal dialogs)
AppleKeyboardUIMode = 3;
# Only hide menu bar in fullscreen
_HIHideMenuBar = false;
# Expand save panel by default
NSNavPanelExpandedStateForSaveMode = true;
# Expand print panel by default
PMPrintingExpandedStateForPrint = true;
# Replace press-and-hold with key repeat
ApplePressAndHoldEnabled = false;
# Set a fast key repeat rate
KeyRepeat = 2;
# Shorten delay before key repeat begins
InitialKeyRepeat = 12;
# Save to local disk by default, not iCloud
NSDocumentSaveNewDocumentsToCloud = false;
# Disable autocorrect capitalization
NSAutomaticCapitalizationEnabled = false;
# Disable autocorrect smart dashes
NSAutomaticDashSubstitutionEnabled = false;
# Disable autocorrect adding periods
NSAutomaticPeriodSubstitutionEnabled = false;
# Disable autocorrect smart quotation marks
NSAutomaticQuoteSubstitutionEnabled = false;
# Disable autocorrect spellcheck
NSAutomaticSpellingCorrectionEnabled = false;
};
dock = {
# Automatically show and hide the dock
autohide = true;
# Add translucency in dock for hidden applications
showhidden = true;
# Enable spring loading on all dock items
enable-spring-load-actions-on-all-items = true;
# Highlight hover effect in dock stack grid view
mouse-over-hilite-stack = true;
mineffect = "genie";
orientation = "bottom";
show-recents = false;
tilesize = 44;
persistent-apps = [
"/Applications/1Password.app"
"${pkgs.slack}/Applications/Slack.app"
"/System/Applications/Calendar.app"
"${pkgs.firefox-bin}/Applications/Firefox.app"
"/System/Applications/Messages.app"
"/System/Applications/Mail.app"
"/Applications/zoom.us.app"
"${pkgs.discord}/Applications/Discord.app"
"${pkgs.obsidian}/Applications/Obsidian.app"
"${pkgs.wezterm}/Applications/WezTerm.app"
];
};
finder = {
# Default Finder window set to column view
FXPreferredViewStyle = "clmv";
# Finder search in current folder by default
FXDefaultSearchScope = "SCcf";
# Disable warning when changing file extension
FXEnableExtensionChangeWarning = false;
# Allow quitting of Finder application
QuitMenuItem = true;
};
# Disable "Are you sure you want to open" dialog
LaunchServices.LSQuarantine = false;
# Disable trackpad tap to click
trackpad.Clicking = false;
# Where to save screenshots
screencapture.location = "~/Downloads";
# Show seconds on the clock
menuExtraClock.ShowSeconds = true;
CustomUserPreferences = {
# Disable disk image verification
"com.apple.frameworks.diskimages" = {
skip-verify = true;
skip-verify-locked = true;
skip-verify-remote = true;
};
# Avoid creating .DS_Store files on network or USB volumes
"com.apple.desktopservices" = {
DSDontWriteNetworkStores = true;
DSDontWriteUSBStores = true;
};
"com.apple.dock" = {
magnification = true;
largesize = 48;
};
# Require password immediately after screen saver begins
"com.apple.screensaver" = {
askForPassword = 1;
askForPasswordDelay = 0;
};
"com.apple.finder" = {
# Disable the warning before emptying the Trash
WarnOnEmptyTrash = false;
# Finder search in current folder by default
FXDefaultSearchScope = "SCcf";
# Default Finder window set to column view
FXPreferredViewStyle = "clmv";
};
"leits.MeetingBar" = {
eventTimeFormat = ''"show"'';
eventTitleFormat = ''"none"'';
eventTitleIconFormat = ''"iconCalendar"'';
slackBrowser = ''{"deletable":true,"arguments":"","name":"Slack","path":""}'';
zoomBrowser = ''{"deletable":true,"arguments":"","name":"Zoom","path":""}'';
teamsBrowser = ''{"deletable":true,"arguments":"","name":"Teams","path":""}'';
KeyboardShortcuts_joinEventShortcut = ''{"carbonModifiers":6400,"carbonKeyCode":38}'';
timeFormat = ''"12-hour"'';
};
};
CustomSystemPreferences = {
};
};
# Settings that don't have an option in nix-darwin
activationScripts.postActivation.text = ''
echo "Allow apps from anywhere"
SPCTL=$(spctl --status)
if ! [ "$SPCTL" = "assessments disabled" ]; then
sudo spctl --master-disable
fi
'';
# User-level settings
activationScripts.postUserActivation.text = ''
echo "Show the ~/Library folder"
chflags nohidden ~/Library
echo "Reduce Menu Bar padding"
defaults write -globalDomain NSStatusItemSelectionPadding -int 6
defaults write -globalDomain NSStatusItemSpacing -int 6
'';
};
};
}

View File

@ -1,137 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options.tmux.enable = lib.mkEnableOption "Tmux terminal multiplexer";
config.home-manager.users.${config.user} = lib.mkIf config.tmux.enable {
programs.tmux = {
enable = true;
baseIndex = 1; # Start windows and panes at 1
escapeTime = 0; # Wait time after escape is input
historyLimit = 100000;
keyMode = "vi";
newSession = true; # Automatically spawn new session
plugins = [ ];
resizeAmount = 10;
shell = "${pkgs.fish}/bin/fish";
terminal = "screen-256color";
extraConfig = ''
# Horizontal and vertical splits
bind \\ split-window -h -c '#{pane_current_path}'
bind - split-window -v -c '#{pane_current_path}'
# Move between panes with vi keys
bind h select-pane -L
bind j select-pane -D
bind K select-pane -U
bind l select-pane -R
# Split out pane
bind b break-pane
# Synchronize panes
bind S set-window-option synchronize-panes
# Copy mode works as Vim
bind Escape copy-mode
bind k copy-mode
bind C-[ copy-mode
# Use v to trigger selection
bind-key -T copy-mode-vi v send-keys -X begin-selection
# Use y to yank current selection
bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel
# Enable mouse mode
set -g mouse on
# Status bar
set -g status-interval 60 # Seconds between refreshes
set -g renumber-windows on
set-option -g status-position bottom
## COLORSCHEME: gruvbox dark
set-option -g status "on"
# Default statusbar color
set-option -g status-style bg=colour237,fg=colour223 # bg=bg1, fg=fg1
# Default window title colors
set-window-option -g window-status-style bg=colour214,fg=colour237 # bg=yellow, fg=bg1
# Default window with an activity alert
set-window-option -g window-status-activity-style bg=colour237,fg=colour248 # bg=bg1, fg=fg3
# Active window title colors
set-window-option -g window-status-current-style bg=red,fg=colour237 # fg=bg1
# Pane border
set-option -g pane-active-border-style fg=colour250 #fg2
set-option -g pane-border-style fg=colour237 #bg1
# Message infos
set-option -g message-style bg=colour239,fg=colour223 # bg=bg2, fg=fg1
# Writing commands inactive
set-option -g message-command-style bg=colour239,fg=colour223 # bg=fg3, fg=bg1
# Pane number display
set-option -g display-panes-active-colour colour250 #fg2
set-option -g display-panes-colour colour237 #bg1
# Clock
set-window-option -g clock-mode-colour colour109 #blue
# Bell
set-window-option -g window-status-bell-style bg=colour167,fg=colour235 # bg=red, fg=bg
# Theme settings mixed with colors (unfortunately, but there is no cleaner way)
set-option -g status-justify "left"
set-option -g status-left-style none
set-option -g status-left-length "80"
set-option -g status-right-style none
set-option -g status-right-length "80"
set-window-option -g window-status-separator ""
set-option -g status-left "#[fg=colour248, bg=colour241] #S #[fg=colour241, bg=colour237, nobold, noitalics, nounderscore]"
set-option -g status-right "#[fg=colour239, bg=colour237, nobold, nounderscore, noitalics]#[fg=colour246,bg=colour239] %Y-%m-%d %H:%M #[fg=colour248, bg=colour239, nobold, noitalics, nounderscore]"
set-window-option -g window-status-current-format "#[fg=colour237, bg=colour214, nobold, noitalics, nounderscore]#[fg=colour239, bg=colour214] #I #[fg=colour239, bg=colour214, bold] #W #[fg=colour214, bg=colour237, nobold, noitalics, nounderscore]"
set-window-option -g window-status-format "#[fg=colour237,bg=colour239,noitalics]#[fg=colour223,bg=colour239] #I #[fg=colour223, bg=colour239] #W #[fg=colour239, bg=colour237, noitalics]"
'';
};
programs.alacritty.settings = {
# shell.args = [
# "--login"
# "--init-command"
# "tmux attach-session -t noah || tmux new-session -s noah"
# ];
key_bindings = [
{
key = "H";
mods = "Super|Shift";
chars = "\\x02p"; # Previous tmux window
}
{
key = "L";
mods = "Super|Shift";
chars = "\\x02n"; # Next tmux window
}
];
};
programs.fish.shellAbbrs = {
ta = "tmux attach-session";
tan = "tmux attach-session -t noah";
tnn = "tmux new-session -s noah";
};
};
}

View File

@ -1,35 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf pkgs.stdenv.isDarwin {
users.users."${config.user}" = {
# macOS user
home = config.homePath;
uid = 502;
# shell = pkgs.fish; # Default shell
};
# This might fix the shell issues
users.knownUsers = [ config.user ];
home-manager.users.${config.user} = {
# Default shell setting doesn't work
home.sessionVariables = {
SHELL = "${pkgs.fish}/bin/fish";
};
# Used for aerc
xdg.enable = true;
};
# Fix for: 'Error: HOME is set to "/var/root" but we expect "/var/empty"'
home-manager.users.root.home.homeDirectory = lib.mkForce "/var/root";
};
}

View File

@ -1,72 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
unfreePackages = [
"consul"
"vault-bin"
# "teams"
];
home-manager.users.${config.user} = lib.mkIf pkgs.stdenv.isDarwin {
home.packages = [
pkgs.visidata # CSV inspector
pkgs.dos2unix # Convert Windows text files
pkgs.inetutils # Includes telnet
pkgs.pandoc # Convert text documents
pkgs.mpd # TUI slideshows
pkgs.mpv # Video player
pkgs.gnupg # Encryption
pkgs.awscli2
pkgs.ssm-session-manager-plugin
pkgs.awslogs
pkgs.stu # TUI for AWS S3
pkgs.google-cloud-sdk
pkgs.vault-bin
pkgs.consul
pkgs.noti # Create notifications programmatically
pkgs.ipcalc # Make IP network calculations
# pkgs.teams
pkgs.cloudflared # Allow connecting to Cloudflare tunnels
pkgs.monitorcontrol # Allows adjusting external displays
(pkgs.writeShellApplication {
name = "ocr";
runtimeInputs = [ pkgs.tesseract ];
text = builtins.readFile ../../modules/common/shell/bash/scripts/ocr.sh;
})
(pkgs.writeShellApplication {
name = "ec2";
runtimeInputs = [
pkgs.awscli2
pkgs.jq
pkgs.fzf
];
text = builtins.readFile ../../modules/common/shell/bash/scripts/aws-ec2.sh;
})
(pkgs.writeShellApplication {
name = "tfinit";
runtimeInputs = [
pkgs.terraform
pkgs.gawk
pkgs.git
];
text = builtins.readFile ../../modules/common/shell/bash/scripts/terraform-init.sh;
})
];
programs.fish.shellAbbrs = {
# Add noti for ghpr in Darwin
ghpr = lib.mkForce "gh pr create && sleep 3 && noti gh run watch";
grw = lib.mkForce "noti gh run watch";
# Shortcut to edit hosts file
hosts = "sudo nvim /etc/hosts";
};
};
}

View File

@ -1,37 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
calendar = {
enable = lib.mkEnableOption {
description = "Enable calendar.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.calendar.enable) {
home-manager.users.${config.user} = {
accounts.calendar.accounts.default = {
basePath = "other/calendars"; # Where to save calendars in ~ directory
name = "personal";
local.type = "filesystem";
primary = true;
remote = {
passwordCommand = [ "" ];
type = "caldav";
url = "https://${config.hostnames.content}/remote.php/dav/principals/users/${config.user}";
userName = config.user;
};
};
home.packages = with pkgs; [ gnome-calendar ];
};
};
}

View File

@ -1,29 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
calibre = {
enable = lib.mkEnableOption {
description = "Enable Calibre.";
default = false;
};
};
};
config = lib.mkIf (config.gui.enable && config.calibre.enable) {
home-manager.users.${config.user} = {
home.packages = with pkgs; [ calibre ];
# home.sessionVariables = { CALIBRE_USE_DARK_PALETTE = 1; };
};
# Forces Calibre to use dark mode
environment.sessionVariables = {
CALIBRE_USE_DARK_PALETTE = "1";
};
};
}

View File

@ -1,9 +0,0 @@
{ ... }:
{
imports = [
./calendar.nix
./calibre.nix
./nautilus.nix
];
}

View File

@ -1,55 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
nautilus = {
enable = lib.mkEnableOption {
description = "Enable Nautilus file manager.";
default = false;
};
};
};
# Install Nautilus file manager
config = lib.mkIf (config.gui.enable && config.nautilus.enable) {
# Quick preview with spacebar
services.gnome.sushi.enable = true;
environment.systemPackages = [ pkgs.nautilus ];
home-manager.users.${config.user} = {
# Quick button for launching nautilus
xsession.windowManager.i3.config.keybindings = {
"${
config.home-manager.users.${config.user}.xsession.windowManager.i3.config.modifier
}+n" = "exec --no-startup-id ${pkgs.nautilus}/bin/nautilus";
};
# Generates a QR code and previews it with sushi
programs.fish.functions = {
qr = {
body = "${pkgs.qrencode}/bin/qrencode $argv[1] -o /tmp/qr.png | ${pkgs.sushi}/bin/sushi /tmp/qr.png";
};
};
# Set Nautilus as default for opening directories
xdg.mimeApps = {
associations.added."inode/directory" = [ "org.gnome.Nautilus.desktop" ];
defaultApplications."inode/directory" = lib.mkBefore [ "org.gnome.Nautilus.desktop" ];
};
};
# Delete Trash files older than 1 week
systemd.user.services.empty-trash = {
description = "Empty Trash on a regular basis";
wantedBy = [ "default.target" ];
script = "${pkgs.trash-cli}/bin/trash-empty 7";
};
};
}

View File

@ -1,14 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options.gaming.chiaki.enable = lib.mkEnableOption "Chiaki PlayStation remote play client.";
config = lib.mkIf config.gaming.chiaki.enable {
environment.systemPackages = with pkgs; [ chiaki ];
};
}

View File

@ -1,29 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
imports = [
./chiaki.nix
./dwarf-fortress.nix
./legendary.nix
./lutris.nix
./minecraft-server.nix
./moonlight.nix
./ryujinx.nix
./steam.nix
];
options.gaming.enable = lib.mkEnableOption "Enable gaming features.";
config = lib.mkIf (config.gaming.enable && pkgs.stdenv.isLinux) {
hardware.graphics = {
enable = true;
enable32Bit = true;
};
programs.gamemode.enable = true;
};
}

View File

@ -1,37 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options.gaming.dwarf-fortress.enable = lib.mkEnableOption "Dwarf Fortress free edition.";
config = lib.mkIf config.gaming.dwarf-fortress.enable {
unfreePackages = [
"dwarf-fortress"
"phoebus-theme"
];
environment.systemPackages =
let
dfDesktopItem = pkgs.makeDesktopItem {
name = "dwarf-fortress";
desktopName = "Dwarf Fortress";
exec = "${pkgs.dwarf-fortress-packages.dwarf-fortress-full}/bin/dfhack";
terminal = false;
};
dtDesktopItem = pkgs.makeDesktopItem {
name = "dwarftherapist";
desktopName = "Dwarf Therapist";
exec = "${pkgs.dwarf-fortress-packages.dwarf-fortress-full}/bin/dwarftherapist";
terminal = false;
};
in
[
pkgs.dwarf-fortress-packages.dwarf-fortress-full
dfDesktopItem
dtDesktopItem
];
};
}

View File

@ -1,160 +0,0 @@
{
config,
pkgs,
lib,
...
}:
let
localPort = 25564;
publicPort = 49732;
rconPort = 25575;
rconPassword = "thiscanbeanything";
in
{
config = lib.mkIf config.services.minecraft-server.enable {
unfreePackages = [ "minecraft-server" ];
services.minecraft-server = {
eula = true;
declarative = true;
whitelist = { };
openFirewall = false;
serverProperties = {
server-port = localPort;
difficulty = "normal";
gamemode = "survival";
white-list = false;
enforce-whitelist = false;
level-name = "world";
motd = "Welcome!";
pvp = true;
player-idle-timeout = 30;
generate-structures = true;
max-players = 20;
snooper-enabled = false;
spawn-npcs = true;
spawn-animals = true;
spawn-monsters = true;
allow-nether = true;
allow-flight = false;
enable-rcon = true;
"rcon.port" = rconPort;
"rcon.password" = rconPassword;
};
};
networking.firewall.allowedTCPPorts = [ publicPort ];
cloudflare.noProxyDomains = [ config.hostnames.minecraft ];
## Automatically start and stop Minecraft server based on player connections
# Adapted shamelessly from:
# https://dataswamp.org/~solene/2022-08-20-on-demand-minecraft-with-systemd.html
# Prevent Minecraft from starting by default
systemd.services.minecraft-server = {
wantedBy = pkgs.lib.mkForce [ ];
};
# Listen for connections on the public port, to trigger the actual
# listen-minecraft service.
systemd.sockets.listen-minecraft = {
wantedBy = [ "sockets.target" ];
requires = [ "network.target" ];
listenStreams = [ "${toString publicPort}" ];
};
# Proxy traffic to local port, and trigger hook-minecraft
systemd.services.listen-minecraft = {
path = [ pkgs.systemd ];
requires = [
"hook-minecraft.service"
"listen-minecraft.socket"
];
after = [
"hook-minecraft.service"
"listen-minecraft.socket"
];
serviceConfig.ExecStart = "${pkgs.systemd.out}/lib/systemd/systemd-socket-proxyd 127.0.0.1:${toString localPort}";
};
# Start Minecraft if required and wait for it to be available
# Then unlock the listen-minecraft.service
systemd.services.hook-minecraft = {
path = with pkgs; [
systemd
libressl
busybox
];
# Start Minecraft and the auto-shutdown timer
script = ''
systemctl start minecraft-server.service
systemctl start stop-minecraft.timer
'';
# Keep checking until the service is available
postStart = ''
for i in $(seq 60); do
if ${pkgs.libressl.nc}/bin/nc -z 127.0.0.1 ${toString localPort} > /dev/null ; then
exit 0
fi
${pkgs.busybox.out}/bin/sleep 1
done
exit 1
'';
};
# Run a player check on a schedule for auto-shutdown
systemd.timers.stop-minecraft = {
timerConfig = {
OnCalendar = "*-*-* *:*:0/20"; # Every 20 seconds
Unit = "stop-minecraft.service";
};
};
# If no players are connected, then stop services and prepare to resume again
systemd.services.stop-minecraft = {
serviceConfig.Type = "oneshot";
script = ''
# Check when service was launched
servicestartsec=$(
date -d \
"$(systemctl show \
--property=ActiveEnterTimestamp \
minecraft-server.service \
| cut -d= -f2)" \
+%s)
# Calculate elapsed time
serviceelapsedsec=$(( $(date +%s) - servicestartsec))
# Ignore if service just started
if [ $serviceelapsedsec -lt 180 ]
then
echo "Server was just started"
exit 0
fi
PLAYERS=$(
printf "list\n" \
| ${pkgs.rcon.out}/bin/rcon -m \
-H 127.0.0.1 -p ${builtins.toString rconPort} -P ${rconPassword} \
)
if echo "$PLAYERS" | grep "are 0 of a"
then
echo "Stopping server"
systemctl stop minecraft-server.service
systemctl stop hook-minecraft.service
systemctl stop stop-minecraft.timer
fi
'';
};
};
}

View File

@ -1,14 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options.gaming.moonlight.enable = lib.mkEnableOption "Enable Moonlight game streaming client.";
config = lib.mkIf config.gaming.moonlight.enable {
environment.systemPackages = with pkgs; [ moonlight-qt ];
};
}

View File

@ -1,42 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options.gaming.steam.enable = lib.mkEnableOption "Steam game launcher.";
config = lib.mkIf (config.gaming.steam.enable && pkgs.stdenv.isLinux) {
hardware.steam-hardware.enable = true;
unfreePackages = [
"steam"
"steam-original"
"steamcmd"
"steam-run"
"steam-unwrapped"
];
programs.steam = {
enable = true;
remotePlay.openFirewall = true;
extraCompatPackages = [ pkgs.proton-ge-bin ];
gamescopeSession.enable = true;
};
environment.systemPackages = with pkgs; [
# Enable terminal interaction
steamcmd
steam-tui
# Overlay with performance monitoring
mangohud
];
# Seems like NetworkManager can help speed up Steam launch
# https://www.reddit.com/r/archlinux/comments/qguhco/steam_startup_time_arch_1451_seconds_fedora_34/hi8opet/
networking.networkmanager.enable = true;
};
}

View File

@ -1,17 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options.gui.dmenu.enable = lib.mkEnableOption "dmenu launcher.";
config = lib.mkIf (config.services.xserver.enable && config.dmenu.enable) {
home-manager.users.${config.user}.home.packages = [ pkgs.dmenu ];
gui.launcherCommand = "${pkgs.dmenu}/bin/dmenu_run";
};
}

View File

@ -1,29 +0,0 @@
{ config, ... }:
{
config = {
home-manager.users.${config.user}.services.dunst = {
enable = false;
settings = {
global = {
width = 300;
height = 200;
offset = "30x50";
origin = "top-right";
transparency = 0;
padding = 20;
horizontal_padding = 20;
frame_color = config.theme.colors.base03;
};
urgency_normal = {
background = config.theme.colors.base00;
foreground = config.theme.colors.base05;
timeout = 10;
};
};
};
};
}

View File

@ -1,58 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
gtk.theme = {
name = lib.mkOption {
type = lib.types.str;
description = "Theme name for GTK applications";
};
package = lib.mkOption {
type = lib.types.package;
description = "Theme package for GTK applications";
default = pkgs.gnome-themes-extra;
};
};
};
config = lib.mkIf config.gui.enable {
home-manager.users.${config.user} = {
gtk =
let
gtkExtraConfig = {
gtk-application-prefer-dark-theme = config.theme.dark;
};
in
{
enable = true;
theme = {
name = config.gtk.theme.name;
package = config.gtk.theme.package;
};
gtk3.extraConfig = gtkExtraConfig;
gtk4.extraConfig = gtkExtraConfig;
};
};
# Required for setting GTK theme (for preferred-color-scheme in browser)
services.dbus.packages = [ pkgs.dconf ];
programs.dconf.enable = true;
# Make the login screen dark
services.xserver.displayManager.lightdm.greeters.gtk.theme = {
name = config.gtk.theme.name;
package = config.gtk.theme.package;
};
environment.sessionVariables = {
GTK_THEME = config.gtk.theme.name;
};
};
}

View File

@ -1,300 +0,0 @@
{
config,
pkgs,
lib,
...
}:
let
lockCmd = "${pkgs.betterlockscreen}/bin/betterlockscreen --lock --display 1 --blur 0.5 --span";
lockUpdate = "${pkgs.betterlockscreen}/bin/betterlockscreen --update ${config.wallpaper} --display 1 --span";
terminal = "wezterm";
in
{
config = lib.mkIf pkgs.stdenv.isLinux {
services.xserver.windowManager = {
i3 = {
enable = config.services.xserver.enable;
};
};
environment.systemPackages = with pkgs; [
feh # Wallpaper
playerctl # Media control
];
home-manager.users.${config.user} = {
xsession.windowManager.i3 = {
enable = config.services.xserver.enable;
config =
let
modifier = "Mod4"; # Super key
ws1 = "1:I";
ws2 = "2:II";
ws3 = "3:III";
ws4 = "4:IV";
ws5 = "5:V";
ws6 = "6:VI";
ws7 = "7:VII";
ws8 = "8:VIII";
ws9 = "9:IX";
ws10 = "10:X";
in
{
modifier = modifier;
assigns = {
"${ws1}" = [ { class = "Firefox"; } ];
"${ws2}" = [
{ class = "aerc"; }
{ class = "kitty"; }
{ class = "obsidian"; }
{ class = "wezterm"; }
];
"${ws3}" = [ { class = "discord"; } ];
"${ws4}" = [
{ class = "steam"; }
{ class = "Steam"; }
];
};
bars = [ { command = "echo"; } ]; # Disable i3bar
colors =
let
background = config.theme.colors.base00;
inactiveBackground = config.theme.colors.base01;
border = config.theme.colors.base01;
inactiveBorder = config.theme.colors.base01;
text = config.theme.colors.base07;
inactiveText = config.theme.colors.base04;
urgentBackground = config.theme.colors.base08;
indicator = "#00000000";
in
{
background = config.theme.colors.base00;
focused = {
inherit
background
indicator
text
border
;
childBorder = background;
};
focusedInactive = {
inherit indicator;
background = inactiveBackground;
border = inactiveBorder;
childBorder = inactiveBackground;
text = inactiveText;
};
# placeholder = { };
unfocused = {
inherit indicator;
background = inactiveBackground;
border = inactiveBorder;
childBorder = inactiveBackground;
text = inactiveText;
};
urgent = {
inherit text indicator;
background = urgentBackground;
border = urgentBackground;
childBorder = urgentBackground;
};
};
floating.modifier = modifier;
focus = {
mouseWarping = true;
newWindow = "urgent";
followMouse = false;
};
keybindings = {
# Adjust screen brightness
"Shift+F12" =
# Disable dynamic sleep
# https://github.com/rockowitz/ddcutil/issues/323
"exec ${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 + 30 && sleep 1; exec ${pkgs.ddcutil}/bin/ddcutil --disable-dynamic-sleep --display 2 setvcp 10 + 30";
"Shift+F11" = "exec ${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 - 30 && sleep 1; exec ${pkgs.ddcutil}/bin/ddcutil --disable-dynamic-sleep --display 2 setvcp 10 - 30";
"XF86MonBrightnessUp" = "exec ${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 + 30 && sleep 1; exec ${pkgs.ddcutil}/bin/ddcutil --disable-dynamic-sleep --display 2 setvcp 10 + 30";
"XF86MonBrightnessDown" = "exec ${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 - 30 && sleep 1; exec ${pkgs.ddcutil}/bin/ddcutil --disable-dynamic-sleep --display 2 setvcp 10 - 30";
# Media player controls
"XF86AudioPlay" = "exec ${pkgs.playerctl}/bin/playerctl play-pause";
"XF86AudioStop" = "exec ${pkgs.playerctl}/bin/playerctl stop";
"XF86AudioNext" = "exec ${pkgs.playerctl}/bin/playerctl next";
"XF86AudioPrev" = "exec ${pkgs.playerctl}/bin/playerctl previous";
# Launchers
"${modifier}+Return" = "exec --no-startup-id ${
config.home-manager.users.${config.user}.programs.rofi.terminal
}; workspace ${ws2}; layout tabbed";
"${modifier}+space" = "exec --no-startup-id ${config.launcherCommand}";
"${modifier}+Shift+s" = "exec --no-startup-id ${config.systemdSearch}";
"${modifier}+Shift+a" = "exec --no-startup-id ${config.audioSwitchCommand}";
"Mod1+Tab" = "exec --no-startup-id ${config.altTabCommand}";
"${modifier}+Shift+period" = "exec --no-startup-id ${config.powerCommand}";
"${modifier}+Shift+m" = "exec --no-startup-id ${config.brightnessCommand}";
"${modifier}+c" = "exec --no-startup-id ${config.calculatorCommand}";
"${modifier}+Shift+c" = "reload";
"${modifier}+Shift+r" = "restart";
"${modifier}+Shift+q" = ''exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'"'';
"${modifier}+Shift+x" = "exec ${lockCmd}";
"${modifier}+Mod1+h" = "exec --no-startup-id ${
config.home-manager.users.${config.user}.programs.rofi.terminal
} -e sh -c '${pkgs.home-manager}/bin/home-manager switch --flake ${config.dotfilesPath}#${config.networking.hostName} || read'";
"${modifier}+Mod1+r" = "exec --no-startup-id ${
config.home-manager.users.${config.user}.programs.rofi.terminal
} -e sh -c 'doas nixos-rebuild switch --flake ${config.dotfilesPath}#${config.networking.hostName} || read'";
# Window options
"${modifier}+q" = "kill";
"${modifier}+b" = "exec ${config.toggleBarCommand}";
"${modifier}+f" = "fullscreen toggle";
"${modifier}+h" = "focus left";
"${modifier}+j" = "focus down";
"${modifier}+k" = "focus up";
"${modifier}+l" = "focus right";
"${modifier}+Left" = "focus left";
"${modifier}+Down" = "focus down";
"${modifier}+Up" = "focus up";
"${modifier}+Right" = "focus right";
"${modifier}+Shift+h" = "move left";
"${modifier}+Shift+j" = "move down";
"${modifier}+Shift+k" = "move up";
"${modifier}+Shift+l" = "move right";
"${modifier}+Shift+Left" = "move left";
"${modifier}+Shift+Down" = "move down";
"${modifier}+Shift+Up" = "move up";
"${modifier}+Shift+Right" = "move right";
# Tiling
"${modifier}+i" = "split h";
"${modifier}+v" = "split v";
"${modifier}+s" = "layout stacking";
"${modifier}+t" = "layout tabbed";
"${modifier}+e" = "layout toggle split";
"${modifier}+Shift+space" = "floating toggle";
"${modifier}+Control+space" = "focus mode_toggle";
"${modifier}+a" = "focus parent";
# Workspaces
"${modifier}+1" = "workspace ${ws1}";
"${modifier}+2" = "workspace ${ws2}";
"${modifier}+3" = "workspace ${ws3}";
"${modifier}+4" = "workspace ${ws4}";
"${modifier}+5" = "workspace ${ws5}";
"${modifier}+6" = "workspace ${ws6}";
"${modifier}+7" = "workspace ${ws7}";
"${modifier}+8" = "workspace ${ws8}";
"${modifier}+9" = "workspace ${ws9}";
"${modifier}+0" = "workspace ${ws10}";
# Move windows
"${modifier}+Shift+1" = "move container to workspace ${ws1}; workspace ${ws1}";
"${modifier}+Shift+2" = "move container to workspace ${ws2}; workspace ${ws2}";
"${modifier}+Shift+3" = "move container to workspace ${ws3}; workspace ${ws3}";
"${modifier}+Shift+4" = "move container to workspace ${ws4}; workspace ${ws4}";
"${modifier}+Shift+5" = "move container to workspace ${ws5}; workspace ${ws5}";
"${modifier}+Shift+6" = "move container to workspace ${ws6}; workspace ${ws6}";
"${modifier}+Shift+7" = "move container to workspace ${ws7}; workspace ${ws7}";
"${modifier}+Shift+8" = "move container to workspace ${ws8}; workspace ${ws8}";
"${modifier}+Shift+9" = "move container to workspace ${ws9}; workspace ${ws9}";
"${modifier}+Shift+0" = "move container to workspace ${ws10}; workspace ${ws10}";
# Move screens
"${modifier}+Control+l" = "move workspace to output right";
"${modifier}+Control+h" = "move workspace to output left";
# Resizing
"${modifier}+r" = ''mode "resize"'';
"${modifier}+Control+Shift+h" = "resize shrink width 10 px or 10 ppt";
"${modifier}+Control+Shift+j" = "resize grow height 10 px or 10 ppt";
"${modifier}+Control+Shift+k" = "resize shrink height 10 px or 10 ppt";
"${modifier}+Control+Shift+l" = "resize grow width 10 px or 10 ppt";
};
modes = { };
startup = [
{
command = "feh --bg-fill ${config.wallpaper}";
always = true;
notification = false;
}
{
command = "i3-msg workspace ${ws2}, move workspace to output right";
notification = false;
}
{
command = "i3-msg workspace ${ws1}, move workspace to output left";
notification = false;
}
];
window = {
border = 0;
hideEdgeBorders = "smart";
titlebar = false;
};
workspaceAutoBackAndForth = false;
workspaceOutputAssign = [ ];
# gaps = {
# bottom = 8;
# top = 8;
# left = 8;
# right = 8;
# horizontal = 15;
# vertical = 15;
# inner = 15;
# outer = 0;
# smartBorders = "off";
# smartGaps = false;
# };
};
extraConfig = "";
};
programs.fish.functions = {
update-lock-screen = lib.mkIf config.services.xserver.enable {
description = "Update lockscreen with wallpaper";
body = lockUpdate;
};
};
# Update lock screen cache only if cache is empty
home.activation.updateLockScreenCache =
let
cacheDir = "${config.homePath}/.cache/betterlockscreen/current";
in
lib.mkIf config.services.xserver.enable (
config.home-manager.users.${config.user}.lib.dag.entryAfter [ "writeBoundary" ] ''
if [ ! -d ${cacheDir} ] || [ -z "$(ls ${cacheDir})" ]; then
$DRY_RUN_CMD ${lockUpdate}
fi
''
);
};
# Ref: https://github.com/betterlockscreen/betterlockscreen/blob/next/system/betterlockscreen%40.service
systemd.services.lock = {
enable = config.services.xserver.enable;
description = "Lock the screen on resume from suspend";
before = [
"sleep.target"
"suspend.target"
];
serviceConfig = {
User = config.user;
Type = "simple";
Environment = "DISPLAY=:0";
TimeoutSec = "infinity";
ExecStart = lockCmd;
ExecStartPost = "${pkgs.coreutils-full}/bin/sleep 1";
};
wantedBy = [
"sleep.target"
"suspend.target"
];
};
};
}

View File

@ -1,57 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf (pkgs.stdenv.isLinux && config.services.xserver.enable) {
home-manager.users.${config.user} = {
services.picom = {
enable = true;
backend = "glx";
settings = {
blur = false;
blurExclude = [ ];
inactiveDim = "0.05";
noDNDShadow = false;
noDockShadow = false;
# shadow-radius = 20
# '';
# shadow-radius = 20
# corner-radius = 10
# blur-size = 20
# rounded-corners-exclude = [
# "window_type = 'dock'",
# "class_g = 'i3-frame'"
# ]
# '';
};
fade = false;
inactiveOpacity = 1.0;
menuOpacity = 1.0;
opacityRules = [
"0:_NET_WM_STATE@[0]:32a = '_NET_WM_STATE_HIDDEN'" # Hide tabbed windows
];
shadow = false;
shadowExclude = [ ];
shadowOffsets = [
(-10)
(-10)
];
shadowOpacity = 0.5;
vSync = true;
};
xsession.windowManager.i3.config.startup = [
{
command = "systemctl --user restart picom";
always = true;
notification = false;
}
];
};
};
}

View File

@ -1,235 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf (pkgs.stdenv.isLinux && config.services.xserver.enable) {
toggleBarCommand = "polybar-msg cmd toggle";
home-manager.users.${config.user} = {
services.polybar = {
enable = true;
package = pkgs.polybar.override {
pulseSupport = true;
githubSupport = true;
i3Support = true;
};
script = "polybar &";
config = {
"bar/main" = {
bottom = false;
width = "100%";
height = "22pt";
radius = 0;
# offset-y = -5;
# offset-y = "5%";
# dpi = 96;
background = config.theme.colors.base01;
foreground = config.theme.colors.base05;
line-size = "3pt";
border-top-size = 0;
border-right-size = 0;
border-left-size = 0;
border-bottom-size = "4pt";
border-color = config.theme.colors.base00;
padding-left = 2;
padding-right = 2;
module-margin = 1;
modules-left = "i3";
modules-center = "xwindow";
modules-right = "mailcount network pulseaudio date power";
cursor-click = "pointer";
cursor-scroll = "ns-resize";
enable-ipc = true;
tray-position = "right";
# wm-restack = "generic";
# wm-restack = "bspwm";
# wm-restack = "i3";
# override-redirect = true;
};
"module/i3" =
let
padding = 2;
in
{
type = "internal/i3";
pin-workspaces = false;
show-urgent = true;
strip-wsnumbers = true;
index-sort = true;
enable-click = true;
wrapping-scroll = true;
fuzzy-match = true;
format = "<label-state> <label-mode>";
label-focused = "%name%";
label-focused-foreground = config.theme.colors.base01;
label-focused-background = config.theme.colors.base05;
label-focused-underline = config.theme.colors.base03;
label-focused-padding = padding;
label-unfocused = "%name%";
label-unfocused-padding = padding;
label-visible = "%name%";
label-visible-underline = config.theme.colors.base01;
label-visible-padding = padding;
label-urgent = "%name%";
label-urgent-foreground = config.theme.colors.base00;
label-urgent-background = config.theme.colors.base08;
label-urgent-underline = config.theme.colors.base0F;
label-urgent-padding = padding;
};
"module/xworkspaces" = {
type = "internal/xworkspaces";
label-active = "%name%";
label-active-background = config.theme.colors.base05;
label-active-foreground = config.theme.colors.base01;
label-active-underline = config.theme.colors.base03;
label-active-padding = 1;
label-occupied = "%name%";
label-occupied-padding = 1;
label-urgent = "%name%";
label-urgent-background = config.theme.colors.base08;
label-urgent-padding = 1;
label-empty = "%name%";
label-empty-foreground = config.theme.colors.base06;
label-empty-padding = 1;
};
"module/xwindow" = {
type = "internal/xwindow";
label = "%title:0:60:...%";
};
# "module/filesystem" = {
# type = "internal/fs";
# interval = 25;
# mount-0 = "/";
# label-mounted = "%{F#F0C674}%mountpoint%%{F-} %percentage_used%%";
# label-unmounted = "%mountpoint% not mounted";
# label-unmounted-foreground = colors.disabled;
# };
"module/mailcount" = {
type = "custom/script";
interval = 10;
format = "<label>";
exec = builtins.toString (
pkgs.writeShellScript "mailcount.sh" ''
${pkgs.notmuch}/bin/notmuch new --quiet 2>&1>/dev/null
UNREAD=$(
${pkgs.notmuch}/bin/notmuch count \
is:inbox and \
is:unread and \
folder:main/Inbox \
2>/dev/null
)
if [ $UNREAD = "0" ]; then
echo ""
else
echo "%{T2}%{T-} $UNREAD "
fi
''
);
click-left = "i3-msg 'exec --no-startup-id kitty --class aerc aerc'; sleep 0.15; i3-msg '[class=aerc] focus'";
};
"module/network" = {
type = "internal/network";
interface-type = "wired";
interval = 3;
accumulate-stats = true;
format-connected = "<label-connected>";
format-disconnected = "<label-disconnected>";
label-connected = "";
label-disconnected = "";
};
"module/pulseaudio" = {
type = "internal/pulseaudio";
# format-volume-prefix = "VOL ";
# format-volume-prefix-foreground = colors.primary;
format-volume = "<ramp-volume> <label-volume>";
# format-volume-background = colors.background;
# label-volume-background = colors.background;
format-volume-foreground = config.theme.colors.base04;
label-volume = "%percentage%%";
label-muted = "󰝟 ---";
label-muted-foreground = config.theme.colors.base03;
ramp-volume-0 = "";
ramp-volume-1 = "󰕾";
ramp-volume-2 = "";
click-right = config.audioSwitchCommand;
};
# "module/xkeyboard" = {
# type = "internal/xkeyboard";
# blacklist-0 = "num lock";
# label-layout = "%layout%";
# label-layout-foreground = colors.primary;
# label-indicator-padding = 2;
# label-indicator-margin = 1;
# label-indicator-foreground = colors.background;
# label-indicator-background = colors.secondary;
# };
# "module/memory" = {
# type = "internal/memory";
# interval = 2;
# format-prefix = "RAM ";
# format-prefix-foreground = colors.primary;
# label = "%percentage_used:2%%";
# };
# "module/cpu" = {
# type = "internal/cpu";
# interval = 2;
# format-prefix = "CPU ";
# format-prefix-foreground = colors.primary;
# label = "%percentage:2%%";
# };
# "network-base" = {
# type = "internal/network";
# interval = 5;
# format-connected = "<label-connected>";
# format-disconnected = "<label-disconnected>";
# label-disconnected = "%{F#F0C674}%ifname%%{F#707880} disconnected";
# };
# "module/wlan" = {
# "inherit" = "network-base";
# interface-type = "wireless";
# label-connected = "%{F#F0C674}%ifname%%{F-} %essid% %local_ip%";
# };
# "module/eth" = {
# "inherit" = "network-base";
# interface-type = "wired";
# label-connected = "%{F#F0C674}%ifname%%{F-} %local_ip%";
# };
"module/date" = {
type = "internal/date";
interval = 1;
date = "%d %b %l:%M %p";
date-alt = "%Y-%m-%d %H:%M:%S";
label = "%date%";
label-foreground = config.theme.colors.base06;
# format-background = colors.background;
};
"module/power" = {
type = "custom/text";
content = " ";
click-left = config.powerCommand;
click-right = "polybar-msg cmd restart";
content-foreground = config.theme.colors.base04;
};
"settings" = {
screenchange-reload = true;
pseudo-transparency = false;
};
};
};
xsession.windowManager.i3.config.startup = [
{
command = "pkill polybar; polybar -r main";
always = true;
notification = false;
}
];
};
};
}

View File

@ -18,165 +18,6 @@ in
config = lib.mkIf (pkgs.stdenv.isLinux && config.services.xserver.enable) {
home-manager.users.${config.user} = {
home.packages = with pkgs; [
jq # Required for rofi-systemd
];
programs.rofi = {
enable = true;
cycle = true;
location = "center";
pass = { };
terminal = lib.mkIf pkgs.stdenv.isLinux config.terminal;
plugins = [
pkgs.rofi-calc
pkgs.rofi-emoji
pkgs.rofi-systemd
];
theme =
let
inherit (config.home-manager.users.${config.user}.lib.formats.rasi) mkLiteral;
in
{
# Inspired by https://github.com/sherubthakur/dotfiles/blob/master/users/modules/desktop-environment/rofi/launcher.rasi
"*" = {
background-color = mkLiteral config.theme.colors.base00;
foreground-color = mkLiteral config.theme.colors.base07;
text-color = mkLiteral config.theme.colors.base07;
border-color = mkLiteral config.theme.colors.base04;
};
# Holds the entire window
"#window" = {
transparency = "real";
background-color = mkLiteral config.theme.colors.base00;
text-color = mkLiteral config.theme.colors.base07;
border = mkLiteral "4px";
border-color = mkLiteral config.theme.colors.base04;
border-radius = mkLiteral "4px";
width = mkLiteral "850px";
padding = mkLiteral "15px";
};
# Wrapper around bar and results
"#mainbox" = {
background-color = mkLiteral config.theme.colors.base00;
border = mkLiteral "0px";
border-radius = mkLiteral "0px";
border-color = mkLiteral config.theme.colors.base04;
children = map mkLiteral [
"inputbar"
"message"
"listview"
];
spacing = mkLiteral "10px";
padding = mkLiteral "10px";
};
# Unknown
"#textbox-prompt-colon" = {
expand = false;
str = ":";
margin = mkLiteral "0px 0.3em 0em 0em";
text-color = mkLiteral config.theme.colors.base07;
};
# Command prompt left of the input
"#prompt" = {
enabled = false;
};
# Actual text box
"#entry" = {
placeholder-color = mkLiteral config.theme.colors.base03;
expand = true;
horizontal-align = "0";
placeholder = "";
padding = mkLiteral "0px 0px 0px 5px";
blink = true;
};
# Top bar
"#inputbar" = {
children = map mkLiteral [
"prompt"
"entry"
];
border = mkLiteral "1px";
border-radius = mkLiteral "4px";
padding = mkLiteral "6px";
};
# Results
"#listview" = {
background-color = mkLiteral config.theme.colors.base00;
padding = mkLiteral "0px";
columns = 1;
lines = 12;
spacing = "5px";
cycle = true;
dynamic = true;
layout = "vertical";
};
# Each result
"#element" = {
orientation = "vertical";
border-radius = mkLiteral "0px";
padding = mkLiteral "5px 0px 5px 5px";
};
"#element.selected" = {
border = mkLiteral "1px";
border-radius = mkLiteral "4px";
border-color = mkLiteral config.theme.colors.base07;
background-color = mkLiteral config.theme.colors.base04;
text-color = mkLiteral config.theme.colors.base00;
};
"#element-text" = {
expand = true;
# horizontal-align = mkLiteral "0.5";
vertical-align = mkLiteral "0.5";
margin = mkLiteral "0px 2.5px 0px 2.5px";
};
"#element-text.selected" = {
background-color = mkLiteral config.theme.colors.base04;
text-color = mkLiteral config.theme.colors.base00;
};
# Not sure how to get icons
"#element-icon" = {
size = mkLiteral "18px";
border = mkLiteral "0px";
padding = mkLiteral "2px 5px 2px 2px";
background-color = mkLiteral config.theme.colors.base00;
};
"#element-icon.selected" = {
background-color = mkLiteral config.theme.colors.base04;
text-color = mkLiteral config.theme.colors.base00;
};
};
xoffset = 0;
yoffset = -20;
extraConfig = {
show-icons = true;
kb-cancel = "Escape,Super+space";
modi = "window,run,ssh,emoji,calc,systemd";
sort = true;
# levenshtein-sort = true;
};
};
home.file.".local/share/rofi/themes" = {
recursive = true;
source = ./rofi/themes;
};
};
launcherCommand = ''${rofi}/bin/rofi -modes drun -show drun -theme-str '@import "launcher.rasi"' '';
systemdSearch = "${pkgs.rofi-systemd}/bin/rofi-systemd";
altTabCommand = "${rofi}/bin/rofi -show window -modi window";

View File

@ -1,45 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf config.gui.enable {
# Enable touchpad support
services.libinput.enable = true;
# Enable the X11 windowing system.
services.xserver = {
enable = config.gui.enable;
# Login screen
displayManager = {
lightdm = {
enable = config.services.xserver.enable;
background = config.wallpaper;
# Show default user
# Also make sure /var/lib/AccountsService/users/<user> has SystemAccount=false
extraSeatDefaults = ''
greeter-hide-users = false
'';
};
};
};
environment.systemPackages = with pkgs; [
xclip # Clipboard
];
home-manager.users.${config.user} = {
programs.fish.shellAliases = {
pbcopy = "xclip -selection clipboard -in";
pbpaste = "xclip -selection clipboard -out";
};
};
};
}

View File

@ -1,82 +0,0 @@
{
config,
pkgs,
lib,
...
}:
let
# These micro-scripts change the volume while also triggering the volume
# notification widget
increaseVolume = pkgs.writeShellScriptBin "increaseVolume" ''
${pkgs.pamixer}/bin/pamixer -i 2
volume=$(${pkgs.pamixer}/bin/pamixer --get-volume)
${pkgs.volnoti}/bin/volnoti-show $volume
'';
decreaseVolume = pkgs.writeShellScriptBin "decreaseVolume" ''
${pkgs.pamixer}/bin/pamixer -d 2
volume=$(${pkgs.pamixer}/bin/pamixer --get-volume)
${pkgs.volnoti}/bin/volnoti-show $volume
'';
toggleMute = pkgs.writeShellScriptBin "toggleMute" ''
${pkgs.pamixer}/bin/pamixer --toggle-mute
mute=$(${pkgs.pamixer}/bin/pamixer --get-mute)
if [ "$mute" == "true" ]; then
${pkgs.volnoti}/bin/volnoti-show --mute
else
volume=$(${pkgs.pamixer}/bin/pamixer --get-volume)
${pkgs.volnoti}/bin/volnoti-show $volume
fi
'';
in
{
config = lib.mkIf (pkgs.stdenv.isLinux && config.gui.enable) {
# Enable PipeWire
services.pipewire = {
enable = true;
pulse.enable = true;
};
# Provides audio source with background noise filtered
programs.noisetorch.enable = true;
# These aren't necessary, but helpful for the user
environment.systemPackages = with pkgs; [
pamixer # Audio control
volnoti # Volume notifications
];
home-manager.users.${config.user} = {
# Graphical volume notifications
services.volnoti.enable = true;
xsession.windowManager.i3.config = {
# Make sure that Volnoti actually starts (home-manager doesn't start
# user daemon's automatically)
startup = [
{
command = "systemctl --user restart volnoti --alpha 0.15 --radius 40 --timeout 0.2";
always = true;
notification = false;
}
];
# i3 keybinds for changing the volume
keybindings = {
"XF86AudioRaiseVolume" = "exec --no-startup-id ${increaseVolume}/bin/increaseVolume";
"XF86AudioLowerVolume" = "exec --no-startup-id ${decreaseVolume}/bin/decreaseVolume";
"XF86AudioMute" = "exec --no-startup-id ${toggleMute}/bin/toggleMute";
# We can mute the mic by adding "--default-source"
"XF86AudioMicMute" = "exec --no-startup-id ${pkgs.pamixer}/bin/pamixer --default-source --toggle-mute";
};
};
};
};
}

View File

@ -1,53 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
boot.loader = lib.mkIf (config.physical && !config.server) {
grub = {
enable = true;
# Not sure what this does, but it involves the UEFI/BIOS
efiSupport = true;
# Check for other OSes and make them available
useOSProber = true;
# Attempt to display GRUB on widescreen monitor
gfxmodeEfi = "1920x1080";
# Limit the total number of configurations to rollback
configurationLimit = 25;
# Install GRUB onto the boot disk
# device = config.fileSystems."/boot".device;
# Don't install GRUB, required for UEFI?
device = "nodev";
# Display menu indefinitely if holding shift key
extraConfig = ''
if keystatus --shift ; then
set timeout=-1
else
set timeout=3
fi
'';
};
# Always display menu indefinitely; default is 5 seconds
# timeout = null;
# Allows GRUB to interact with the UEFI/BIOS I guess
efi.canTouchEfiVariables = true;
};
# Allow reading from Windows drives
boot.supportedFilesystems = lib.mkIf config.physical [ "ntfs" ];
# Use latest released Linux kernel by default
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
}

View File

@ -1,22 +0,0 @@
{ lib, ... }:
{
imports = [
./audio.nix
./boot.nix
./disk.nix
./keyboard.nix
./monitors.nix
./mouse.nix
./networking.nix
./server.nix
./sleep.nix
./wifi.nix
./zfs.nix
];
options = {
physical = lib.mkEnableOption "Whether this machine is a physical device.";
server = lib.mkEnableOption "Whether this machine is a server.";
};
}

View File

@ -1,7 +0,0 @@
{ config, lib, ... }:
{
# Enable fstrim, which tracks free space on SSDs for garbage collection
# More info: https://www.reddit.com/r/NixOS/comments/rbzhb1/if_you_have_a_ssd_dont_forget_to_enable_fstrim/
services.fstrim.enable = lib.mkIf config.physical true;
}

View File

@ -1,22 +0,0 @@
{
config,
lib,
modulesPath,
...
}:
{
# options.iso.enable = lib.mkEnableOption "Enable creating as an ISO.";
#
# imports = [ "${toString modulesPath}/installer/cd-dvd/iso-image.nix" ];
# config = lib.mkIf config.iso.enable {
#
# # EFI booting
# isoImage.makeEfiBootable = true;
#
# # USB booting
# isoImage.makeUsbBootable = true;
#
# };
}

View File

@ -1,42 +0,0 @@
{ config, lib, ... }:
{
config = lib.mkIf config.physical {
services.xserver = {
xkb.layout = "us";
# Keyboard responsiveness
autoRepeatDelay = 250;
autoRepeatInterval = 40;
};
# Swap Caps-Lock with Escape when pressed or LCtrl when held/combined with others
# Inspired by: https://www.youtube.com/watch?v=XuQVbZ0wENE
services.kanata = {
enable = true;
keyboards.default = {
devices = [
"/dev/input/by-id/usb-Logitech_Logitech_G710_Keyboard-event-kbd"
"/dev/input/by-id/usb-Logitech_Logitech_G710_Keyboard-if01-event-kbd"
];
extraDefCfg = "process-unmapped-keys yes";
config = ''
(defsrc
caps
)
(defalias
escctrl (tap-hold-press 1000 1000 esc lctrl)
)
(deflayer base
@escctrl
)
'';
};
};
# Enable num lock on login
home-manager.users.${config.user}.xsession.numlock.enable = true;
};
}

View File

@ -1,53 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf config.gui.enable {
environment.systemPackages = with pkgs; [
ddcutil # Monitor brightness control
];
# Reduce blue light at night
services.redshift = {
enable = true;
brightness = {
day = "1.0";
night = "1.0";
};
};
# Detect monitors (brightness) for ddcutil
hardware.i2c.enable = true;
# Grant main user access to external monitors
users.users.${config.user}.extraGroups = [ "i2c" ];
services.xserver.displayManager = {
# Put the login screen on the left monitor
lightdm.greeters.gtk.extraConfig = ''
active-monitor=0
'';
# Set up screen position and rotation
setupCommands = ''
${pkgs.xorg.xrandr}/bin/xrandr --output DisplayPort-1 \
--primary \
--rotate normal \
--mode 2560x1440 \
--rate 165 \
--output DisplayPort-2 \
--right-of DisplayPort-1 \
--rotate left \
--output DVI-0 --off \
--output DVI-1 --off \
|| echo "xrandr failed"
'';
};
};
}

View File

@ -1,35 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf config.gui.enable {
# Mouse customization
services.ratbagd.enable = true;
environment.systemPackages = with pkgs; [
libratbag # Mouse adjustments
piper # Mouse adjustments GUI
];
services.libinput.mouse = {
# Disable mouse acceleration
accelProfile = "flat";
accelSpeed = "1.15";
};
# Cursor
home-manager.users.${config.user}.home.pointerCursor = {
name = "Adwaita";
package = pkgs.adwaita-icon-theme;
size = 24;
gtk.enable = true;
x11.enable = true;
};
};
}

View File

@ -1,34 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf config.physical {
networking.useDHCP = !config.networking.networkmanager.enable;
networking.firewall.allowPing = lib.mkIf config.server true;
# DNS service discovery
services.avahi = {
enable = true;
domainName = "local";
ipv6 = false; # Should work either way
# Resolve local hostnames using Avahi DNS
nssmdns4 = true;
publish = {
enable = true;
addresses = true;
domain = true;
workstation = true;
};
};
environment.systemPackages = [
(pkgs.writeShellScriptBin "wake-tempest" "${pkgs.wakeonlan}/bin/wakeonlan --ip=192.168.1.255 74:56:3C:40:37:5D")
];
};
}

View File

@ -1,10 +0,0 @@
{ config, lib, ... }:
{
config = lib.mkIf config.server {
# Servers need a bootloader or they won't start
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
};
}

View File

@ -1,36 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf (config.physical && !config.server) {
# Use power button to sleep instead of poweroff
services.logind.powerKey = "suspend";
services.logind.powerKeyLongPress = "poweroff";
# Prevent wake from keyboard
powerManagement.powerDownCommands = ''
set +e
# Fix for Gigabyte motherboard
# /r/archlinux/comments/y7b97e/my_computer_wakes_up_immediately_after_i_suspend/isu99sr/
# Disable if enabled
if (grep "GPP0.*enabled" /proc/acpi/wakeup >/dev/null); then
echo GPP0 | ${pkgs.doas}/bin/doas tee /proc/acpi/wakeup
fi
sleep 2
set -e
'';
services.udev.extraRules = ''
ACTION=="add", SUBSYSTEM=="usb", DRIVER=="usb", ATTR{power/wakeup}="disabled"
ACTION=="add", SUBSYSTEM=="i2c", ATTR{power/wakeup}="disabled"
'';
};
}

View File

@ -1,17 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf (config.physical && pkgs.stdenv.isLinux) {
# Enables wireless support via wpa_supplicant.
networking.wireless.enable = !config.networking.networkmanager.enable;
# Allows the user to control the WiFi settings.
networking.wireless.userControlled.enable = true;
};
}

View File

@ -1,24 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
options = {
zfs.enable = lib.mkEnableOption "ZFS file system.";
};
config = lib.mkIf (config.server && config.zfs.enable) {
# Only use compatible Linux kernel, since ZFS can be behind
boot.kernelPackages = pkgs.linuxPackages; # Defaults to latest LTS
boot.kernelParams = [ "nohibernate" ];
boot.supportedFilesystems = [ "zfs" ];
services.prometheus.exporters.zfs.enable = config.prometheus.exporters.enable;
prometheus.scrapeTargets = [
"127.0.0.1:${builtins.toString config.services.prometheus.exporters.zfs.port}"
];
};
}

View File

@ -1,74 +0,0 @@
{ config, lib, ... }:
{
options = {
services.actualbudget = {
enable = lib.mkEnableOption "ActualBudget budgeting service";
port = lib.mkOption {
type = lib.types.port;
description = "Port to use for the localhost";
default = 5006;
};
};
};
config = lib.mkIf config.services.actualbudget.enable {
virtualisation.podman.enable = lib.mkDefault true;
users.users.actualbudget = {
isSystemUser = true;
group = "shared";
uid = 980;
};
# Create budget directory, allowing others to manage it
systemd.tmpfiles.rules = [
"d /var/lib/actualbudget 0770 actualbudget shared"
];
virtualisation.oci-containers.containers.actualbudget = {
workdir = null;
volumes = [ "/var/lib/actualbudget:/data" ];
user = "${toString (builtins.toString config.users.users.actualbudget.uid)}";
pull = "missing";
privileged = false;
ports = [ "127.0.0.1:${builtins.toString config.services.actualbudget.port}:5006" ];
networks = [ ];
log-driver = "journald";
labels = {
app = "actualbudget";
};
image = "ghcr.io/actualbudget/actual-server:25.1.0";
hostname = null;
environmentFiles = [ ];
environment = {
DEBUG = "actual:config"; # Enable debug logging
ACTUAL_TRUSTED_PROXIES = builtins.concatStringsSep "," [ "127.0.0.1" ];
};
dependsOn = [ ];
autoStart = true;
};
# Allow web traffic to Caddy
caddy.routes = [
{
match = [ { host = [ config.hostnames.budget ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.actualbudget.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.budget ];
# Backups
services.restic.backups.default.paths = [ "/var/lib/actualbudget" ];
};
}

View File

@ -1,285 +0,0 @@
{
config,
pkgs,
lib,
...
}:
let
# This config specifies ports for Prometheus to scrape information
arrConfig = {
radarr = {
exportarrPort = "9707";
url = "localhost:7878";
apiKey = config.secrets.radarrApiKey.dest;
};
readarr = {
exportarrPort = "9711";
url = "localhost:8787";
apiKey = config.secrets.readarrApiKey.dest;
};
sonarr = {
exportarrPort = "9708";
url = "localhost:8989";
apiKey = config.secrets.sonarrApiKey.dest;
};
prowlarr = {
exportarrPort = "9709";
url = "localhost:9696";
apiKey = config.secrets.prowlarrApiKey.dest;
};
sabnzbd = {
exportarrPort = "9710";
url = "localhost:8085";
apiKey = config.secrets.sabnzbdApiKey.dest;
};
};
in
{
options = {
arrs.enable = lib.mkEnableOption "Arr services";
};
config = lib.mkIf config.arrs.enable {
# Broken on 2024-12-07
# https://discourse.nixos.org/t/solved-sonarr-is-broken-in-24-11-unstable-aka-how-the-hell-do-i-use-nixpkgs-config-permittedinsecurepackages/
insecurePackages = [
"aspnetcore-runtime-wrapped-6.0.36"
"aspnetcore-runtime-6.0.36"
"dotnet-sdk-wrapped-6.0.428"
"dotnet-sdk-6.0.428"
];
services = {
bazarr = {
enable = true;
group = "shared";
};
jellyseerr.enable = true;
prowlarr.enable = true;
sabnzbd = {
enable = true;
group = "shared";
# The config file must be editable within the application
# It contains server configs and credentials
configFile = "/data/downloads/sabnzbd/sabnzbd.ini";
};
sonarr = {
enable = true;
group = "shared";
};
radarr = {
enable = true;
group = "shared";
};
readarr = {
enable = true;
group = "shared";
};
};
# Allows shared group to read/write the sabnzbd directory
users.users.sabnzbd.homeMode = "0770";
unfreePackages = [ "unrar" ]; # Required as a dependency for sabnzbd
# Requires updating the base_url config value in each service
# If you try to rewrite the URL, the service won't redirect properly
caddy.routes = [
{
# Group means that routes with the same name are mutually exclusive,
# so they are split between the appropriate services.
group = "download";
match = [
{
host = [ config.hostnames.download ];
path = [ "/sonarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
# We're able to reference the url and port of the service dynamically
upstreams = [ { dial = arrConfig.sonarr.url; } ];
}
];
}
{
group = "download";
match = [
{
host = [ config.hostnames.download ];
path = [ "/radarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = arrConfig.radarr.url; } ];
}
];
}
{
group = "download";
match = [
{
host = [ config.hostnames.download ];
path = [ "/readarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = arrConfig.readarr.url; } ];
}
];
}
{
group = "download";
match = [
{
host = [ config.hostnames.download ];
path = [ "/prowlarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
# Prowlarr doesn't offer a dynamic config, so we have to hardcode it
upstreams = [ { dial = "localhost:9696"; } ];
}
];
}
{
group = "download";
match = [
{
host = [ config.hostnames.download ];
path = [ "/bazarr*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [
{
# Bazarr only dynamically sets the port, not the host
dial = "localhost:${builtins.toString config.services.bazarr.listenPort}";
}
];
}
];
}
{
group = "download";
match = [
{
host = [ config.hostnames.download ];
path = [ "/sabnzbd*" ];
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = arrConfig.sabnzbd.url; } ];
}
];
}
{
group = "download";
match = [ { host = [ config.hostnames.download ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.jellyseerr.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.download ];
# Enable Prometheus exporters
systemd.services = lib.mapAttrs' (name: attrs: {
name = "prometheus-${name}-exporter";
value = {
description = "Export Prometheus metrics for ${name}";
after = [ "network.target" ];
wantedBy = [ "${name}.service" ];
serviceConfig = {
Type = "simple";
DynamicUser = true;
ExecStart =
let
# Sabnzbd doesn't accept the URI path, unlike the others
url = if name != "sabnzbd" then "http://${attrs.url}/${name}" else "http://${attrs.url}";
in
# Exportarr is trained to pull from the arr services
''
${pkgs.exportarr}/bin/exportarr ${name} \
--url ${url} \
--port ${attrs.exportarrPort}'';
EnvironmentFile = lib.mkIf (builtins.hasAttr "apiKey" attrs) attrs.apiKey;
Restart = "on-failure";
ProtectHome = true;
ProtectSystem = "strict";
PrivateTmp = true;
PrivateDevices = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
NoNewPrivileges = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
RemoveIPC = true;
PrivateMounts = true;
};
};
}) arrConfig;
# Secrets for Prometheus exporters
secrets.radarrApiKey = {
source = ../../../private/radarr-api-key.age;
dest = "/var/private/radarr-api";
prefix = "API_KEY=";
};
secrets.readarrApiKey = {
source = ../../../private/radarr-api-key.age;
dest = "/var/private/readarr-api";
prefix = "API_KEY=";
};
secrets.sonarrApiKey = {
source = ../../../private/sonarr-api-key.age;
dest = "/var/private/sonarr-api";
prefix = "API_KEY=";
};
secrets.prowlarrApiKey = {
source = ../../../private/prowlarr-api-key.age;
dest = "/var/private/prowlarr-api";
prefix = "API_KEY=";
};
secrets.sabnzbdApiKey = {
source = ../../../private/sabnzbd-api-key.age;
dest = "/var/private/sabnzbd-api";
prefix = "API_KEY=";
};
# Prometheus scrape targets (expose Exportarr to Prometheus)
prometheus.scrapeTargets = map (
key:
"127.0.0.1:${
lib.attrsets.getAttrFromPath [
key
"exportarrPort"
] arrConfig
}"
) (builtins.attrNames arrConfig);
};
}

View File

@ -1,29 +0,0 @@
{ config, lib, ... }:
{
config = lib.mkIf config.services.audiobookshelf.enable {
services.audiobookshelf = {
group = "shared";
dataDir = "audiobookshelf";
};
# Allow web traffic to Caddy
caddy.routes = [
{
match = [ { host = [ config.hostnames.audiobooks ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.audiobookshelf.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.audiobooks ];
};
}

View File

@ -1,88 +0,0 @@
# Bind is a DNS service. This allows me to resolve public domains locally so
# when I'm at home, I don't have to travel over the Internet to reach my
# server.
# To set this on all home machines, I point my router's DNS resolver to the
# local IP address of the machine running this service (swan).
{
config,
pkgs,
lib,
...
}:
let
localIp = "192.168.1.218";
localServices = [
config.hostnames.stream
config.hostnames.content
config.hostnames.books
config.hostnames.download
config.hostnames.photos
];
mkRecord = service: "${service} A ${localIp}";
localRecords = lib.concatLines (map mkRecord localServices);
in
{
config = lib.mkIf config.services.bind.enable {
# Normally I block all requests not coming from Cloudflare, so I have to also
# allow my local network.
caddy.cidrAllowlist = [ "192.168.0.0/16" ];
services.bind = {
# Allow requests coming from these IPs. This way I don't somehow get
# spammed with DNS requests coming from the Internet.
cacheNetworks = [
"127.0.0.0/24"
"192.168.0.0/16"
"::1/128" # Required because IPv6 loopback now added to resolv.conf
# (see: https://github.com/NixOS/nixpkgs/pull/302228)
];
# When making normal DNS requests, forward them to Cloudflare to resolve.
forwarders = [
"1.1.1.1"
"1.0.0.1"
];
ipv4Only = false;
# Use rpz zone as an override
extraOptions = ''response-policy { zone "rpz"; };'';
zones = {
rpz = {
master = true;
file = pkgs.writeText "db.rpz" ''
$TTL 60 ; 1 minute
@ IN SOA localhost. root.localhost. (
2023071800 ; serial
1h ; refresh
30m ; retry
1w ; expire
30m ; minimum ttl
)
IN NS localhost.
localhost A 127.0.0.1
${localRecords}
'';
};
};
};
# We must allow DNS traffic to hit our machine as well
networking.firewall.allowedTCPPorts = [ 53 ];
networking.firewall.allowedUDPPorts = [ 53 ];
# Set our own nameservers to ourselves
networking.nameservers = [
"127.0.0.1"
"::1"
];
};
}

View File

@ -1,223 +0,0 @@
# Caddy is a reverse proxy, like Nginx or Traefik. This creates an ingress
# point from my local network or the public (via Cloudflare). Instead of a
# Caddyfile, I'm using the more expressive JSON config file format. This means
# I can source routes from other areas in my config and build the JSON file
# using the result of the expression.
# Caddy helpfully provides automatic ACME cert generation and management, but
# it requires a form of validation. We are using a custom build of Caddy
# (compiled with an overlay) to insert a plugin for managing DNS validation
# with Cloudflare's DNS API.
{
config,
pkgs,
lib,
...
}:
{
options = {
caddy = {
tlsPolicies = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Caddy JSON TLS policies";
default = [ ];
};
routes = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Caddy JSON routes for http servers";
default = [ ];
};
blocks = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Caddy JSON error blocks for http servers";
default = [ ];
};
cidrAllowlist = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "CIDR blocks to allow for requests";
default = [ ];
};
};
};
config = lib.mkIf config.services.caddy.enable {
# Force Caddy to 403 if not coming from allowlisted source
caddy.cidrAllowlist = [ "127.0.0.1/32" ];
caddy.routes = lib.mkBefore [
{
match = [ { not = [ { remote_ip.ranges = config.caddy.cidrAllowlist; } ]; } ];
handle = [
{
handler = "static_response";
status_code = "403";
}
];
}
];
services.caddy =
let
default_logger_name = "other";
roll_size_mb = 25;
# Extract list of hostnames (fqdns) from current caddy routes
getHostnameFromMatch = match: if (lib.hasAttr "host" match) then match.host else [ ];
getHostnameFromRoute =
route:
if (lib.hasAttr "match" route) then (lib.concatMap getHostnameFromMatch route.match) else [ ];
hostnames_non_unique = lib.concatMap getHostnameFromRoute config.caddy.routes;
hostnames = lib.unique hostnames_non_unique;
# Create attrset of subdomains to their fqdns
hostname_map = builtins.listToAttrs (
map (hostname: {
name = builtins.head (lib.splitString "." hostname);
value = hostname;
}) hostnames
);
in
{
adapter = "''"; # Required to enable JSON
configFile = pkgs.writeText "Caddyfile" (
builtins.toJSON {
apps.http.servers.main = {
listen = [ ":443" ];
# These routes are pulled from the rest of this repo
routes = config.caddy.routes;
errors.routes = config.caddy.blocks;
# Uncommenting collects access logs
logs = {
inherit default_logger_name;
# Invert hostnames keys and values
logger_names = lib.mapAttrs' (name: value: {
name = value;
value = name;
}) hostname_map;
};
};
apps.http.servers.metrics = { }; # Enables Prometheus metrics
apps.tls.automation.policies = config.caddy.tlsPolicies;
# Setup logging to journal and files
logging.logs =
{
# System logs and catch-all
# Must be called `default` to override Caddy's built-in default logger
default = {
level = "INFO";
encoder.format = "console";
writer = {
output = "stderr";
};
exclude = (map (hostname: "http.log.access.${hostname}") (builtins.attrNames hostname_map)) ++ [
"http.log.access.${default_logger_name}"
];
};
# This is for the default access logs (anything not captured by hostname)
other = {
level = "INFO";
encoder.format = "json";
writer = {
output = "file";
filename = "${config.services.caddy.logDir}/other.log";
roll = true;
inherit roll_size_mb;
};
include = [ "http.log.access.${default_logger_name}" ];
};
# This is for using the Caddy API, which will probably never happen
admin = {
level = "INFO";
encoder.format = "json";
writer = {
output = "file";
filename = "${config.services.caddy.logDir}/admin.log";
roll = true;
inherit roll_size_mb;
};
include = [ "admin" ];
};
# This is for TLS cert management tracking
tls = {
level = "INFO";
encoder.format = "json";
writer = {
output = "file";
filename = "${config.services.caddy.logDir}/tls.log";
roll = true;
inherit roll_size_mb;
};
include = [ "tls" ];
};
# This is for debugging
debug = {
level = "DEBUG";
encoder.format = "json";
writer = {
output = "file";
filename = "${config.services.caddy.logDir}/debug.log";
roll = true;
roll_keep = 1;
inherit roll_size_mb;
};
};
}
# These are the access logs for individual hostnames
// (lib.mapAttrs (name: value: {
level = "INFO";
encoder.format = "json";
writer = {
output = "file";
filename = "${config.services.caddy.logDir}/${name}-access.log";
roll = true;
inherit roll_size_mb;
};
include = [ "http.log.access.${name}" ];
}) hostname_map)
# We also capture just the errors separately for easy debugging
// (lib.mapAttrs' (name: value: {
name = "${name}-error";
value = {
level = "ERROR";
encoder.format = "json";
writer = {
output = "file";
filename = "${config.services.caddy.logDir}/${name}-error.log";
roll = true;
inherit roll_size_mb;
};
include = [ "http.log.access.${name}" ];
};
}) hostname_map);
}
);
};
systemd.services.caddy.serviceConfig = {
# Allows Caddy to serve lower ports (443, 80)
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
# Prevent flooding of logs by rate-limiting
LogRateLimitIntervalSec = "5s"; # Limit period
LogRateLimitBurst = 100; # Limit threshold
};
# Required for web traffic to reach this machine
networking.firewall.allowedTCPPorts = [
80
443
];
# HTTP/3 QUIC uses UDP (not sure if being used)
networking.firewall.allowedUDPPorts = [ 443 ];
# Caddy exposes Prometheus metrics with the admin API
# https://caddyserver.com/docs/api
prometheus.scrapeTargets = [ "127.0.0.1:2019" ];
};
}

View File

@ -1,92 +0,0 @@
# Calibre-web is an E-Book library and management tool.
# - Exposed to the public via Caddy.
# - Hostname defined with config.hostnames.books
# - File directory backed up to S3 on a cron schedule.
{
config,
pkgs,
lib,
...
}:
let
libraryPath = "/data/books";
in
{
options = {
backups.calibre = lib.mkOption {
type = lib.types.bool;
description = "Whether to backup Calibre library";
default = true;
};
};
config = lib.mkIf config.services.calibre-web.enable {
services.calibre-web = {
group = "shared";
openFirewall = true;
options = {
reverseProxyAuth.enable = false;
enableBookConversion = true;
enableBookUploading = true;
calibreLibrary = libraryPath;
};
};
# Allow web traffic to Caddy
caddy.routes = [
{
match = [ { host = [ config.hostnames.books ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [
{ dial = "localhost:${builtins.toString config.services.calibre-web.listen.port}"; }
];
# This is required when calibre-web is behind a reverse proxy
# https://github.com/janeczku/calibre-web/issues/19
headers.request.add."X-Script-Name" = [ "/calibre-web" ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.books ];
# Grant user access to Calibre directories
users.users.${config.user}.extraGroups = [ "calibre-web" ];
# Run a backup on a schedule
systemd.timers.calibre-backup = lib.mkIf config.backups.calibre {
timerConfig = {
OnCalendar = "*-*-* 00:00:00"; # Once per day
Unit = "calibre-backup.service";
};
wantedBy = [ "timers.target" ];
};
# Backup Calibre data to object storage
systemd.services.calibre-backup = lib.mkIf config.backups.calibre {
description = "Backup Calibre data";
environment.AWS_ACCESS_KEY_ID = config.backup.s3.accessKeyId;
serviceConfig = {
Type = "oneshot";
User = "calibre-web";
Group = "backup";
EnvironmentFile = config.secrets.backup.dest;
};
script = ''
${pkgs.awscli2}/bin/aws s3 sync \
${libraryPath}/ \
s3://${config.backup.s3.bucket}/calibre/ \
--endpoint-url=https://${config.backup.s3.endpoint}
'';
};
};
}

View File

@ -1,99 +0,0 @@
# Cloudflare Tunnel is a service for accessing the network even behind a
# firewall, through outbound-only requests. It works by installing an agent on
# our machines that exposes services through Cloudflare Access (Zero Trust),
# similar to something like Tailscale.
# In this case, we're using Cloudflare Tunnel to enable SSH access over a web
# browser even when outside of my network. This is probably not the safest
# choice but I feel comfortable enough with it anyway.
{ config, lib, ... }:
# First time setup:
# nix-shell -p cloudflared
# cloudflared tunnel login
# cloudflared tunnel create <host>
# nix run github:nmasur/dotfiles#encrypt-secret > private/cloudflared-<host>.age
# Paste ~/.cloudflared/<id>.json
# Set tunnel.id = "<id>"
# Remove ~/.cloudflared/
# For SSH access:
# Cloudflare Zero Trust -> Access -> Applications -> Create Application
# Service Auth -> SSH -> Select Application -> Generate Certificate
# Set ca = "<public key>"
{
options.cloudflareTunnel = {
enable = lib.mkEnableOption "Use Cloudflare Tunnel";
id = lib.mkOption {
type = lib.types.str;
description = "Cloudflare tunnel ID";
};
credentialsFile = lib.mkOption {
type = lib.types.path;
description = "Cloudflare tunnel credentials file (age-encrypted)";
};
ca = lib.mkOption {
type = lib.types.str;
description = "Cloudflare tunnel CA public key";
};
};
config = lib.mkIf config.cloudflareTunnel.enable {
services.cloudflared = {
enable = true;
tunnels = {
"${config.cloudflareTunnel.id}" = {
credentialsFile = config.secrets.cloudflared.dest;
# Catch-all if no match (should never happen anyway)
default = "http_status:404";
# Match from ingress of any valid server name to SSH access
ingress = {
"*.masu.rs" = "ssh://localhost:22";
};
};
};
};
# Grant Cloudflare access to SSH into this server
environment.etc = {
"ssh/ca.pub".text = ''
${config.cloudflareTunnel.ca}
'';
# Must match the username portion of the email address in Cloudflare
# Access
"ssh/authorized_principals".text = ''
${config.user}
'';
};
# Adjust SSH config to allow access from Cloudflare's certificate
services.openssh.extraConfig = ''
PubkeyAuthentication yes
TrustedUserCAKeys /etc/ssh/ca.pub
Match User '${config.user}'
AuthorizedPrincipalsFile /etc/ssh/authorized_principals
# if there is no existing AuthenticationMethods
AuthenticationMethods publickey
'';
services.openssh.settings.Macs = [ "hmac-sha2-512" ]; # Fix for failure to find matching mac
# Create credentials file for Cloudflare
secrets.cloudflared = {
source = config.cloudflareTunnel.credentialsFile;
dest = "${config.secretsDirectory}/cloudflared";
owner = "cloudflared";
group = "cloudflared";
permissions = "0440";
};
systemd.services.cloudflared-secret = {
requiredBy = [ "cloudflared-tunnel-${config.cloudflareTunnel.id}.service" ];
before = [ "cloudflared-tunnel-${config.cloudflareTunnel.id}.service" ];
};
};
}

View File

@ -153,39 +153,5 @@ in
requires = [ "cloudflare-api-secret.service" ];
};
# Run a second copy of dyn-dns for non-proxied domains
# Adapted from: https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/services/networking/cloudflare-dyndns.nix
systemd.services.cloudflare-dyndns-noproxy =
lib.mkIf ((builtins.length config.cloudflare.noProxyDomains) > 0)
{
description = "CloudFlare Dynamic DNS Client (no proxy)";
after = [
"network.target"
"cloudflare-api-secret.service"
];
requires = [ "cloudflare-api-secret.service" ];
wantedBy = [ "multi-user.target" ];
startAt = "*:0/5";
environment = {
CLOUDFLARE_DOMAINS = toString config.cloudflare.noProxyDomains;
};
serviceConfig = {
Type = "simple";
DynamicUser = true;
StateDirectory = "cloudflare-dyndns-noproxy";
EnvironmentFile = config.services.cloudflare-dyndns.apiTokenFile;
ExecStart =
let
args =
[ "--cache-file /var/lib/cloudflare-dyndns-noproxy/ip.cache" ]
++ (if config.services.cloudflare-dyndns.ipv4 then [ "-4" ] else [ "-no-4" ])
++ (if config.services.cloudflare-dyndns.ipv6 then [ "-6" ] else [ "-no-6" ])
++ lib.optional config.services.cloudflare-dyndns.deleteMissing "--delete-missing";
in
"${pkgs.cloudflare-dyndns}/bin/cloudflare-dyndns ${toString args}";
};
};
};
}

View File

@ -1,68 +0,0 @@
{
config,
pkgs,
lib,
...
}:
let
dataDir = "/var/lib/filebrowser";
settings = {
port = 8020;
baseURL = "";
address = "";
log = "stdout";
database = "${dataDir}/filebrowser.db";
root = "";
"auth.method" = "json";
username = config.user;
# Generate password: htpasswd -nBC 10 "" | tr -d ':\n'
password = "$2y$10$ze1cMob0k6pnXRjLowYfZOVZWg4G.dsPtH3TohbUeEbI0sdkG9.za";
};
in
{
options.filebrowser.enable = lib.mkEnableOption "Use Filebrowser.";
config = lib.mkIf config.filebrowser.enable {
environment.etc."filebrowser/.filebrowser.json".text = builtins.toJSON settings;
systemd.services.filebrowser = lib.mkIf config.filebrowser.enable {
description = "Filebrowser cloud file services";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
startLimitIntervalSec = 14400;
startLimitBurst = 10;
serviceConfig = {
ExecStart = "${pkgs.filebrowser}/bin/filebrowser";
DynamicUser = true;
Group = "shared";
ReadWritePaths = [ dataDir ];
StateDirectory = [ "filebrowser" ];
Restart = "on-failure";
RestartPreventExitStatus = 1;
RestartSec = "5s";
};
path = [ pkgs.getent ]; # Fix: getent not found in $PATH
};
caddy.routes = [
{
match = [ { host = [ config.hostnames.files ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString settings.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.files ];
};
}

View File

@ -1,65 +0,0 @@
# Gitea Actions is a CI/CD service for the Gitea source code server, meaning it
# allows us to run code operations (such as testing or deploys) when our git
# repositories are updated. Any machine can act as a Gitea Action Runner, so
# the Runners don't necessarily need to be running Gitea. All we need is an API
# key for Gitea to connect to it and register ourselves as a Runner.
{
config,
pkgs,
lib,
...
}:
{
options.giteaRunner.enable = lib.mkEnableOption "Enable Gitea Actions runner.";
config = lib.mkIf config.giteaRunner.enable {
services.gitea-actions-runner.instances.${config.networking.hostName} = {
enable = true;
labels = [
# Provide a Debian base with NodeJS for actions
# "debian-latest:docker://node:18-bullseye"
# Fake the Ubuntu name, because Node provides no Ubuntu builds
# "ubuntu-latest:docker://node:18-bullseye"
# Provide native execution on the host using below packages
"native:host"
];
hostPackages = with pkgs; [
bash
coreutils
curl
gawk
gitMinimal
gnused
nodejs
wget
];
name = config.networking.hostName;
url = "https://${config.hostnames.git}";
tokenFile = config.secrets.giteaRunnerToken.dest;
};
# Make sure the runner doesn't start until after Gitea
systemd.services."gitea-runner-${config.networking.hostName}".after = [ "gitea.service" ];
# API key needed to connect to Gitea
secrets.giteaRunnerToken = {
source = ../../../private/gitea-runner-token.age; # TOKEN=xyz
dest = "${config.secretsDirectory}/gitea-runner-token";
};
systemd.services.giteaRunnerToken-secret = {
requiredBy = [
"gitea-runner-${
config.services.gitea-actions-runner.instances.${config.networking.hostName}.name
}.service"
];
before = [
"gitea-runner-${
config.services.gitea-actions-runner.instances.${config.networking.hostName}.name
}.service"
];
};
};
}

View File

@ -1,153 +0,0 @@
{
config,
pkgs,
lib,
...
}:
let
giteaPath = "/var/lib/gitea"; # Default service directory
in
{
config = lib.mkIf config.services.gitea.enable {
services.gitea = {
database.type = "sqlite3";
settings = {
actions.ENABLED = true;
metrics.ENABLED = true;
repository = {
# Pushing to a repo that doesn't exist automatically creates one as
# private.
DEFAULT_PUSH_CREATE_PRIVATE = true;
# Allow git over HTTP.
DISABLE_HTTP_GIT = false;
# Allow requests hitting the specified hostname.
ACCESS_CONTROL_ALLOW_ORIGIN = config.hostnames.git;
# Automatically create viable users/orgs on push.
ENABLE_PUSH_CREATE_USER = true;
ENABLE_PUSH_CREATE_ORG = true;
# Default when creating new repos.
DEFAULT_BRANCH = "main";
};
server = {
HTTP_PORT = 3001;
HTTP_ADDRESS = "127.0.0.1";
ROOT_URL = "https://${config.hostnames.git}/";
SSH_PORT = 22;
START_SSH_SERVER = false; # Use sshd instead
DISABLE_SSH = false;
};
# Don't allow public users to register accounts.
service.DISABLE_REGISTRATION = true;
# Force using HTTPS for all session access.
session.COOKIE_SECURE = true;
# Hide users' emails.
ui.SHOW_USER_EMAIL = false;
};
extraConfig = null;
};
users.users.${config.user}.extraGroups = [ "gitea" ];
caddy.routes = [
# Prevent public access to Prometheus metrics.
{
match = [
{
host = [ config.hostnames.git ];
path = [ "/metrics*" ];
}
];
handle = [
{
handler = "static_response";
status_code = "403";
}
];
}
# Allow access to primary server.
{
match = [ { host = [ config.hostnames.git ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [
{ dial = "localhost:${builtins.toString config.services.gitea.settings.server.HTTP_PORT}"; }
];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.git ];
# Scrape the metrics endpoint for Prometheus.
prometheus.scrapeTargets = [
"127.0.0.1:${builtins.toString config.services.gitea.settings.server.HTTP_PORT}"
];
## Backup config
# Open to groups, allowing for backups
systemd.services.gitea.serviceConfig.StateDirectoryMode = lib.mkForce "0770";
systemd.tmpfiles.rules = [ "f ${giteaPath}/data/gitea.db 0660 gitea gitea" ];
# Allow litestream and gitea to share a sqlite database
users.users.litestream.extraGroups = [ "gitea" ];
users.users.gitea.extraGroups = [ "litestream" ];
# Backup sqlite database with litestream
services.litestream = {
settings = {
dbs = [
{
path = "${giteaPath}/data/gitea.db";
replicas = [ { url = "s3://${config.backup.s3.bucket}.${config.backup.s3.endpoint}/gitea"; } ];
}
];
};
};
# Don't start litestream unless gitea is up
systemd.services.litestream = {
after = [ "gitea.service" ];
requires = [ "gitea.service" ];
};
# Run a repository file backup on a schedule
systemd.timers.gitea-backup = lib.mkIf (config.backup.s3.endpoint != null) {
timerConfig = {
OnCalendar = "*-*-* 00:00:00"; # Once per day
Unit = "gitea-backup.service";
};
wantedBy = [ "timers.target" ];
};
# Backup Gitea repos to object storage
systemd.services.gitea-backup = lib.mkIf (config.backup.s3.endpoint != null) {
description = "Backup Gitea data";
environment.AWS_ACCESS_KEY_ID = config.backup.s3.accessKeyId;
serviceConfig = {
Type = "oneshot";
User = "gitea";
Group = "backup";
EnvironmentFile = config.secrets.backup.dest;
};
script = ''
${pkgs.awscli2}/bin/aws s3 sync --exclude */gitea.db* \
${giteaPath}/ \
s3://${config.backup.s3.bucket}/gitea-data/ \
--endpoint-url=https://${config.backup.s3.endpoint}
'';
};
};
}

View File

@ -1,25 +0,0 @@
# GPG is an encryption tool. This isn't really in use for me at the moment.
{
config,
pkgs,
lib,
...
}:
{
options.gpg.enable = lib.mkEnableOption "GnuPG encryption.";
config.home-manager.users.${config.user} = lib.mkIf config.gpg.enable {
programs.gpg.enable = true;
services.gpg-agent = {
enable = true;
defaultCacheTtl = 86400; # Resets when used
defaultCacheTtlSsh = 86400; # Resets when used
maxCacheTtl = 34560000; # Can never reset
maxCacheTtlSsh = 34560000; # Can never reset
pinentryFlavor = "tty";
};
home = lib.mkIf config.gui.enable { packages = with pkgs; [ pinentry ]; };
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
{ config, ... }:
{
# Wait for secret to be placed on the machine
systemd.services.wait-for-identity = {
description = "Wait until identity file exists on the machine";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
for i in $(seq 1 10); do
if [ -f ${config.identityFile} ]; then
echo "Identity file found."
exit 0
fi
sleep 6
done
'';
};
}

View File

@ -1,48 +0,0 @@
{ config, lib, ... }:
{
config = lib.mkIf config.services.immich.enable {
services.immich = {
port = 2283;
group = "shared";
database.enable = true;
redis.enable = true;
machine-learning.enable = true;
machine-learning.environment = { };
mediaLocation = "/data/images";
secretsFile = null;
settings.server.externalDomain = "https://${config.hostnames.photos}";
environment = {
IMMICH_ENV = "production";
IMMICH_LOG_LEVEL = "log";
NO_COLOR = "false";
IMMICH_TRUSTED_PROXIES = "127.0.0.1";
};
};
caddy.routes = [
{
match = [ { host = [ config.hostnames.photos ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.immich.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.photos ];
# Point localhost to the local domain
networking.hosts."127.0.0.1" = [ config.hostnames.photos ];
# Backups
services.restic.backups.default.paths = [ "/data/images" ];
};
}

View File

@ -1,65 +0,0 @@
# InfluxDB is a timeseries database similar to Prometheus. While
# VictoriaMetrics can also act as an InfluxDB, this version of it allows for
# infinite retention separate from our other metrics, which can be nice for
# recording health information, for example.
{ config, lib, ... }:
{
config = lib.mkIf config.services.influxdb2.enable {
services.influxdb2 = {
provision = {
enable = true;
initialSetup = {
bucket = "default";
organization = "main";
passwordFile = config.secrets.influxdb2Password.dest;
retention = 0; # Keep data forever
tokenFile = config.secrets.influxdb2Token.dest;
username = "admin";
};
};
settings = { };
};
# Create credentials file for InfluxDB admin
secrets.influxdb2Password = lib.mkIf config.services.influxdb2.enable {
source = ../../../private/influxdb2-password.age;
dest = "${config.secretsDirectory}/influxdb2-password";
owner = "influxdb2";
group = "influxdb2";
permissions = "0440";
};
systemd.services.influxdb2Password-secret = lib.mkIf config.services.influxdb2.enable {
requiredBy = [ "influxdb2.service" ];
before = [ "influxdb2.service" ];
};
secrets.influxdb2Token = lib.mkIf config.services.influxdb2.enable {
source = ../../../private/influxdb2-token.age;
dest = "${config.secretsDirectory}/influxdb2-token";
owner = "influxdb2";
group = "influxdb2";
permissions = "0440";
};
systemd.services.influxdb2Token-secret = lib.mkIf config.services.influxdb2.enable {
requiredBy = [ "influxdb2.service" ];
before = [ "influxdb2.service" ];
};
caddy.routes = lib.mkIf config.services.influxdb2.enable [
{
match = [ { host = [ config.hostnames.influxdb ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:8086"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.influxdb ];
};
}

View File

@ -1,35 +0,0 @@
{ config, lib, ... }:
{
config = lib.mkIf config.services.thelounge.enable {
services.thelounge = {
public = false;
port = 9000;
extraConfig = {
reverseProxy = true;
maxHistory = 10000;
};
};
# Adding new users:
# nix shell nixpkgs#thelounge
# sudo su - thelounge -s /bin/sh -c "thelounge add myuser"
# Allow web traffic to Caddy
caddy.routes = [
{
match = [ { host = [ config.hostnames.irc ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.thelounge.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.irc ];
};
}

View File

@ -1,79 +0,0 @@
# Jellyfin is a self-hosted video streaming service. This means I can play my
# server's videos from a webpage, mobile app, or TV client.
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf config.services.jellyfin.enable {
services.jellyfin.group = "shared";
users.users.jellyfin = {
isSystemUser = true;
};
caddy.routes = [
# Prevent public access to Prometheus metrics.
{
match = [
{
host = [ config.hostnames.stream ];
path = [ "/metrics*" ];
}
];
handle = [
{
handler = "static_response";
status_code = "403";
}
];
}
# Allow access to normal route.
{
match = [ { host = [ config.hostnames.stream ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:8096"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.stream ];
# Create videos directory, allow anyone in Jellyfin group to manage it
systemd.tmpfiles.rules = [
"d /var/lib/jellyfin 0775 jellyfin shared"
"d /var/lib/jellyfin/library 0775 jellyfin shared"
];
# Enable VA-API for hardware transcoding
hardware.graphics = {
enable = true;
extraPackages = [ pkgs.libva ];
};
environment.systemPackages = [ pkgs.libva-utils ];
environment.variables = {
# VAAPI and VDPAU config for accelerated video.
# See https://wiki.archlinux.org/index.php/Hardware_video_acceleration
"VDPAU_DRIVER" = "radeonsi";
"LIBVA_DRIVER_NAME" = "radeonsi";
};
users.users.jellyfin.extraGroups = [
"render"
"video"
]; # Access to /dev/dri
# Fix issue where Jellyfin-created directories don't allow access for media group
systemd.services.jellyfin.serviceConfig.UMask = lib.mkForce "0007";
# Requires MetricsEnable is true in /var/lib/jellyfin/config/system.xml
prometheus.scrapeTargets = [ "127.0.0.1:8096" ];
};
}

View File

@ -1,40 +0,0 @@
# Keybase is an encrypted communications tool with a synchronized encrypted
# filestore that can be mounted onto a machine's filesystem.
{
config,
pkgs,
lib,
...
}:
{
options.keybase.enable = lib.mkEnableOption "Keybase.";
config = lib.mkIf config.keybase.enable {
home-manager.users.${config.user} = lib.mkIf config.keybase.enable {
services.keybase.enable = true;
services.kbfs = {
enable = true;
mountPoint = "keybase";
};
# https://github.com/nix-community/home-manager/issues/4722
systemd.user.services.kbfs.Service.PrivateTmp = lib.mkForce false;
home.packages = [ (lib.mkIf config.gui.enable pkgs.keybase-gui) ];
home.file =
let
ignorePatterns = ''
keybase/
kbfs/'';
in
{
".rgignore".text = ignorePatterns;
".fdignore".text = ignorePatterns;
};
};
};
}

View File

@ -1,18 +0,0 @@
# Mullvad is a VPN service. This isn't currently in use for me at the moment.
{
config,
pkgs,
lib,
...
}:
{
options.mullvad.enable = lib.mkEnableOption "Mullvad VPN.";
config = lib.mkIf config.mullvad.enable {
services.mullvad-vpn.enable = true;
environment.systemPackages = [ pkgs.mullvad-vpn ];
};
}

View File

@ -1,40 +0,0 @@
# n8n is an automation integration tool for connecting data from services
# together with triggers.
{ config, lib, ... }:
{
config = lib.mkIf config.services.n8n.enable {
unfreePackages = [ "n8n" ];
services.n8n = {
webhookUrl = "https://${config.hostnames.n8n}";
settings = {
listen_address = "127.0.0.1";
port = 5678;
};
};
systemd.services.n8n.environment = {
N8N_EDITOR_BASE_URL = config.services.n8n.webhookUrl;
};
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.n8n ];
# Allow web traffic to Caddy
caddy.routes = [
{
match = [ { host = [ config.hostnames.n8n ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.n8n.settings.port}"; } ];
}
];
}
];
};
}

View File

@ -1,20 +0,0 @@
# Netdata is an out-of-the-box monitoring tool that exposes many different
# metrics. Not currently in use, in favor of VictoriaMetrics and Grafana.
{ config, lib, ... }:
{
options.netdata.enable = lib.mkEnableOption "Netdata metrics.";
config = lib.mkIf config.netdata.enable {
services.netdata = {
enable = true;
# Disable local dashboard (unsecured)
config = {
web.mode = "none";
};
};
};
}

View File

@ -1,228 +0,0 @@
{
config,
pkgs,
lib,
...
}:
{
config = lib.mkIf config.services.nextcloud.enable {
services.nextcloud = {
package = pkgs.nextcloud30; # Required to specify
configureRedis = true;
datadir = "/data/nextcloud";
database.createLocally = true;
https = true;
hostName = "localhost";
maxUploadSize = "50G";
config = {
adminpassFile = config.secrets.nextcloud.dest;
dbtype = "pgsql";
};
settings = {
default_phone_region = "US";
# Allow access when hitting either of these hosts or IPs
trusted_domains = [ config.hostnames.content ];
trusted_proxies = [ "127.0.0.1" ];
maintenance_window_start = 4; # Run jobs at 4am UTC
log_type = "file";
loglevel = 1; # Include all actions in the log
};
extraAppsEnable = true;
extraApps = {
calendar = config.services.nextcloud.package.packages.apps.calendar;
contacts = config.services.nextcloud.package.packages.apps.contacts;
# These apps are defined and pinned by overlay in flake.
news = pkgs.nextcloudApps.news;
external = pkgs.nextcloudApps.external;
cookbook = pkgs.nextcloudApps.cookbook;
snappymail = pkgs.nextcloudApps.snappymail;
};
phpOptions = {
"opcache.interned_strings_buffer" = "16";
"output_buffering" = "0";
};
};
# Don't let Nginx use main ports (using Caddy instead)
services.nginx.enable = false;
services.phpfpm.pools.nextcloud.settings = {
"listen.owner" = config.services.caddy.user;
"listen.group" = config.services.caddy.group;
};
users.users.caddy.extraGroups = [ "nextcloud" ];
# Point Caddy to Nginx
caddy.routes = [
{
match = [ { host = [ config.hostnames.content ]; } ];
handle = [
{
handler = "subroute";
routes = [
# Sets variables and headers
{
handle = [
{
handler = "vars";
# Grab the webroot out of the written config
# The webroot is a symlinked combined Nextcloud directory
root = config.services.nginx.virtualHosts.${config.services.nextcloud.hostName}.root;
}
{
handler = "headers";
response.set.Strict-Transport-Security = [ "max-age=31536000;" ];
}
];
}
# Reroute carddav and caldav traffic
{
match = [
{
path = [
"/.well-known/carddav"
"/.well-known/caldav"
];
}
];
handle = [
{
handler = "static_response";
headers = {
Location = [ "/remote.php/dav" ];
};
status_code = 301;
}
];
}
# Block traffic to sensitive files
{
match = [
{
path = [
"/.htaccess"
"/data/*"
"/config/*"
"/db_structure"
"/.xml"
"/README"
"/3rdparty/*"
"/lib/*"
"/templates/*"
"/occ"
"/console.php"
];
}
];
handle = [
{
handler = "static_response";
status_code = 404;
}
];
}
# Redirect index.php to the homepage
{
match = [
{
file = {
try_files = [ "{http.request.uri.path}/index.php" ];
};
not = [ { path = [ "*/" ]; } ];
}
];
handle = [
{
handler = "static_response";
headers = {
Location = [ "{http.request.orig_uri.path}/" ];
};
status_code = 308;
}
];
}
# Rewrite paths to be relative
{
match = [
{
file = {
split_path = [ ".php" ];
try_files = [
"{http.request.uri.path}"
"{http.request.uri.path}/index.php"
"index.php"
];
};
}
];
handle = [
{
handler = "rewrite";
uri = "{http.matchers.file.relative}";
}
];
}
# Send all PHP traffic to Nextcloud PHP service
{
match = [ { path = [ "*.php" ]; } ];
handle = [
{
handler = "reverse_proxy";
transport = {
protocol = "fastcgi";
split_path = [ ".php" ];
};
upstreams = [ { dial = "unix//run/phpfpm/nextcloud.sock"; } ];
}
];
}
# Finally, send the rest to the file server
{ handle = [ { handler = "file_server"; } ]; }
];
}
];
terminal = true;
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.content ];
# Create credentials file for nextcloud
secrets.nextcloud = {
source = ../../../private/nextcloud.age;
dest = "${config.secretsDirectory}/nextcloud";
owner = "nextcloud";
group = "nextcloud";
permissions = "0440";
};
systemd.services.nextcloud-secret = {
requiredBy = [ "nextcloud-setup.service" ];
before = [ "nextcloud-setup.service" ];
};
# Grant user access to Nextcloud directories
users.users.${config.user}.extraGroups = [ "nextcloud" ];
# Open to groups, allowing for backups
systemd.services.phpfpm-nextcloud.serviceConfig.StateDirectoryMode = lib.mkForce "0770";
# Log metrics to prometheus
networking.hosts."127.0.0.1" = [ config.hostnames.content ];
services.prometheus.exporters.nextcloud = {
enable = config.prometheus.exporters.enable;
username = config.services.nextcloud.config.adminuser;
url = "https://${config.hostnames.content}";
passwordFile = config.services.nextcloud.config.adminpassFile;
};
prometheus.scrapeTargets = [
"127.0.0.1:${builtins.toString config.services.prometheus.exporters.nextcloud.port}"
];
# Allows nextcloud-exporter to read passwordFile
users.users.nextcloud-exporter.extraGroups =
lib.mkIf config.services.prometheus.exporters.nextcloud.enable
[ "nextcloud" ];
};
}

View File

@ -1,33 +0,0 @@
{ config, lib, ... }:
{
config = lib.mkIf config.services.ntfy-sh.enable {
services.ntfy-sh = {
settings = {
base-url = "https://${config.hostnames.notifications}";
upstream-base-url = "https://ntfy.sh";
listen-http = ":8333";
behind-proxy = true;
auth-default-access = "deny-all";
auth-file = "/var/lib/ntfy-sh/user.db";
};
};
caddy.routes = [
{
match = [ { host = [ config.hostnames.notifications ]; } ];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost${config.services.ntfy-sh.settings.listen-http}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.notifications ];
};
}

View File

@ -1,65 +0,0 @@
# Paperless-ngx is a document scanning and management solution.
{ config, lib, ... }:
{
config = lib.mkIf config.services.paperless.enable {
services.paperless = {
mediaDir = "/data/generic/paperless";
passwordFile = config.secrets.paperless.dest;
settings = {
PAPERLESS_OCR_USER_ARGS = builtins.toJSON { invalidate_digital_signatures = true; };
# Enable if changing the path name in Caddy
# PAPERLESS_FORCE_SCRIPT_NAME = "/paperless";
# PAPERLESS_STATIC_URL = "/paperless/static/";
};
};
# Allow Nextcloud and user to see files
users.users.nextcloud.extraGroups = lib.mkIf config.services.nextcloud.enable [ "paperless" ];
users.users.${config.user}.extraGroups = [ "paperless" ];
caddy.routes = [
{
match = [
{
host = [ config.hostnames.paperless ];
# path = [ "/paperless*" ]; # Change path name in Caddy
}
];
handle = [
{
handler = "reverse_proxy";
upstreams = [ { dial = "localhost:${builtins.toString config.services.paperless.port}"; } ];
}
];
}
];
# Configure Cloudflare DNS to point to this machine
services.cloudflare-dyndns.domains = [ config.hostnames.paperless ];
secrets.paperless = {
source = ../../../private/prometheus.age;
dest = "${config.secretsDirectory}/paperless";
owner = "paperless";
group = "paperless";
permissions = "0440";
};
systemd.services.paperless-secret = {
requiredBy = [ "paperless.service" ];
before = [ "paperless.service" ];
};
# Fix paperless shared permissions
systemd.services.paperless-web.serviceConfig.UMask = lib.mkForce "0026";
systemd.services.paperless-scheduler.serviceConfig.UMask = lib.mkForce "0026";
systemd.services.paperless-task-queue.serviceConfig.UMask = lib.mkForce "0026";
# Backups
services.restic.backups.default.paths = [ "/data/generic/paperless/documents" ];
};
}

Some files were not shown because too many files have changed in this diff Show More