Upgrade fastapi-vue-setup 1.0.2
This commit is contained in:
+18
-43
@@ -5,23 +5,20 @@ import logging
|
||||
import os
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from fastapi_vue import server
|
||||
from fastapi_vue.hostutil import parse_endpoint
|
||||
from uvicorn import Config as UvicornConfig
|
||||
from uvicorn import Server
|
||||
from uvicorn import run as uvicorn_run
|
||||
|
||||
from paskia import db
|
||||
from paskia import globals as _globals
|
||||
from paskia.bootstrap import bootstrap_if_needed
|
||||
from paskia.config import PaskiaConfig
|
||||
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
|
||||
|
||||
DEFAULT_PORT = 4401
|
||||
DEVMODE = bool(os.getenv("PASKIA_FRONTEND_URL"))
|
||||
DEVMODE = os.getenv("PASKIA_DEV") == "1"
|
||||
|
||||
EPILOG = """\
|
||||
Example:
|
||||
@@ -87,6 +84,7 @@ def main():
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--listen",
|
||||
action="append",
|
||||
metavar="LISTEN",
|
||||
help=(
|
||||
"Endpoint to listen on (default: localhost:4401). "
|
||||
@@ -106,7 +104,7 @@ def main():
|
||||
args.listen = None
|
||||
|
||||
# Init db and load stored config
|
||||
asyncio.run(db_init(rp_id=args.rp_id))
|
||||
asyncio.run(db.init(rp_id=args.rp_id))
|
||||
stored_config = db.data().config
|
||||
|
||||
# Apply defaults from stored config
|
||||
@@ -119,8 +117,9 @@ def main():
|
||||
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)
|
||||
# Parse first endpoint for config display and site_url
|
||||
first_listen = args.listen[0] if isinstance(args.listen, list) else args.listen
|
||||
endpoints = parse_endpoint(first_listen, DEFAULT_PORT)
|
||||
|
||||
# Extract host/port/uds from first endpoint for config display and site_url
|
||||
ep = endpoints[0] if endpoints else {}
|
||||
@@ -208,20 +207,7 @@ def main():
|
||||
listen=args.listen,
|
||||
)
|
||||
|
||||
run_kwargs: dict = {
|
||||
"log_level": "warning", # Suppress startup messages; we use custom logging
|
||||
"access_log": False, # We use custom AccessLogMiddleware instead
|
||||
}
|
||||
|
||||
if DEVMODE:
|
||||
# Security: dev mode must run on localhost:4402 to prevent
|
||||
# accidental public exposure of the Vite dev server
|
||||
if host != "localhost" or port != 4402:
|
||||
raise SystemExit(f"Dev mode requires localhost:4402, got {host}:{port}")
|
||||
run_kwargs["reload"] = True
|
||||
run_kwargs["reload_dirs"] = ["paskia"]
|
||||
|
||||
async def async_main():
|
||||
async def startup():
|
||||
await _globals.init(
|
||||
rp_id=config.rp_id,
|
||||
rp_name=config.rp_name,
|
||||
@@ -235,28 +221,17 @@ def main():
|
||||
await db.update_config(cli_config)
|
||||
await flush()
|
||||
|
||||
if len(endpoints) > 1:
|
||||
async with asyncio.TaskGroup() as tg:
|
||||
for ep in endpoints:
|
||||
tg.create_task(
|
||||
Server(
|
||||
UvicornConfig(app="paskia.fastapi:app", **run_kwargs, **ep)
|
||||
).serve()
|
||||
)
|
||||
elif DEVMODE:
|
||||
# Use uvicorn.run for proper reload support (it handles subprocess spawning)
|
||||
ep = endpoints[0]
|
||||
uvicorn_run("paskia.fastapi:app", **run_kwargs, **ep)
|
||||
else:
|
||||
server = Server(
|
||||
UvicornConfig(app="paskia.fastapi:app", **run_kwargs, **endpoints[0])
|
||||
)
|
||||
await server.serve()
|
||||
asyncio.run(startup())
|
||||
|
||||
try:
|
||||
asyncio.run(async_main())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
dev = {"reload": True, "reload_dirs": ["paskia"]} if DEVMODE else {}
|
||||
server.run(
|
||||
"paskia.fastapi.mainapp:app",
|
||||
listen=args.listen,
|
||||
default_port=DEFAULT_PORT,
|
||||
log_level="warning",
|
||||
access_log=False,
|
||||
**dev,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+31
-42
@@ -1,28 +1,5 @@
|
||||
#!/usr/bin/env -S uv run
|
||||
"""Run Vite development server for frontend and Paskia backend with auto-reload.
|
||||
|
||||
This script is only available when running from the git repository source,
|
||||
not from the installed package. It starts both the Vite frontend dev server
|
||||
and the Paskia backend with auto-reload enabled.
|
||||
|
||||
Usage:
|
||||
uv run scripts/devserver.py [-l host:port] [options...]
|
||||
|
||||
The optional -l/--listen argument sets where the Vite frontend listens.
|
||||
All other options are forwarded to `paskia`.
|
||||
Backend always listens on localhost:4402.
|
||||
|
||||
Environment:
|
||||
PASKIA_FRONTEND_URL Set by this script for the backend to know where Vite is.
|
||||
PASKIA_BACKEND_URL Set by this script for Vite to know where to proxy API calls.
|
||||
PASKIA_SITE_URL User-facing URL for reset links (Caddy HTTPS or Vite HTTP).
|
||||
|
||||
Options:
|
||||
--caddy Run Caddy as HTTPS proxy on port 443 (requires sudo)
|
||||
--rp-id HOST Relying Party ID (used as hostname for Caddy)
|
||||
--origin URL Allowed origin(s), passed to backend
|
||||
--auth-host H Dedicated auth host, passed to backend
|
||||
"""
|
||||
"""Run Vite development server for Vue app and FastAPI backend with auto-reload."""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
@@ -45,8 +22,8 @@ from devutil import ( # noqa: E402
|
||||
setup_vite,
|
||||
)
|
||||
|
||||
DEFAULT_VITE_PORT = 4403 # overrides by CLI option
|
||||
BACKEND_PORT = 4402 # hardcoded, also in vite.config.ts
|
||||
DEFAULT_VITE_PORT = 4403
|
||||
DEFAULT_DEV_PORT = 4402
|
||||
CADDY_PORT = 443 # HTTPS port for Caddy proxy
|
||||
CADDY_HTTP_PORT = 80 # HTTP port for ACME challenges
|
||||
|
||||
@@ -54,17 +31,17 @@ CADDYFILE_SITE_BLOCK = """\
|
||||
SITE_ADDR {
|
||||
# WebSockets bypass directly to backend (workaround for bun proxy bug)
|
||||
handle /auth/ws/* {
|
||||
reverse_proxy localhost:BACKEND_PORT
|
||||
reverse_proxy BACKEND_ADDR
|
||||
}
|
||||
# Everything else goes to or via Vite
|
||||
handle {
|
||||
reverse_proxy localhost:VITE_PORT
|
||||
reverse_proxy VITE_ADDR
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def build_caddyfile(origins: list[str], vite_port: int) -> str:
|
||||
def build_caddyfile(origins: list[str], viteurl: str, backurl: str) -> str:
|
||||
"""Build a Caddyfile for the given origins."""
|
||||
caddyfile_parts = []
|
||||
for origin in origins:
|
||||
@@ -78,21 +55,23 @@ def build_caddyfile(origins: list[str], vite_port: int) -> str:
|
||||
site_addr = f"{scheme}://{host}:{port}"
|
||||
block = (
|
||||
CADDYFILE_SITE_BLOCK.replace("SITE_ADDR", site_addr)
|
||||
.replace("BACKEND_PORT", str(BACKEND_PORT))
|
||||
.replace("VITE_PORT", str(vite_port))
|
||||
.replace("BACKEND_ADDR", backurl)
|
||||
.replace("VITE_ADDR", viteurl)
|
||||
)
|
||||
caddyfile_parts.append(block)
|
||||
return "\n".join(caddyfile_parts)
|
||||
|
||||
|
||||
async def run_caddy(origins: list[str], vite_port: int) -> asyncio.subprocess.Process:
|
||||
async def run_caddy(
|
||||
origins: list[str], viteurl: str, backurl: str
|
||||
) -> asyncio.subprocess.Process:
|
||||
"""Start Caddy as HTTPS reverse proxy, wait for ready signal."""
|
||||
caddy_path = shutil.which("caddy")
|
||||
if not caddy_path:
|
||||
logger.warning("Caddy not found. Install it to use --caddy option.")
|
||||
raise SystemExit(1)
|
||||
|
||||
caddyfile = build_caddyfile(origins, vite_port)
|
||||
caddyfile = build_caddyfile(origins, viteurl, backurl)
|
||||
cmd = ["sudo", caddy_path, "run", "--config", "-", "--adapter", "caddyfile"]
|
||||
|
||||
logger.info(">>> sudo caddy @ %s", " ".join(origins))
|
||||
@@ -163,10 +142,7 @@ async def run_devserver(args: argparse.Namespace, remaining: list[str]) -> None:
|
||||
raise SystemExit(1)
|
||||
|
||||
viteurl, npm_install, vite = setup_vite(args.listen, DEFAULT_VITE_PORT)
|
||||
backurl, paskia = setup_cli("paskia", f"localhost:{BACKEND_PORT}", BACKEND_PORT)
|
||||
|
||||
# Extract vite port for Caddy config
|
||||
vite_port = int(viteurl.rsplit(":", 1)[1])
|
||||
backurl, paskia = setup_cli("paskia", args.backend, DEFAULT_DEV_PORT)
|
||||
|
||||
# Build paskia command with options
|
||||
paskia.extend(["--rp-id", args.rp_id])
|
||||
@@ -197,16 +173,17 @@ async def run_devserver(args: argparse.Namespace, remaining: list[str]) -> None:
|
||||
caddy_origins = [x for x in caddy_origins if not (x in seen or seen.add(x))]
|
||||
|
||||
# Set environment for subprocesses
|
||||
os.environ["PASKIA_FRONTEND_URL"] = viteurl
|
||||
os.environ["PASKIA_VITE_URL"] = viteurl
|
||||
os.environ["PASKIA_BACKEND_URL"] = backurl
|
||||
os.environ["PASKIA_SITE_URL"] = caddy_origins[0] if args.caddy else viteurl
|
||||
os.environ["PASKIA_DEV"] = "1"
|
||||
if args.auth_host:
|
||||
os.environ["PASKIA_AUTH_HOST"] = args.auth_host
|
||||
|
||||
async with ProcessGroup() as pg:
|
||||
# Start Caddy first if requested (needs to bind ports)
|
||||
if args.caddy:
|
||||
caddy_proc = await run_caddy(caddy_origins, vite_port)
|
||||
caddy_proc = await run_caddy(caddy_origins, viteurl, backurl)
|
||||
pg._procs.append(caddy_proc)
|
||||
pg._cmds[caddy_proc.pid] = "caddy"
|
||||
|
||||
@@ -222,9 +199,13 @@ def main():
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--listen",
|
||||
metavar="ENDPOINT",
|
||||
default=None,
|
||||
help="Vite frontend endpoint (default: localhost:4403)",
|
||||
metavar="addr",
|
||||
help=f"Vite (default: localhost:{DEFAULT_VITE_PORT})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--backend",
|
||||
metavar="addr",
|
||||
help=f"FastAPI (default: localhost:{DEFAULT_DEV_PORT})",
|
||||
)
|
||||
parser.add_argument("--caddy", action="store_true", help="Run Caddy as HTTPS proxy")
|
||||
parser.add_argument("--rp-id", default="localhost", help="Relying Party ID")
|
||||
@@ -238,5 +219,13 @@ def main():
|
||||
asyncio.run(run_devserver(args, remaining))
|
||||
|
||||
|
||||
HELP_EPILOG = """
|
||||
Other options are forwarded to paskia [args]
|
||||
|
||||
JS_RUNTIME environment variable can be used to select the JS runtime:
|
||||
npm, deno, bun, or full path to the runtime executable (node maps to npm).
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user