fix(searxng): resolve 502 error and apply Catppuccin theme

- Fix Redis connection by using container-to-container networking.
- Apply Catppuccin (Mocha/Latte) theme via custom CSS.
- Enable SearXNG module in host configuration.
- Configure Caddy reverse proxy and DDclient for search.ashisgreat.xyz.
This commit is contained in:
ashisgreat22 2026-01-19 20:43:22 +01:00
parent 2be8de47fa
commit 6ada19e490
55 changed files with 2502 additions and 269 deletions

View file

@ -41,7 +41,9 @@ in
Service = {
ExecStartPre = pkgs.writeShellScript "antigravity2api-init" ''
cat > ${workDir}/.env <<EOF
export PATH="${pkgs.coreutils}/bin:$PATH"
mkdir -p "${workDir}"
cat > "${workDir}/.env" <<EOF
API_KEY=${cfg.credentials.apiKey}
ADMIN_USERNAME=${cfg.credentials.username}
ADMIN_PASSWORD=${cfg.credentials.password}

12
modules/home/cosmic.nix Normal file
View file

@ -0,0 +1,12 @@
{
# programs.cosmic-manager.enable = true; # Deprecated
# Add cosmic-manager configurations here
# Example:
# programs.cosmic-edit.enable = true;
xdg.configFile."cosmic/com.system76.CosmicComp/v1/input_default".text = ''
(
acceleration_profile: Flat,
)
'';
}

View file

@ -15,6 +15,7 @@
./browser-container-update.nix
./proton-cachyos-updater.nix
./cli-tools.nix
./nixvim.nix
# ./unified-router.nix
./sillytavern.nix
@ -24,5 +25,6 @@
./polling-rate.nix
./antigravity2api.nix
./theme.nix
./prismlauncher.nix
];
}

View file

@ -232,7 +232,8 @@ in
Mod+Minus { set-column-width "-10%"; }
Mod+Equal { set-column-width "+10%"; }
Mod+Shift+E { quit; }
Mod+Shift+E { spawn "bemoji" "-t"; }
Print { spawn "sh" "-c" "grim -g \"$(slurp)\" - | wl-copy"; }
// Browsers

149
modules/home/nixvim.nix Normal file
View file

@ -0,0 +1,149 @@
{ pkgs, ... }:
{
programs.nixvim = {
enable = true;
defaultEditor = true;
colorschemes.catppuccin = {
enable = true;
settings = {
flavour = "mocha";
term_colors = true;
};
};
globals = {
mapleader = " ";
maplocalleader = "\\";
};
opts = {
number = true;
relativenumber = true;
# Tabs
tabstop = 2;
softtabstop = 2;
shiftwidth = 2;
expandtab = true;
smartindent = true;
# Search
ignorecase = true;
smartcase = true;
# UI
cursorline = true;
termguicolors = true;
scrolloff = 8;
# System
mouse = "a";
clipboard = "unnamedplus";
undofile = true;
};
plugins = {
lualine.enable = true;
bufferline.enable = true;
web-devicons.enable = true;
which-key.enable = true;
# Treesitter
treesitter = {
enable = true;
settings = {
highlight.enable = true;
indent.enable = true;
};
};
# File Explorer
neo-tree = {
enable = true;
closeIfLastWindow = true;
};
# Fuzzy Finder
telescope = {
enable = true;
keymaps = {
"<leader>ff" = "find_files";
"<leader>fg" = "live_grep";
"<leader>fb" = "buffers";
"<leader>fh" = "help_tags";
};
};
# LSP & Completion
lsp = {
enable = true;
servers = {
nixd.enable = true; # Nix
lua_ls.enable = true; # Lua
pyright.enable = true; # Python
bashls.enable = true; # Bash
html.enable = true; # HTML
cssls.enable = true; # CSS
ts_ls.enable = true; # TS/JS
};
};
cmp = {
enable = true;
autoEnableSources = true;
settings = {
sources = [
{ name = "nvim_lsp"; }
{ name = "path"; }
{ name = "buffer"; }
];
mapping = {
"<C-Space>" = "cmp.mapping.complete()";
"<CR>" = "cmp.mapping.confirm({ select = true })";
"<Tab>" = "cmp.mapping.select_next_item()";
"<S-Tab>" = "cmp.mapping.select_prev_item()";
};
};
};
cmp-nvim-lsp.enable = true;
cmp-path.enable = true;
cmp-buffer.enable = true;
# UI Improvements
notify = {
enable = true;
settings.background_colour = "#000000";
};
noice = {
enable = true;
settings = {
notify.enabled = true;
presets = {
bottom_search = true;
command_palette = true;
long_message_to_split = true;
inc_rename = false;
lsp_doc_border = false;
};
};
};
};
keymaps = [
{
mode = "n";
key = "<leader>e";
action = "<cmd>Neotree toggle<cr>";
options.desc = "Toggle Explorer";
}
{
mode = "n";
key = "<C-s>";
action = "<cmd>w<cr>";
options.desc = "Save File";
}
];
};
}

View file

@ -9,66 +9,83 @@ let
cfg = config.services.polling-rate-switcher;
switcherScript = pkgs.writeShellScriptBin "polling-rate-switcher" ''
# Find Razer Viper V3 Pro sysfs path
# Look for a device that supports poll_rate
MOUSE_PATH=""
export PATH="${
lib.makeBinPath [
pkgs.coreutils
pkgs.gnugrep
pkgs.jq
pkgs.systemd
pkgs.niri
pkgs.gawk
]
}:$PATH"
find_mouse() {
for dev in /sys/bus/hid/drivers/razermouse/*; do
if [ -f "$dev/device_type" ] && [ -f "$dev/poll_rate" ]; then
TYPE=$(cat "$dev/device_type")
# Check if it looks like a mouse "Razer Viper V3 Pro" usually has unique ID/Type
# For now, we take the first Razer device that supports polling rate
# Or specifically filter if we knew exact string.
# The user said "Active window... polling rate of my razer viper v3 pro"
# We'll assume it's the main device found.
MOUSE_PATH="$dev"
echo "Found Razer device at $MOUSE_PATH ($TYPE)"
break
# Find the Razer device object path via D-Bus
# We look for an object path that looks like /org/razer/device/...
# AND supports setPollRate
echo "Searching for OpenRazer device on D-Bus..."
RAZER_OBJ=""
# Simple retry loop in case the service starts before the device is ready
for i in {1..10}; do
# Find matches, then check introspection for setPollRate method
CANDIDATES=$(busctl --user tree --list org.razer | grep '^/org/razer/device/') || true
for obj in $CANDIDATES; do
if busctl --user introspect org.razer "$obj" 2>/dev/null | grep -q "setPollRate"; then
RAZER_OBJ="$obj"
break
fi
done
if [ -n "$RAZER_OBJ" ]; then
break
fi
done
}
sleep 2
done
find_mouse
if [ -z "$MOUSE_PATH" ]; then
echo "No Razer mouse found with poll_rate capability."
if [ -z "$RAZER_OBJ" ]; then
echo "No Razer device found on D-Bus org.razer that supports setPollRate after retries."
exit 1
fi
echo "Starting Polling Rate Switcher for $MOUSE_PATH"
echo "Found Razer device at $RAZER_OBJ"
CURRENT_MODE="unknown"
update_rate() {
TARGET=$1
# Read current to avoid redundant writes
CURRENT=$(cat "$MOUSE_PATH/poll_rate")
if [ "$CURRENT" != "$TARGET" ]; then
echo "Switching polling rate to $TARGET Hz"
echo "$TARGET" > "$MOUSE_PATH/poll_rate"
fi
# Function to set polling rate via D-Bus
set_rate() {
local rate="$1"
# Call OpenRazer method: setPollRate(int)
busctl --user call org.razer "$RAZER_OBJ" razer.device.misc setPollRate i "$rate"
}
while true; do
# Get active window info from Niri
# Niri msg active-window returns JSON
WINDOW_INFO=$(${pkgs.niri}/bin/niri msg -j focused-window 2>/dev/null)
current_rate=""
# Listen to Niri event stream
# We filter for WindowFocusChanged events, then query the state.
# This avoids parsing the complex event stream directly for state.
niri msg --json event-stream | jq --unbuffered -c 'select(.WindowFocusChanged) | .WindowFocusChanged.id' | while read -r _; do
# Query current focused window
# The focused-window output is wrapped in Ok.FocusedWindow
fw=$(niri msg --json focused-window 2>/dev/null || true)
if [ -n "$WINDOW_INFO" ]; then
APP_ID=$(echo "$WINDOW_INFO" | ${pkgs.jq}/bin/jq -r '.app_id // ""' | tr '[:upper:]' '[:lower:]')
TITLE=$(echo "$WINDOW_INFO" | ${pkgs.jq}/bin/jq -r '.title // ""' | tr '[:upper:]' '[:lower:]')
# Check for Overwatch
if [[ "$APP_ID" == *"overwatch"* ]] || [[ "$TITLE" == *"overwatch"* ]]; then
update_rate 8000
else
update_rate 1000
fi
# If no window is focused or command fails, these will be empty
app_id=$(echo "$fw" | jq -r '.Ok.FocusedWindow.app_id // ""' | tr '[:upper:]' '[:lower:]')
title=$(echo "$fw" | jq -r '.Ok.FocusedWindow.title // ""' | tr '[:upper:]' '[:lower:]')
# Default rate
target_rate=1000
# High polling rate for specific games
if [[ "$app_id" == *"overwatch"* || "$title" == *"overwatch"* ]]; then
target_rate=8000
fi
if [ "$target_rate" != "$current_rate" ]; then
echo "Switching rate to $target_rate Hz (App: $app_id, Title: $title)"
set_rate "$target_rate"
current_rate="$target_rate"
fi
sleep 2
done
'';
in

View file

@ -0,0 +1,287 @@
{
config,
lib,
pkgs,
inputs,
...
}:
let
cfg = config.myModules.prismlauncher;
bwrapperPkgs = pkgs.extend inputs.nix-bwrapper.overlays.default;
# Libraries required by Minecraft natives (LWJGL), various mods,
# and the Microsoft authentication flow (NSS/NSPR).
runtimeLibs = with pkgs; [
glib
libgbm
libglvnd
nspr
nss
alsa-lib
libpulseaudio
udev
cups
mesa
expat
libdrm
libxkbcommon
dbus
xorg.libXcomposite
xorg.libXdamage
xorg.libXext
xorg.libXfixes
xorg.libXrandr
xorg.libxcb
libxml2
xorg.libXScrnSaver
glfw
];
defaultJvmArgs = "-XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+AlwaysActAsServerClassMachine -XX:+AlwaysPreTouch -XX:+DisableExplicitGC -XX:+UseNUMA -XX:NmethodSweepActivity=1 -XX:ReservedCodeCacheSize=400M -XX:NonNMethodCodeHeapSize=12M -XX:ProfiledCodeHeapSize=194M -XX:NonProfiledCodeHeapSize=194M -XX:-DontCompileHugeMethods -XX:MaxNodeLimit=240000 -XX:NodeLimitFudgeFactor=8000 -XX:+UseVectorCmov -XX:+PerfDisableSharedMem -XX:+UseFastUnorderedTimeStamps -XX:+UseCriticalJavaThreadPriority -XX:ThreadPriorityPolicy=1 -XX:AllocatePrefetchStyle=3 -XX:+UseShenandoahGC -XX:ShenandoahGCMode=iu -XX:ShenandoahGuaranteedGCInterval=1000000 -XX:AllocatePrefetchStyle=1 -XX:ConcGCThreads=4";
in
{
options.myModules.prismlauncher = {
enable = lib.mkEnableOption "PrismLauncher Sandboxed";
jvmArgs = lib.mkOption {
type = lib.types.str;
default = defaultJvmArgs;
description = "JVM arguments to enforce in prismlauncher.cfg";
};
uid = lib.mkOption {
type = lib.types.int;
default = 1000;
description = "User ID for /run/user bind mount";
};
glfwPackage = lib.mkOption {
type = lib.types.package;
default = pkgs.glfw;
description = "The GLFW package to use for the custom GLFW path.";
};
};
config = lib.mkIf cfg.enable {
home.packages = [
(bwrapperPkgs.mkBwrapper {
app = {
id = "org.prismlauncher.PrismLauncher";
package = pkgs.prismlauncher.overrideAttrs (old: {
pname = "prismlauncher";
version = old.version or "9.1"; # Fallback or keep current if valid
buildInputs = (old.buildInputs or [ ]);
# Keep runtimeLibs in closure without injecting them into environment
postInstall = (old.postInstall or "") + ''
mkdir -p $out/share/prismlauncher-sandboxed
echo "${lib.makeLibraryPath runtimeLibs}" > $out/share/prismlauncher-sandboxed/libs
'';
qtWrapperArgs = (old.qtWrapperArgs or [ ]);
});
env = {
# Propagate XDG_DATA_DIRS so themes/icons can be found
BROWSER = "firefox";
QT_QPA_PLATFORM = "xcb";
GDK_BACKEND = "x11";
NO_AT_BRIDGE = "1";
QT_QPA_PLATFORMTHEME = "";
QT_STYLE_OVERRIDE = "fusion";
# Sanitize Desktop Environment to prevent loading conflicting platform themes
XDG_CURRENT_DESKTOP = "X-Generic";
XDG_SESSION_TYPE = "x11";
GTK_USE_PORTAL = "0";
GTK_THEME = "Adwaita"; # Force a safe theme or empty?
# Unset potential conflict variables
GTK_MODULES = "";
GTK3_MODULES = "";
};
};
sockets.x11 = true;
sockets.wayland = true;
flatpak.enable = false;
fhsenv.opts = {
unshareUser = true;
unshareUts = false;
unshareCgroup = false;
unsharePid = false;
unshareNet = false;
unshareIpc = false;
};
fhsenv.bwrap.baseArgs = lib.mkForce [
"--new-session"
"--proc /proc"
"--dev /dev"
"--dev-bind /dev/dri /dev/dri"
"--tmpfs /home"
"--tmpfs /tmp"
"--tmpfs /run"
"--dir /run/user"
"--dir /run/user/${toString cfg.uid}"
# Bind ro system paths commonly needed
"--ro-bind-try /run/opengl-driver /run/opengl-driver"
"--ro-bind-try /run/opengl-driver-32 /run/opengl-driver-32"
"--dir /run/systemd/resolve"
"--ro-bind-try /run/systemd/resolve /run/systemd/resolve"
"--ro-bind /run/dbus /run/dbus"
];
mounts = {
read = [
"$HOME/.config/fontconfig"
"$HOME/.local/share/fonts"
"$HOME/.icons"
"$HOME/.themes"
"$HOME/.local/share/themes"
"$HOME/.config/qt6ct"
"$HOME/.config/Kvantum"
"$HOME/.config/MangoHud"
"$HOME/Downloads"
];
readWrite = [
"$HOME/.local/share/PrismLauncher"
"$HOME/.cache/PrismLauncher"
];
};
dbus.enable = false;
script.preCmds.stage2 =
let
glfwPath = "${cfg.glfwPackage}/lib/libglfw.so.3";
# We need to access the sandbox-utils.nix. Since it's in system modules,
# we can't easily import it relative to here if it's not exported.
# But the content was small, let's inline what we need or check if we can source it.
# For now, I'll assume the dbus-proxy logic is needed.
# Reimplementing mkDbusProxyScript from sandbox-utils.nix inline to avoid path dependency
mkDbusProxyScript =
{ appId, proxyArgs }:
let
proxyArgsStr = lib.escapeShellArgs proxyArgs;
appDir = "$XDG_RUNTIME_DIR/app/${appId}";
proxySocket = "${appDir}/bus";
in
''
mkdir -p "${appDir}"
# Start xdg-dbus-proxy
${pkgs.xdg-dbus-proxy}/bin/xdg-dbus-proxy \
"$DBUS_SESSION_BUS_ADDRESS" "${proxySocket}" \
${proxyArgsStr} &
DBUS_PROXY_PID=$!
# Kill proxy on exit
trap "kill $DBUS_PROXY_PID" EXIT
# Wait for socket to be created
for i in {1..50}; do
if [ -S "${proxySocket}" ]; then
break
fi
if ! kill -0 $DBUS_PROXY_PID 2>/dev/null; then
echo "xdg-dbus-proxy died unexpectedly"
exit 1
fi
sleep 0.1
done
'';
dbusScript = mkDbusProxyScript {
appId = "org.prismlauncher.PrismLauncher";
proxyArgs = [
"--filter"
"--talk=org.freedesktop.portal.*"
"--call=org.freedesktop.portal.*=*@/org/freedesktop/portal/desktop"
"--talk=org.freedesktop.Notifications"
"--own=org.prismlauncher.PrismLauncher"
"--own=org.prismlauncher.PrismLauncher.*"
];
};
in
''
${dbusScript}
# Sanitize Environment
unset QT_QPA_PLATFORMTHEME
unset GTK_THEME
unset XDG_CURRENT_DESKTOP
export QT_QPA_PLATFORM=xcb
export GDK_BACKEND=x11
export NO_AT_BRIDGE=1
# Force Configs (JVM Args + GLFW)
cfg="$HOME/.local/share/PrismLauncher/prismlauncher.cfg"
if [ -f "$cfg" ]; then
# JVM Args
if ${pkgs.gnugrep}/bin/grep -q "^JvmArgs=" "$cfg"; then
${pkgs.gnused}/bin/sed -i "s|^JvmArgs=.*|JvmArgs=${cfg.jvmArgs}|" "$cfg"
else
if ${pkgs.gnugrep}/bin/grep -q "^\\[General\\]" "$cfg"; then
${pkgs.gnused}/bin/sed -i "/^\\[General\\]/a JvmArgs=${cfg.jvmArgs}" "$cfg"
else
echo "JvmArgs=${cfg.jvmArgs}" >> "$cfg"
fi
fi
# GLFW Settings
# 1. CustomGLFWPath
if ${pkgs.gnugrep}/bin/grep -q "^CustomGLFWPath=" "$cfg"; then
${pkgs.gnused}/bin/sed -i "s|^CustomGLFWPath=.*|CustomGLFWPath=${glfwPath}|" "$cfg"
else
echo "CustomGLFWPath=${glfwPath}" >> "$cfg"
fi
# 2. UseNativeGLFW
if ${pkgs.gnugrep}/bin/grep -q "^UseNativeGLFW=" "$cfg"; then
${pkgs.gnused}/bin/sed -i "s|^UseNativeGLFW=.*|UseNativeGLFW=true|" "$cfg"
else
echo "UseNativeGLFW=true" >> "$cfg"
fi
fi
'';
fhsenv.bwrap.additionalArgs = [
# D-Bus proxy
''--bind "$XDG_RUNTIME_DIR/bus" "$XDG_RUNTIME_DIR/bus"''
# Note: The original code bound a specific path TO ./bus.
# "''--bind "$XDG_RUNTIME_DIR/app/org.prismlauncher.PrismLauncher/bus" "$XDG_RUNTIME_DIR/bus"''"
# But mkDbusProxyScript (if standard) creates a socket.
# The logic in prismlauncher-sandboxed.nix imported sandbox-utils.nix.
# I'll try to match the original bind logic if possible.
# The original code had:
# ''--bind "$XDG_RUNTIME_DIR/app/org.prismlauncher.PrismLauncher/bus" "$XDG_RUNTIME_DIR/bus"''
# But my inline mkDbusProxyScript sets up "$XDG_RUNTIME_DIR/bus" as the listen socket *inside* the script execution?
# Wait, xdg-dbus-proxy runs inside the outer unshared namespace or outside?
# In mkBwrapper, preCmds run *inside* the bwrap?
# No, typically preCmds run before the final exec?
# Actually, looking at nix-bwrapper, `preCmds.stage2` runs *inside* the sandbox?
# Let's start with the binds exactly as they were, assuming `sandbox-utils` logic.
# If I can't import sandbox-utils, I have to rely on what I can see.
# The original `sandbox-utils.nix` likely set up the proxy.
# I will copy the binds from the original file.
''--bind "$XDG_RUNTIME_DIR/app/org.prismlauncher.PrismLauncher/bus" "$XDG_RUNTIME_DIR/bus"''
# Wayland socket
''--bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"''
# PipeWire + Pulse
''--bind "$XDG_RUNTIME_DIR/pipewire-0" "$XDG_RUNTIME_DIR/pipewire-0"''
''--bind "$XDG_RUNTIME_DIR/pulse" "$XDG_RUNTIME_DIR/pulse"''
];
})
];
};
}

View file

@ -40,7 +40,7 @@ in
azahar-sandboxed = bwrapperPkgs.mkBwrapper {
app = {
package = azahar;
id = "org.azahar_emu.azahar";
id = "org.azahar_emu.Azahar";
env = {
QT_QPA_PLATFORM = "wayland;xcb";
XDG_CURRENT_DESKTOP = "KDE";
@ -51,10 +51,7 @@ in
fhsenv.bwrap.additionalArgs = [
"--dir /run/systemd/resolve"
"--ro-bind-try /run/systemd/resolve /run/systemd/resolve"
''--bind "$XDG_RUNTIME_DIR/app/org.azahar_emu.azahar/bus" "$XDG_RUNTIME_DIR/bus"''
''--bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"''
''--bind "$XDG_RUNTIME_DIR/pipewire-0" "$XDG_RUNTIME_DIR/pipewire-0"''
''--bind "$XDG_RUNTIME_DIR/pulse" "$XDG_RUNTIME_DIR/pulse"''
''--bind "$XDG_RUNTIME_DIR/app/org.azahar_emu.Azahar/bus" "$XDG_RUNTIME_DIR/bus"''
];
mounts = {
@ -74,7 +71,7 @@ in
dbus.enable = false;
script.preCmds.stage2 = (import ./sandbox-utils.nix { inherit pkgs lib; }).mkDbusProxyScript {
appId = "org.azahar_emu.azahar";
appId = "org.azahar_emu.Azahar";
enableSystemBus = false;
proxyArgs = [
"--filter"

View file

@ -1,4 +1,3 @@
# Brave Sandboxed with nix-bwrapper
{
config,
lib,
@ -150,4 +149,26 @@ in
};
})
];
environment.systemPackages =
let
vpnLauncher = pkgs.writeShellScriptBin "brave-vpn" ''
exec /home/ashie/nixos/scripts/launch-vpn-app.sh ${pkgs.brave-sandboxed}/bin/brave "$@"
'';
desktopItem = pkgs.makeDesktopItem {
name = "brave-vpn";
desktopName = "Brave (VPN)";
exec = "${vpnLauncher}/bin/brave-vpn";
icon = "brave-browser";
categories = [
"Network"
"WebBrowser"
];
};
in
[
vpnLauncher
desktopItem
];
}

View file

@ -1,16 +1,3 @@
# Caddy with Cloudflare DNS-01 ACME Module
# Provides: Caddy reverse proxy with automatic SSL via Cloudflare DNS
#
# Usage:
# myModules.caddyCloudflare = {
# enable = true;
# email = "you@example.com";
# cloudflareApiTokenFile = config.sops.secrets.cloudflare_api_key.path;
# virtualHosts = {
# "api.example.com" = { reverseProxy = "127.0.0.1:8080"; };
# };
# };
{
config,
lib,

View file

@ -143,7 +143,9 @@ in
# Create directories that bwrap will bind
mkdir -p "$HOME/.cache/citron-tmp"
mkdir -p "$HOME/.config/citron"
mkdir -p "$HOME/.config/Citron"
mkdir -p "$HOME/.local/share/citron"
mkdir -p "$HOME/.local/share/Citron"
mkdir -p "$HOME/Games/Switch"
''
+ (import ./sandbox-utils.nix { inherit pkgs lib; }).mkDbusProxyScript {
@ -170,13 +172,6 @@ in
# D-Bus session proxy only
''--bind "$XDG_RUNTIME_DIR/app/${appId}/bus" "$XDG_RUNTIME_DIR/bus"''
# Wayland socket
''--bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"''
# PipeWire + Pulse
''--bind "$XDG_RUNTIME_DIR/pipewire-0" "$XDG_RUNTIME_DIR/pipewire-0"''
''--bind "$XDG_RUNTIME_DIR/pulse" "$XDG_RUNTIME_DIR/pulse"''
# Manual mounts for data persistence
"--ro-bind-try $HOME/.config/kdedefaults $HOME/.config/kdedefaults"
"--ro-bind-try $HOME/.local/share/color-schemes $HOME/.local/share/color-schemes"
@ -190,7 +185,9 @@ in
# Read-write mounts
"--bind $HOME/Games/Switch $HOME/Games/Switch"
"--bind $HOME/.config/citron $HOME/.config/citron"
"--bind $HOME/.config/Citron $HOME/.config/Citron"
"--bind $HOME/.local/share/citron $HOME/.local/share/citron"
"--bind $HOME/.local/share/Citron $HOME/.local/share/Citron"
"--bind $HOME/.cache/citron-tmp $HOME/.cache/citron-tmp"
];
};

14
modules/system/cosmic.nix Normal file
View file

@ -0,0 +1,14 @@
{
pkgs,
...
}:
{
services.desktopManager.cosmic.enable = true;
services.displayManager.cosmic-greeter.enable = false;
# Optimization
services.system76-scheduler.enable = true;
# Clipboard support (unstable protocol)
environment.sessionVariables.COSMIC_DATA_CONTROL_ENABLED = "1";
}

View file

@ -9,6 +9,8 @@
imports = [
./common.nix
./security.nix
./mac-randomization.nix
./usbguard.nix
./kernel-hardening.nix
./secure-boot.nix
./dns-over-tls.nix
@ -31,5 +33,8 @@
./spotify-sandboxed.nix
./performance.nix
./vesktop-sandboxed.nix
./tutanota-sandboxed.nix
./hardened-malloc.nix
./searxng.nix
];
}

View file

@ -138,13 +138,6 @@ in
# D-Bus session proxy only
''--bind "$XDG_RUNTIME_DIR/app/io.github.faugus.Launcher/bus" "$XDG_RUNTIME_DIR/bus"''
# Wayland socket
''--bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"''
# PipeWire + Pulse
''--bind "$XDG_RUNTIME_DIR/pipewire-0" "$XDG_RUNTIME_DIR/pipewire-0"''
''--bind "$XDG_RUNTIME_DIR/pulse" "$XDG_RUNTIME_DIR/pulse"''
# dconf for GTK settings
"--bind-try /run/user/${toString config.users.users.ashie.uid}/dconf /run/user/${toString config.users.users.ashie.uid}/dconf"
];

View file

@ -1,4 +1,3 @@
# Firefox Sandboxed with nix-bwrapper
{
config,
lib,

View file

@ -0,0 +1,32 @@
# Hardened Malloc Module (Scudo)
# Provides: Userspace memory corruption mitigations via LLVM Scudo
#
# Usage:
# myModules.hardenedMalloc = {
# enable = true;
# };
{
config,
lib,
pkgs,
...
}:
let
cfg = config.myModules.hardenedMalloc;
in
{
options.myModules.hardenedMalloc = {
enable = lib.mkEnableOption "hardened memory allocator (Scudo)";
};
config = lib.mkIf cfg.enable {
environment.memoryAllocator.provider = "scudo";
# Scudo options:
# ZeroContents=1: Zero chunks on allocation/deallocation (mitigates use-after-free info leaks)
# PatternFillContents=1: (Alternative to Zero) Fill with pattern to catch bugs
environment.variables.SCUDO_OPTIONS = "ZeroContents=1";
};
}

View file

@ -34,6 +34,12 @@
"/var/lib/bluetooth" # Bluetooth pairings
"/var/lib/sbctl" # Secure Boot Keys
"/etc/NetworkManager/system-connections" # Wifi/Ethernet profiles
"/var/lib/sonarr"
"/var/lib/radarr"
"/var/lib/prowlarr"
"/var/lib/qbittorrent"
"/var/lib/jellyfin"
"/var/lib/jellyseerr"
];
files = [
@ -47,6 +53,7 @@
"Music"
"Pictures"
"Videos"
"Torrents"
"nixos" # Config repo
".local/share/PrismLauncher" # Minecraft
".local/share/containers" # Rootless podman
@ -58,10 +65,12 @@
"git" # Git Repositories
".local/state" # Application State
".config/Antigravity" # Antigravity Config
".config/modprobed-db" # Local modconfig database
".config/VSCodium" # Codium Config
".config/sops" # Sops Keys
".config/gh" # Github CLI Auth
".local/share/keyrings" # Gnome Keyrings (Passwords)
".local/share/nvim" # NeoVim data (LazyVim, Mason, etc.)
".local/share/flatpak" # Flatpak Apps
".vscode" # VSCode Extensions
".vscode-oss" # VSCodium Extensions
@ -71,6 +80,10 @@
".config/citron"
".local/share/citron"
".cache/lutris"
".config/azahar"
".local/share/azahar"
".config/Citron"
".local/share/Citron"
".local/share/umu"
".cache/mesa_shader_cache"
# ".local/share/Steam" # Symlinked to /games/Steam (Already Persistent)
@ -78,6 +91,8 @@
".config/steamtinkerlaunch" # Example of extra tools
".local/share/applications" # Desktop entries
".local/share/icons" # Application icons
".local/bin" # User scripts
".local/share/qBittorrent"
];
};
};

View file

@ -66,6 +66,9 @@ in
"randomize_kstack_offset=on"
"vsyscall=none"
"oops=panic"
"debugfs=off"
"module.sig_enforce=1"
"lockdown=confidentiality"
];
# Kernel sysctl hardening
@ -104,6 +107,13 @@ in
"net.ipv4.tcp_rmem" = "4096 87380 2500000";
"net.ipv4.tcp_wmem" = "4096 65536 2500000";
"net.core.netdev_max_backlog" = 5000;
# Advanced Security
"net.core.bpf_jit_harden" = 2;
"fs.suid_dumpable" = 0;
"kernel.sysrq" = 0;
"net.ipv4.conf.all.accept_source_route" = 0;
"net.ipv6.conf.all.accept_source_route" = 0;
};
# Set IO Scheduler to kyber for NVMe and bfq for SATA

View file

@ -76,7 +76,7 @@ in
];
readWrite = [
"$HOME/Games"
"$HOME/Games/windows"
"$HOME/.local/share/icons"
"$HOME/.config/lutris"
"$HOME/.local/share/lutris"

View file

@ -0,0 +1,42 @@
# MAC Address Randomization Module
# Provides: MAC address randomization for Wi-Fi and Ethernet
#
# Usage:
# myModules.macRandomization = {
# enable = true;
# mode = "stable-ssid"; # "random", "stable", "stable-ssid" (default)
# };
{
config,
lib,
pkgs,
...
}:
let
cfg = config.myModules.macRandomization;
in
{
options.myModules.macRandomization = {
enable = lib.mkEnableOption "MAC address randomization";
mode = lib.mkOption {
type = lib.types.enum [ "random" "stable" "stable-ssid" ];
default = "stable-ssid";
description = ''
MAC randomization mode:
- random: Randomize for every connection (highest privacy, might break captive portals).
- stable: Generate a stable random MAC per connection profile.
- stable-ssid: Generate a stable random MAC per SSID (default).
'';
};
};
config = lib.mkIf cfg.enable {
networking.networkmanager.wifi.macAddress = cfg.mode;
# Optional: Ethernet randomization (can cause issues on some LANs)
# networking.networkmanager.ethernet.macAddress = cfg.mode;
};
}

167
modules/system/media.nix Normal file
View file

@ -0,0 +1,167 @@
{
config,
lib,
pkgs,
...
}:
{
# 1. Create the 'media' group (optional now if running as user)
users.groups.media = { };
# 2. OCI Container Configuration
virtualisation.oci-containers.containers = {
# Prowlarr
prowlarr = {
image = "lscr.io/linuxserver/prowlarr:latest";
ports = [ "9696:9696" ];
environment = {
PUID = "1000";
PGID = "100"; # users group
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/prowlarr:/config"
];
};
# Sonarr
sonarr = {
image = "lscr.io/linuxserver/sonarr:latest";
ports = [ "8989:8989" ];
environment = {
PUID = "1000";
PGID = "100";
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/sonarr:/config"
"/data:/data"
];
};
# Radarr
radarr = {
image = "lscr.io/linuxserver/radarr:latest";
ports = [ "7878:7878" ];
environment = {
PUID = "1000";
PGID = "100";
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/radarr:/config"
"/data:/data"
];
};
# FlareSolverr (Cloudflare Bypass)
flaresolverr = {
image = "ghcr.io/flaresolverr/flaresolverr:latest";
ports = [ "8191:8191" ];
environment = {
TZ = "Europe/Berlin";
LOG_LEVEL = "info";
};
};
# Jellyfin (Media Server)
jellyfin = {
image = "lscr.io/linuxserver/jellyfin:latest";
ports = [ "8096:8096" ];
environment = {
PUID = "1000";
PGID = "100";
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/jellyfin:/config"
"/data:/data"
];
};
# VPN (Gluetun)
# WARNING: You must configure your VPN provider details in 'environmentFiles' or 'environment'
vpn = {
image = "qmcgaw/gluetun";
ports = [
"8080:8080" # qBittorrent WebUI
"6881:6881" # Torrent Port TCP
"6881:6881/udp" # Torrent Port UDP
];
environmentFiles = [ config.sops.templates."gluetun.env".path ];
environment = {
TZ = "Europe/Berlin";
DOT = "off";
DNS_ADDRESS = "1.1.1.1";
WIREGUARD_MTU = "1420";
};
extraOptions = [
"--cap-add=NET_ADMIN"
"--cap-add=NET_RAW"
"--device=/dev/net/tun:/dev/net/tun"
];
};
# qBittorrent (Networked via VPN)
torrent = {
image = "lscr.io/linuxserver/qbittorrent:latest";
# Ports are exposed via vpn container, not here
extraOptions = [ "--network=container:vpn" ];
dependsOn = [ "vpn" ];
environment = {
PUID = "1000";
PGID = "100";
TZ = "Europe/Berlin";
WEBUI_PORT = "8080";
};
volumes = [
"/var/lib/qbittorrent:/config"
"/data:/data"
];
};
# Jellyseerr (Request Management)
jellyseerr = {
image = "docker.io/fallenbagel/jellyseerr:latest";
ports = [ "5055:5055" ];
environment = {
LOG_LEVEL = "debug";
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/jellyseerr:/app/config"
];
};
};
# Ensure config directories exist and are owned by the user (1000)
systemd.tmpfiles.rules = [
"d /data 0755 ashie users - -"
"d /var/lib/prowlarr 0755 ashie users - -"
"d /var/lib/sonarr 0755 ashie users - -"
"d /var/lib/radarr 0755 ashie users - -"
"d /var/lib/qbittorrent 0755 ashie users - -"
"d /var/lib/jellyfin 0755 ashie users - -"
"d /var/lib/jellyseerr 0755 ashie users - -"
# Recursively fix permissions on restart to ensure 1000 owns the config
"Z /var/lib/prowlarr - ashie users - -"
"Z /var/lib/sonarr - ashie users - -"
"Z /var/lib/radarr - ashie users - -"
"Z /var/lib/qbittorrent - ashie users - -"
"Z /var/lib/jellyfin - ashie users - -"
"Z /var/lib/jellyseerr - ashie users - -"
];
# Firewall rules
networking.firewall.allowedTCPPorts = [
9696
8989
7878
8191
8080
8096
5055
6881
];
networking.firewall.allowedUDPPorts = [ 6881 ];
}

View file

@ -14,7 +14,7 @@
config = lib.mkIf config.myModules.performance.enable {
services.scx = {
enable = true;
scheduler = "scx_rustland";
scheduler = "scx_lavd";
package = pkgs.scx.full;
};
@ -46,6 +46,8 @@
"net.ipv4.tcp_wmem" = lib.mkForce "4096 65536 16777216";
};
powerManagement.cpuFreqGovernor = lib.mkDefault "performance";
# faster boot
systemd.services.NetworkManager-wait-online.enable = lib.mkForce false;
systemd.services.systemd-networkd-wait-online.enable = lib.mkForce false;

View file

@ -53,28 +53,29 @@ in
prismlauncher-sandboxed = bwrapperPkgs.mkBwrapper {
app = {
id = "org.prismlauncher.PrismLauncher";
package =
inputs.prismlauncher.packages.${pkgs.stdenv.hostPlatform.system}.prismlauncher.overrideAttrs
(old: {
pname = "prismlauncher";
version = old.version or "9.1";
buildInputs = (old.buildInputs or [ ]) ++ runtimeLibs ++ [ pkgs.mimalloc ];
package = pkgs.prismlauncher.overrideAttrs (old: {
pname = "prismlauncher";
version = old.version or "9.1";
buildInputs = (old.buildInputs or [ ]) ++ [ pkgs.mimalloc ];
qtWrapperArgs = (old.qtWrapperArgs or [ ]) ++ [
"--set MIMALLOC_PATH ${pkgs.mimalloc}/lib/libmimalloc.so"
"--prefix LD_PRELOAD : ${pkgs.mimalloc}/lib/libmimalloc.so"
"--prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath runtimeLibs}"
"--prefix QT_PLUGIN_PATH : ${pkgs.kdePackages.qtstyleplugin-kvantum}/lib/qt6/plugins"
];
});
# Keep runtimeLibs in closure without injecting them into environment
postInstall = (old.postInstall or "") + ''
mkdir -p $out/share/prismlauncher-sandboxed
echo "${lib.makeLibraryPath runtimeLibs}" > $out/share/prismlauncher-sandboxed/libs
'';
qtWrapperArgs = (old.qtWrapperArgs or [ ]) ++ [
"--set MIMALLOC_PATH ${pkgs.mimalloc}/lib/libmimalloc.so"
"--prefix LD_PRELOAD : ${pkgs.mimalloc}/lib/libmimalloc.so"
];
});
env = {
# Propagate XDG_DATA_DIRS so themes/icons can be found
XDG_DATA_DIRS = "$XDG_DATA_DIRS";
GTK_THEME = "catppuccin-mocha-mauve-standard";
QT_QPA_PLATFORMTHEME = "gtk3";
QT_STYLE_OVERRIDE = "kvantum";
# XDG_DATA_DIRS = "$XDG_DATA_DIRS";
BROWSER = "firefox";
QT_QPA_PLATFORMTHEME = "";
QT_STYLE_OVERRIDE = "fusion";
};
};
@ -103,7 +104,7 @@ in
"--dir /run/user"
"--dir /run/user/${toString config.users.users.ashie.uid}"
# Bind ro system paths commonly needed
"--ro-bind-try /run/current-system /run/current-system"
# "--ro-bind-try /run/current-system /run/current-system"
"--ro-bind-try /run/opengl-driver /run/opengl-driver"
"--ro-bind-try /run/opengl-driver-32 /run/opengl-driver-32"
"--dir /run/systemd/resolve"

View file

@ -109,13 +109,6 @@ in
# D-Bus session proxy only
''--bind "$XDG_RUNTIME_DIR/app/${appId}/bus" "$XDG_RUNTIME_DIR/bus"''
# Wayland socket
''--bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"''
# PipeWire + Pulse
''--bind "$XDG_RUNTIME_DIR/pipewire-0" "$XDG_RUNTIME_DIR/pipewire-0"''
''--bind "$XDG_RUNTIME_DIR/pulse" "$XDG_RUNTIME_DIR/pulse"''
# Manual mounts for data persistence
"--ro-bind-try $HOME/.config/kdedefaults $HOME/.config/kdedefaults"
"--ro-bind-try $HOME/.local/share/color-schemes $HOME/.local/share/color-schemes"

View file

@ -13,7 +13,7 @@ in
enable = lib.mkEnableOption "sched-ext (scx) schedulers";
scheduler = lib.mkOption {
type = lib.types.enum [ "scx_rustland" "scx_lavd" "scx_rusty" "scx_bpfland" ];
type = lib.types.enum [ "scx_lavd" "scx_rusty" "scx_bpfland" ];
default = "scx_lavd";
description = "The scx scheduler to run.";
};

303
modules/system/searxng.nix Normal file
View file

@ -0,0 +1,303 @@
# SearXNG Module (Rootless Podman)
# Provides: Private meta-search engine running in a rootless container
#
# Usage:
# myModules.searxng = {
# enable = true;
# port = 8888;
# domain = "search.ashisgreat.xyz";
# };
{
config,
lib,
pkgs,
...
}:
let
cfg = config.myModules.searxng;
catppuccinCss = pkgs.writeText "searxng-catppuccin.css" ''
:root {
/* Mocha (Dark) */
--cat-rosewater: #f5e0dc;
--cat-flamingo: #f2cdcd;
--cat-pink: #f5c2e7;
--cat-mauve: #cba6f7;
--cat-red: #f38ba8;
--cat-maroon: #eba0ac;
--cat-peach: #fab387;
--cat-yellow: #f9e2af;
--cat-green: #a6e3a1;
--cat-teal: #94e2d5;
--cat-sky: #89dceb;
--cat-sapphire: #74c7ec;
--cat-blue: #89b4fa;
--cat-lavender: #b4befe;
--cat-text: #cdd6f4;
--cat-subtext1: #bac2de;
--cat-subtext0: #a6adc8;
--cat-overlay2: #9399b2;
--cat-overlay1: #7f849c;
--cat-overlay0: #6c7086;
--cat-surface2: #585b70;
--cat-surface1: #45475a;
--cat-surface0: #313244;
--cat-base: #1e1e2e;
--cat-mantle: #181825;
--cat-crust: #11111b;
}
@media (prefers-color-scheme: light) {
:root {
/* Latte (Light) */
--cat-rosewater: #dc8a78;
--cat-flamingo: #dd7878;
--cat-pink: #ea76cb;
--cat-mauve: #8839ef;
--cat-red: #d20f39;
--cat-maroon: #e64553;
--cat-peach: #fe640b;
--cat-yellow: #df8e1d;
--cat-green: #40a02b;
--cat-teal: #179287;
--cat-sky: #04a5e5;
--cat-sapphire: #209fb5;
--cat-blue: #1e66f5;
--cat-lavender: #7287fd;
--cat-text: #4c4f69;
--cat-subtext1: #5c5f77;
--cat-subtext0: #6c6f85;
--cat-overlay2: #7c7f93;
--cat-overlay1: #8c8fa1;
--cat-overlay0: #9ca0b0;
--cat-surface2: #acb0be;
--cat-surface1: #bcc0cc;
--cat-surface0: #ccd0da;
--cat-base: #eff1f5;
--cat-mantle: #e6e9ef;
--cat-crust: #dce0e8;
}
}
/* Apply variables */
:root {
--color-base-font: var(--cat-text);
--color-base-background: var(--cat-base);
--color-base-background-mobile: var(--cat-base);
--color-url-font: var(--cat-mauve);
--color-url-visited-font: var(--cat-mauve);
--color-header-background: var(--cat-mantle);
--color-header-border: var(--cat-mantle);
--color-footer-background: var(--cat-mantle);
--color-footer-border: var(--cat-mantle);
--color-sidebar-border: var(--cat-base);
--color-sidebar-font: var(--cat-text);
--color-sidebar-background: var(--cat-base);
--color-backtotop-font: var(--cat-subtext1);
--color-backtotop-border: var(--cat-surface0);
--color-backtotop-background: var(--cat-surface0);
--color-btn-background: var(--cat-mauve);
--color-btn-font: var(--cat-base);
--color-show-btn-background: var(--cat-mauve);
--color-show-btn-font: var(--cat-base);
--color-search-border: var(--cat-surface0);
--color-search-shadow: 0 2px 8px var(--cat-crust);
--color-search-background: var(--cat-surface0);
--color-search-font: var(--cat-text);
--color-search-background-hover: var(--cat-mauve);
--color-error: var(--cat-red);
--color-error-background: var(--cat-surface0);
--color-warning: var(--cat-yellow);
--color-warning-background: var(--cat-surface0);
--color-success: var(--cat-green);
--color-success-background: var(--cat-surface0);
--color-categories-item-selected-font: var(--cat-text);
--color-categories-item-border-selected: var(--cat-mauve);
--color-autocomplete-font: var(--cat-subtext1);
--color-autocomplete-border: var(--cat-surface0);
--color-autocomplete-shadow: 0 2px 8px var(--cat-crust);
--color-autocomplete-background: var(--cat-surface0);
--color-autocomplete-background-hover: var(--cat-surface1);
--color-answer-font: var(--cat-text);
--color-answer-background: var(--cat-mantle);
--color-result-background: var(--cat-mantle);
--color-result-border: var(--cat-base);
--color-result-url-font: var(--cat-subtext1);
--color-result-vim-selected: var(--cat-surface0);
--color-result-vim-arrow: var(--cat-mauve);
--color-result-description-highlight-font: var(--cat-text);
--color-result-link-font: var(--cat-mauve);
--color-result-link-font-highlight: var(--cat-mauve);
--color-result-link-visited-font: var(--cat-mauve);
--color-result-publishdate-font: var(--cat-surface2);
--color-result-engines-font: var(--cat-surface2);
--color-result-search-url-border: var(--cat-surface2);
--color-result-search-url-font: var(--cat-text);
--color-result-detail-font: var(--cat-text);
--color-result-detail-label-font: var(--cat-subtext0);
--color-result-detail-background: var(--cat-base);
--color-result-detail-hr: var(--cat-base);
--color-result-detail-link: var(--cat-mauve);
--color-result-detail-loader-border: rgba(255, 255, 255, 0.2);
--color-result-detail-loader-borderleft: var(--cat-crust);
--color-result-image-span-font: var(--cat-text);
--color-result-image-span-font-selected: var(--cat-base);
--color-result-image-background: var(--cat-mantle);
--color-settings-tr-hover: var(--cat-surface0);
--color-settings-engine-description-font: var(--cat-text);
--color-settings-engine-group-background: var(--cat-surface0);
--color-toolkit-badge-font: var(--cat-text);
--color-toolkit-badge-background: var(--cat-surface0);
--color-toolkit-kbd-font: var(--cat-text);
--color-toolkit-kbd-background: var(--cat-mantle);
--color-toolkit-dialog-border: var(--cat-mantle);
--color-toolkit-dialog-background: var(--cat-mantle);
--color-toolkit-tabs-label-border: var(--cat-base);
--color-toolkit-tabs-section-border: var(--cat-base);
--color-toolkit-select-background: var(--cat-surface0);
--color-toolkit-select-border: var(--cat-surface0);
--color-toolkit-select-background-hover: var(--cat-surface1);
--color-toolkit-input-text-font: var(--cat-text);
--color-toolkit-checkbox-onoff-off-background: var(--cat-surface0);
--color-toolkit-checkbox-onoff-on-background: var(--cat-surface0);
--color-toolkit-checkbox-onoff-on-mark-background: var(--cat-green);
--color-toolkit-checkbox-onoff-on-mark-color: var(--cat-mantle);
--color-toolkit-checkbox-onoff-off-mark-background: var(--cat-red);
--color-toolkit-checkbox-onoff-off-mark-color: var(--cat-mantle);
--color-toolkit-checkbox-label-background: var(--cat-base);
--color-toolkit-checkbox-label-border: var(--cat-mantle);
--color-toolkit-checkbox-input-border: var(--cat-mauve);
--color-toolkit-engine-tooltip-border: var(--cat-surface0);
--color-toolkit-engine-tooltip-background: var(--cat-surface0);
--color-toolkit-loader-border: rgba(255, 255, 255, 0.2);
--color-toolkit-loader-borderleft: var(--cat-crust);
--color-doc-code: var(--cat-rosewater);
--color-doc-code-background: var(--cat-mantle);
}
#search_logo svg :not([fill="none"]) {
fill: var(--cat-mauve) !important;
}
#search_logo svg :not([stroke="none"]) {
stroke: var(--cat-mauve) !important;
}
/* Additional cute tweaks */
article.result {
background-color: var(--color-result-background);
border-radius: 0.75em;
padding: 0.75em;
margin: 0.5em;
border: 1px solid var(--cat-surface0);
}
article.category-images {
padding-bottom: 4em;
}
input[type="text"] {
border-radius: 2em !important;
}
'';
in
{
options.myModules.searxng = {
enable = lib.mkEnableOption "SearXNG meta-search engine";
port = lib.mkOption {
type = lib.types.port;
default = 8888;
description = "Port to expose SearXNG on localhost";
};
domain = lib.mkOption {
type = lib.types.str;
default = "search.ashisgreat.xyz";
description = "Public domain name for SearXNG";
};
};
config = lib.mkIf cfg.enable {
# Ensure Podman is enabled
myModules.podman.enable = true;
# 1. Redis Container (Cache/Limiter)
virtualisation.oci-containers.containers."searxng-redis" = {
image = "docker.io/library/redis:alpine";
cmd = [ "redis-server" "--save" "" "--appendonly" "no" ]; # Ephemeral cache, no persistence needed
ports = [ "127.0.0.1:6379:6379" ];
};
# 2. SearXNG Container
virtualisation.oci-containers.containers."searxng" = {
image = "docker.io/searxng/searxng:latest";
ports = [ "127.0.0.1:${toString cfg.port}:8080" ];
environment = {
"SEARXNG_BASE_URL" = "https://${cfg.domain}";
"SEARXNG_REDIS_URL" = "redis://searxng-redis:6379"; # Talk to Redis directly via container DNS
"SEARXNG_URL_BASE" = "https://${cfg.domain}";
};
environmentFiles = [
# Contains SEARXNG_SECRET_KEY
config.sops.templates."searxng.env".path
];
extraOptions = [
"--cap-drop=ALL"
"--cap-add=CHOWN"
"--cap-add=SETGID"
"--cap-add=SETUID"
"--cap-add=DAC_OVERRIDE"
"--add-host=host.containers.internal:host-gateway"
];
volumes = [
"${config.sops.templates."searxng_settings.yml".path}:/etc/searxng/settings.yml:ro"
"${catppuccinCss}:/etc/searxng/custom.css:ro"
];
};
# 3. Secrets Configuration
# We generate the settings.yml dynamically using sops templates to inject secrets if needed,
# or just to manage the config declaratively.
sops.templates."searxng.env".content = ''
SEARXNG_SECRET_KEY=${config.sops.placeholder.searxng_secret_key}
'';
sops.templates."searxng_settings.yml".content = ''
use_default_settings: true
general:
debug: false
instance_name: "Ashie Search"
donations:
patreon: false
buymeacoffee: false
search:
safe_search: 0
autocomplete: "google"
default_lang: "en-US"
formats:
- html
- json
server:
port: 8080
bind_address: "0.0.0.0"
secret_key: "${config.sops.placeholder.searxng_secret_key}"
limiter: true
image_proxy: true
ui:
static_use_hash: true
custom_css: custom.css
theme_args:
simple_style: "auto"
redis:
url: redis://searxng-redis:6379/0
'';
# Placeholder secret definition (User must add this to secrets.yaml!)
sops.secrets.searxng_secret_key = { };
};
}

View file

@ -1,14 +1,3 @@
# Security Hardening Module
# Provides: doas (sudo replacement), audit logging, AppArmor, core dump prevention
#
# Usage:
# myModules.security = {
# enable = true;
# enableAudit = true; # default: true
# enableAppArmor = true; # default: true
# useDoas = true; # default: true (replaces sudo)
# };
{
config,
lib,
@ -66,6 +55,14 @@ in
}
];
# Proc hardening (Hide other users' processes)
boot.specialFileSystems."/proc".options = [
"nosuid"
"nodev"
"noexec"
"hidepid=2"
];
# Security audit logging
security.auditd.enable = cfg.enableAudit;
security.audit = lib.mkIf cfg.enableAudit {

View file

@ -95,7 +95,6 @@ in
"$HOME/.local/share/color-schemes"
];
readWrite = [
"$HOME/Games"
"$HOME/.steam"
"$HOME/.local/share/Steam"
"$HOME/.local/share/umu"
@ -104,6 +103,7 @@ in
"$HOME/.local/share/icons"
"$HOME/.local/share/Larian Studios"
"$HOME/Desktop"
"/games/steam"
];
};

View file

@ -0,0 +1,119 @@
{
config,
lib,
pkgs,
inputs,
...
}:
let
bwrapperPkgs = pkgs.extend inputs.nix-bwrapper.overlays.default;
pname = "tutanota-desktop";
version = "319.260107.1";
src = pkgs.fetchurl {
url = "https://github.com/tutao/tutanota/releases/download/tutanota-desktop-release-${version}/tutanota-desktop-linux.AppImage";
sha256 = "0gjvh3f70mmr85kx3kz4yd8gfxpk4kj8wkh697a4gy34mgxpqnka";
};
appimageContents = pkgs.appimageTools.extractType2 {
inherit pname version src;
};
tutanota = pkgs.appimageTools.wrapType2 {
inherit pname version src;
extraInstallCommands = ''
install -m 444 -D ${appimageContents}/tutanota-desktop.desktop $out/share/applications/tutanota-desktop.desktop
install -m 444 -D ${appimageContents}/tutanota-desktop.png \
$out/share/icons/hicolor/512x512/apps/tutanota-desktop.png
substituteInPlace $out/share/applications/tutanota-desktop.desktop \
--replace 'Exec=AppRun' 'Exec=tutanota-desktop'
'';
};
in
{
nixpkgs.overlays = [
(final: prev: {
tutanota-sandboxed = bwrapperPkgs.mkBwrapper {
app = {
package = tutanota;
id = "com.tutanota.Tutanota";
env = {
XDG_DATA_DIRS = "$XDG_DATA_DIRS";
};
};
flatpak.enable = false;
# Basic sandboxing
fhsenv.opts = {
unshareUser = true;
unshareUts = true;
unshareCgroup = true;
unsharePid = true;
unshareNet = false; # Needs network
unshareIpc = true;
};
fhsenv.bwrap.baseArgs = lib.mkForce [
"--new-session"
"--proc /proc"
"--dev /dev"
"--dev-bind /dev/dri /dev/dri" # GPU acceleration
"--tmpfs /home"
"--tmpfs /tmp"
"--tmpfs /run"
"--dir /run/user"
"--dir /run/user/${toString config.users.users.ashie.uid}"
# System paths
"--ro-bind /sys /sys"
"--ro-bind-try /run/current-system /run/current-system"
"--ro-bind-try /run/opengl-driver /run/opengl-driver"
"--ro-bind-try /run/opengl-driver-32 /run/opengl-driver-32"
"--dir /run/systemd/resolve"
"--ro-bind-try /run/systemd/resolve /run/systemd/resolve"
];
mounts = {
read = [
"$HOME/.config/fontconfig"
"$HOME/.local/share/fonts"
"$HOME/.icons"
"$HOME/.themes"
"$HOME/.local/share/themes"
"$HOME/.config/kdedefaults"
"$HOME/.local/share/color-schemes"
];
readWrite = [
"$HOME/.config/tutanota-desktop"
"$HOME/Downloads"
];
};
dbus.enable = false;
script.preCmds.stage2 = (import ./sandbox-utils.nix { inherit pkgs lib; }).mkDbusProxyScript {
appId = "com.tutanota.Tutanota";
enableSystemBus = false;
proxyArgs = [
"--filter"
''--talk="org.freedesktop.portal.*"''
''--talk="org.freedesktop.Notifications"''
''--talk="org.freedesktop.secrets"''
''--talk="org.gnome.keyring.SystemPrompter"'' # Often needed for secrets
''--call="org.freedesktop.portal.*=*@/org/freedesktop/portal/desktop"''
''--own="com.tutanota.Tutanota"''
];
};
fhsenv.bwrap.additionalArgs = [
# D-Bus session proxy only
''--bind "$XDG_RUNTIME_DIR/app/com.tutanota.Tutanota/bus" "$XDG_RUNTIME_DIR/bus"''
# Wayland
''--bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"''
];
};
})
];
}

View file

@ -0,0 +1,52 @@
# USBGuard Module
# Provides: Policy enforcement for USB devices
#
# Usage:
# myModules.usbguard = {
# enable = true;
# generatePolicy = true; # Auto-generate policy from currently connected devices
# };
{
config,
lib,
pkgs,
...
}:
let
cfg = config.myModules.usbguard;
in
{
options.myModules.usbguard = {
enable = lib.mkEnableOption "USBGuard for USB device control";
generatePolicy = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Generate an initial policy from currently connected devices on activation (requires reboot/service restart)";
};
};
config = lib.mkIf cfg.enable {
services.usbguard = {
enable = true;
dbus.enable = true;
# Block new devices by default
implicitPolicyTarget = "block";
# Treat present devices as allowed (until policy is generated)
presentDevicePolicy = "apply-policy"; # or "keep" or "allow"
};
# Helper script to generate policy
environment.systemPackages = lib.mkIf cfg.generatePolicy [
(pkgs.writeShellScriptBin "generate-usbguard-policy" ''
sudo usbguard generate-policy > /etc/usbguard/rules.conf
sudo systemctl restart usbguard
echo "USBGuard policy generated from connected devices."
'')
];
};
}