This commit is contained in:
ashisgreat22 2026-01-21 23:58:24 +01:00
parent 759163965a
commit faf14881a3
92 changed files with 1405 additions and 698 deletions

View file

@ -227,7 +227,6 @@ sops updatekeys secrets/secrets.yaml
- `page_alloc.shuffle=1` - Randomizes page allocator
- `randomize_kstack_offset=on` - Randomizes kernel stack
- `vsyscall=none` - Disables legacy vsyscall
- `debugfs=off` - Disables kernel debug interface
- `oops=panic` - Panics on kernel oops
**Sysctl Settings**:

53
capture_arr_user.sh Normal file
View file

@ -0,0 +1,53 @@
#!/usr/bin/env bash
set -e
# Output file for the declarative script
OUTPUT_SCRIPT="/home/ashie/nixos/ensure_arr_users.sh"
echo "Capturing user from Sonarr..."
# Extract the first user row (assuming it's the admin)
# Format: ID|Identifier|Username|Password|Salt|Iterations
USER_ROW=$(nix run nixpkgs#sqlite -- /var/lib/nixarr/sonarr/sonarr.db "SELECT Identifier, Username, Password, Salt, Iterations FROM Users LIMIT 1;")
if [ -z "$USER_ROW" ]; then
echo "No user found in Sonarr DB! Please create a user in the Web UI first."
exit 1
fi
IFS='|' read -r IDENTIFIER USERNAME PASSWORD SALT ITERATIONS <<< "$USER_ROW"
echo "Found User: $USERNAME"
# Generate the script
cat <<EOF > "$OUTPUT_SCRIPT"
#!/usr/bin/env bash
set -e
# Function to ensure user exists
ensure_user() {
SERVICE=\$1
DB_PATH=\$2
echo "Ensuring user '$USERNAME' exists in \$SERVICE..."
# Check if user exists
COUNT=\$(nix run nixpkgs#sqlite -- "\$DB_PATH" "SELECT count(*) FROM Users WHERE Username='$USERNAME';")
if [ "\$COUNT" -eq "0" ]; then
echo "Creating user '$USERNAME'..."
nix run nixpkgs#sqlite -- "\$DB_PATH" "INSERT INTO Users (Identifier, Username, Password, Salt, Iterations) VALUES ('$IDENTIFIER', '$USERNAME', '$PASSWORD', '$SALT', '$ITERATIONS');"
else
echo "User '$USERNAME' already exists."
fi
}
ensure_user "Sonarr" "/var/lib/nixarr/sonarr/sonarr.db"
ensure_user "Radarr" "/var/lib/nixarr/radarr/radarr.db"
ensure_user "Prowlarr" "/var/lib/nixarr/prowlarr/prowlarr.db"
# Jellyseerr uses a different DB structure, skipping for now (it likely synced via Jellyfin or has its own auth)
EOF
chmod +x "$OUTPUT_SCRIPT"
echo "Generated $OUTPUT_SCRIPT. You can now use this to ensure the user exists."

50
enable_arr_auth.sh Normal file
View file

@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -e
echo "Stopping services..."
systemctl stop sonarr radarr prowlarr
# Function to enable auth
enable_auth() {
SERVICE=$1
CONFIG_FILE=$2
if [ -f "$CONFIG_FILE" ]; then
echo "Enabling Forms Auth for $SERVICE..."
cp "$CONFIG_FILE" "$CONFIG_FILE.bak"
# Set AuthenticationMethod to Forms
if grep -q "<AuthenticationMethod>" "$CONFIG_FILE"; then
sed -i 's|<AuthenticationMethod>.*</AuthenticationMethod>|<AuthenticationMethod>Forms</AuthenticationMethod>|g' "$CONFIG_FILE"
else
# Insert if missing (unlikely, but inside <Config> usually)
sed -i 's|<Config>|<Config>\n <AuthenticationMethod>Forms</AuthenticationMethod>|g' "$CONFIG_FILE"
fi
# Set AuthenticationRequired to Enabled (Correct Enum Value)
if grep -q "<AuthenticationRequired>" "$CONFIG_FILE"; then
sed -i 's|<AuthenticationRequired>.*</AuthenticationRequired>|<AuthenticationRequired>Enabled</AuthenticationRequired>|g' "$CONFIG_FILE"
else
# Insert
sed -i 's|<Config>|<Config>\n <AuthenticationRequired>Enabled</AuthenticationRequired>|g' "$CONFIG_FILE"
fi
echo "$SERVICE updated."
else
echo "Config for $SERVICE not found at $CONFIG_FILE"
fi
}
enable_auth "Sonarr" "/var/lib/nixarr/sonarr/config.xml"
enable_auth "Radarr" "/var/lib/nixarr/radarr/config.xml"
enable_auth "Prowlarr" "/var/lib/nixarr/prowlarr/config.xml"
# Jellyseerr usually enforces login by default if users exist.
# Its config is in database, not easily scriptable via settings.json for auth mode.
echo "Restarting services..."
systemctl start sonarr radarr prowlarr
echo "Authentication enabled!"
echo "WARNING: If you do not have a user created in these apps, you may be locked out."
echo "If locked out, edit the config.xml file manually and set AuthenticationMethod back to 'None'."

View file

@ -102,32 +102,31 @@
{
# Expose reusable NixOS modules for others to import
nixosModules = {
security = import ./modules/system/security.nix;
kernelHardening = import ./modules/system/kernel-hardening.nix;
secureBoot = import ./modules/system/secure-boot.nix;
dnsOverTls = import ./modules/system/dns-over-tls.nix;
cloudflareFirewall = import ./modules/system/cloudflare-firewall.nix;
caddyCloudflare = import ./modules/system/caddy-cloudflare.nix;
podman = import ./modules/system/podman.nix;
browserVpn = import ./modules/system/browser-vpn.nix;
security = import ./modules/nixos/security.nix;
kernelHardening = import ./modules/nixos/kernel-hardening.nix;
secureBoot = import ./modules/nixos/secure-boot.nix;
dnsOverTls = import ./modules/nixos/dns-over-tls.nix;
cloudflareFirewall = import ./modules/nixos/cloudflare-firewall.nix;
podman = import ./modules/nixos/podman.nix;
browserVpn = import ./modules/nixos/browser-vpn.nix;
default = import ./modules;
};
# Expose reusable Home Manager modules
homeManagerModules = {
hyprlandCatppuccin = import ./modules/home/hyprland-catppuccin.nix;
gluetunUser = import ./modules/home/gluetun-user.nix;
qbittorrentVpn = import ./modules/home/qbittorrent-vpn.nix;
browserContainerUpdate = import ./modules/home/browser-container-update.nix;
protonCachyosUpdater = import ./modules/home/proton-cachyos-updater.nix;
default = import ./modules/home;
hyprlandCatppuccin = import ./modules/home-manager/hyprland-catppuccin.nix;
gluetunUser = import ./modules/home-manager/gluetun-user.nix;
qbittorrentVpn = import ./modules/home-manager/qbittorrent-vpn.nix;
browserContainerUpdate = import ./modules/home-manager/browser-container-update.nix;
protonCachyosUpdater = import ./modules/home-manager/proton-cachyos-updater.nix;
default = import ./modules/home-manager;
};
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
./hosts/nixos/configuration.nix
./modules # Import all system modules
inputs.sops-nix.nixosModules.sops
inputs.nixflix.nixosModules.default
@ -141,10 +140,10 @@
useGlobalPkgs = true;
useUserPackages = true;
backupFileExtension = "backup";
users.ashie = import ./home.nix;
users.ashie = import ./hosts/nixos/home.nix;
};
}
./modules/system/impermanence.nix
./modules/nixos/impermanence.nix
];
};
@ -152,9 +151,10 @@
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
./hosts/nixos/configuration.nix
./modules
inputs.sops-nix.nixosModules.sops
inputs.nixflix.nixosModules.default
home-manager.nixosModules.home-manager
inputs.catppuccin.nixosModules.catppuccin
inputs.nixvim.nixosModules.nixvim
@ -165,10 +165,10 @@
useGlobalPkgs = true;
useUserPackages = true;
backupFileExtension = "backup";
users.ashie = import ./home.nix;
users.ashie = import ./hosts/nixos/home.nix;
};
}
./modules/system/impermanence.nix
./modules/nixos/impermanence.nix
];
};
};

1
flaresolverr_test.json Normal file

File diff suppressed because one or more lines are too long

View file

@ -37,7 +37,7 @@
};
imports = [
./hosts/nixos/default.nix # Host-specific configuration
./default.nix # Host-specific configuration
./hardware-configuration.nix
./system/boot.nix # Boot loader settings (non-hardening parts)
./system/networking.nix # Host-specific networking (hostname, ddclient)
@ -46,14 +46,15 @@
./system/packages.nix # Package list
./system/users.nix # User accounts
./system/greetd.nix # Display manager
./modules/system/cosmic.nix # Cosmic Desktop
../../modules/nixos/cosmic.nix # Cosmic Desktop
./system/kernel.nix # CachyOS kernel
./system/locate.nix # mlocate
./system/secrets.nix # SOPS secrets
./system/compatibility.nix # Compatibility layers (nix-ld)
./system/game-drive.nix
./system/vpn-namespace.nix # Isolated VPN Namespace
./modules/system/media.nix # Arr Stack
./system/authelia.nix # SSO/2FA
../../modules/nixos/media.nix # Arr Stack
];
nixpkgs.config.allowUnfreePredicate =

