move nixos and darwin back into modules dir

This commit is contained in:
Noah Masur
2023-02-20 20:37:37 -05:00
parent ded498f4c9
commit cc84f1d37a
163 changed files with 30 additions and 30 deletions

View File

@ -0,0 +1,19 @@
{ 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; };
};
environment.sessionVariables = { CALIBRE_USE_DARK_PALETTE = "1"; };
};
}

View File

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

13
modules/nixos/default.nix Normal file
View File

@ -0,0 +1,13 @@
{ ... }: {
imports = [
./applications
./gaming
./graphical
./hardware
./services
./system
./wsl
];
}

View File

@ -0,0 +1,20 @@
{ config, pkgs, lib, ... }: {
imports = [
./leagueoflegends.nix
./legendary.nix
./lutris.nix
./minecraft-server.nix
./steam.nix
];
options.gaming.enable = lib.mkEnableOption "Enable gaming features.";
config = lib.mkIf (config.gaming.enable && pkgs.stdenv.isLinux) {
hardware.opengl = {
enable = true;
driSupport = true;
driSupport32Bit = true;
};
};
}

View File

@ -0,0 +1,32 @@
{ config, pkgs, lib, ... }: {
options.gaming.leagueoflegends.enable =
lib.mkEnableOption "League of Legends";
config =
lib.mkIf (config.gaming.leagueoflegends.enable && pkgs.stdenv.isLinux) {
# League of Legends anti-cheat requirement
boot.kernel.sysctl = { "abi.vsyscall32" = 0; };
environment.systemPackages = with pkgs; [
# Lutris requirement to install the game
lutris
amdvlk
wineWowPackages.stable
# vulkan-tools
# Required according to https://lutris.net/games/league-of-legends/
openssl
gnome.zenity
# Don't remember if this is required
dconf
];
environment.sessionVariables = { QT_X11_NO_MITSHM = "1"; };
};
}

View File

@ -0,0 +1,56 @@
{ config, pkgs, lib, ... }:
let home-packages = config.home-manager.users.${config.user}.home.packages;
in {
options.gaming.legendary.enable =
lib.mkEnableOption "Legendary Epic Games launcher.";
config = lib.mkIf config.gaming.legendary.enable {
environment.systemPackages = with pkgs; [
legendary-gl
rare # GUI for Legendary (not working)
wineWowPackages.stable # 32-bit and 64-bit wineWowPackages, see https://nixos.wiki/wiki/Wine
];
home-manager.users.${config.user} = {
xdg.configFile."legendary/config.ini".text = ''
[Legendary]
; Disables the automatic update check
disable_update_check = false
; Disables the notice about an available update on exit
disable_update_notice = true
; Set install directory
install_dir = ${config.homePath}/media/games
; Make output quiet
log_level = error
'';
home.file = let
ignorePatterns = ''
.wine/
drive_c/'';
in {
".rgignore".text = ignorePatterns;
".fdignore".text = ignorePatterns;
};
programs.fish.functions =
lib.mkIf (builtins.elem pkgs.fzf home-packages) {
epic-games = {
body = ''
set game (legendary list 2>/dev/null \
| awk '/^ \* / { print $0; }' \
| sed -e 's/ (.*)$//' -e 's/ \* //' \
| fzf)
and legendary launch "$game" &> /dev/null
'';
};
};
};
};
}

View File

@ -0,0 +1,13 @@
{ config, pkgs, lib, ... }: {
options.gaming.lutris.enable = lib.mkEnableOption "Lutris game installer.";
config = lib.mkIf config.gaming.lutris.enable {
environment.systemPackages = with pkgs; [
lutris
amdvlk # Vulkan drivers (probably already installed)
wineWowPackages.stable # 32-bit and 64-bit wineWowPackages
];
};
}

View File

@ -0,0 +1,152 @@
{ config, pkgs, lib, ... }:
let
localPort = 25564;
publicPort = 49732;
rconPort = 25575;
rconPassword = "thiscanbeanything";
in {
options.gaming.minecraft-server.enable =
lib.mkEnableOption "Minecraft Server.";
config = lib.mkIf config.gaming.minecraft-server.enable {
unfreePackages = [ "minecraft-server" ];
services.minecraft-server = {
enable = true;
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 ];
## 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

@ -0,0 +1,19 @@
{ 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" ];
environment.systemPackages = with pkgs; [
steam
# Enable terminal interaction
steamPackages.steamcmd
steam-tui
];
};
}

View File

@ -0,0 +1,31 @@
{ lib, ... }: {
imports =
[ ./xorg.nix ./fonts.nix ./i3.nix ./polybar.nix ./picom.nix ./rofi.nix ];
options = {
launcherCommand = lib.mkOption {
type = lib.types.str;
description = "Command to use for launching";
};
systemdSearch = lib.mkOption {
type = lib.types.str;
description = "Command to use for interacting with systemd";
};
altTabCommand = lib.mkOption {
type = lib.types.str;
description = "Command to use for choosing windows";
};
toggleBarCommand = lib.mkOption {
type = lib.types.str;
description = "Command to hide and show the status bar.";
};
wallpaper = lib.mkOption {
type = lib.types.path;
description = "Wallpaper background image file";
};
};
}

View File

@ -0,0 +1,14 @@
{ 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

@ -0,0 +1,29 @@
{ config, pkgs, lib, ... }:
let fontName = "Victor Mono";
in {
config = lib.mkIf (config.gui.enable && pkgs.stdenv.isLinux) {
fonts.fonts = with pkgs; [
victor-mono # Used for Vim and Terminal
(nerdfonts.override { fonts = [ "Hack" ]; }) # For Polybar, Rofi
];
fonts.fontconfig.defaultFonts.monospace = [ fontName ];
home-manager.users.${config.user} = {
xsession.windowManager.i3.config.fonts = {
names = [ "pango:${fontName}" ];
# style = "Regular";
# size = 11.0;
};
services.polybar.config."bar/main".font-0 = "Hack Nerd Font:size=10;2";
programs.rofi.font = "Hack Nerd Font 14";
programs.alacritty.settings.font.normal.family = fontName;
programs.kitty.font.name = fontName;
};
};
}

View File

@ -0,0 +1,280 @@
{ 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";
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 = "kitty"; }];
"${ws3}" = [{ class = "discord"; }];
"${ws4}" = [{ 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" =
"exec ${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 + 30 && sleep 1; exec ${pkgs.ddcutil}/bin/ddcutil --display 2 setvcp 10 + 30";
"Shift+F11" =
"exec ${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 - 30 && sleep 1; exec ${pkgs.ddcutil}/bin/ddcutil --display 2 setvcp 10 - 30";
"XF86MonBrightnessUp" =
"exec ${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 + 30 && sleep 1; exec ${pkgs.ddcutil}/bin/ddcutil --display 2 setvcp 10 + 30";
"XF86MonBrightnessDown" =
"exec ${pkgs.ddcutil}/bin/ddcutil --display 1 setvcp 10 - 30 && sleep 1; exec ${pkgs.ddcutil}/bin/ddcutil --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 kitty; workspace ${ws2}; layout tabbed";
"${modifier}+space" =
"exec --no-startup-id ${config.launcherCommand}";
"${modifier}+Shift+s" =
"exec --no-startup-id ${config.systemdSearch}";
"Mod1+Tab" = "exec --no-startup-id ${config.altTabCommand}";
"${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}";
# 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

@ -0,0 +1,48 @@
{ 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

@ -0,0 +1,185 @@
{ 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 = "pulseaudio date";
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/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.base0B;
label-volume = "%percentage%%";
label-muted = " ---";
label-muted-foreground = config.theme.colors.base03;
ramp-volume-0 = "";
ramp-volume-1 = "";
ramp-volume-2 = "";
};
# "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.base0A;
# format-background = colors.background;
};
"settings" = {
screenchange-reload = true;
pseudo-transparency = false;
};
};
};
xsession.windowManager.i3.config.startup = [{
command = "systemctl --user restart polybar";
always = true;
notification = false;
}];
};
};
}

View File

@ -0,0 +1,153 @@
{ config, pkgs, lib, ... }:
{
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 = { };
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" "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 = "Launch Program";
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";
};
};
};
launcherCommand = "${pkgs.rofi}/bin/rofi -show run -modi run";
systemdSearch = "${pkgs.rofi-systemd}/bin/rofi-systemd";
altTabCommand = "${pkgs.rofi}/bin/rofi -show window -modi window";
};
}

View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# THEME="$HOME/.config/rofi/config.rasi"
ICON_UP=""
ICON_DOWN=""
ICON_OPT=""
options="$ICON_UP\n$ICON_OPT\n$ICON_DOWN"
chosen="$(echo -e "$options" | rofi -theme-str 'listview { layout:horizontal; }' -dmenu)"
echo "$chosen"

View File

@ -0,0 +1,85 @@
{ 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.str;
description = "Theme package name for GTK applications";
default = "gnome-themes-extra";
};
};
};
config = let
gtkTheme = {
name = config.gtk.theme.name;
package = pkgs."${config.gtk.theme.package}";
};
in lib.mkIf config.gui.enable {
# Enable the X11 windowing system.
services.xserver = {
enable = config.gui.enable;
# Enable touchpad support
libinput.enable = true;
# Login screen
displayManager = {
lightdm = {
enable = config.services.xserver.enable;
background = config.wallpaper;
# Make the login screen dark
greeters.gtk.theme = gtkTheme;
# Show default user
extraSeatDefaults = ''
greeter-hide-users = false
'';
};
};
};
environment.systemPackages = with pkgs;
[
xclip # Clipboard
];
# Required for setting GTK theme (for preferred-color-scheme in browser)
services.dbus.packages = [ pkgs.dconf ];
programs.dconf.enable = true;
environment.sessionVariables = { GTK_THEME = config.gtk.theme.name; };
home-manager.users.${config.user} = {
programs.fish.shellAliases = {
pbcopy = "xclip -selection clipboard -in";
pbpaste = "xclip -selection clipboard -out";
};
gtk = let
gtkExtraConfig = {
gtk-application-prefer-dark-theme = config.theme.dark;
};
in {
enable = true;
theme = gtkTheme;
gtk3.extraConfig = gtkExtraConfig;
gtk4.extraConfig = gtkExtraConfig;
};
};
};
}

View File

@ -0,0 +1,84 @@
{ 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) {
sound.enable = true;
# 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

@ -0,0 +1,43 @@
{ config, pkgs, lib, ... }: {
boot.loader = lib.mkIf (config.physical && pkgs.stdenv.isLinux) {
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";
# 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=0
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 && pkgs.stdenv.isLinux) [ "ntfs" ];
}

View File

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

View File

@ -0,0 +1,16 @@
{ ... }: {
services.xserver = {
layout = "us";
# Keyboard responsiveness
autoRepeatDelay = 250;
autoRepeatInterval = 40;
# Swap escape key with caps lock key
xkbOptions = "eurosign:e,caps:swapescape";
};
}

View File

@ -0,0 +1,51 @@
{ config, pkgs, lib, ... }: {
config =
lib.mkIf (config.gui.enable && config.physical && pkgs.stdenv.isLinux) {
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 \
--mode 1920x1200 \
--pos 1920x0 \
--rotate left \
--output HDMI-A-0 \
--primary \
--mode 1920x1080 \
--pos 0x560 \
--rotate normal \
--output DVI-0 --off \
--output DVI-1 --off \
'';
};
};
}

View File

@ -0,0 +1,22 @@
{ config, pkgs, lib, ... }: {
config =
lib.mkIf (config.gui.enable && config.physical && pkgs.stdenv.isLinux) {
# Mouse customization
services.ratbagd.enable = true;
environment.systemPackages = with pkgs; [
libratbag # Mouse adjustments
piper # Mouse adjustments GUI
];
services.xserver.libinput.mouse = {
# Disable mouse acceleration
accelProfile = "flat";
accelSpeed = "1.15";
};
};
}

View File

@ -0,0 +1,14 @@
{ config, pkgs, lib, ... }: {
config = lib.mkIf (config.physical && pkgs.stdenv.isLinux) {
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
# Per-interface useDHCP will be mandatory in the future, so this generated config
# replicates the default behaviour.
networking.useDHCP = false;
networking.interfaces.enp5s0.useDHCP = true;
networking.interfaces.wlp4s0.useDHCP = true;
};
}

View File

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

View File

@ -0,0 +1,12 @@
{ config, pkgs, lib, ... }: {
config = lib.mkIf (config.physical && pkgs.stdenv.isLinux) {
# Prevent wake from keyboard
powerManagement.powerDownCommands = ''
for wakeup in /sys/bus/usb/devices/1-*/power/wakeup; do echo disabled > $wakeup; done
'';
};
}

View File

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

View File

@ -0,0 +1,67 @@
{ config, pkgs, lib, ... }: {
options = {
backup.s3 = {
endpoint = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "S3 endpoint for backups";
default = null;
};
bucket = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "S3 bucket for backups";
default = null;
};
accessKeyId = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "S3 access key ID for backups";
default = null;
};
};
};
config = lib.mkIf (config.backup.s3.endpoint != null) {
users.groups.backup = { };
secrets.backup = {
source = ../../private/backup.age;
dest = "${config.secretsDirectory}/backup";
group = "backup";
permissions = "0440";
};
users.users.litestream.extraGroups = [ "backup" ];
services.litestream = {
enable = true;
environmentFile = config.secrets.backup.dest;
};
# Wait for secret to exist
systemd.services.litestream = {
after = [ "backup-secret.service" ];
requires = [ "backup-secret.service" ];
environment.AWS_ACCESS_KEY_ID = config.backup.s3.accessKeyId;
};
# # Backup library to object storage
# services.restic.backups.calibre = {
# user = "calibre-web";
# repository =
# "s3://${config.backup.s3.endpoint}/${config.backup.s3.bucket}/calibre";
# paths = [
# "/var/books"
# "/var/lib/calibre-web/app.db"
# "/var/lib/calibre-web/gdrive.db"
# ];
# initialize = true;
# timerConfig = { OnCalendar = "00:05:00"; };
# environmentFile = backup.s3File;
# };
};
}

View File

@ -0,0 +1,37 @@
{ config, pkgs, lib, ... }: {
options = {
caddy.enable = lib.mkEnableOption "Caddy reverse proxy.";
caddy.routes = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Caddy JSON routes for http servers";
default = [ ];
};
caddy.blocks = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Caddy JSON error blocks for http servers";
default = [ ];
};
};
config = lib.mkIf (config.caddy.enable && config.caddy.routes != [ ]) {
services.caddy = {
enable = true;
adapter = "''"; # Required to enable JSON
configFile = pkgs.writeText "Caddyfile" (builtins.toJSON {
apps.http.servers.main = {
listen = [ ":443" ];
routes = config.caddy.routes;
errors.routes = config.caddy.blocks;
};
});
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
networking.firewall.allowedUDPPorts = [ 443 ];
};
}

View File

@ -0,0 +1,63 @@
{ config, pkgs, lib, ... }: {
options = {
bookServer = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Hostname for Calibre library";
default = null;
};
};
config = lib.mkIf (config.bookServer != null) {
services.calibre-web = {
enable = true;
openFirewall = true;
options = {
reverseProxyAuth.enable = false;
enableBookConversion = true;
enableBookUploading = true;
};
};
caddy.routes = [{
match = [{ host = [ config.bookServer ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:8083"; }];
headers.request.add."X-Script-Name" = [ "/calibre-web" ];
}];
}];
# Run a backup on a schedule
systemd.timers.calibre-backup = {
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 =
let libraryPath = "/var/lib/calibre-web"; # Default location
in {
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

@ -0,0 +1,56 @@
# This module is necessary for hosts that are serving through Cloudflare.
{ config, lib, ... }:
let
cloudflareIpRanges = [
# Cloudflare IPv4: https://www.cloudflare.com/ips-v4
"173.245.48.0/20"
"103.21.244.0/22"
"103.22.200.0/22"
"103.31.4.0/22"
"141.101.64.0/18"
"108.162.192.0/18"
"190.93.240.0/20"
"188.114.96.0/20"
"197.234.240.0/22"
"198.41.128.0/17"
"162.158.0.0/15"
"104.16.0.0/13"
"104.24.0.0/14"
"172.64.0.0/13"
"131.0.72.0/22"
# Cloudflare IPv6: https://www.cloudflare.com/ips-v6
"2400:cb00::/32"
"2606:4700::/32"
"2803:f800::/32"
"2405:b500::/32"
"2405:8100::/32"
"2a06:98c0::/29"
"2c0f:f248::/32"
];
in {
options.cloudflare.enable = lib.mkEnableOption "Use Cloudflare.";
config = lib.mkIf config.cloudflare.enable {
# Forces Caddy to error if coming from a non-Cloudflare IP
caddy.blocks = [{
match = [{ not = [{ remote_ip.ranges = cloudflareIpRanges; }]; }];
handle = [{
handler = "static_response";
abort = true;
}];
}];
# Allows Nextcloud to trust Cloudflare IPs
services.nextcloud.config.trustedProxies = cloudflareIpRanges;
};
}

View File

@ -0,0 +1,25 @@
{ ... }: {
imports = [
./backups.nix
./caddy.nix
./calibre.nix
./cloudflare.nix
./gitea.nix
./gnupg.nix
./honeypot.nix
./jellyfin.nix
./keybase.nix
./mullvad.nix
./n8n.nix
./netdata.nix
./nextcloud.nix
./prometheus.nix
./secrets.nix
./sshd.nix
./transmission.nix
./vaultwarden.nix
./wireguard.nix
];
}

View File

@ -0,0 +1,92 @@
{ config, lib, ... }:
let giteaPath = "/var/lib/gitea"; # Default service directory
in {
options = {
giteaServer = lib.mkOption {
description = "Hostname for Gitea.";
type = lib.types.nullOr lib.types.str;
default = null;
};
};
config = lib.mkIf (config.giteaServer != null) {
services.gitea = {
enable = true;
httpPort = 3001;
httpAddress = "127.0.0.1";
rootUrl = "https://${config.giteaServer}/";
database.type = "sqlite3";
settings = {
repository = {
DEFAULT_PUSH_CREATE_PRIVATE = true;
DISABLE_HTTP_GIT = false;
ACCESS_CONTROL_ALLOW_ORIGIN = config.giteaServer;
ENABLE_PUSH_CREATE_USER = true;
ENABLE_PUSH_CREATE_ORG = true;
DEFAULT_BRANCH = "main";
};
server = {
SSH_PORT = 22;
START_SSH_SERVER = false; # Use sshd instead
DISABLE_SSH = false;
# SSH_LISTEN_HOST = "0.0.0.0";
# SSH_LISTEN_PORT = 122;
};
service.DISABLE_REGISTRATION = true;
session.COOKIE_SECURE = true;
ui.SHOW_USER_EMAIL = false;
};
extraConfig = null;
};
networking.firewall.allowedTCPPorts = [ 122 ];
caddy.routes = [{
match = [{ host = [ config.giteaServer ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:3001"; }];
}];
}];
## Backup config
# Open to groups, allowing for backups
systemd.services.gitea.serviceConfig.StateDirectoryMode =
lib.mkForce "0770";
systemd.tmpfiles.rules = [
"d ${giteaPath}/data 0775 gitea gitea"
"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" ];
};
};
}

View File

@ -0,0 +1,18 @@
{ 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 ]; };
};
}

View File

@ -0,0 +1,77 @@
{ config, lib, pkgs, ... }:
# Currently has some issues that don't make this viable.
# Taken from:
# https://dataswamp.org/~solene/2022-09-29-iblock-implemented-in-nixos.html
# You will need to flush all rules when removing:
# https://serverfault.com/questions/200635/best-way-to-clear-all-iptables-rules
let
portsToBlock = [ 25545 25565 25570 ];
portsString =
builtins.concatStringsSep "," (builtins.map builtins.toString portsToBlock);
# Block IPs for 20 days
expire = 60 * 60 * 24 * 20;
rules = table: [
"INPUT -i eth0 -p tcp -m multiport --dports ${portsString} -m state --state NEW -m recent --set"
"INPUT -i eth0 -p tcp -m multiport --dports ${portsString} -m state --state NEW -m recent --update --seconds 10 --hitcount 1 -j SET --add-set ${table} src"
"INPUT -i eth0 -p tcp -m set --match-set ${table} src -j nixos-fw-refuse"
"INPUT -i eth0 -p udp -m set --match-set ${table} src -j nixos-fw-refuse"
];
create-rules = lib.concatStringsSep "\n"
(builtins.map (rule: "iptables -C " + rule + " || iptables -A " + rule)
(rules "blocked") ++ builtins.map
(rule: "ip6tables -C " + rule + " || ip6tables -A " + rule)
(rules "blocked6"));
delete-rules = lib.concatStringsSep "\n"
(builtins.map (rule: "iptables -C " + rule + " && iptables -D " + rule)
(rules "blocked") ++ builtins.map
(rule: "ip6tables -C " + rule + " && ip6tables -D " + rule)
(rules "blocked6"));
in {
options.honeypot.enable = lib.mkEnableOption "Honeypot fail2ban system.";
config.networking.firewall = lib.mkIf config.honeypot.enable {
extraPackages = [ pkgs.ipset ];
# allowedTCPPorts = portsToBlock;
# Restore ban list when starting up
extraCommands = ''
if test -f /var/lib/ipset.conf
then
ipset restore -! < /var/lib/ipset.conf
else
ipset -exist create blocked hash:ip ${
if expire > 0 then "timeout ${toString expire}" else ""
}
ipset -exist create blocked6 hash:ip family inet6 ${
if expire > 0 then "timeout ${toString expire}" else ""
}
fi
${create-rules}
'';
# Save list when shutting down
extraStopCommands = ''
ipset -exist create blocked hash:ip ${
if expire > 0 then "timeout ${toString expire}" else ""
}
ipset -exist create blocked6 hash:ip family inet6 ${
if expire > 0 then "timeout ${toString expire}" else ""
}
ipset save > /var/lib/ipset.conf
${delete-rules}
'';
};
}

View File

@ -0,0 +1,31 @@
{ config, lib, ... }: {
options = {
streamServer = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Hostname for Jellyfin library";
default = null;
};
};
config = lib.mkIf (config.streamServer != null) {
services.jellyfin.enable = true;
caddy.routes = [{
match = [{ host = [ config.streamServer ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:8096"; }];
}];
}];
# Create videos directory, allow anyone in Jellyfin group to manage it
systemd.tmpfiles.rules = [
"d /var/lib/jellyfin 0775 jellyfin jellyfin"
"d /var/lib/jellyfin/library 0775 jellyfin jellyfin"
];
};
}

View File

@ -0,0 +1,34 @@
{ config, pkgs, lib, ... }: {
options.keybase.enable = lib.mkEnableOption "Keybase.";
config = lib.mkIf config.keybase.enable {
services.keybase.enable = true;
services.kbfs = {
enable = true;
# enableRedirector = true;
mountPoint = "/run/user/1000/keybase/kbfs";
};
security.wrappers.keybase-redirector = {
setuid = true;
owner = "root";
group = "root";
source = "${pkgs.kbfs}/bin/redirector";
};
home-manager.users.${config.user} = {
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

@ -0,0 +1,12 @@
{ 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

@ -0,0 +1,33 @@
{ config, pkgs, lib, ... }: {
options = {
n8nServer = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Hostname for n8n automation";
default = null;
};
};
config = lib.mkIf (config.n8nServer != null) {
services.n8n = {
enable = true;
settings = {
n8n = {
listenAddress = "127.0.0.1";
port = 5678;
};
};
};
caddy.routes = [{
match = [{ host = [ config.n8nServer ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:5678"; }];
}];
}];
};
}

View File

@ -0,0 +1,16 @@
{ config, pkgs, 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

@ -0,0 +1,86 @@
{ config, pkgs, lib, ... }: {
options = {
nextcloudServer = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Hostname for Nextcloud";
default = null;
};
};
config = lib.mkIf (config.nextcloudServer != null) {
services.nextcloud = {
enable = true;
package = pkgs.nextcloud25; # Required to specify
https = true;
hostName = "localhost";
maxUploadSize = "50G";
config = {
adminpassFile = config.secrets.nextcloud.dest;
extraTrustedDomains = [ config.nextcloudServer ];
};
};
# Don't let Nginx use main ports (using Caddy instead)
services.nginx.virtualHosts."localhost".listen = [{
addr = "127.0.0.1";
port = 8080;
}];
# Point Caddy to Nginx
caddy.routes = [{
match = [{ host = [ config.nextcloudServer ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:8080"; }];
}];
}];
# 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" ];
};
## Backup config
# Open to groups, allowing for backups
systemd.services.phpfpm-nextcloud.serviceConfig.StateDirectoryMode =
lib.mkForce "0770";
# Allow litestream and nextcloud to share a sqlite database
users.users.litestream.extraGroups = [ "nextcloud" ];
users.users.nextcloud.extraGroups = [ "litestream" ];
# Backup sqlite database with litestream
services.litestream = {
settings = {
dbs = [{
path = "${config.services.nextcloud.datadir}/data/nextcloud.db";
replicas = [{
url =
"s3://${config.backup.s3.bucket}.${config.backup.s3.endpoint}/nextcloud";
}];
}];
};
};
# Don't start litestream unless nextcloud is up
systemd.services.litestream = {
after = [ "phpfpm-nextcloud.service" ];
requires = [ "phpfpm-nextcloud.service" ];
};
};
}

View File

@ -0,0 +1,35 @@
{ config, pkgs, lib, ... }: {
options.metricsServer = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Hostname of the Grafana server.";
default = null;
};
config = lib.mkIf (config.metricsServer != null) {
services.grafana.enable = true;
# Required to fix error in latest nixpkgs
services.grafana.settings = { };
services.prometheus = {
enable = true;
exporters.node.enable = true;
scrapeConfigs = [{
job_name = "local";
static_configs = [{ targets = [ "127.0.0.1:9100" ]; }];
}];
};
caddy.routes = [{
match = [{ host = [ config.metricsServer ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:3000"; }];
}];
}];
};
}

View File

@ -0,0 +1,91 @@
# Secrets management method taken from here:
# https://xeiaso.net/blog/nixos-encrypted-secrets-2021-01-20
# In my case, I pre-encrypt my secrets and commit them to git.
{ config, pkgs, lib, ... }: {
options = {
secretsDirectory = lib.mkOption {
type = lib.types.str;
description = "Default path to place secrets.";
default = "/var/private";
};
secrets = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
source = lib.mkOption {
type = lib.types.path;
description = "Path to encrypted secret.";
};
dest = lib.mkOption {
type = lib.types.str;
description = "Resulting path for decrypted secret.";
};
owner = lib.mkOption {
default = "root";
type = lib.types.str;
description = "User to own the secret.";
};
group = lib.mkOption {
default = "root";
type = lib.types.str;
description = "Group to own the secret.";
};
permissions = lib.mkOption {
default = "0400";
type = lib.types.str;
description = "Permissions expressed as octal.";
};
};
});
description = "Set of secrets to decrypt to disk.";
default = { };
};
};
config = lib.mkIf (pkgs.stdenv.isLinux && !config.wsl.enable) {
# Create a default directory to place secrets
systemd.tmpfiles.rules = [ "d ${config.secretsDirectory} 0755 root wheel" ];
# Declare oneshot service to decrypt secret using SSH host key
# - Requires that the secret is already encrypted for the host
# - Encrypt secrets: nix run github:nmasur/dotfiles#encrypt-secret
systemd.services = lib.mapAttrs' (name: attrs: {
name = "${name}-secret";
value = {
description = "Decrypt secret for ${name}";
wantedBy = [ "multi-user.target" ];
serviceConfig.Type = "oneshot";
script = ''
${pkgs.age}/bin/age --decrypt \
--identity ${config.identityFile} \
--output ${attrs.dest} \
${attrs.source}
chown '${attrs.owner}':'${attrs.group}' '${attrs.dest}'
chmod '${attrs.permissions}' '${attrs.dest}'
'';
};
}) config.secrets;
# Example declaration
# config.secrets.my-secret = {
# source = ../../private/my-secret.age;
# dest = "/var/lib/private/my-secret";
# owner = "my-app";
# group = "my-app";
# permissions = "0440";
# };
};
}

View File

@ -0,0 +1,37 @@
{ config, pkgs, lib, ... }: {
options = {
publicKey = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Public SSH key authorized for this system.";
};
permitRootLogin = lib.mkOption {
type = lib.types.str;
description = "Root login settings.";
default = "no";
};
};
config = lib.mkIf
(pkgs.stdenv.isLinux && !config.wsl.enable && config.publicKey != null) {
services.openssh = {
enable = true;
ports = [ 22 ];
passwordAuthentication = false;
gatewayPorts = "no";
forwardX11 = false;
allowSFTP = true;
permitRootLogin = config.permitRootLogin;
};
users.users.${config.user}.openssh.authorizedKeys.keys =
[ config.publicKey ];
# Implement a simple fail2ban service for sshd
services.sshguard.enable = true;
# Add terminfo for SSH from popular terminal emulators
environment.enableAllTerminfo = true;
};
}

View File

@ -0,0 +1,82 @@
{ config, pkgs, lib, ... }: {
options = {
transmissionServer = lib.mkOption {
type = lib.types.str;
description = "Hostname for Transmission";
default = null;
};
};
config = let
namespace = config.networking.wireguard.interfaces.wg0.interfaceNamespace;
vpnIp = lib.strings.removeSuffix "/32"
(builtins.head config.networking.wireguard.interfaces.wg0.ips);
in lib.mkIf (config.wireguard.enable && config.transmissionServer != null) {
# Setup transmission
services.transmission = {
enable = true;
settings = {
port-forwarding-enabled = false;
rpc-authentication-required = true;
rpc-port = 9091;
rpc-bind-address = "0.0.0.0";
rpc-username = config.user;
rpc-host-whitelist = config.transmissionServer;
rpc-host-whitelist-enabled = true;
rpc-whitelist = "127.0.0.1,${vpnIp}";
rpc-whitelist-enabled = true;
};
credentialsFile = config.secrets.transmission.dest;
};
# Bind transmission to wireguard namespace
systemd.services.transmission = {
bindsTo = [ "netns@${namespace}.service" ];
requires = [ "network-online.target" "transmission-secret.service" ];
after = [ "wireguard-wg0.service" "transmission-secret.service" ];
unitConfig.JoinsNamespaceOf = "netns@${namespace}.service";
serviceConfig.NetworkNamespacePath = "/var/run/netns/${namespace}";
};
# Create reverse proxy for web UI
caddy.routes = [{
match = [{ host = [ config.transmissionServer ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:9091"; }];
}];
}];
# Caddy and Transmission both try to set rmem_max for larger UDP packets.
# We will choose Transmission's recommendation (4 MB).
boot.kernel.sysctl."net.core.rmem_max" = 4194304;
# Allow inbound connections to reach namespace
systemd.services.transmission-web-netns = {
description = "Forward to transmission in wireguard namespace";
requires = [ "transmission.service" ];
after = [ "transmission.service" ];
serviceConfig = {
Restart = "on-failure";
TimeoutStopSec = 300;
};
wantedBy = [ "multi-user.target" ];
script = ''
${pkgs.iproute2}/bin/ip netns exec ${namespace} ${pkgs.iproute2}/bin/ip link set dev lo up
${pkgs.socat}/bin/socat tcp-listen:9091,fork,reuseaddr exec:'${pkgs.iproute2}/bin/ip netns exec ${namespace} ${pkgs.socat}/bin/socat STDIO "tcp-connect:${vpnIp}:9091"',nofork
'';
};
# Create credentials file for transmission
secrets.transmission = {
source = ../../private/transmission.json.age;
dest = "${config.secretsDirectory}/transmission.json";
owner = "transmission";
group = "transmission";
};
};
}

View File

@ -0,0 +1,123 @@
{ config, pkgs, lib, ... }:
let vaultwardenPath = "/var/lib/bitwarden_rs"; # Default service directory
in {
options = {
vaultwardenServer = lib.mkOption {
description = "Hostname for Vaultwarden.";
type = lib.types.nullOr lib.types.str;
default = null;
};
};
config = lib.mkIf (config.vaultwardenServer != null) {
services.vaultwarden = {
enable = true;
config = {
DOMAIN = "https://${config.vaultwardenServer}";
SIGNUPS_ALLOWED = false;
SIGNUPS_VERIFY = true;
INVITATIONS_ALLOWED = true;
WEB_VAULT_ENABLED = true;
ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = 8222;
WEBSOCKET_ENABLED = true;
WEBSOCKET_ADDRESS = "0.0.0.0";
WEBSOCKET_PORT = 3012;
LOGIN_RATELIMIT_SECONDS = 60;
LOGIN_RATELIMIT_MAX_BURST = 10;
ADMIN_RATELIMIT_SECONDS = 300;
ADMIN_RATELIMIT_MAX_BURST = 3;
};
environmentFile = config.secrets.vaultwarden.dest;
dbBackend = "sqlite";
};
secrets.vaultwarden = {
source = ../../private/vaultwarden.age;
dest = "${config.secretsDirectory}/vaultwarden";
owner = "vaultwarden";
group = "vaultwarden";
};
networking.firewall.allowedTCPPorts = [ 3012 ];
caddy.routes = [{
match = [{ host = [ config.vaultwardenServer ]; }];
handle = [{
handler = "reverse_proxy";
upstreams = [{ dial = "localhost:8222"; }];
headers.request.add."X-Real-IP" = [ "{http.request.remote.host}" ];
}];
}];
## Backup config
# Open to groups, allowing for backups
systemd.services.vaultwarden.serviceConfig.StateDirectoryMode =
lib.mkForce "0770";
systemd.tmpfiles.rules = [
"f ${vaultwardenPath}/db.sqlite3 0660 vaultwarden vaultwarden"
"f ${vaultwardenPath}/db.sqlite3-shm 0660 vaultwarden vaultwarden"
"f ${vaultwardenPath}/db.sqlite3-wal 0660 vaultwarden vaultwarden"
];
# Allow litestream and vaultwarden to share a sqlite database
users.users.litestream.extraGroups = [ "vaultwarden" ];
users.users.vaultwarden.extraGroups = [ "litestream" ];
# Backup sqlite database with litestream
services.litestream = {
settings = {
dbs = [{
path = "${vaultwardenPath}/db.sqlite3";
replicas = [{
url =
"s3://${config.backup.s3.bucket}.${config.backup.s3.endpoint}/vaultwarden";
}];
}];
};
};
# Don't start litestream unless vaultwarden is up
systemd.services.litestream = {
after = [ "vaultwarden.service" ];
requires = [ "vaultwarden.service" ];
};
# Run a separate file backup on a schedule
systemd.timers.vaultwarden-backup = {
timerConfig = {
OnCalendar = "*-*-* 06:00:00"; # Once per day
Unit = "vaultwarden-backup.service";
};
wantedBy = [ "timers.target" ];
};
# Backup other Vaultwarden data to object storage
systemd.services.vaultwarden-backup = {
description = "Backup Vaultwarden files";
environment.AWS_ACCESS_KEY_ID = config.backup.s3.accessKeyId;
serviceConfig = {
Type = "oneshot";
User = "vaultwarden";
Group = "backup";
EnvironmentFile = config.secrets.backup.dest;
};
script = ''
${pkgs.awscli2}/bin/aws s3 sync \
${vaultwardenPath}/ \
s3://${config.backup.s3.bucket}/vaultwarden/ \
--endpoint-url=https://${config.backup.s3.endpoint} \
--exclude "*db.sqlite3*" \
--exclude ".db.sqlite3*"
'';
};
};
}

View File

@ -0,0 +1,44 @@
{ config, pkgs, lib, ... }: {
options.wireguard.enable = lib.mkEnableOption "Wireguard VPN setup.";
config = lib.mkIf (pkgs.stdenv.isLinux && config.wireguard.enable) {
networking.wireguard = {
enable = true;
interfaces = {
wg0 = {
# Establishes identity of this machine
generatePrivateKeyFile = false;
privateKeyFile = config.secrets.wireguard.dest;
# Move to network namespace for isolating programs
interfaceNamespace = "wg";
};
};
};
# Create namespace for Wireguard
# This allows us to isolate specific programs to Wireguard
systemd.services."netns@" = {
description = "%I network namespace";
before = [ "network.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${pkgs.iproute2}/bin/ip netns add %I";
ExecStop = "${pkgs.iproute2}/bin/ip netns del %I";
};
};
# Create private key file for wireguard
secrets.wireguard = {
source = ../../private/wireguard.age;
dest = "${config.secretsDirectory}/wireguard";
};
};
}

View File

@ -0,0 +1,13 @@
{ config, pkgs, lib, ... }: {
imports = [ ./user.nix ./timezone.nix ./doas.nix ];
config = lib.mkIf pkgs.stdenv.isLinux {
# Pin a state version to prevent warnings
system.stateVersion =
config.home-manager.users.${config.user}.home.stateVersion;
};
}

View File

@ -0,0 +1,35 @@
# Replace sudo with doas
{ config, pkgs, lib, ... }: {
config = lib.mkIf pkgs.stdenv.isLinux {
security = {
# Remove sudo
sudo.enable = false;
# Add doas
doas = {
enable = true;
# No password required
wheelNeedsPassword = false;
# Pass environment variables from user to root
# Also requires removing password here
extraRules = [{
groups = [ "wheel" ];
noPass = true;
keepEnv = true;
}];
};
};
home-manager.users.${config.user}.programs.fish.shellAliases = {
sudo = "doas";
};
};
}

View File

@ -0,0 +1,19 @@
{ config, pkgs, lib, ... }: {
config = lib.mkIf pkgs.stdenv.isLinux {
# Service to determine location for time zone
services.geoclue2.enable = true;
services.geoclue2.enableWifi = false; # Breaks when it can't connect
location = { provider = "geoclue2"; };
# Enable local time based on time zone
services.localtimed.enable = true;
# Required to get localtimed to talk to geoclue2
services.geoclue2.appConfig.localtimed.isSystem = true;
services.geoclue2.appConfig.localtimed.isAllowed = true;
};
}

View File

@ -0,0 +1,52 @@
{ config, pkgs, lib, ... }: {
options = {
passwordHash = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Password created with mkpasswd -m sha-512";
default = null;
# Test it by running: mkpasswd -m sha-512 --salt "PZYiMGmJIIHAepTM"
};
};
config = lib.mkIf (pkgs.stdenv.isLinux) {
# Allows us to declaritively set password
users.mutableUsers = false;
# Define a user account. Don't forget to set a password with passwd.
users.users.${config.user} = {
# Create a home directory for human user
isNormalUser = true;
# Automatically create a password to start
hashedPassword = config.passwordHash;
extraGroups = [
"wheel" # Sudo privileges
];
};
home-manager.users.${config.user}.xdg = {
userDirs = {
enable = true;
createDirectories = true;
documents = "$HOME/documents";
download = config.userDirs.download;
music = "$HOME/media/music";
pictures = "$HOME/media/images";
videos = "$HOME/media/videos";
desktop = "$HOME/other/desktop";
publicShare = "$HOME/other/public";
templates = "$HOME/other/templates";
extraConfig = { XDG_DEV_DIR = "$HOME/dev"; };
};
};
};
}

View File

@ -0,0 +1,23 @@
{ config, pkgs, lib, ... }: {
config = lib.mkIf (pkgs.stdenv.isLinux && config.wsl.enable) {
# Systemd doesn't work in WSL so these must be disabled
services.geoclue2.enable = lib.mkForce false;
location = { provider = lib.mkForce "manual"; };
services.localtimed.enable = lib.mkForce false;
# Used by NeoVim for clipboard sharing with Windows
# home-manager.users.${config.user}.home.sessionPath =
# [ "/mnt/c/Program Files/win32yank/" ];
# Replace config directory with our repo, since it sources from config on
# every launch
system.activationScripts.configDir.text = ''
rm -rf /etc/nixos
ln --symbolic --no-dereference --force ${config.dotfilesPath} /etc/nixos
'';
};
}