diff --git a/paskia/db/operations.py b/paskia/db/operations.py index 539578c..2046cdd 100644 --- a/paskia/db/operations.py +++ b/paskia/db/operations.py @@ -821,3 +821,19 @@ def bootstrap( _db.reset_tokens[reset_token.key] = reset_token return reset_passphrase + + +# ------------------------------------------------------------------------- +# Config operations +# ------------------------------------------------------------------------- + + +def get_config() -> Config: + """Get the stored configuration.""" + return _db.config + + +async def set_config(config: Config) -> None: + """Update the stored configuration.""" + async with _db.transaction("update_config"): + _db.config = config diff --git a/paskia/db/structs.py b/paskia/db/structs.py index 591f47d..6c90dfd 100644 --- a/paskia/db/structs.py +++ b/paskia/db/structs.py @@ -397,6 +397,16 @@ class SessionContext(msgspec.Struct): permissions: list[Permission] = [] +class Config(msgspec.Struct, dict=True, omit_defaults=True): + """Stored configuration for the instance.""" + + rp_id: str | None = None + rp_name: str | None = None + origins: list[str] | None = None + auth_host: str | None = None + listen: str | None = None + + # ------------------------------------------------------------------------- # Database storage structure # ------------------------------------------------------------------------- @@ -412,6 +422,7 @@ class DB(msgspec.Struct, dict=True, omit_defaults=False): credentials: dict[UUID, Credential] = {} sessions: dict[str, Session] = {} reset_tokens: dict[bytes, ResetToken] = {} + config: Config = Config() def __post_init__(self): # Store reference for persistence (not serialized) diff --git a/paskia/fastapi/__main__.py b/paskia/fastapi/__main__.py index f950428..d301527 100644 --- a/paskia/fastapi/__main__.py +++ b/paskia/fastapi/__main__.py @@ -12,7 +12,10 @@ from uvicorn import run as uvicorn_run from paskia import globals as _globals from paskia.bootstrap import bootstrap_if_needed from paskia.config import PaskiaConfig +from paskia.db import get_config, set_config +from paskia.db import init as db_init from paskia.db.background import flush +from paskia.db.structs import Config from paskia.util import startupbox from paskia.util.hostutil import normalize_origin @@ -62,6 +65,11 @@ def add_common_options(p: argparse.ArgumentParser) -> None: "--auth-host", help=("Dedicated authentication site (optionally with scheme/port)"), ) + p.add_argument( + "--save", + action="store_true", + help="Save the CLI options to database for future runs.", + ) def main(): @@ -88,6 +96,28 @@ def main(): args = parser.parse_args() + # Handle clearing options + if getattr(args, "auth_host", None) == "": + args.auth_host = None + if getattr(args, "rp_name", None) == "": + args.rp_name = None + if getattr(args, "listen", None) == "": + args.listen = None + + # Init db and load stored config + asyncio.run(db_init(rp_id=args.rp_id)) + stored_config = get_config() + + # Apply defaults from stored config + if args.rp_name is None and stored_config.rp_name is not None: + args.rp_name = stored_config.rp_name + if args.origins is None and stored_config.origins is not None: + args.origins = stored_config.origins + if args.auth_host is None and stored_config.auth_host is not None: + args.auth_host = stored_config.auth_host + if args.listen is None and stored_config.listen is not None: + args.listen = stored_config.listen + # Parse endpoint using fastapi_vue.hostutil endpoints = parse_endpoint(args.listen, DEFAULT_PORT) @@ -168,6 +198,16 @@ def main(): startupbox.print_startup_config(config) + if args.save: + new_config = Config( + rp_id=args.rp_id, + rp_name=args.rp_name, + origins=args.origins, + auth_host=args.auth_host, + listen=args.listen, + ) + asyncio.run(set_config(new_config)) + run_kwargs: dict = { "log_level": "warning", # Suppress startup messages; we use custom logging "access_log": False, # We use custom AccessLogMiddleware instead