feat: added basic structure for searXNG and netdata
This commit is contained in:
parent
fa5de8f1bb
commit
aa607747c3
8 changed files with 1357 additions and 57 deletions
|
@ -0,0 +1,316 @@
|
|||
# SearXNG Service Configuration for Home Lab
|
||||
#
|
||||
# This module provides SearXNG configuration for private metasearch engine
|
||||
# SearXNG aggregates results from various search engines while preserving privacy
|
||||
#
|
||||
# Features:
|
||||
# - Uses reverse proxy for outbound traffic to search engines
|
||||
# - Web UI only accessible from Tailscale network
|
||||
# - No logging of user queries for privacy
|
||||
# - Nginx reverse proxy with security headers
|
||||
# - Rate limiting and security hardening
|
||||
#
|
||||
# Usage:
|
||||
# In your machine's configuration.nix, add:
|
||||
# imports = [ ../../modules/services/SearXNG.nix ];
|
||||
# services.searxng-lab = {
|
||||
# enable = true;
|
||||
# hostName = "searxng.your-domain.com";
|
||||
# reverseProxyHost = "reverse-proxy";
|
||||
# };
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
cfg = config.services.searxng-lab;
|
||||
|
||||
# Generate a unique secret key for this instance
|
||||
secretKeyFile = pkgs.writeText "searxng-secret" (builtins.hashString "sha256" cfg.hostName);
|
||||
|
||||
# SearXNG settings configuration
|
||||
searxngSettings = {
|
||||
use_default_settings = true;
|
||||
|
||||
server = {
|
||||
port = cfg.port;
|
||||
bind_address = "127.0.0.1";
|
||||
secret_key = "@SECRET_KEY@"; # Will be replaced at runtime
|
||||
base_url = "http://${cfg.hostName}";
|
||||
image_proxy = true;
|
||||
public_instance = false;
|
||||
limiter = true;
|
||||
method = "POST";
|
||||
|
||||
default_http_headers = {
|
||||
X-Content-Type-Options = "nosniff";
|
||||
X-Download-Options = "noopen";
|
||||
X-Robots-Tag = "noindex, nofollow";
|
||||
Referrer-Policy = "no-referrer";
|
||||
X-Frame-Options = "DENY";
|
||||
X-XSS-Protection = "1; mode=block";
|
||||
};
|
||||
};
|
||||
|
||||
general = {
|
||||
debug = false;
|
||||
instance_name = "Private Search - ${cfg.hostName}";
|
||||
privacypolicy_url = false;
|
||||
donation_url = false;
|
||||
contact_url = false;
|
||||
enable_metrics = false;
|
||||
};
|
||||
|
||||
search = {
|
||||
safe_search = 0;
|
||||
autocomplete = "";
|
||||
default_lang = "en";
|
||||
ban_time_on_fail = 5;
|
||||
max_ban_time_on_fail = 120;
|
||||
formats = ["html" "json"];
|
||||
};
|
||||
|
||||
ui = {
|
||||
static_use_hash = true;
|
||||
default_locale = "en";
|
||||
query_in_title = false;
|
||||
infinite_scroll = false;
|
||||
center_alignment = false;
|
||||
default_theme = "simple";
|
||||
hotkeys = "default";
|
||||
search_on_category_select = true;
|
||||
};
|
||||
|
||||
# Configure outbound requests through reverse proxy
|
||||
outgoing = {
|
||||
request_timeout = 3.0;
|
||||
useragent_suffix = "";
|
||||
pool_connections = 100;
|
||||
pool_maxsize = 10;
|
||||
enable_http2 = true;
|
||||
using_tor_proxy = false;
|
||||
|
||||
# Use reverse proxy for all outbound requests
|
||||
proxies = {
|
||||
http = "http://${cfg.reverseProxyHost}:${toString cfg.reverseProxyPort}";
|
||||
https = "http://${cfg.reverseProxyHost}:${toString cfg.reverseProxyPort}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Convert settings to YAML format
|
||||
settingsFile = pkgs.writeText "searxng-settings.yml" (lib.generators.toYAML {} searxngSettings);
|
||||
in {
|
||||
options.services.searxng-lab = {
|
||||
enable = lib.mkEnableOption "SearXNG metasearch engine for home lab";
|
||||
|
||||
hostName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "searxng.local";
|
||||
example = "search.example.com";
|
||||
description = "Hostname for the SearXNG instance";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8888;
|
||||
description = "Port for SearXNG to listen on";
|
||||
};
|
||||
|
||||
reverseProxyHost = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "reverse-proxy";
|
||||
example = "proxy.internal.lan";
|
||||
description = "Hostname of the reverse proxy for outbound traffic";
|
||||
};
|
||||
|
||||
reverseProxyPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 3128;
|
||||
description = "Port of the reverse proxy for outbound traffic";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to open firewall for HTTP access";
|
||||
};
|
||||
|
||||
tailscaleOnly = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to restrict access to Tailscale network only";
|
||||
};
|
||||
|
||||
nginxVhost = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to create Nginx virtual host";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# Create SearXNG user and group
|
||||
users.users.searxng = {
|
||||
isSystemUser = true;
|
||||
group = "searxng";
|
||||
home = "/var/lib/searxng";
|
||||
createHome = true;
|
||||
description = "SearXNG metasearch engine user";
|
||||
};
|
||||
|
||||
users.groups.searxng = {};
|
||||
|
||||
# Install SearXNG package
|
||||
environment.systemPackages = with pkgs; [
|
||||
searxng
|
||||
];
|
||||
|
||||
# Create configuration directory and files
|
||||
environment.etc."searxng/settings.yml" = {
|
||||
source = settingsFile;
|
||||
mode = "0644";
|
||||
};
|
||||
|
||||
# SearXNG systemd service
|
||||
systemd.services.searxng = {
|
||||
description = "SearXNG metasearch engine";
|
||||
after = ["network-online.target"];
|
||||
wants = ["network-online.target"];
|
||||
wantedBy = ["multi-user.target"];
|
||||
|
||||
environment = {
|
||||
SEARXNG_SETTINGS_PATH = "/etc/searxng/settings.yml";
|
||||
PYTHONPATH = "${pkgs.searxng.pythonPath}";
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "exec";
|
||||
User = "searxng";
|
||||
Group = "searxng";
|
||||
ExecStart = "${pkgs.searxng}/bin/searxng-run";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ReadWritePaths = ["/var/lib/searxng" "/tmp"];
|
||||
RestrictAddressFamilies = ["AF_INET" "AF_INET6"];
|
||||
|
||||
# Process limits
|
||||
LimitNOFILE = 4096;
|
||||
MemoryMax = "512M";
|
||||
|
||||
# Network restrictions for Tailscale-only access
|
||||
IPAddressDeny = lib.mkIf cfg.tailscaleOnly "any";
|
||||
IPAddressAllow = lib.mkIf cfg.tailscaleOnly [
|
||||
"100.0.0.0/8" # Tailscale network
|
||||
"127.0.0.0/8" # Localhost
|
||||
"::1/128" # IPv6 localhost
|
||||
];
|
||||
};
|
||||
|
||||
preStart = ''
|
||||
# Generate secret key and update settings
|
||||
SECRET_KEY=$(${pkgs.openssl}/bin/openssl rand -hex 32)
|
||||
${pkgs.gnused}/bin/sed -i "s/@SECRET_KEY@/$SECRET_KEY/g" /etc/searxng/settings.yml
|
||||
|
||||
# Ensure proper permissions
|
||||
mkdir -p /var/lib/searxng/cache
|
||||
chown -R searxng:searxng /var/lib/searxng
|
||||
'';
|
||||
};
|
||||
|
||||
# Nginx reverse proxy configuration
|
||||
services.nginx = lib.mkIf cfg.nginxVhost {
|
||||
enable = true;
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
virtualHosts."${cfg.hostName}" = {
|
||||
listen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
port = 80;
|
||||
}
|
||||
];
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:${toString cfg.port}";
|
||||
|
||||
extraConfig = ''
|
||||
# Proxy headers
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Hide server information
|
||||
proxy_hide_header X-Powered-By;
|
||||
proxy_hide_header Server;
|
||||
|
||||
${lib.optionalString cfg.tailscaleOnly ''
|
||||
# Restrict access to Tailscale network only
|
||||
allow 100.0.0.0/8; # Tailscale network
|
||||
allow 127.0.0.1; # Localhost
|
||||
allow ::1; # IPv6 localhost
|
||||
deny all; # Deny all other access
|
||||
''}
|
||||
'';
|
||||
};
|
||||
|
||||
# Security headers for all responses
|
||||
extraConfig = ''
|
||||
# Security headers
|
||||
add_header X-Frame-Options DENY always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
|
||||
# Content Security Policy for SearXNG
|
||||
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'; connect-src 'self'; frame-ancestors 'none'; form-action 'self';" always;
|
||||
|
||||
# Hide server information
|
||||
server_tokens off;
|
||||
more_clear_headers Server;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# Firewall configuration
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [80];
|
||||
|
||||
# Tailscale-specific firewall rules
|
||||
extraCommands = lib.optionalString cfg.tailscaleOnly ''
|
||||
# Allow Tailscale network access to HTTP
|
||||
iptables -I nixos-fw -i tailscale0 -p tcp --dport 80 -j ACCEPT
|
||||
iptables -I nixos-fw -s 100.0.0.0/8 -p tcp --dport 80 -j ACCEPT
|
||||
'';
|
||||
|
||||
extraStopCommands = lib.optionalString cfg.tailscaleOnly ''
|
||||
# Clean up Tailscale rules
|
||||
iptables -D nixos-fw -i tailscale0 -p tcp --dport 80 -j ACCEPT 2>/dev/null || true
|
||||
iptables -D nixos-fw -s 100.0.0.0/8 -p tcp --dport 80 -j ACCEPT 2>/dev/null || true
|
||||
'';
|
||||
};
|
||||
|
||||
# Ensure required directories exist
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/searxng 0755 searxng searxng -"
|
||||
"d /var/lib/searxng/cache 0755 searxng searxng -"
|
||||
];
|
||||
|
||||
# Add any additional packages needed
|
||||
environment.systemPackages = with pkgs; [
|
||||
curl # For health checks
|
||||
jq # For JSON processing if needed
|
||||
];
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue