Bootstrapped the site from scratch following a data loss event. Set up a simple HTML/CSS page served from nginx. Added a navbar with the index page anchored in the top-left, a blog page link, and a link to the Forgejo instance. Added contact info and a genealogy blurb to the index page. Added an example resume page linked from the navbar.
140 lines
3.9 KiB
Nix
140 lines
3.9 KiB
Nix
{ config, pkgs, lib, ... }:
|
|
|
|
let
|
|
# Read multiple API keys from the secrets file at build time
|
|
# Note: This embeds the secrets in the Nix store, which is a trade-off
|
|
# Alternative: Keep secrets file and read via njs module or external auth service
|
|
secretsFile = "/srv/nginx/secrets";
|
|
|
|
# Read API keys from file (one key per line, will be evaluated at build time)
|
|
# If the file doesn't exist yet, this will fail - create it first
|
|
apiKeysRaw = builtins.readFile secretsFile;
|
|
apiKeys = lib.filter (k: k != "") (lib.splitString "\n" apiKeysRaw);
|
|
|
|
# Generate map entries for each key
|
|
mapEntries = lib.concatMapStringsSep "\n "
|
|
(key: ''"Bearer ${key}" "authorized";'')
|
|
apiKeys;
|
|
|
|
in
|
|
{
|
|
services.nginx = {
|
|
enable = true;
|
|
|
|
# Recommended settings
|
|
recommendedGzipSettings = true;
|
|
recommendedOptimisation = true;
|
|
recommendedProxySettings = true;
|
|
recommendedTlsSettings = true;
|
|
|
|
# Increase bucket size for long Bearer tokens
|
|
mapHashBucketSize = 128;
|
|
|
|
# Map directive to check Authorization header against multiple keys
|
|
appendHttpConfig = ''
|
|
# Check if the Authorization header matches any expected value
|
|
map $http_authorization $auth_status {
|
|
default "unauthorized";
|
|
"" "no_auth";
|
|
${mapEntries}
|
|
}
|
|
'';
|
|
|
|
# Virtual hosts configuration
|
|
virtualHosts = {
|
|
|
|
# Main website - Static HTML/CSS
|
|
"www.binning.net" = {
|
|
enableACME = true;
|
|
forceSSL = true;
|
|
|
|
root = "/srv/www/binning.net";
|
|
|
|
locations."/" = {
|
|
index = "index.html";
|
|
tryFiles = "$uri $uri/ =404";
|
|
extraConfig = ''
|
|
# Enable Server Side Includes for navbar/footer includes
|
|
ssi on;
|
|
'';
|
|
};
|
|
|
|
# Optional: Custom 404 page
|
|
extraConfig = ''
|
|
error_page 404 /404.html;
|
|
'';
|
|
};
|
|
|
|
# Ollama with Bearer token authentication
|
|
"ollama.binning.net" = {
|
|
enableACME = true;
|
|
forceSSL = true;
|
|
|
|
locations."/" = {
|
|
extraConfig = ''
|
|
# Check auth status
|
|
if ($auth_status = "no_auth") {
|
|
return 401 "Unauthorized: Bearer token required\n";
|
|
}
|
|
if ($auth_status = "unauthorized") {
|
|
return 403 "Forbidden: Invalid API key\n";
|
|
}
|
|
|
|
# Proxy to Ollama (only if authorized)
|
|
proxy_pass http://localhost:11434;
|
|
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;
|
|
|
|
# Timeouts for long-running requests
|
|
proxy_read_timeout 300s;
|
|
proxy_connect_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
|
|
# Allow large request bodies
|
|
client_max_body_size 100M;
|
|
|
|
# Logging
|
|
access_log /var/log/nginx/ollama_access.log;
|
|
error_log /var/log/nginx/ollama_error.log;
|
|
'';
|
|
};
|
|
};
|
|
|
|
# Forgejo
|
|
"forgejo.binning.net" = {
|
|
enableACME = true;
|
|
forceSSL = true;
|
|
|
|
locations."/" = {
|
|
proxyPass = "http://127.0.0.1:3000";
|
|
# No extraConfig needed - recommendedProxySettings handles headers
|
|
};
|
|
};
|
|
|
|
# Radicale
|
|
"radicale.binning.net" = {
|
|
enableACME = true;
|
|
forceSSL = true;
|
|
|
|
locations."/" = {
|
|
proxyPass = "http://127.0.0.1:5232";
|
|
# recommendedProxySettings handles most headers
|
|
extraConfig = ''
|
|
proxy_set_header X-Script-Name "";
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
# Firewall
|
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
|
|
|
# ACME/Let's Encrypt
|
|
security.acme = {
|
|
acceptTerms = true;
|
|
defaults.email = "hvanb@pm.me";
|
|
};
|
|
}
|