diff --git a/deploy/solargod.service b/deploy/solargod.service index 0eab574..1401710 100644 --- a/deploy/solargod.service +++ b/deploy/solargod.service @@ -1,5 +1,7 @@ -# HERALD — systemd unit for SOLARGOD. serve.py binds 127.0.0.1 only; nginx -# reverse-proxies the public domain to it (mirror of the godstrument.pro shape). +# HERALD — systemd unit for SOLARGOD on the botchat VPS. serve.py binds +# 127.0.0.1 only; the cloudflared tunnel's ingress rule for +# solargod.godstrument.pro forwards to it (same shape as godstrument.pro — +# no nginx in this topology; nginx-solargod.conf is the alternative shape). # Install: sudo cp deploy/solargod.service /etc/systemd/system/ && sudo systemctl enable --now solargod [Unit] Description=SOLARGOD orrery — serve.py (static + JPL Horizons/SBDB/CAD proxy + disk cache) @@ -7,9 +9,10 @@ After=network.target [Service] Type=simple -User=www-data -WorkingDirectory=/opt/solargod -ExecStart=/usr/bin/python3 /opt/solargod/serve.py 8147 +User=humanjing +WorkingDirectory=/home/humanjing/solargod +Environment=SOLARGOD_READONLY=1 +ExecStart=/usr/bin/python3 /home/humanjing/solargod/serve.py 8147 Restart=on-failure RestartSec=3 # serve.py writes its disk cache under WorkingDirectory/cache — keep it writable. diff --git a/serve.py b/serve.py index 3612098..a07cf9e 100644 --- a/serve.py +++ b/serve.py @@ -32,6 +32,9 @@ PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8147 ROOT = Path(__file__).resolve().parent CACHE_DIR = ROOT / "cache" CACHE_TTL_SEC = 24 * 3600 +# Production (HERALD): SOLARGOD_READONLY=1 disables the dev-only /snap write +# endpoint — behind a public tunnel, the server must accept no writes at all. +READONLY = os.environ.get("SOLARGOD_READONLY") == "1" UPSTREAMS = { "horizons": "https://ssd.jpl.nasa.gov/api/horizons.api", @@ -182,7 +185,10 @@ class Handler(SimpleHTTPRequestHandler): def do_POST(self): # Dev-only screenshot sink: POST /snap?name= with base64 PNG (or a # data:image/png;base64,... URL) → docs/.png. Bound to 127.0.0.1; the - # name is sanitized so the write can never escape docs/. + # name is sanitized so the write can never escape docs/. Disabled entirely + # in production (SOLARGOD_READONLY=1) — a tunneled deploy must not write. + if READONLY: + return self.send_error(403, "read-only deployment") if not (self.path == "/snap" or self.path.startswith("/snap?")): return self.send_error(404, "not found") _, _, query = self.path.partition("?")