From abdca4f6484aef6cffbbe5652611230e5d3e4ae7 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Mon, 9 Feb 2026 18:33:29 +0000 Subject: [PATCH] Make config part of bootstrap. --- paskia/bootstrap.py | 15 +++++++++++---- paskia/db/logging.py | 10 ++++++++-- paskia/db/operations.py | 7 +++++++ paskia/fastapi/__main__.py | 26 +++++++++++++------------- 4 files changed, 39 insertions(+), 19 deletions(-) diff --git a/paskia/bootstrap.py b/paskia/bootstrap.py index bc22699..10caa2d 100644 --- a/paskia/bootstrap.py +++ b/paskia/bootstrap.py @@ -10,6 +10,7 @@ import asyncio import logging from paskia import authsession, db, globals +from paskia.db.structs import Config from paskia.util import hostutil logger = logging.getLogger(__name__) @@ -30,15 +31,18 @@ def _log_reset_link(passphrase: str, message: str | None = None) -> str: return reset_link -async def bootstrap_system() -> None: +async def bootstrap_system(config: Config | None = None) -> None: """ Bootstrap the entire system with default data. Uses db.bootstrap() which performs all operations in a single transaction. The transaction log will show a single "bootstrap" action with all changes. + + Args: + config: Configuration to store (rp_id, rp_name, origins, etc.) """ # Call the single-transaction bootstrap function - reset_passphrase = db.bootstrap() + reset_passphrase = db.bootstrap(config=config) # Log the reset link (this is separate from the transaction log) _log_reset_link(reset_passphrase, "✅ Bootstrap completed!") @@ -89,10 +93,13 @@ async def check_admin_credentials() -> bool: return False -async def bootstrap_if_needed() -> bool: +async def bootstrap_if_needed(config: Config | None = None) -> bool: """ Check if system needs bootstrapping and perform it if necessary. + Args: + config: Configuration to store during bootstrap (rp_id, rp_name, origins, etc.) + Returns: bool: True if bootstrapping was performed, False if system was already set up """ @@ -105,7 +112,7 @@ async def bootstrap_if_needed() -> bool: # No admin permission found, need to bootstrap # Bootstrap creates the admin user AND the reset link, so no need to check credentials after - await bootstrap_system() + await bootstrap_system(config=config) return True diff --git a/paskia/db/logging.py b/paskia/db/logging.py index 4b84bcc..63aa0fb 100644 --- a/paskia/db/logging.py +++ b/paskia/db/logging.py @@ -109,13 +109,19 @@ class UuidResolver: return role_data["display_name"] # Check permissions - if "permissions" in self._previous and uuid_str in self._previous["permissions"]: + if ( + "permissions" in self._previous + and uuid_str in self._previous["permissions"] + ): perm_data = self._previous["permissions"][uuid_str] if isinstance(perm_data, dict) and "display_name" in perm_data: return perm_data["display_name"] # Check credentials - look up user name - if "credentials" in self._previous and uuid_str in self._previous["credentials"]: + if ( + "credentials" in self._previous + and uuid_str in self._previous["credentials"] + ): cred_data = self._previous["credentials"][uuid_str] if isinstance(cred_data, dict) and "user" in cred_data: user_uuid = cred_data["user"] diff --git a/paskia/db/operations.py b/paskia/db/operations.py index 22c31e8..6928489 100644 --- a/paskia/db/operations.py +++ b/paskia/db/operations.py @@ -723,6 +723,7 @@ def bootstrap( admin_name: str = "Admin", reset_passphrase: str | None = None, reset_expiry: datetime | None = None, + config: Config | None = None, ) -> str: """Bootstrap the entire system in a single transaction. @@ -732,6 +733,7 @@ def bootstrap( - Organization with Administration role - Admin user with Administration role - Reset token for admin registration + - Config (if provided) This is the only way to create a new database file. All data is created atomically - if any step fails, nothing is written. @@ -741,6 +743,7 @@ def bootstrap( admin_name: Display name for the admin user (default: "Admin") reset_passphrase: Passphrase for the reset token (generated if not provided) reset_expiry: Expiry datetime for the reset token (default: 14 days) + config: Configuration to store (rp_id, rp_name, origins, etc.) Returns: The reset passphrase for admin registration. @@ -821,6 +824,10 @@ def bootstrap( ) _db.reset_tokens[reset_token.key] = reset_token + # Set config if provided + if config is not None: + _db.config = config + return reset_passphrase diff --git a/paskia/fastapi/__main__.py b/paskia/fastapi/__main__.py index 1e13cef..b1508be 100644 --- a/paskia/fastapi/__main__.py +++ b/paskia/fastapi/__main__.py @@ -199,16 +199,14 @@ def main(): startupbox.print_startup_config(config) - # Build config to save (will be saved after bootstrap in async_main) - save_config = None - if args.save: - save_config = Config( - rp_id=args.rp_id, - rp_name=args.rp_name, - origins=args.origins, - auth_host=args.auth_host, - listen=args.listen, - ) + # Build config to save (for bootstrap or explicit --save) + cli_config = Config( + rp_id=args.rp_id, + rp_name=args.rp_name, + origins=args.origins, + auth_host=args.auth_host, + listen=args.listen, + ) run_kwargs: dict = { "log_level": "warning", # Suppress startup messages; we use custom logging @@ -230,9 +228,11 @@ def main(): origins=config.origins, bootstrap=False, ) - await bootstrap_if_needed() - if save_config is not None: - await set_config(save_config) + # Pass config to bootstrap - it will be saved within the bootstrap transaction + await bootstrap_if_needed(config=cli_config) + # Also save config if --save was explicitly used (even without bootstrap) + if args.save: + await set_config(cli_config) await flush() if len(endpoints) > 1: