Discover WAN via ddns:<host> then trace:<url> before listen. Persist to self_ip.json. Previous addresses stay denied for keep_previous. Failure keeps the last known set. /healthz reports age and source.
193 lines
5.9 KiB
Nix
193 lines
5.9 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
let
|
|
cfg = config.services.sift;
|
|
settingsFormat = pkgs.formats.toml { };
|
|
in
|
|
{
|
|
options.services.sift = {
|
|
enable = lib.mkEnableOption "sift LLM-oriented fetch and search";
|
|
|
|
package = lib.mkOption {
|
|
type = lib.types.package;
|
|
description = "sift package. The flake module sets this to packages.sift.";
|
|
};
|
|
|
|
listen = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "127.0.0.1:8890";
|
|
description = "Address the HTTP server binds to (loopback by default).";
|
|
};
|
|
|
|
tailscaleServe = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Advertise the listener on tailscale serve at https://<host>:8443.";
|
|
};
|
|
|
|
tailscaleHttpsPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 8443;
|
|
description = "HTTPS port for tailscale serve (443 is SearXNG on orion).";
|
|
};
|
|
|
|
denyPrefixes = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ ];
|
|
description = ''
|
|
Extra CIDRs the SSRF guard blocks at pre-check and connect time.
|
|
Put home WAN /32s here (hairpin into LAN). Set on the host; do not
|
|
commit residential IPs to the sift repo.
|
|
'';
|
|
};
|
|
|
|
selfIP = {
|
|
sources = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ "trace:https://1.1.1.1/cdn-cgi/trace" ];
|
|
description = ''
|
|
Discovery sources in order: ddns:<hostname> and/or
|
|
trace:<url>. Results that are not global unicast are ignored.
|
|
'';
|
|
};
|
|
interval = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "10m";
|
|
description = "How often to re-discover the WAN address.";
|
|
};
|
|
keepPrevious = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "24h";
|
|
description = "How long to keep a previous WAN address in the deny set.";
|
|
};
|
|
};
|
|
|
|
settings = lib.mkOption {
|
|
type = settingsFormat.type;
|
|
default = { };
|
|
description = "Written to /etc/sift/config.toml. Unused keys are reserved for P1+.";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = cfg.package != null;
|
|
message = "services.sift.package must be set (the flake module does this).";
|
|
}
|
|
];
|
|
|
|
users.users.sift = {
|
|
isSystemUser = true;
|
|
group = "sift";
|
|
home = "/var/lib/sift";
|
|
};
|
|
users.groups.sift = { };
|
|
|
|
environment.etc."sift/config.toml".source = settingsFormat.generate "sift.toml" (
|
|
{
|
|
listen = cfg.listen;
|
|
self_ip = {
|
|
sources = cfg.selfIP.sources;
|
|
interval = cfg.selfIP.interval;
|
|
keep_previous = cfg.selfIP.keepPrevious;
|
|
path = "/var/lib/sift/self_ip.json";
|
|
};
|
|
}
|
|
// cfg.settings
|
|
);
|
|
|
|
# Keys and env secrets are created on-box, never in the store.
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/lib/sift 0750 root sift -"
|
|
];
|
|
|
|
system.activationScripts.sift-secrets = {
|
|
deps = [ "users" ];
|
|
text = ''
|
|
install -d -m 0750 -o root -g sift /var/lib/sift
|
|
if [ ! -f /var/lib/sift/keys.toml ]; then
|
|
umask 027
|
|
printf '%s\n' '# key-hash → { name, workspace }. Generated empty; fill on-box.' > /var/lib/sift/keys.toml
|
|
chown root:sift /var/lib/sift/keys.toml
|
|
chmod 0640 /var/lib/sift/keys.toml
|
|
fi
|
|
if [ ! -f /var/lib/sift/secret.env ]; then
|
|
umask 027
|
|
printf '%s\n' '# BRAVE_API_KEY= (set on-box; do not read SearXNG secret.env)' > /var/lib/sift/secret.env
|
|
chown root:sift /var/lib/sift/secret.env
|
|
chmod 0640 /var/lib/sift/secret.env
|
|
fi
|
|
'';
|
|
};
|
|
|
|
systemd.services.sift = {
|
|
description = "sift LLM-oriented fetch and search";
|
|
after = [ "network-online.target" ];
|
|
wants = [ "network-online.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "sift";
|
|
Group = "sift";
|
|
ExecStart = "${lib.getExe cfg.package} serve -listen ${cfg.listen} -config /etc/sift/config.toml"
|
|
+ lib.concatMapStrings (p: " -deny-prefix ${lib.escapeShellArg p}") cfg.denyPrefixes;
|
|
WorkingDirectory = "/var/lib/sift";
|
|
Restart = "on-failure";
|
|
RestartSec = "5s";
|
|
NoNewPrivileges = true;
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectControlGroups = true;
|
|
ProtectHostname = true;
|
|
ProtectClock = true;
|
|
RestrictAddressFamilies = [
|
|
"AF_UNIX"
|
|
"AF_INET"
|
|
"AF_INET6"
|
|
];
|
|
RestrictNamespaces = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
LockPersonality = true;
|
|
MemoryDenyWriteExecute = true;
|
|
MemoryMax = "512M";
|
|
SystemCallArchitectures = "native";
|
|
SystemCallFilter = [
|
|
"@system-service"
|
|
"~@privileged"
|
|
];
|
|
CapabilityBoundingSet = "";
|
|
AmbientCapabilities = "";
|
|
ReadWritePaths = [ "/var/lib/sift" ];
|
|
ConfigurationDirectory = "sift";
|
|
StateDirectory = "sift";
|
|
};
|
|
};
|
|
|
|
systemd.services.tailscale-serve-sift = lib.mkIf cfg.tailscaleServe {
|
|
description = "Advertise sift on Tailscale Serve :${toString cfg.tailscaleHttpsPort}";
|
|
after = [
|
|
"tailscaled.service"
|
|
"network-online.target"
|
|
"sift.service"
|
|
];
|
|
wants = [
|
|
"tailscaled.service"
|
|
"network-online.target"
|
|
];
|
|
wantedBy = [ "multi-user.target" ];
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
Restart = "on-failure";
|
|
RestartSec = "5s";
|
|
ExecStart = "${lib.getExe pkgs.tailscale} serve --bg --yes --https=${toString cfg.tailscaleHttpsPort} http://${cfg.listen}";
|
|
};
|
|
};
|
|
};
|
|
}
|