View file

@ -55,10 +55,11 @@
enable = true;
allowLocalTraffic = true;
enablePodmanWorkaround = true;
restrictedPorts = [
publicPorts = [
80
443
];
restrictedPorts = [ ];
};
# Base Podman container runtime

View file

@ -38,12 +38,13 @@
};
fileSystems."/games" = {
device = "/dev/mapper/cryptdata";
device = "/dev/mapper/cryptroot";
fsType = "btrfs";
options = [
"subvol=@games"
"compress-force=zstd"
"noatime"
"discard=async"
];
};
@ -54,16 +55,18 @@
"subvol=@media"
"compress-force=zstd"
"noatime"
"discard=async"
];
};
fileSystems."/games/steam" = {
device = "/dev/mapper/cryptdata";
device = "/dev/mapper/cryptroot";
fsType = "btrfs";
options = [
"subvol=@steam"
"compress-force=zstd"
"noatime"
"discard=async"
];
};
@ -73,9 +76,10 @@
fsType = "btrfs";
options = [
"subvol=@nix"
"compress-force=zstd"
"compress-force=zstd:5"
"noatime"
"autodefrag"
"discard=async"
];
neededForBoot = true;
};
@ -85,9 +89,46 @@
fsType = "btrfs";
options = [
"subvol=@persist"
"compress-force=zstd"
"compress=zstd:6"
"noatime"
"autodefrag"
"discard=async"
];
neededForBoot = true;
};
fileSystems."/persist/var/log" = {
device = lib.mkForce "/dev/mapper/cryptroot";
fsType = "btrfs";
options = [
"subvol=@log"
"compress=zstd:6"
"noatime"
"discard=async"
];
neededForBoot = true;
};
fileSystems."/persist/var/lib/containers" = {
device = lib.mkForce "/dev/mapper/cryptroot";
fsType = "btrfs";
options = [
"subvol=@containers"
"compress=no"
"noatime"
"discard=async"
];
neededForBoot = true;
};
fileSystems."/persist/var/lib/ollama" = {
device = lib.mkForce "/dev/mapper/cryptroot";
fsType = "btrfs";
options = [
"subvol=@models"
"compress=no"
"noatime"
"discard=async"
];
neededForBoot = true;
};

View file

@ -6,15 +6,15 @@
}:
{
imports = [
./modules/home/gluetun-user.nix
./modules/home/cosmic.nix
../../modules/home-manager/gluetun-user.nix
../../modules/home-manager/cosmic.nix
inputs.sops-nix.homeManagerModules.sops
inputs.steam-config-nix.homeModules.default
inputs.catppuccin.homeManagerModules.catppuccin
inputs.nixvim.homeManagerModules.nixvim
# inputs.unified-router-mcp.homeManagerModules.default
./modules/home # Import all Home Manager modules
./hosts/nixos/home-modules.nix # Host-specific module configuration
../../modules/home-manager # Import all Home Manager modules
./home-modules.nix # Host-specific module configuration
./home/fastfetch.nix
./home/vscode.nix
./home/kitty.nix
@ -65,7 +65,7 @@
'')
];
sops.defaultSopsFile = ./secrets/secrets.yaml;
sops.defaultSopsFile = ../../secrets/secrets.yaml;
sops.defaultSopsFormat = "yaml";
sops.age.keyFile = "/home/ashie/.config/sops/age/keys.txt";
@ -179,7 +179,7 @@
xdg.desktopEntries.youtube = {
name = "YouTube";
genericName = "Video Player";
exec = "brave --app=https://youtube.com";
exec = "brave --profile-directory=YouTube --app=https://youtube.com";
terminal = false;
categories = [
"Network"

View file

@ -0,0 +1,101 @@
{
config,
lib,
pkgs,
...
}:
{
services.authelia.instances.main = {
enable = true;
# Secrets
secrets = {
jwtSecretFile = config.sops.secrets.authelia_jwt_secret.path;
storageEncryptionKeyFile = config.sops.secrets.authelia_storage_encryption_key.path;
sessionSecretFile = config.sops.secrets.authelia_session_secret.path;
};
settings = {
theme = "dark";
default_2fa_method = "totp";
server = {
address = "127.0.0.1:9099";
disable_healthcheck = false;
};
log = {
level = "info";
format = "text";
};
totp = {
issuer = "ashisgreat.xyz";
};
authentication_backend = {
file = {
path = "/var/lib/authelia-main/users_database.yml";
watch = true;
};
};
access_control = {
default_policy = "deny";
rules = [
# Public access to Authelia itself
{
domain = "auth.ashisgreat.xyz";
policy = "bypass";
}
# Protected services (2FA required)
{
domain = [
"sonarr.ashisgreat.xyz"
"radarr.ashisgreat.xyz"
"prowlarr.ashisgreat.xyz"
"jellyfin.ashisgreat.xyz" # Jellyfin can use its own auth, but wrapping it adds 2FA
"torrent.ashisgreat.xyz"
"jellyseer.ashisgreat.xyz" # Note: Typo in services.nix maintained here, check if corrected in Caddy
];
policy = "two_factor";
}
];
};
session = {
name = "authelia_session";
domain = "ashisgreat.xyz";
same_site = "lax";
expiration = "1h";
inactivity = "5m";
remember_me = "1M";
};
regulation = {
max_retries = 3;
find_time = "2m";
ban_time = "5m";
};
storage = {
local = {
path = "/var/lib/authelia-main/db.sqlite3";
};
};
notifier = {
filesystem = {
filename = "/var/lib/authelia-main/notification.txt";
};
};
};
};
# Ensure the directory exists for users_database.yml
systemd.tmpfiles.rules = [
"d /var/lib/authelia-main 0700 authelia-main authelia-main -"
"f /var/lib/authelia-main/users_database.yml 0600 authelia-main authelia-main -"
];
}

View file

@ -14,7 +14,7 @@
# Ensure the mount point exists on the tmpfs root
systemd.tmpfiles.rules = [
"d /home/ashie/Games 0755 ashie users -"
"L+ /home/ashie/.local/share/Steam - - - - /home/ashie/Games/Steam"
"L+ /home/ashie/.local/share/Steam - - - - /home/ashie/Games/steam"
];
fileSystems."/home/ashie/Games" = {

View file

@ -49,8 +49,8 @@
"net.ipv6.conf.lo.disable_ipv6" = 1;
};
# Basic firewall settings (Cloudflare rules are in the module)
# networking.firewall.enable = true; # Handled by modules/system/cloudflare-firewall.nix
# Basic firewall settings
networking.firewall.enable = true;
networking.nftables.enable = true;
# Dynamic DNS for Cloudflare
@ -62,6 +62,7 @@
passwordFile = config.sops.secrets.cloudflare_api_key.path;
domains = [
"api.ashisgreat.xyz"
"auth.ashisgreat.xyz"
"chat.ashisgreat.xyz"
"stream.ashisgreat.xyz"
"stream-api.ashisgreat.xyz"

View file

@ -108,7 +108,7 @@
nspr
firefox-sandboxed
tutanota-sandboxed
brave-sandboxed
# brave-sandboxed # Imported via module, wrapper provided there
eddie
appimage-run
rivalcfg

View file

@ -31,6 +31,10 @@
owner = "ashie";
};
sops.secrets.wireguard_dns = {
owner = "ashie";
};
sops.secrets.open_webui_env = {
owner = "ashie";
};
@ -44,15 +48,24 @@
WIREGUARD_PRIVATE_KEY=${config.sops.placeholder.wireguard_private_key}
WIREGUARD_ADDRESSES=${config.sops.placeholder.wireguard_addresses}
WIREGUARD_PRESHARED_KEY=${config.sops.placeholder.wireguard_preshared_key}
DNS_ADDRESS=${config.sops.placeholder.wireguard_dns}
WIREGUARD_MTU=1320
VPN_SERVICE_PROVIDER=custom
VPN_TYPE=wireguard
VPN_PORT_FORWARDING=off
'';
};
# Cloudflare secrets
sops.secrets.cloudflare_api_key = { };
# Cloudflare ACME Environment File
sops.templates."cloudflare-acme.env" = {
content = ''
CLOUDFLARE_DNS_API_TOKEN=${config.sops.placeholder.cloudflare_api_key}
'';
};
# Unified API Key
sops.secrets.master_api_key = {
owner = "ashie";
@ -76,11 +89,20 @@
neededForUsers = true;
};
sops.templates."caddy.env" = {
owner = "caddy";
group = "caddy";
content = ''
CF_API_TOKEN=${config.sops.placeholder.cloudflare_api_key}
'';
# Nixflix secrets
sops.secrets.nixflix_password = { };
sops.secrets.sonarr_api_key = { };
sops.secrets.radarr_api_key = { };
sops.secrets.prowlarr_api_key = { };
# Authelia Secrets
sops.secrets.authelia_jwt_secret = {
owner = "authelia-main";
};
sops.secrets.authelia_session_secret = {
owner = "authelia-main";
};
sops.secrets.authelia_storage_encryption_key = {
owner = "authelia-main";
};
}

View file

@ -0,0 +1,264 @@
{
config,
lib,
pkgs,
...
}:
{
services.flatpak.enable = false;
services.snowflake-proxy = {
enable = false;
capacity = 10;
};
services.timesyncd.enable = false;
services.chrony = {
enable = true;
enableNTS = true;
servers = [
"time.cloudflare.com"
"nts.netnod.se"
"ptbtime1.ptb.de"
];
extraConfig = ''
user chrony
pidfile /run/chrony/chrony.pid
driftfile /var/lib/chrony/drift
makestep 1.0 3
'';
};
services.fstrim.enable = true;
services.dbus.implementation = "broker";
services.earlyoom = {
enable = false;
enableNotifications = true;
freeMemThreshold = 5;
};
services.openssh = {
enable = true;
ports = [ 5732 ];
hostKeys = [
{
path = "/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
}
];
settings = {
PasswordAuthentication = false;
PermitRootLogin = "no";
X11Forwarding = false;
AllowAgentForwarding = false;
UseDns = false;
};
};
services.gnome.gnome-keyring.enable = true;
security.pam.services.greetd.enableGnomeKeyring = true;
programs.hyprland = {
enable = true;
xwayland.enable = true;
};
programs.firefox.enable = false;
# Nginx Configuration
myModules.nginx.enable = true;
services.nginx.virtualHosts = {
"search.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8888";
proxyWebsockets = true;
};
};
"api.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
extraConfig = ''
add_header X-Frame-Options "DENY";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:;";
'';
locations."/" = {
proxyPass = "http://127.0.0.1:8045";
proxyWebsockets = true;
};
};
"chat.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
extraConfig = ''
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; connect-src 'self' wss: https:; worker-src 'self' blob:;";
'';
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
proxyWebsockets = true;
};
};
"stream.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
# Basic auth is tricky to port directly without htpasswd file management.
# Since it was hardcoded in Caddy, we'll note that it needs to be set up properly or omitted if not critical for now.
# For now, excluding basic_auth to avoid breakage, user can add it back via sops secrets for htpasswd.
locations."/" = {
proxyPass = "http://127.0.0.1:3333";
proxyWebsockets = true;
};
};
"stream-api.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
extraConfig = ''
add_header Access-Control-Allow-Origin "https://stream.ashisgreat.xyz";
'';
locations."/" = {
proxyPass = "http://127.0.0.1:3334";
proxyWebsockets = true;
};
};
"auth.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:9099";
proxyWebsockets = true;
};
};
"sonarr.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8989";
proxyWebsockets = true;
};
};
"radarr.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:7878";
proxyWebsockets = true;
};
};
"prowlarr.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:9696";
proxyWebsockets = true;
};
};
"torrent.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8080";
proxyWebsockets = true;
};
};
"jellyfin.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8096";
proxyWebsockets = true;
};
};
"jellyseer.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:5055";
proxyWebsockets = true;
};
};
# Redirect typo domain
"jellyseerr.ashisgreat.xyz" = {
useACMEHost = "ashisgreat.xyz";
forceSSL = true;
globalRedirect = "jellyseer.ashisgreat.xyz";
};
};
# Hardening for Chrony
systemd.services.chronyd.serviceConfig = {
ProtectSystem = lib.mkForce "strict";
ProtectHome = true;
PrivateTmp = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
# Chrony needs to adjust time, preserve CAP_SYS_TIME and CAP_NET_BIND_SERVICE
CapabilityBoundingSet = [
"CAP_SYS_TIME"
"CAP_NET_BIND_SERVICE"
];
MemoryDenyWriteExecute = true;
LockPersonality = true;
};
# Hardening for EarlyOOM
systemd.services.earlyoom.serviceConfig = {
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
};
# Hardening for Snowflake Proxy
systemd.services.snowflake-proxy.serviceConfig = {
DynamicUser = true;
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
RestrictRealtime = true;
SystemCallFilter = [
"@system-service"
"~@privileged"
];
};
# Hardening for DDClient
systemd.services.ddclient.serviceConfig = {
ProtectSystem = "full";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
ReadWritePaths = [ "/run/ddclient" ];
NoNewPrivileges = true;
};
}

View file

@ -9,8 +9,9 @@
users.users.ashie = {
isNormalUser = true;
initialPassword = "password"; # Temporary password, change with 'passwd' after login
# hashedPasswordFile = config.sops.secrets.hashed_password.path;
linger = true;
# initialPassword = "password"; # Temporary password, change with 'passwd' after login
hashedPasswordFile = config.sops.secrets.hashed_password.path;
uid = 1000;
shell = pkgs.fish;
extraGroups = [
@ -24,12 +25,20 @@
tree
];
subUidRanges = [
{
startUid = 100000;
count = 65536;
}
{
startUid = 200000000;
count = 100000000;
}
];
subGidRanges = [
{
startGid = 100000;
count = 65536;
}
{
startGid = 200000000;
count = 100000000;

View file

@ -14,6 +14,6 @@
{ ... }:
{
imports = [
./system
./nixos
];
}

View file

@ -0,0 +1,6 @@
{ pkgs, ... }:
{
home.packages = with pkgs; [
boilr
];
}

View file

@ -0,0 +1,14 @@
{ pkgs, ... }:
{
# Rootless podman policy configuration
# Required for user-level podman to pull container images
home.file.".config/containers/policy.json".text = builtins.toJSON {
default = [ { type = "insecureAcceptAnything"; } ];
transports = {
docker-daemon = {
"" = [ { type = "insecureAcceptAnything"; } ];
};
};
};
}

View file

@ -2,7 +2,7 @@
# Import this to get all home-manager modules
#
# Usage in home.nix:
# imports = [ ./modules/home ];
# imports = [ ./modules/home-manager ];
{ ... }:
{
@ -20,11 +20,12 @@
# ./unified-router.nix
./sillytavern.nix
./niri.nix
./noctalia.nix
./polling-rate.nix
./antigravity2api.nix
./theme.nix
./prismlauncher.nix
./boilr.nix
./containers-policy.nix
];
}

View file

@ -131,7 +131,7 @@ in
GTK_THEME "catppuccin-mocha-mauve-standard"
QT_QPA_PLATFORMTHEME "gtk3"
QT_STYLE_OVERRIDE "kvantum"
XCURSOR_THEME "Bibata-Modern-Ice"
XCURSOR_THEME "Gloomi-x"
XCURSOR_SIZE "24"
}

View file

@ -72,7 +72,7 @@ in
package = pkgs.prismlauncher.overrideAttrs (old: {
pname = "prismlauncher";
version = old.version or "9.1"; # Fallback or keep current if valid
buildInputs = (old.buildInputs or [ ]);
buildInputs = (old.buildInputs or [ ]) ++ [ pkgs.jemalloc ];
# Keep runtimeLibs in closure without injecting them into environment
postInstall = (old.postInstall or "") + ''
@ -80,7 +80,10 @@ in
echo "${lib.makeLibraryPath runtimeLibs}" > $out/share/prismlauncher-sandboxed/libs
'';
qtWrapperArgs = (old.qtWrapperArgs or [ ]);
qtWrapperArgs = (old.qtWrapperArgs or [ ]) ++ [
"--set JEMALLOC_PATH ${pkgs.jemalloc}/lib/libjemalloc.so"
"--prefix LD_PRELOAD : ${pkgs.jemalloc}/lib/libjemalloc.so"
];
});
env = {

View file

@ -1,12 +1,6 @@
# Proton CachyOS Auto-Updater Module (Home Manager)
# Provides: Auto-update timer for Proton CachyOS from GitHub releases
#
# Usage:
# myModules.protonCachyosUpdater = {
# enable = true;
# arch = "x86_64_v3";
# schedule = "daily";
# };
# Now supports both Steam and Lutris
{
config,
@ -21,13 +15,15 @@ let
updateScript = pkgs.writeShellScript "update-proton-cachyos" ''
set -euo pipefail
COMPAT_DIR="${cfg.compatToolsDir}"
# Path where we store the real Proton files
# We default to Steam's compat tools dir as it typically lives on a game drive
STORAGE_DIR="${cfg.steam.compatToolsDir}"
ARCH="${cfg.arch}"
GITHUB_API="https://api.github.com/repos/CachyOS/proton-cachyos/releases/latest"
VERSION_FILE="$COMPAT_DIR/.proton-cachyos-version"
VERSION_FILE="$STORAGE_DIR/.proton-cachyos-version"
# Ensure directory exists
mkdir -p "$COMPAT_DIR"
# Ensure storage directory exists
mkdir -p "$STORAGE_DIR"
echo "=== Checking for Proton CachyOS updates ==="
@ -49,12 +45,46 @@ let
fi
echo "Current version: ''${CURRENT_VERSION:-none}"
update_links() {
local versioned_dir="$1"
# Steam Integration
if [ "${if cfg.steam.enable then "1" else "0"}" = "1" ]; then
echo "Updating Steam wrapper: $STORAGE_DIR/proton-cachyos-latest"
rm -rf "$STORAGE_DIR/proton-cachyos-latest"
mkdir -p "$STORAGE_DIR/proton-cachyos-latest"
cat > "$STORAGE_DIR/proton-cachyos-latest/compatibilitytool.vdf" <<EOF
"compatibilitytools"
{
"compat_tools"
{
"proton-cachyos-latest"
{
"install_path" "$STORAGE_DIR/$versioned_dir"
"display_name" "Proton CachyOS (Latest)"
"from_oslist" "windows"
"to_oslist" "linux"
}
}
}
EOF
fi
# Lutris Integration
if [ "${if cfg.lutris.enable then "1" else "0"}" = "1" ]; then
local runners_dir="${cfg.lutris.runnersDir}"
echo "Updating Lutris symlink: $runners_dir/proton-cachyos-latest"
mkdir -p "$runners_dir"
ln -sfn "$STORAGE_DIR/$versioned_dir" "$runners_dir/proton-cachyos-latest"
fi
}
if [ "$LATEST_TAG" = "$CURRENT_VERSION" ]; then
echo "Already up to date!"
# Still ensure symlink exists
LATEST_DIR=$(ls -v "$COMPAT_DIR" | grep -E "^proton-cachyos" | grep -v "latest" | tail -1)
# Still ensure links exist and are correct
LATEST_DIR=$(ls -v "$STORAGE_DIR" | grep -E "^proton-cachyos" | grep -v "latest" | tail -1)
if [ -n "$LATEST_DIR" ]; then
ln -sfn "$COMPAT_DIR/$LATEST_DIR" "$COMPAT_DIR/proton-cachyos-latest"
update_links "$LATEST_DIR"
fi
exit 0
fi
@ -77,10 +107,10 @@ let
${pkgs.curl}/bin/curl -L --progress-bar -o "$TEMP_DIR/$FILENAME" "$DOWNLOAD_URL"
echo "Extracting..."
${pkgs.gnutar}/bin/tar -xf "$TEMP_DIR/$FILENAME" -C "$COMPAT_DIR"
${pkgs.gnutar}/bin/tar -xf "$TEMP_DIR/$FILENAME" -C "$STORAGE_DIR"
# Find extracted directory name
EXTRACTED_DIR=$(ls -v "$COMPAT_DIR" | grep -E "^proton-cachyos" | grep -v "latest" | tail -1)
EXTRACTED_DIR=$(ls -v "$STORAGE_DIR" | grep -E "^proton-cachyos" | grep -v "latest" | tail -1)
if [ -z "$EXTRACTED_DIR" ]; then
echo "Failed to find extracted directory"
@ -88,30 +118,9 @@ let
fi
echo "Installed: $EXTRACTED_DIR"
# Create wrapper directory (remove old symlink/dir first)
rm -rf "$COMPAT_DIR/proton-cachyos-latest"
mkdir -p "$COMPAT_DIR/proton-cachyos-latest"
# Create wrapper compatibilitytool.vdf
# This allows Steam to see "proton-cachyos-latest" as a distinct tool pointing to the real files
cat > "$COMPAT_DIR/proton-cachyos-latest/compatibilitytool.vdf" <<EOF
"compatibilitytools"
{
"compat_tools"
{
"proton-cachyos-latest"
{
"install_path" "$COMPAT_DIR/$EXTRACTED_DIR"
"display_name" "Proton CachyOS (Latest)"
"from_oslist" "windows"
"to_oslist" "linux"
}
}
}
EOF
echo "Updated wrapper: proton-cachyos-latest -> $EXTRACTED_DIR"
# Update links for Steam and Lutris
update_links "$EXTRACTED_DIR"
# Save version
echo "$LATEST_TAG" > "$VERSION_FILE"
@ -147,10 +156,30 @@ in
description = "Random delay before running update";
};
compatToolsDir = lib.mkOption {
type = lib.types.str;
default = "$HOME/.local/share/Steam/compatibilitytools.d";
description = "Steam compatibility tools directory";
steam = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Enable Steam integration (creates compatibilitytool.vdf wrapper)";
};
compatToolsDir = lib.mkOption {
type = lib.types.str;
default = "$HOME/.local/share/Steam/compatibilitytools.d";
description = "Steam compatibility tools directory (also used as primary storage)";
};
};
lutris = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Enable Lutris integration (creates symlink in runners directory)";
};
runnersDir = lib.mkOption {
type = lib.types.str;
default = "$HOME/.local/share/lutris/runners/wine";
description = "Lutris wine runners directory";
};
};
};

View file

@ -6,8 +6,10 @@
home.pointerCursor = {
gtk.enable = true;
x11.enable = true;
package = pkgs.bibata-cursors;
name = "Bibata-Modern-Ice";
package = pkgs.runCommand "gloomi-x-cursor" { } ''
mkdir -p $out/share/icons
'';
name = "Gloomi-x";
size = 24;
};
@ -22,7 +24,10 @@
package = pkgs.catppuccin-gtk.override {
accents = [ "mauve" ];
size = "standard";
tweaks = [ "rimless" "black" ];
tweaks = [
"rimless"
"black"
];
variant = "mocha";
};
};
@ -33,4 +38,4 @@
platformTheme.name = "gtk";
style.name = "gtk2";
};
}
}

View file

@ -150,25 +150,19 @@ 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
];
environment.systemPackages = [
(pkgs.writeShellScriptBin "brave" ''
exec /home/ashie/nixos/scripts/launch-vpn-app.sh ${pkgs.brave-sandboxed}/bin/brave "$@"
'')
(pkgs.makeDesktopItem {
name = "brave-vpn";
desktopName = "Brave Web Browser";
exec = "brave %U";
icon = "brave-browser";
categories = [
"Network"
"WebBrowser"
];
})
];
}

View file

@ -31,6 +31,12 @@ in
description = "Ports to restrict to Cloudflare IPs only";
};
publicPorts = lib.mkOption {
type = lib.types.listOf lib.types.port;
default = [ ];
description = "Ports to open to the public (any IP)";
};
allowLocalTraffic = lib.mkOption {
type = lib.types.bool;
default = false;
@ -45,11 +51,13 @@ in
};
config = lib.mkIf cfg.enable {
networking.firewall.enable = lib.mkForce false;
# Ensure nf_conntrack is loaded for ct state rules
boot.kernelModules = [ "nf_conntrack" ];
networking.nftables = {
enable = true;
enable = lib.mkForce true;
tables.cloudflare = {
family = "inet";
@ -120,11 +128,19 @@ in
''}
ip saddr 169.254.0.0/16 accept
ip saddr @cloudflare_ipv4 tcp dport { ${portsStr} } accept
ip6 saddr @cloudflare_ipv6 tcp dport { ${portsStr} } accept
# Drop all other traffic to restricted ports (redundant with policy drop but good for clarity/logging if needed)
tcp dport { ${portsStr} } drop
# Public Ports (Open to everyone)
${lib.optionalString (cfg.publicPorts != [ ]) ''
tcp dport { ${lib.concatStringsSep ", " (map toString cfg.publicPorts)} } accept
''}
# Restricted Ports (Cloudflare only)
${lib.optionalString (cfg.restrictedPorts != [ ]) ''
ip saddr @cloudflare_ipv4 tcp dport { ${lib.concatStringsSep ", " (map toString cfg.restrictedPorts)} } accept
ip6 saddr @cloudflare_ipv6 tcp dport { ${lib.concatStringsSep ", " (map toString cfg.restrictedPorts)} } accept
# Drop all other traffic to restricted ports
tcp dport { ${lib.concatStringsSep ", " (map toString cfg.restrictedPorts)} } drop
''}
}
chain forward {

View file

@ -16,7 +16,7 @@
./dns-over-tls.nix
./cloudflare-firewall.nix
./sched-ext.nix
./caddy-cloudflare.nix
./nginx.nix
./podman.nix
./browser-vpn.nix
./ollama-rocm.nix

View file

@ -23,10 +23,10 @@ in
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";
# environment.variables.SCUDO_OPTIONS = "ZeroContents=1";
};
}

View file

@ -11,6 +11,12 @@
boot.initrd.supportedFilesystems = [ "btrfs" ];
boot.initrd.systemd.enable = true;
systemd.tmpfiles.rules = [
"h /persist/var/lib/containers - - - - +C"
"h /persist/var/lib/ollama - - - - +C"
];
fileSystems."/etc/ssh" = {
device = "/persist/etc/ssh";
fsType = "none";
@ -29,17 +35,15 @@
"/var/lib/containers" # Podman/Docker images and containers
"/var/lib/ollama" # LLM models
"/var/lib/open-webui" # Chat history
"/var/lib/caddy" # SSL certs
"/var/lib/acme" # Let's Encrypt SSL certs (nginx)
"/var/lib/tailscale" # Tailscale identity
"/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"
"/var/lib/nixarr"
"/var/lib/nixflix"
"/var/lib/authelia-main"
];
files = [
@ -93,6 +97,8 @@
".local/share/icons" # Application icons
".local/bin" # User scripts
".local/share/qBittorrent"
".local/share/jellyfin-desktop"
".cache/jellyfin-desktop"
];
};
};

View file

@ -66,9 +66,7 @@ in
"randomize_kstack_offset=on"
"vsyscall=none"
"oops=panic"
"debugfs=off"
"module.sig_enforce=1"
"lockdown=confidentiality"
];
# Kernel sysctl hardening

512
modules/nixos/media.nix Normal file
View file

