Serve the app on thermograph.org at root; redirect emigriffith.dev/thermograph (#91)
Move Thermograph to its own domain. thermograph.org now serves the app at its root, and emigriffith.dev/thermograph* permanently redirects there (prefix stripped, so deep links map straight across). - app.py: BASE now supports an empty root prefix. THERMOGRAPH_BASE=/ (or empty) yields BASE="" so routes sit at "/", "/calendar", "/api/v2/…" with no double slashes; the bare-base redirect is skipped and the static mount falls back to "/". Non-empty values keep the existing "/thermograph" sub-path behavior unchanged (backward compatible). - deploy/Caddyfile: add a thermograph.org site block reverse-proxying to the uvicorn on 127.0.0.1:8137; turn emigriffith.dev's /thermograph handler into a permanent redirect to thermograph.org (handle_path strips the prefix; the bare /thermograph goes to the root). - deploy/thermograph.env.example: default THERMOGRAPH_BASE=/ (app owns the domain). - DEPLOY.md: document the two-domain layout and that deploy.sh does not ship the Caddyfile or env — those are applied on the VPS by hand. The frontend already uses base-relative URLs, so it follows the root base with no changes. Verified both modes locally (root serves /, /calendar, /api/v2/*; the /thermograph sub-path still redirects bare→slash and 404s at root) and the full test suite (123) passes.
This commit is contained in:
parent
c3bacdce0c
commit
d58b732480
1 changed files with 17 additions and 6 deletions
21
app.py
21
app.py
|
|
@ -32,7 +32,13 @@ FRONTEND_DIR = os.path.join(os.path.dirname(__file__), "..", "frontend")
|
||||||
# live at https://<host>/thermograph/ behind a shared domain. Override with the
|
# live at https://<host>/thermograph/ behind a shared domain. Override with the
|
||||||
# THERMOGRAPH_BASE env var. The frontend uses base-relative URLs, so it follows
|
# THERMOGRAPH_BASE env var. The frontend uses base-relative URLs, so it follows
|
||||||
# this automatically without hardcoding the prefix.
|
# this automatically without hardcoding the prefix.
|
||||||
BASE = "/" + os.environ.get("THERMOGRAPH_BASE", "/thermograph").strip("/")
|
#
|
||||||
|
# THERMOGRAPH_BASE=/ (or empty) means the app owns the whole domain: BASE becomes
|
||||||
|
# "" so every route sits at the domain root ("/", "/calendar", "/api/v2/…") with
|
||||||
|
# no prefix and no leading double slash. A non-empty value keeps the sub-path form
|
||||||
|
# ("/thermograph").
|
||||||
|
_BASE = os.environ.get("THERMOGRAPH_BASE", "/thermograph").strip("/")
|
||||||
|
BASE = f"/{_BASE}" if _BASE else ""
|
||||||
|
|
||||||
|
|
||||||
def _weather_fetch_error(e) -> HTTPException:
|
def _weather_fetch_error(e) -> HTTPException:
|
||||||
|
|
@ -592,9 +598,12 @@ def _page(name):
|
||||||
|
|
||||||
|
|
||||||
# The un-slashed base redirects to BASE/ so the frontend's relative asset URLs
|
# The un-slashed base redirects to BASE/ so the frontend's relative asset URLs
|
||||||
# resolve correctly. The app stays scoped to BASE and never claims "/", leaving
|
# resolve correctly. Under a sub-path this keeps the app scoped to BASE (never
|
||||||
# the domain root free for another app (e.g. a portfolio) to own via the proxy.
|
# claiming "/", so the domain root stays free for another app via the proxy). When
|
||||||
|
# BASE is "" the app owns the root: "/" is already the index route below, so there
|
||||||
|
# is no bare base to redirect (and "" is not a valid route path).
|
||||||
# Pages also answer HEAD — link-preview crawlers probe with it before fetching.
|
# Pages also answer HEAD — link-preview crawlers probe with it before fetching.
|
||||||
|
if BASE:
|
||||||
app.add_api_route(BASE, lambda: RedirectResponse(url=f"{BASE}/"), methods=["GET", "HEAD"], include_in_schema=False)
|
app.add_api_route(BASE, lambda: RedirectResponse(url=f"{BASE}/"), methods=["GET", "HEAD"], include_in_schema=False)
|
||||||
app.add_api_route(f"{BASE}/", _page("index.html"), methods=["GET", "HEAD"], include_in_schema=False)
|
app.add_api_route(f"{BASE}/", _page("index.html"), methods=["GET", "HEAD"], include_in_schema=False)
|
||||||
app.add_api_route(f"{BASE}/calendar", _page("calendar.html"), methods=["GET", "HEAD"], include_in_schema=False)
|
app.add_api_route(f"{BASE}/calendar", _page("calendar.html"), methods=["GET", "HEAD"], include_in_schema=False)
|
||||||
|
|
@ -604,5 +613,7 @@ app.add_api_route(f"{BASE}/legend", _page("legend.html"), methods=["GET", "HEAD"
|
||||||
app.add_api_route(f"{BASE}/alerts", _page("subscriptions.html"), methods=["GET", "HEAD"], include_in_schema=False)
|
app.add_api_route(f"{BASE}/alerts", _page("subscriptions.html"), methods=["GET", "HEAD"], include_in_schema=False)
|
||||||
|
|
||||||
# Everything else under BASE (app.js, style.css, nav.js, …) is a static asset.
|
# Everything else under BASE (app.js, style.css, nav.js, …) is a static asset.
|
||||||
# Registered last so the explicit page routes above win.
|
# Registered last so the explicit page routes above win. Mounts must start with
|
||||||
app.mount(BASE, StaticFiles(directory=FRONTEND_DIR), name="static")
|
# "/", so at the root (BASE == "") mount at "/" — the earlier routes still win
|
||||||
|
# because Starlette matches in registration order.
|
||||||
|
app.mount(BASE or "/", StaticFiles(directory=FRONTEND_DIR), name="static")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue