Pass CLI config to the app as JSON in PAGERITE_CONFIG
This commit is contained in:
@@ -26,6 +26,7 @@ Vite builds ES-module `.js` outputs; in dev the backend links them as `<script t
|
||||
|
||||
All site data lives under `<hostname>/` in the cwd — `content.kantadb`,
|
||||
`analytics.json` and `files/` — where `<hostname>` is the CLI's first
|
||||
positional argument (default `localhost`, exported as `PAGERITE_HOSTNAME`;
|
||||
positional argument (default `localhost`, passed to the app as JSON in
|
||||
`PAGERITE_CONFIG`, see `pagerite/config.py`;
|
||||
`PAGERITE_DB`/`PAGERITE_ANALYTICS`/`PAGERITE_FILES` override individual
|
||||
paths). gitignored. Do not delete it without asking.
|
||||
|
||||
+9
-14
@@ -4,8 +4,10 @@ import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import msgspec
|
||||
from fastapi_vue import server
|
||||
from fastapi_vue.hostutil import parse_endpoints
|
||||
|
||||
from pagerite.config import Config
|
||||
|
||||
DEFAULT_PORT = 8100
|
||||
DEVMODE = os.getenv("PAGERITE_DEV") == "1"
|
||||
@@ -36,19 +38,12 @@ def main() -> None:
|
||||
help="Download/update the DB-IP city lite database before starting.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
# Export the hostname before pagerite.app is imported: it derives the
|
||||
# data directory and public origin from it at import time.
|
||||
os.environ["PAGERITE_HOSTNAME"] = args.hostname
|
||||
# And the listen port: the app prints the translator WS URL at startup,
|
||||
# which for localhost includes the actual port.
|
||||
for endpoint in parse_endpoints(args.listen, DEFAULT_PORT):
|
||||
if "port" in endpoint:
|
||||
os.environ["PAGERITE_PORT"] = str(endpoint["port"])
|
||||
break
|
||||
# --dbip: the app lifespan downloads/updates the DB-IP database, where
|
||||
# logging is already set up.
|
||||
if args.dbip:
|
||||
os.environ["PAGERITE_DBIP"] = "1"
|
||||
# Hand configuration to the app as JSON in PAGERITE_CONFIG; it must be
|
||||
# set before pagerite.app is imported, as state.py reads it at import
|
||||
# time (data directory, public origin).
|
||||
os.environ["PAGERITE_CONFIG"] = msgspec.json.encode(
|
||||
Config(hostname=args.hostname, dbip=args.dbip)
|
||||
).decode()
|
||||
run_args: dict = {}
|
||||
if args.hostname != "localhost":
|
||||
# A public site sits behind TLS on its hostname; show that URL in the
|
||||
|
||||
+2
-3
@@ -30,7 +30,6 @@ walking the tree (``resolve``), moves are slot detach/attach
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
@@ -39,7 +38,7 @@ from fastapi.responses import Response
|
||||
from pagerite import api, files, pages, tracking
|
||||
from pagerite.__main__ import DEVMODE
|
||||
from pagerite.files import file_store
|
||||
from pagerite.state import analytics_store, frontend, kanta
|
||||
from pagerite.state import analytics_store, config, frontend, kanta
|
||||
from collections.abc import AsyncGenerator
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -53,7 +52,7 @@ async def lifespan(_app: FastAPI) -> AsyncGenerator:
|
||||
# --dbip: update the DB-IP database first, then decompress/open the
|
||||
# MMDB once. Lookups are then read-only and safe to run in
|
||||
# background ``to_thread`` workers.
|
||||
if os.environ.get("PAGERITE_DBIP") == "1":
|
||||
if config.dbip:
|
||||
await asyncio.to_thread(tracking._download_dbip)
|
||||
await asyncio.to_thread(tracking._geoip._load)
|
||||
analytics_store.subscribe(tracking._schedule_analytics_broadcast)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""CLI → app configuration, passed as JSON in the ``PAGERITE_CONFIG`` env var.
|
||||
|
||||
Kept dependency-free (msgspec only) so ``__main__`` can build and serialize
|
||||
the config before any app module is imported, and the app side parses the
|
||||
same struct back. Import-time safe: nothing here reads the environment
|
||||
until ``load()`` is called.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import msgspec
|
||||
|
||||
|
||||
class Config(msgspec.Struct):
|
||||
"""Configuration passed from the CLI entry point to the app."""
|
||||
|
||||
#: Public hostname of the site; names the per-site data directory
|
||||
#: ``<hostname>/{content.kantadb, analytics.json, files}`` under the cwd.
|
||||
hostname: str = "localhost"
|
||||
#: Download/update the DB-IP city lite database at startup (--dbip).
|
||||
dbip: bool = False
|
||||
|
||||
|
||||
def load() -> Config:
|
||||
"""Parse ``PAGERITE_CONFIG``, or the defaults when unset."""
|
||||
if raw := os.getenv("PAGERITE_CONFIG"):
|
||||
return msgspec.json.decode(raw.encode(), type=Config)
|
||||
return Config()
|
||||
+6
-2
@@ -29,6 +29,7 @@ from zstandard import ZstdCompressor
|
||||
from pagerite import analytics, i18n, seed, translate, views
|
||||
from pagerite.__main__ import DEVMODE
|
||||
from pagerite.chunks import store_chunks
|
||||
from pagerite.config import load
|
||||
from pagerite.data import (
|
||||
Data,
|
||||
Node,
|
||||
@@ -39,10 +40,13 @@ from pagerite.data import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#: The CLI-passed configuration (PAGERITE_CONFIG) for this process.
|
||||
config = load()
|
||||
|
||||
# Site identity: the hostname comes from the CLI (first positional argument,
|
||||
# exported as PAGERITE_HOSTNAME) and names the per-site data directory
|
||||
# passed in PAGERITE_CONFIG) and names the per-site data directory
|
||||
# ``<hostname>/{content.kantadb, analytics.json, files}`` under the cwd.
|
||||
HOSTNAME = os.getenv("PAGERITE_HOSTNAME", "localhost")
|
||||
HOSTNAME = config.hostname
|
||||
SITE_DIR = Path(HOSTNAME)
|
||||
#: Public origin of the site, used for absolute social/canonical/sitemap
|
||||
#: URLs. Localhost serves varying ports, so it falls back to the request's
|
||||
|
||||
+7
-2
@@ -25,6 +25,7 @@ from html5tagger import HTML, Document, E, Template
|
||||
from platformdirs import site_data_dir, user_data_path
|
||||
|
||||
from pagerite import i18n
|
||||
from pagerite.config import load as _load_config
|
||||
from pagerite.data import Data, Node, node_markdown, prettify, resolve, sorted_nodes
|
||||
from pagerite.i18n import Translation
|
||||
from pagerite.markdown import render
|
||||
@@ -45,6 +46,10 @@ def _data_roots() -> list[Path]:
|
||||
return [Path(r) for r in roots]
|
||||
|
||||
|
||||
#: The CLI-passed configuration (PAGERITE_CONFIG) for this process.
|
||||
config = _load_config()
|
||||
|
||||
|
||||
def _theme_dirs() -> list[Path]:
|
||||
"""Theme search roots, most specific first; first match wins per file.
|
||||
|
||||
@@ -57,7 +62,7 @@ def _theme_dirs() -> list[Path]:
|
||||
"""
|
||||
return [
|
||||
Path("themes"),
|
||||
Path(os.getenv("PAGERITE_HOSTNAME", "localhost")) / "themes",
|
||||
Path(config.hostname) / "themes",
|
||||
*(root / "themes" for root in _data_roots()),
|
||||
Path(__file__).parent / "themes",
|
||||
]
|
||||
@@ -73,7 +78,7 @@ THEME_DIRS = _theme_dirs()
|
||||
# built-in --font-* variables.
|
||||
FONT_DIRS = [
|
||||
Path("fonts"),
|
||||
Path(os.getenv("PAGERITE_HOSTNAME", "localhost")) / "fonts",
|
||||
Path(config.hostname) / "fonts",
|
||||
*(root / "fonts" for root in _data_roots()),
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user