Initial commit: Canteen GPS Map - interactive map combining booking + photo EXIF GPS data
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
This commit is contained in:
+137
@@ -0,0 +1,137 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user