@ -0,0 +1,512 @@
{
config,
lib,
pkgs,
...
}:
{
# Nixarr Configuration
# Replaces OCI containers with native NixOS services
# Nixflix Configuration
nixflix = {
enable = false; # Disabled to revert to Podman
stateDir = "/var/lib/nixflix";
mediaDir = "/data";
sonarr.enable = false;
radarr.enable = false;
prowlarr.enable = false;
jellyfin.enable = false;
jellyseerr.enable = false;
# We use external OCI containers for these
sabnzbd.enable = false;
mullvad.enable = false;
# Jellyseerr defaults to VPN=true, but we disabled Mullvad, so we must disable VPN here too.
jellyseerr.vpn.enable = false;
};
# Homepage Dashboard
services.homepage-dashboard = {
enable = true;
listenPort = 8082;
# Custom settings for better visual appearance
settings = {
title = "Media Dashboard";
theme = "dark";
color = "slate";
headerStyle = "boxed";
layout = {
"Media" = {
style = "row";
columns = 2;
};
"Automation" = {
style = "row";
columns = 3;
};
"Downloads" = {
style = "row";
columns = 2;
};
};
};
services = [
{
"Media" = [
{
"Jellyfin" = {
icon = "jellyfin.png";
href = "http://localhost:8096";
description = "Media Server";
widget = {
type = "jellyfin";
url = "http://localhost:8096";
key = "{{HOMEPAGE_VAR_JELLYFIN_API_KEY}}";
enableBlocks = true;
enableNowPlaying = true;
};
};
}
{
"Jellyseerr" = {
icon = "jellyseerr.png";
href = "http://localhost:5055";
description = "Media Requests";
widget = {
type = "jellyseerr";
url = "http://localhost:5055";
key = "{{HOMEPAGE_VAR_JELLYSEERR_API_KEY}}";
};
};
}
];
}
{
"Automation" = [
{
"Sonarr" = {
icon = "sonarr.png";
href = "http://localhost:8989";
description = "TV Series";
widget = {
type = "sonarr";
url = "http://localhost:8989";
key = "{{HOMEPAGE_VAR_SONARR_API_KEY}}";
enableQueue = true;
};
};
}
{
"Radarr" = {
icon = "radarr.png";
href = "http://localhost:7878";
description = "Movies";
widget = {
type = "radarr";
url = "http://localhost:7878";
key = "{{HOMEPAGE_VAR_RADARR_API_KEY}}";
enableQueue = true;
};
};
}
{
"Prowlarr" = {
icon = "prowlarr.png";
href = "http://localhost:9696";
description = "Indexer Manager";
widget = {
type = "prowlarr";
url = "http://localhost:9696";
key = "{{HOMEPAGE_VAR_PROWLARR_API_KEY}}";
};
};
}
];
}
{
"Downloads" = [
{
"qBittorrent" = {
icon = "qbittorrent.png";
href = "http://localhost:8080";
description = "Torrent Client";
widget = {
type = "qbittorrent";
url = "http://localhost:8080";
username = "{{HOMEPAGE_VAR_QBITTORRENT_USERNAME}}";
password = "{{HOMEPAGE_VAR_QBITTORRENT_PASSWORD}}";
};
};
}
];
}
];
bookmarks = [
{
"Dev" = [
{
"GitHub" = [
{
abbr = "GH";
href = "https://github.com";
}
];
}
{
"NixOS Search" = [
{
abbr = "NO";
href = "https://search.nixos.org";
}
];
}
{
"Home Manager" = [
{
abbr = "HM";
href = "https://nix-community.github.io/home-manager/options.xhtml";
}
];
}
];
}
{
"Media" = [
{
"Trakt" = [
{
abbr = "TR";
href = "https://trakt.tv";
}
];
}
{
"IMDb" = [
{
abbr = "IM";
href = "https://imdb.com";
}
];
}
];
}
];
widgets = [
{
resources = {
cpu = true;
disk = "/";
memory = true;
uptime = true;
};
}
{
search = {
provider = "duckduckgo";
target = "_blank";
};
}
{
datetime = {
text_size = "xl";
format = {
dateStyle = "long";
timeStyle = "short";
hour12 = false;
};
};
}
{
openmeteo = {
label = "Berlin";
latitude = 52.52;
longitude = 13.405;
units = "metric";
cache = 5;
};
}
];
};
# SOPS Secrets for Homepage
sops.templates."homepage.env" = {
content = ''
HOMEPAGE_VAR_JELLYFIN_API_KEY=
HOMEPAGE_VAR_JELLYSEERR_API_KEY=
HOMEPAGE_VAR_SONARR_API_KEY=${config.sops.placeholder.sonarr_api_key}
HOMEPAGE_VAR_RADARR_API_KEY=${config.sops.placeholder.radarr_api_key}
HOMEPAGE_VAR_PROWLARR_API_KEY=${config.sops.placeholder.prowlarr_api_key}
HOMEPAGE_VAR_QBITTORRENT_USERNAME=
HOMEPAGE_VAR_QBITTORRENT_PASSWORD=
'';
};
# Inject secrets into Homepage service
systemd.services.homepage-dashboard = {
serviceConfig = {
EnvironmentFile = lib.mkForce config.sops.templates."homepage.env".path;
};
};
# OCI Containers for Media Stack
virtualisation.oci-containers.containers = {
# VPN (Gluetun)
vpn = {
image = "docker.io/qmcgaw/gluetun";
ports = [
"8080:8080" # qBittorrent WebUI
"36630:36630" # Torrent Port TCP
"36630:36630/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";
# Allow access to local Podman network (for Prowlarr/Jellyseerr)
FIREWALL_OUTBOUND_SUBNETS = "10.88.0.0/16";
};
extraOptions = [
"--cap-add=NET_ADMIN"
"--cap-add=NET_RAW"
"--device=/dev/net/tun:/dev/net/tun"
"--network=media" # Join the shared media network
];
};
# qBittorrent (Networked via VPN)
torrent = {
image = "lscr.io/linuxserver/qbittorrent:latest";
extraOptions = [ "--network=container:vpn" ];
dependsOn = [ "vpn" ];
environment = {
PUID = "1000"; # ashie
PGID = "100"; # users
TZ = "Europe/Berlin";
WEBUI_PORT = "8080";
};
volumes = [
"/var/lib/qbittorrent:/config"
"/data:/data"
];
};
# Flaresolverr (Direct connection)
flaresolverr = {
image = "ghcr.io/flaresolverr/flaresolverr:latest";
extraOptions = [ "--network=media" ];
ports = [ "8191:8191" ];
environment = {
TZ = "Europe/Berlin";
};
};
# Prowlarr (Direct connection)
prowlarr = {
image = "lscr.io/linuxserver/prowlarr:latest";
extraOptions = [ "--network=media" ];
ports = [ "9696:9696" ];
environment = {
PUID = "1000";
PGID = "100";
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/nixarr/prowlarr:/config"
];
};
# Sonarr (Direct connection)
sonarr = {
image = "lscr.io/linuxserver/sonarr:latest";
extraOptions = [ "--network=media" ];
ports = [ "8989:8989" ];
environment = {
PUID = "1000";
PGID = "100";
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/nixarr/sonarr:/config"
"/data:/data"
];
};
# Radarr (Direct connection)
radarr = {
image = "lscr.io/linuxserver/radarr:latest";
extraOptions = [ "--network=media" ];
ports = [ "7878:7878" ];
environment = {
PUID = "1000";
PGID = "100";
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/nixarr/radarr:/config"
"/data:/data"
];
};
# Jellyfin (Direct connection)
jellyfin = {
image = "lscr.io/linuxserver/jellyfin:latest";
extraOptions = [ "--network=media" ];
ports = [ "8096:8096" ];
environment = {
PUID = "1000";
PGID = "100";
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/nixarr/jellyfin:/config"
"/data:/data"
];
};
# Jellyseerr (Direct connection)
jellyseerr = {
image = "ghcr.io/fallenbagel/jellyseerr:latest";
extraOptions = [ "--network=media" ];
ports = [ "5055:5055" ];
environment = {
PUID = "1000";
PGID = "100";
TZ = "Europe/Berlin";
};
volumes = [
"/var/lib/nixarr/jellyseerr:/app/config"
];
};
};
# Define the dedicated media network
systemd.services.create-media-network = {
script = ''
${pkgs.podman}/bin/podman network exists media || ${pkgs.podman}/bin/podman network create media
'';
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
User = "ashie";
};
};
# Ensure the /data directory exists (Nixarr uses it)
systemd.tmpfiles.rules = [
# Data directory: owned by ashie:media so both qBittorrent (ashie) and others can access
"d /data 0775 ashie media - -"
# Ensure config directories exist with correct permissions
"d /var/lib/nixarr/prowlarr 0755 ashie users - -"
"d /var/lib/nixarr/sonarr 0755 ashie users - -"
"d /var/lib/nixarr/radarr 0755 ashie users - -"
"d /var/lib/nixarr/jellyfin 0755 ashie users - -"
"d /var/lib/nixarr/jellyseerr 0755 ashie users - -"
# qBittorrent directory
"d /var/lib/qbittorrent 0755 ashie users - -"
];
# Add ashie to media group to ensure access to /data
users.users.ashie.extraGroups = [ "media" ];
# Firewall rules
networking.firewall.allowedTCPPorts = [
80 # HTTP
443 # HTTPS
9696 # Prowlarr
8989 # Sonarr
7878 # Radarr
8096 # Jellyfin
5055 # Jellyseerr
8080 # qBittorrent WebUI
36630 # Torrent
8082 # Homepage
];
networking.firewall.allowedUDPPorts = [
36630
443
];
# Rootless Container Overrides
# Force these containers to run as user 'ashie'
systemd.services."podman-vpn".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-vpn".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-vpn".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-vpn".serviceConfig.Delegate = true;
systemd.services."podman-torrent".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-torrent".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-torrent".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-torrent".serviceConfig.Delegate = true;
systemd.services."podman-flaresolverr".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-flaresolverr".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-flaresolverr".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-flaresolverr".serviceConfig.Delegate = true;
systemd.services."podman-prowlarr".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-prowlarr".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-prowlarr".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-prowlarr".serviceConfig.Delegate = true;
systemd.services."podman-sonarr".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-sonarr".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-sonarr".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-sonarr".serviceConfig.Delegate = true;
systemd.services."podman-radarr".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-radarr".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-radarr".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-radarr".serviceConfig.Delegate = true;
systemd.services."podman-jellyfin".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-jellyfin".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-jellyfin".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-jellyfin".serviceConfig.Delegate = true;
systemd.services."podman-jellyseerr".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-jellyseerr".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-jellyseerr".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-jellyseerr".serviceConfig.Delegate = true;
}

53
modules/nixos/nginx.nix Normal file
View file

@ -0,0 +1,53 @@
# Nginx with Cloudflare ACME Module
# Provides: Secure Nginx setup with automated SSL handling via Cloudflare DNS challenge
{
config,
lib,
pkgs,
...
}:
let
cfg = config.myModules.nginx;
in
{
options.myModules.nginx = {
enable = lib.mkEnableOption "Nginx with Cloudflare ACME";
};
config = lib.mkIf cfg.enable {
security.acme = {
acceptTerms = true;
defaults.email = "mails@ashisgreat.xyz";
certs."ashisgreat.xyz" = {
domain = "ashisgreat.xyz";
extraDomainNames = [ "*.ashisgreat.xyz" ];
dnsProvider = "cloudflare";
group = "nginx";
environmentFile = config.sops.templates."cloudflare-acme.env".path;
# Reload Nginx when certs change
reloadServices = [ "nginx" ];
};
};
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
# SSL Hardening
sslProtocols = "TLSv1.2 TLSv1.3";
sslCiphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
# Use the wildcard cert by default for these domains
commonHttpConfig = ''
# HSTS 1 year
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
'';
};
};
}

View file

@ -12,6 +12,12 @@
};
config = lib.mkIf config.myModules.performance.enable {
# SCX requires debugfs to be mounted
fileSystems."/sys/kernel/debug" = {
device = "debugfs";
fsType = "debugfs";
};
services.scx = {
enable = true;
scheduler = "scx_lavd";

View file

@ -46,6 +46,18 @@ in
oci-containers.backend = "podman";
};
# Required for rootless podman image pulling
environment.etc."containers/policy.json".text = lib.mkForce (
builtins.toJSON {
default = [ { type = "insecureAcceptAnything"; } ];
transports = {
docker-daemon = {
"" = [ { type = "insecureAcceptAnything"; } ];
};
};
}
);
environment.systemPackages = [ pkgs.podman ];
# Ensure required kernel modules are loaded at boot for locked kernel

View file

@ -224,18 +224,24 @@ in
# 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
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";
image = "ghcr.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}";
"SEARXNG_URL_BASE" = "https://${cfg.domain}";
};
environmentFiles = [
# Contains SEARXNG_SECRET_KEY
@ -264,14 +270,14 @@ in
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"
@ -279,25 +285,42 @@ in
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 = { };
# Rootless Overrides
systemd.services."podman-searxng".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-searxng".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-searxng".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-searxng".serviceConfig.Delegate = true;
systemd.services."podman-searxng-redis".serviceConfig.User = lib.mkForce "ashie";
systemd.services."podman-searxng-redis".environment = {
HOME = "/home/ashie";
XDG_RUNTIME_DIR = "/run/user/1000";
};
systemd.services."podman-searxng-redis".serviceConfig.Type = lib.mkForce "simple";
systemd.services."podman-searxng-redis".serviceConfig.Delegate = true;
};
}
}

View file

@ -1,105 +0,0 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.myModules.caddyCloudflare;
# Generate virtual host configs with security headers
mkVirtualHost = name: hostCfg: {
extraConfig = ''
# Security headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "${hostCfg.frameOptions}"
Referrer-Policy "strict-origin-when-cross-origin"
${lib.optionalString (hostCfg.csp != null) ''Content-Security-Policy "${hostCfg.csp}"''}
-Server
}
reverse_proxy ${hostCfg.reverseProxy}
'';
};
in
{
options.myModules.caddyCloudflare = {
enable = lib.mkEnableOption "Caddy with Cloudflare DNS-01 ACME";
email = lib.mkOption {
type = lib.types.str;
description = "Email for ACME certificate registration";
};
cloudflareApiTokenFile = lib.mkOption {
type = lib.types.path;
description = "Path to file containing Cloudflare API token";
};
virtualHosts = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
options = {
reverseProxy = lib.mkOption {
type = lib.types.str;
description = "Backend address (e.g., 127.0.0.1:8080)";
};
frameOptions = lib.mkOption {
type = lib.types.str;
default = "DENY";
description = "X-Frame-Options header value";
};
csp = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:;";
description = "Content-Security-Policy header (null to disable)";
};
};
}
);
default = { };
description = "Virtual host configurations";
};
hardenSystemd = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Apply systemd hardening to Caddy service";
};
};
config = lib.mkIf cfg.enable {
services.caddy = {
enable = true;
email = cfg.email;
# Caddy with Cloudflare DNS plugin
package = pkgs.caddy.withPlugins {
plugins = [ "github.com/caddy-dns/cloudflare@v0.2.3-0.20251204174556-6dc1fbb7e925" ];
hash = "sha256-htrfa7whiIK2pqtKl6pKFby928dCkMmJp3Hu0e3JBX4=";
};
globalConfig = ''
acme_dns cloudflare {env.CF_API_TOKEN}
servers {
protocols h1 h2
}
'';
virtualHosts = lib.mapAttrs mkVirtualHost cfg.virtualHosts;
};
# Systemd hardening
systemd.services.caddy.serviceConfig = lib.mkIf cfg.hardenSystemd {
NoNewPrivileges = true;
ProtectHome = true;
ProtectSystem = "strict";
PrivateTmp = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
EnvironmentFile = cfg.cloudflareApiTokenFile;
};
};
}

View file

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

13
proxy_enable.json Normal file
View file

@ -0,0 +1,13 @@
{
"enable": true,
"name": "FlareSolverr",
"implementation": "FlareSolverr",
"implementationName": "FlareSolverr",
"configContract": "FlareSolverrSettings",
"fields": [
{ "name": "host", "value": "http://localhost:8191/" },
{ "name": "requestTimeout", "value": 60 }
],
"tags": [1],
"id": 1
}

2
result
View file

@ -1 +1 @@
/nix/store/5a7l4w0z3m0fn0cy45rbd3ahms1ncpx1-nixos-system-nixos-26.05.20260111.ffbc9f8
/nix/store/6sp5wmsjz0avs6rqv3ng6vx5hzpilx75-nixos-system-nixos-26.05.20260116.e4bae1b

View file

@ -18,9 +18,7 @@ if ! ip netns list | grep -q "$NAMESPACE"; then
exit 1
fi
COMMAND="$@"
if [ -z "$COMMAND" ]; then
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <command> [args...]"
exit 1
fi
@ -35,4 +33,4 @@ exec ip netns exec "$NAMESPACE" doas -u "$USER" env \
XDG_RUNTIME_DIR="/run/user/$(id -u $USER)" \
WAYLAND_DISPLAY="$WAYLAND_DISPLAY" \
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u $USER)/bus" \
$COMMAND
"$@"

View file

@ -0,0 +1,10 @@
users:
admin:
displayname: Admin
# Password: password
# Hash generated with: --memory 16384 --iterations 1 --parallelism 1
password: "$argon2id$v=19$m=16384,t=1,p=1$ygXlX9Am6U1CnfvXK/B0Xw$YspmBrCGnq987Z1QYjgSrA+oMkLCks7g8m/jKUtMKEg"
email: admin@ashisgreat.xyz
groups:
- admins
- dev

View file

@ -1,23 +1,26 @@
wireguard_private_key: ENC[AES256_GCM,data:k2M3YwaqyZt5ToK590ooi4m1yuG67alUqqJRiQ9mu8Y07t4b1jq8DovtsUo=,iv:/3P8RwQTnqW41Ig4APs3aTzPnNq1ocXahhNlfsDEYic=,tag:vnUGfPDhz8ZYqIda2quavA==,type:str]
wireguard_public_key: ENC[AES256_GCM,data:80Y60Wp+eBuq91WWRO5LmhDCg+bLX7WZpa5/lTLK/RJ1BpwYPzXAe68QUok=,iv:lDLmHvqDUwXty61by0h80BsX5b3H4mAykfrLKLHkL88=,tag:a4o3rZm4PQvK1gJRJvihKg==,type:str]
wireguard_addresses: ENC[AES256_GCM,data:vQqHxAiemIXuhI0+6c0DawE=,iv:+qi8nkZmEpBdrePRtc9l/LvAp41h4IDucXfH7oi42Vw=,tag:GIvKywb5T83aBFht7Vc53w==,type:str]
wireguard_endpoint_ip: ENC[AES256_GCM,data:fvpWVmC3u52bbJe8,iv:WXY9sRrQQAPZuggm/Rm4cDAGD7yuZB5bwPCnf+9gX6g=,tag:thAR6mG7NMwWT4OK2Ognrg==,type:str]
wireguard_endpoint_ip: ENC[AES256_GCM,data:DvCDj7//VnsLQDkF,iv:wldScNrUg2nRgePbA+Wgm1Gpe6oqMy3pwpVylvZuaEQ=,tag:XUr2eRbuQ0SC7KFU+Ja2AQ==,type:str]
wireguard6_adresses: ENC[AES256_GCM,data:NqF5/RiiDooleKB5b8JGCmf0dzX8BngJC3yxiOyPsD56XrbCJSEYL5O7mg==,iv:H/b/6gH1gVSIlCvsj1IgoJmmn2dmqXUHblYLdBf7gvI=,tag:LFQYPFqGb8wWuzih8tZO2w==,type:str]
wireguard_endpoint_port: ENC[AES256_GCM,data:wfeiNw==,iv:J0ElbpEHu1S4j2XK9FI4J0jc4QzyZIPCZq+Q+ZOWfeY=,tag:k/JQNRezgIAbTpcSqPkCJA==,type:str]
wireguard_preshared_key: ENC[AES256_GCM,data:b0QklQZX8pWyTfDoAZsx+fM/7/D1MA2udxOwJdpsBCbrxVvXc03SoLjLsHI=,iv:hP9YiqpPy/j5LvOUPr48LKgX0cP7xq722Mphvp11lh8=,tag:QKK9sFk8P8nC6LcZzQ2g0w==,type:str]
cloudflare_api_key: ENC[AES256_GCM,data:CBlFgYq7+S0Jsga+7zkdcs2aGHZwvQ0bb+Lpwubpo6HACKmYGC9UlA==,iv:5cB6M4GuBoPe9alrvkWPCrNnr1rutNPLN0u1TKrVeOk=,tag:RSjItKjnNV6mCa72fyeJ4Q==,type:str]
wireguard_dns: ENC[AES256_GCM,data:Wxc/0TsSZpwlww==,iv:5Yb+BCg+Zpvl33rYhH0Yg3kPMUWnhe6qMUZ60fzTt80=,tag:LnLhoQQP4y7ghTrDwmPDVg==,type:str]
wireguard6_dns: ENC[AES256_GCM,data:PtIO2HbrChEeAv+tscdSBc6sUvyxeg==,iv:8VduJGd148wjdQQWWXnuZ/ayt6voyv4NTtiqlxNbfe0=,tag:WTbsqmvYNmtPSShnFyjwSQ==,type:str]
open_webui_env: ENC[AES256_GCM,data:m5Z/2kZSM13/XsfqktpiuA8D4oQ2/xBWiufK/wznBmUGokOC0znXuaZqVLEDCwxNGpZk4fQSnB/VFV+2xxGd4D1sTmGSCx5fRHM/avcgyKe//GG7LpF4Q3lPNGJReJcumZtzo42CZsQ0hptpH+Ztq0dC8I0TBr7dys7DmFFiYTf5clqglQscb3OQhj8pVtJMiSL+wrCs8I0alZ3aTyqjsSNljGRFj7oTxKD71dx009FOEt5Bvtoec3tx2LMKMJvnZv7jTr9A2uN5Rg7o9mtIoWOMXyrYLDr+fA2U96r3U83f5eF/lKANB11iSSq9LB2mz+uE6pWzX6ehbGxy/UgXO/dlFYBdismvV04i1X9nhAc8Nd0=,iv:UCWCyIaVno2/p2OlRCbKcHKmOauaT5WaMT6dMs7jyIU=,tag:JJZwEs3Tk15Da8WZaYemlA==,type:str]
master_api_key: ENC[AES256_GCM,data:2ByHyqPt3Epvy1rJTFAxzf/Wq0TC4uJ5aQ9e/c4J,iv:CUyyC3QMVhxhkhwS3W2vk8lBq8/up52wElOgnYUaCmw=,tag:A2wFwJxOLULHdu2WeAaUQA==,type:str]
jwt_secret: ENC[AES256_GCM,data:9C5s3uVf8MjoYavILkRl6jN8NuqyEMGU9REah5u2Q2I51SDvGuikXbag0ZbdPi07t7ta/0Nx+xjLGsIOSYQvCw==,iv:urG3iAAoU+W556xu24WAv/z4P4DsPPaIhTl4zfyi/4I=,tag:rxX8VuePRHFSOlpfNW9NOg==,type:str]
#ENC[AES256_GCM,data:u1jbICA5cr4HQJnWGpnGcEorXLtDezYVMg0ZVzkxBnuy9Kb/4xxNWWiK//XvxzEQarHrgN3KoVwqUp43YFNtMuuhiPL9yQvT4fmAfPY/fwndzI/cr3NSy+XeEoDFOQ==,iv:XaHxZASGAi3Z6vHP9PHpTHVJ0GqL+KNd/3bCaUCmC0c=,tag:9b/4Jt2zTXMVHIHR5mBi+w==,type:comment]
hashed_password: ENC[AES256_GCM,data:s8ADA68VHHZwGEuYsCw0J0/fNlaZ+7W2PSKckGM/cAcn6kU3IoS0W073FW0IcvIRmiPZrOmW8Wz+ZrnFpa5nuskH9Sr2ZDqkyhk/GlcDR+84RcDL4RCW3Nu/eMVWt0/PpoCbAMgaNgAhgQ==,iv:F2liuSVf1IoSY8Ho6iXEMrnfpBOeVEyQlO7FCZQrqYE=,tag:ZIy/N0TWSCZIvBFtps3W2A==,type:str]
pstream_tmdb_api_key: ENC[AES256_GCM,data:bh+jCUiAAyFOpP0EOe/6vDnUQvC9HURG/2oIU2mIbQNo0lNXP5pFy2hE/9ys2DMnnTvopkMLNu4wGyjXuxLMiJtIrq0Wv7kmH0dVmIXeAwQoa8j8QbU2OWuaHambq0sIxY6+UQJo9lhz88uEtys9zDvKDi3sn4JQREvHnCnVljeGQJnXCAALSgFdzhyU2Zy9fX9kyGPkUu7WA1ZBw0JjGjvxSoYcFJZmG5t2J1rRv6gxVcDvOxddkugoZ4Q4d0Fkac+ybAvqGaqy6m/C5FJ5zYUth88wqppB9SUm6XigliRWrUtTT+WrNoRPXusc,iv:uueZ41EkhJOVuC+xfGFORlWKrandTAM3KN/4PxRDwz0=,tag:DbywbqDgWCvsuo21XwDtmQ==,type:str]
pstream_jwt_secret: ENC[AES256_GCM,data:jW81qa96mTaCrGvZOZ3t5eVrUk6H78db5Q1kCd1/u9C0oi8MYFzTY7nH4GaYpkhIbfd65ozDOx78y9G71IbFbg==,iv:tPKT4GlttwtVn43ZpDoNMdSFP4AsqAYdL+hRnN88vLY=,tag:wANFe22Vliabmbb9NKsmDA==,type:str]
pstream_postgres_password: ENC[AES256_GCM,data:X00fVgOG+1o6NomujERC30ifp3cRrSIQ5LttgIaFXwPHJb0oXRTE//gHnS46hlKSoZYc9Vz1RQ0ecDOW9bmvWQ==,iv:JHfjowWFUS5CVZ0MgFtAjWuZJxmgQFpYYhUBOWb2ob0=,tag:Raj+yNEkojAnHKTN1Ti3uQ==,type:str]
hashed_password: ENC[AES256_GCM,data:MdmmRC0YYH2RfavikMUf8zDXjHKX4bB3Kf4/VYRaFPodV3Z5VVeneH6ObsDb6hTGCJRX+JmEM/MT7f7d5g1Fw+UcEBWS+gqOLXDfEm9hr8k7E5P32NYUXZtXuzbqoYn3dC30FFf8rz5FSw==,iv:sAPDAmBO7ATTV3JOii6Z9xEpiveVJS/K5bWNK5DGjYM=,tag:VYyzTDxY+4VsNZkzhScsXA==,type:str]
searxng_secret_key: ENC[AES256_GCM,data:eFsJko9dEva9IlBCF+YCCj7j6HYi1dUqkOrDBTxiixNfGgu0FakujOyiyDGOdzrTE7j6Q6RVZ2pGG7/Xl21YtQ==,iv:k7hEaTdDvdFzQMWmFVbUrSIm1fLIc0qq3vdasJZlIlk=,tag:dhi4JP8FQLcdTuUR6mhZMw==,type:str]
nixflix_password: ENC[AES256_GCM,data:oBAbEFJnDhOPkAAZ5TR/hQjBH0YTmfmHbZ78mLRjUs+RcFD0Uw3k80LNrmB3qpnq8NJwazPRg39ukTh5HDU7Gz9D1/DlypOfk0qjKrfVY5IYhyBs+v8B3tsZX0StcGK/74BzMxtT8ahI8dU/glaetcRQKz90PUnHHkUeAJZIkg==,iv:Y7sATA/v3q0Z4AH5BN/xbEzxA4jIa81ooJx7LVWg3e0=,tag:jtwgs5pY25vek7XTVSzRvQ==,type:str]
sonarr_api_key: ENC[AES256_GCM,data:bANmRK6E65/YoxBdCAWrZRLlHNcUweNIv502PPXGRdk=,iv:7xTfpNBwCMJTbIP+cAqN1XsLGtl/evulq79tLSMRYkQ=,tag:sq/2kWMNkrtheYEOsKSYPA==,type:str]
radarr_api_key: ENC[AES256_GCM,data:bGaBbRm9SKXPkMvDS2/3JzCoMfhRQPdXMN6V3ILL2nU=,iv:tWT8Bf/Ae4NKdsKEzN1iEMdj+nGMO6XScs2LoGl9Drs=,tag:JgbILcdC7KmNuzw1Kr1P3A==,type:str]
prowlarr_api_key: ENC[AES256_GCM,data:jONkYK9GNrN9+R/0SSNGAP8VrUi3QU1XwUakkrmLbhk=,iv:qSswJCOejpXiHbdW+2on7duP8eaUB6ToRjeAO0FusuQ=,tag:726JkJmCP0AsFc+Cm9luIg==,type:str]
authelia_jwt_secret: ENC[AES256_GCM,data:3hNXsaKu+gk4YjPTQsvR43fhRgpwgsR8GsyalGnkOjPfBJ7KuuykYg/6xvvF1KM8pnlHgJNXznpeS1Jokjndbg==,iv:w+V2O6Kjq29D5h51Toj0Fgmq/GA0LgYWqESRAFOajq8=,tag:dCIZG1X8pVPJvZYkK5frBQ==,type:str]
authelia_session_secret: ENC[AES256_GCM,data:jNB1QBocUNZSljjgIrtgTtmDQKthuGY8Gv8/9jzY9c5s6HvUVNCACYBtfCgA7wN6XHinpO5Vlr5D6BFaTKLMiw==,iv:exBc+tfk2OC5c5HPSo1Qh95vzJqkYiErFokVPoPbErc=,tag:nxmUb+jThN58zuq8ebTqzw==,type:str]
authelia_storage_encryption_key: ENC[AES256_GCM,data:2ELisYBEh7rY+Jcfy5c2mBOvFUi6uYnTHtkMviOffqkIkSUmgc+43Kt2tQA7P97y6Tn8/1w/3jJjx/1voz3ojQ==,iv:85k4x8bMoaJEGz4JgyrpmcTJktGypQjK/2GUxIFymOc=,tag:cJTW6YwZTtoUQDSXnYS32A==,type:str]
sops:
age:
- recipient: age1g76q4cec3qykmkzrd6f4fxxpafj5fsut4jk7pklweuff97scpuusnwdknu
@ -38,7 +41,7 @@ sops:
cVlpL1pxTFN3d1llbEhiNzlCcDV6NzAK6RlVB106woOkrmlINKB5hjoQs8CBfMAI
nAjTYfHW0h4PznY0JpWfeNaVRD4EbDwbE2m8X6OzQEWJJB1WESw4Zg==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-01-19T19:08:13Z"
mac: ENC[AES256_GCM,data:7k41gGei0vW/Hh7RnB4drkXDBlVQmbZULrZnEevLhNQMkeE3f01EGcknzDdLDcmcV5AbZka75aJKE68wypfDRBv7JiT9yG/5PH5he7NONZ47Im40f0zwwT5A2SGOvwS8A37t0DxrGGxmF2IGK6o3PDSnPtsog05juXXKd3TzOwo=,iv:gwW3z9SDF9Cj0XAn+E/7+GMvaAWcNiTB+NZffPpWcK4=,tag:LpNRT5Cown0u8+ngbrN/Lg==,type:str]
lastmodified: "2026-01-20T22:18:47Z"
mac: ENC[AES256_GCM,data:RJENwpgbopALD5EBsObfBF3gUijneA5aiqkUwQ8PRB8u2bvwTM/GoOGHYjr41f7kENQHJ8Q6gLkbBxBdLVrve1sYj2isOs1/A5sQxSFffkrdOtsHo18Mr18rmRC5sdJW6gOs9xUrsoUsxUs+mDbKowp8WfQxM/ALvOQatMYitWI=,iv:eEH4pmIA5JWHQy84l6B4Jyq1Cs8m/nX12tgNB52yNXc=,tag:piwqpJInnhH0VUvvO6iRhQ==,type:str]
unencrypted_suffix: _unencrypted
version: 3.11.0

View file

@ -1,266 +0,0 @@
{
config,
lib,
pkgs,
...
}:
{
services.flatpak.enable = false;
services.snowflake-proxy = {
enable = true;
capacity = 10;
};
services.timesyncd.enable = false;
services.chrony = {
enable = true;
enableNTS = true;
servers = [
"time.cloudflare.com"
"nts.netnod.se"
"ptbtime1.ptb.de"
];
extraConfig = ''
user chrony
pidfile /run/chrony/chrony.pid
driftfile /var/lib/chrony/drift
makestep 1.0 3
'';
};
services.fstrim.enable = true;
services.dbus.implementation = "broker";
services.earlyoom = {
enable = true;
enableNotifications = true;
freeMemThreshold = 5;
};
services.openssh = {
enable = true;
ports = [ 5732 ];
hostKeys = [
{
path = "/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
}
];
settings = {
PasswordAuthentication = false;
PermitRootLogin = "no";
X11Forwarding = false;
AllowAgentForwarding = false;
UseDns = false;
};
};
services.gnome.gnome-keyring.enable = true;
security.pam.services.greetd.enableGnomeKeyring = true;
programs.hyprland = {
enable = true;
xwayland.enable = true;
};
programs.firefox.enable = false;
services.caddy = {
enable = true;
email = "mails@ashisgreat.xyz";
package = pkgs.caddy.withPlugins {
plugins = [ "github.com/caddy-dns/cloudflare@v0.2.3-0.20251204174556-6dc1fbb7e925" ];
hash = "sha256-htrfa7whiIK2pqtKl6pKFby928dCkMmJp3Hu0e3JBX4=";
};
globalConfig = ''
acme_dns cloudflare {env.CF_API_TOKEN}
servers {
protocols h1 h2 h3
}
'';
extraConfig = ''
(security_headers) {
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
X-XSS-Protection "1; mode=block"
Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()"
-Server
}
}
'';
virtualHosts."search.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
reverse_proxy 127.0.0.1:8888
'';
};
virtualHosts."api.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
header {
X-Frame-Options "DENY"
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:;"
}
reverse_proxy 127.0.0.1:8045
'';
};
virtualHosts."chat.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
header {
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: blob:; font-src 'self' data:; connect-src 'self' wss: https:; worker-src 'self' blob:;"
}
reverse_proxy 127.0.0.1:3000
'';
};
virtualHosts."stream.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
basic_auth {
admin $2a$14$2kaAS6oLx6SdyuM2lksnYOZidfRWb7AGPXT5hhg/s5nseL7bjHsx2
}
reverse_proxy 127.0.0.1:3333
'';
};
virtualHosts."stream-api.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
header {
Access-Control-Allow-Origin "https://stream.ashisgreat.xyz"
}
reverse_proxy 127.0.0.1:3334
'';
};
virtualHosts."sonarr.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
reverse_proxy 127.0.0.1:8989
'';
};
virtualHosts."radarr.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
reverse_proxy 127.0.0.1:7878
'';
};
virtualHosts."prowlarr.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
reverse_proxy 127.0.0.1:9696
'';
};
virtualHosts."torrent.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
reverse_proxy 127.0.0.1:8080
'';
};
virtualHosts."jellyfin.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
reverse_proxy 127.0.0.1:8096
'';
};
virtualHosts."jellyseer.ashisgreat.xyz" = {
extraConfig = ''
import security_headers
reverse_proxy 127.0.0.1:5055
'';
};
virtualHosts."jellyseerr.ashisgreat.xyz" = {
extraConfig = ''
redir https://jellyseer.ashisgreat.xyz{uri}
'';
};
};
# Hardening for Chrony
systemd.services.chronyd.serviceConfig = {
ProtectSystem = lib.mkForce "strict";
ProtectHome = true;
PrivateTmp = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
# Chrony needs to adjust time, preserve CAP_SYS_TIME and CAP_NET_BIND_SERVICE
CapabilityBoundingSet = [
"CAP_SYS_TIME"
"CAP_NET_BIND_SERVICE"
];
MemoryDenyWriteExecute = true;
LockPersonality = true;
};
# Hardening for EarlyOOM
systemd.services.earlyoom.serviceConfig = {
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
};
systemd.services.caddy.serviceConfig = {
NoNewPrivileges = true;
ProtectHome = true;
ProtectSystem = "strict";
PrivateTmp = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
};
systemd.services.caddy.serviceConfig.EnvironmentFile = config.sops.templates."caddy.env".path;
# Hardening for Snowflake Proxy
systemd.services.snowflake-proxy.serviceConfig = {
DynamicUser = true;
ProtectSystem = "strict";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
RestrictRealtime = true;
SystemCallFilter = [ "@system-service" "~@privileged" ];
};
# Hardening for DDClient
systemd.services.ddclient.serviceConfig = {
ProtectSystem = "full";
ProtectHome = true;
PrivateTmp = true;
PrivateDevices = true;
ProtectKernelTunables = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
ReadWritePaths = [ "/run/ddclient" ];
NoNewPrivileges = true;
};
}