Add API server and Caddy config
This commit is contained in:
parent
5ea3feb015
commit
210cf6b73a
2 changed files with 116 additions and 0 deletions
95
api_server.py
Normal file
95
api_server.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Nerdhalla API server — serves POI data and server status."""
|
||||
import json
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
DB_PATH = "/tmp/valheim_poi.db"
|
||||
NERDCADE_SSH = "aelith@100.86.206.39"
|
||||
NERDCADE_PORT = 46129
|
||||
|
||||
class APIHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
parsed = urlparse(self.path)
|
||||
path = parsed.path
|
||||
params = parse_qs(parsed.query)
|
||||
|
||||
if path == "/api/status":
|
||||
self._handle_status()
|
||||
elif path == "/api/pois":
|
||||
self._handle_pois(params)
|
||||
else:
|
||||
self.send_error(404, "Not found")
|
||||
|
||||
def _handle_status(self):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["ssh", "-p", str(NERDCADE_PORT), "-o", "ConnectTimeout=5",
|
||||
"-o", "StrictHostKeyChecking=no", NERDCADE_SSH,
|
||||
"systemctl --user is-active amp* 2>/dev/null || echo 'inactive'"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
online = "active" in result.stdout.lower()
|
||||
data = {"online": online, "players": None}
|
||||
except Exception:
|
||||
data = {"online": False, "players": None}
|
||||
|
||||
self._json_response(data)
|
||||
|
||||
def _handle_pois(self, params):
|
||||
try:
|
||||
limit = min(int(params.get("limit", [500])[0]), 11309)
|
||||
offset = int(params.get("offset", [0])[0])
|
||||
biome = params.get("biome", [None])[0]
|
||||
search = params.get("search", [None])[0]
|
||||
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cur = conn.cursor()
|
||||
|
||||
where_clauses = []
|
||||
bindings = []
|
||||
if biome:
|
||||
where_clauses.append("category = ?")
|
||||
bindings.append(biome)
|
||||
if search:
|
||||
where_clauses.append("prefab_name LIKE ?")
|
||||
bindings.append(f"%{search}%")
|
||||
|
||||
where = ""
|
||||
if where_clauses:
|
||||
where = "WHERE " + " AND ".join(where_clauses)
|
||||
|
||||
cur.execute(f"SELECT pos_x, pos_y, pos_z, prefab_name, category FROM locations {where} ORDER BY prefab_name LIMIT ? OFFSET ?", bindings + [limit, offset])
|
||||
rows = [{
|
||||
"x": r["pos_x"],
|
||||
"y": r["pos_z"], # Valheim uses Z as north-south
|
||||
"prefab": r["prefab_name"],
|
||||
"category": r["category"]
|
||||
} for r in cur.fetchall()]
|
||||
conn.close()
|
||||
|
||||
self._json_response(rows)
|
||||
except Exception as e:
|
||||
self._json_response({"error": str(e)}, status=500)
|
||||
|
||||
def _json_response(self, data, status=200):
|
||||
body = json.dumps(data).encode()
|
||||
self.send_response(status)
|
||||
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, format, *args):
|
||||
sys.stderr.write("[API] %s - %s\n" % (self.client_address[0], format % args))
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = 8081
|
||||
server = HTTPServer(("127.0.0.1", port), APIHandler)
|
||||
print(f"[Nerdhalla API] Listening on 127.0.0.1:{port}")
|
||||
server.serve_forever()
|
||||
21
nerdhalla.conf
Normal file
21
nerdhalla.conf
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
nerdhalla.nerdcade.cc {
|
||||
root * /var/www/nerdhalla
|
||||
file_server
|
||||
|
||||
# API reverse proxy
|
||||
handle_path /api/* {
|
||||
reverse_proxy 127.0.0.1:8081
|
||||
}
|
||||
|
||||
# Security headers
|
||||
header {
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-Frame-Options "DENY"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
}
|
||||
|
||||
# Logs
|
||||
log {
|
||||
output file /var/log/caddy/nerdhalla.log
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue