bfcf876d42
Features: - Dark-themed interactive Leaflet map with marker clustering - Combined GPS sources: booking (technician check-in) + photo EXIF + account locations - Collapsible filter bar with search, source toggles, and source type dropdown - Info popups on click showing machine details, work orders, serials, photos - Account location layer toggle (3,487 accounts) - Export filtered data as JSON - Mobile-responsive (40vh map on mobile, filter collapse) - FastAPI backend with in-memory caching - Systemd service for auto-start - NPMPlus reverse proxy at gps.ourpad.casa Stats: 94 unique machines (73 booking, 21 photo), 231 total GPS points, 3,487 account locations
138 lines
4.1 KiB
Python
138 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Set up NPMPlus reverse proxy for Canteen GPS Map."""
|
|
import json
|
|
import urllib.request
|
|
import ssl
|
|
import subprocess
|
|
|
|
NPM_BASE = "https://192.168.0.115:81"
|
|
DOMAIN = "gps.ourpad.casa"
|
|
BACKEND_HOST = "192.168.0.127"
|
|
BACKEND_PORT = 8913
|
|
BACKEND_SCHEME = "http"
|
|
|
|
# Get token
|
|
with open("/tmp/npmplus_token.json") as f:
|
|
token_data = json.load(f)
|
|
TOKEN = token_data["token"]
|
|
ctx = ssl._create_unverified_context()
|
|
|
|
|
|
def api(method, path, data=None):
|
|
url = f"{NPM_BASE}{path}"
|
|
body = json.dumps(data).encode() if data else None
|
|
req = urllib.request.Request(url, data=body, method=method)
|
|
req.add_header("Authorization", f"Bearer {TOKEN}")
|
|
req.add_header("Content-Type", "application/json")
|
|
try:
|
|
resp = urllib.request.urlopen(req, context=ctx, timeout=15)
|
|
return json.loads(resp.read().decode())
|
|
except urllib.error.HTTPError as e:
|
|
err = e.read().decode()
|
|
print(f"HTTP {e.code}: {err}")
|
|
return None
|
|
|
|
|
|
# Get CF token from pass
|
|
cf_token = subprocess.check_output(
|
|
["pass", "npmplus/cloudflare-api-token"], text=True
|
|
).strip()
|
|
|
|
# 1. Check existing certificates
|
|
print("=== Checking existing certificates ===")
|
|
certs = api("GET", "/api/nginx/certificates")
|
|
existing_cert_id = None
|
|
if certs:
|
|
for c in certs:
|
|
domains = c.get("domain_names", [])
|
|
if DOMAIN in domains:
|
|
existing_cert_id = c["id"]
|
|
print(f"Found existing cert for {DOMAIN}: ID={existing_cert_id}, expires={c.get('expires_on','?')}")
|
|
break
|
|
|
|
if not existing_cert_id:
|
|
# 2. Create Let's Encrypt cert via DNS challenge
|
|
print(f"\n=== Creating Let's Encrypt cert for {DOMAIN} ===")
|
|
cert_result = api(
|
|
"POST",
|
|
"/api/nginx/certificates",
|
|
{
|
|
"domain_names": [DOMAIN],
|
|
"provider": "letsencrypt",
|
|
"meta": {
|
|
"letsencrypt_agree": True,
|
|
"dns_challenge": True,
|
|
"dns_provider": "cloudflare",
|
|
"dns_provider_credentials": f"dns_cloudflare_api_token={cf_token}",
|
|
},
|
|
},
|
|
)
|
|
if cert_result and "id" in cert_result:
|
|
existing_cert_id = cert_result["id"]
|
|
print(f"Created cert: ID={existing_cert_id}")
|
|
else:
|
|
print("Failed to create cert:", cert_result)
|
|
exit(1)
|
|
|
|
# 3. Check existing proxy hosts
|
|
print(f"\n=== Checking existing proxy hosts for {DOMAIN} ===")
|
|
hosts = api("GET", "/api/nginx/proxy-hosts")
|
|
existing_host_id = None
|
|
if hosts:
|
|
for h in hosts:
|
|
if DOMAIN in h.get("domain_names", []):
|
|
existing_host_id = h["id"]
|
|
print(f"Found existing host: ID={existing_host_id}")
|
|
break
|
|
|
|
if existing_host_id:
|
|
# Update existing
|
|
print(f"\n=== Updating proxy host {existing_host_id} ===")
|
|
result = api(
|
|
"PUT",
|
|
f"/api/nginx/proxy-hosts/{existing_host_id}",
|
|
{
|
|
"domain_names": [DOMAIN],
|
|
"forward_host": BACKEND_HOST,
|
|
"forward_port": BACKEND_PORT,
|
|
"certificate_id": existing_cert_id,
|
|
"ssl_forced": True,
|
|
"forward_scheme": BACKEND_SCHEME,
|
|
"enabled": True,
|
|
"block_exploits": False,
|
|
"locations": [],
|
|
"advanced_config": "",
|
|
},
|
|
)
|
|
if result:
|
|
print("Updated successfully")
|
|
else:
|
|
print("Update failed")
|
|
else:
|
|
# Create new
|
|
print(f"\n=== Creating proxy host for {DOMAIN} ===")
|
|
result = api(
|
|
"POST",
|
|
"/api/nginx/proxy-hosts",
|
|
{
|
|
"domain_names": [DOMAIN],
|
|
"forward_host": BACKEND_HOST,
|
|
"forward_port": BACKEND_PORT,
|
|
"certificate_id": existing_cert_id,
|
|
"ssl_forced": True,
|
|
"forward_scheme": BACKEND_SCHEME,
|
|
"enabled": True,
|
|
"block_exploits": False,
|
|
"locations": [],
|
|
"advanced_config": "",
|
|
},
|
|
)
|
|
if result and "id" in result:
|
|
print(f"Created proxy host: ID={result['id']}")
|
|
else:
|
|
print("Create failed:", result)
|
|
|
|
print("\n=== Done ===")
|
|
print(f"Access: https://{DOMAIN}/ (via Tailscale)")
|
|
print(f"Tailscale DNS override: {DOMAIN} → NPMPlus Tailscale IP")
|