.
This commit is contained in:
parent
759163965a
commit
faf14881a3
92 changed files with 1405 additions and 698 deletions
|
|
@ -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
53
capture_arr_user.sh
Normal 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
50
enable_arr_auth.sh
Normal 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'."
|
||||
40
flake.nix
40
flake.nix
|
|
@ -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
1
flaresolverr_test.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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 =
|
||||
|
|
@ -55,10 +55,11 @@
|
|||
enable = true;
|
||||
allowLocalTraffic = true;
|
||||
enablePodmanWorkaround = true;
|
||||
restrictedPorts = [
|
||||
publicPorts = [
|
||||
80
|
||||
443
|
||||
];
|
||||
restrictedPorts = [ ];
|
||||
};
|
||||
|
||||
# Base Podman container runtime
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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"
|
||||
101
hosts/nixos/system/authelia.nix
Normal file
101
hosts/nixos/system/authelia.nix
Normal 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 -"
|
||||
];
|
||||
}
|
||||
|
|
@ -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" = {
|
||||
|
|
@ -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"
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
nspr
|
||||
firefox-sandboxed
|
||||
tutanota-sandboxed
|
||||
brave-sandboxed
|
||||
# brave-sandboxed # Imported via module, wrapper provided there
|
||||
eddie
|
||||
appimage-run
|
||||
rivalcfg
|
||||
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
264
hosts/nixos/system/services.nix
Normal file
264
hosts/nixos/system/services.nix
Normal 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;
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -14,6 +14,6 @@
|
|||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./system
|
||||
./nixos
|
||||
];
|
||||
}
|
||||
|
|
|
|||
6
modules/home-manager/boilr.nix
Normal file
6
modules/home-manager/boilr.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
home.packages = with pkgs; [
|
||||
boilr
|
||||
];
|
||||
}
|
||||
14
modules/home-manager/containers-policy.nix
Normal file
14
modules/home-manager/containers-policy.nix
Normal 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"; } ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -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
|
||||
];
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
@ -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 = {
|
||||
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
];
|
||||
})
|
||||
];
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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
|
||||
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
|
|
@ -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"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
|
@ -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
512
modules/nixos/media.nix
Normal 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
53
modules/nixos/nginx.nix
Normal 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;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -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
13
proxy_enable.json
Normal 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
2
result
|
|
@ -1 +1 @@
|
|||
/nix/store/5a7l4w0z3m0fn0cy45rbd3ahms1ncpx1-nixos-system-nixos-26.05.20260111.ffbc9f8
|
||||
/nix/store/6sp5wmsjz0avs6rqv3ng6vx5hzpilx75-nixos-system-nixos-26.05.20260116.e4bae1b
|
||||
|
|
@ -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
|
||||
"$@"
|
||||
|
|
|
|||
10
secrets/authelia_users_template.yml
Normal file
10
secrets/authelia_users_template.yml
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue