diff --git a/flake.nix b/flake.nix index ba0ef6c..7fb4452 100644 --- a/flake.nix +++ b/flake.nix @@ -56,6 +56,7 @@ ./modules/services/static-sites ./modules/services/webhook-deploy.nix ./modules/services/jirorian.nix + ./modules/services/nip05.nix ./modules/users/tobias.nix ]; }; diff --git a/modules/services/nip05.nix b/modules/services/nip05.nix new file mode 100644 index 0000000..567ec3b --- /dev/null +++ b/modules/services/nip05.nix @@ -0,0 +1,99 @@ +{ pkgs, lib, ... }: +let + domains = [ "otherwhere.app" "www.otherwhere.app" ]; + port = 8892; + relayUrl = "wss://relay.otherwhere.app/"; + dbPath = "/var/lib/nostr-rs-relay/nostr.db"; + + script = pkgs.writeText "nip05-server.py" '' + import json, sqlite3 + from http.server import HTTPServer, BaseHTTPRequestHandler + from urllib.parse import urlparse, parse_qs + + DB_PATH = "${dbPath}" + RELAY_URL = "${relayUrl}" + PORT = ${toString port} + + class NIP05Handler(BaseHTTPRequestHandler): + def do_GET(self): + parsed = urlparse(self.path) + if parsed.path != "/.well-known/nostr.json": + self.send_response(404) + self.end_headers() + return + + params = parse_qs(parsed.query) + name_filter = params.get("name", [None])[0] + if name_filter: + name_filter = name_filter.strip().lower() + + try: + con = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True) + rows = con.execute(""" + SELECT pub_key, content FROM event + WHERE kind = 0 AND (hidden IS NULL OR hidden = 0) + GROUP BY pub_key + HAVING created_at = MAX(created_at) + """).fetchall() + con.close() + except Exception: + self.send_response(500) + self.end_headers() + return + + names = {} + relays = {} + for pub_key, content in rows: + try: + meta = json.loads(content) + name = meta.get("name", "").strip().lower() + if not name: + continue + if name_filter and name != name_filter: + continue + names[name] = pub_key + relays[pub_key] = [RELAY_URL] + except (json.JSONDecodeError, AttributeError): + continue + + body = json.dumps({"names": names, "relays": relays}).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, fmt, *args): + pass + + HTTPServer(("127.0.0.1", PORT), NIP05Handler).serve_forever() + ''; +in +{ + systemd.services.nip05 = { + description = "NIP-05 identifier server"; + after = [ "network.target" "nostr-rs-relay.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + User = "nostr-rs-relay"; + Group = "nostr-rs-relay"; + ExecStart = "${pkgs.python3}/bin/python3 ${script}"; + Restart = "on-failure"; + RestartSec = 5; + }; + }; + + environment.etc."caddy/Caddyfile.d/nip05.caddyfile".text = '' + ${lib.concatStringsSep ", " domains} { + handle /.well-known/nostr.json { + reverse_proxy localhost:${toString port} + } + handle { + header Content-Type "text/html; charset=utf-8" + respond `
otherwhere.app
` 200 + } + } + ''; +}