Pass CLI config to the app as JSON in PAGERITE_CONFIG

This commit is contained in:
2026-09-03 16:14:59 +00:00
parent 029bfe105e
commit 9d70f17587
6 changed files with 54 additions and 22 deletions
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+28
View File
@@ -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
View File
@@ -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
View File
@@ -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()),
]