add command-line options for Uvicorn server configuration
This commit is contained in:
@@ -41,10 +41,21 @@ siteharbor
|
|||||||
|
|
||||||
Open `http://127.0.0.1:8787`.
|
Open `http://127.0.0.1:8787`.
|
||||||
|
|
||||||
|
The `siteharbor` command runs the FastAPI application with Uvicorn. Override the bind port and
|
||||||
|
start multiple Uvicorn worker processes when needed:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
siteharbor --port 9000 --workers 2
|
||||||
|
```
|
||||||
|
|
||||||
|
`--port` accepts ports from 1 through 65535 and overrides `SITEHARBOR_PORT`. `--workers` defaults
|
||||||
|
to 1 and controls Uvicorn processes, not the capture-worker count configured by
|
||||||
|
`SITEHARBOR_WORKER_CONCURRENCY`.
|
||||||
|
|
||||||
To use Uvicorn directly:
|
To use Uvicorn directly:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
python -m uvicorn app.main:app --host 127.0.0.1 --port 8787
|
python -m uvicorn app.main:app --host 127.0.0.1 --port 8787 --workers 2
|
||||||
```
|
```
|
||||||
|
|
||||||
The default bind address is loopback. Do not expose SiteHarbor directly to the public Internet without authentication, a network egress policy, rate limits, and operational controls.
|
The default bind address is loopback. Do not expose SiteHarbor directly to the public Internet without authentication, a network egress policy, rate limits, and operational controls.
|
||||||
@@ -56,9 +67,9 @@ All configuration uses environment variables prefixed with `SITEHARBOR_`.
|
|||||||
| Variable | Default | Purpose |
|
| Variable | Default | Purpose |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `SITEHARBOR_HOST` | `127.0.0.1` | Bind address used by `siteharbor`. |
|
| `SITEHARBOR_HOST` | `127.0.0.1` | Bind address used by `siteharbor`. |
|
||||||
| `SITEHARBOR_PORT` | `8787` | Bind port used by `siteharbor`. |
|
| `SITEHARBOR_PORT` | `8787` | Default bind port used by `siteharbor`; overridden by `--port`. |
|
||||||
| `SITEHARBOR_DATA_DIR` | `./data` | SQLite database, work directories, artifacts, and reports. |
|
| `SITEHARBOR_DATA_DIR` | `./data` | SQLite database, work directories, artifacts, and reports. |
|
||||||
| `SITEHARBOR_WORKER_CONCURRENCY` | `1` | Capture workers per application process. |
|
| `SITEHARBOR_WORKER_CONCURRENCY` | `1` | Capture workers per Uvicorn process; separate from `--workers`. |
|
||||||
| `SITEHARBOR_FETCH_CONCURRENCY` | `6` | Maximum parallel fetches a visitor may select for one capture. |
|
| `SITEHARBOR_FETCH_CONCURRENCY` | `6` | Maximum parallel fetches a visitor may select for one capture. |
|
||||||
| `SITEHARBOR_LEASE_SECONDS` | `120` | SQLite worker lease duration; values below 15 seconds are rejected. |
|
| `SITEHARBOR_LEASE_SECONDS` | `120` | SQLite worker lease duration; values below 15 seconds are rejected. |
|
||||||
| `SITEHARBOR_ALLOW_PRIVATE_NETWORKS` | `false` | Development-only override that permits loopback and private hosts. |
|
| `SITEHARBOR_ALLOW_PRIVATE_NETWORKS` | `false` | Development-only override that permits loopback and private hosts. |
|
||||||
|
|||||||
+46
-2
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
@@ -387,8 +388,51 @@ def _safe_data_file(path_value: str, root: Path) -> Path | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def run() -> None:
|
def _positive_integer(value: str) -> int:
|
||||||
uvicorn.run("app.main:app", host=settings.host, port=settings.port, reload=False)
|
try:
|
||||||
|
number = int(value)
|
||||||
|
except ValueError as error:
|
||||||
|
raise argparse.ArgumentTypeError("must be a whole number") from error
|
||||||
|
if number < 1:
|
||||||
|
raise argparse.ArgumentTypeError("must be at least 1")
|
||||||
|
return number
|
||||||
|
|
||||||
|
|
||||||
|
def _port_number(value: str) -> int:
|
||||||
|
port = _positive_integer(value)
|
||||||
|
if port > 65_535:
|
||||||
|
raise argparse.ArgumentTypeError("must be between 1 and 65535")
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_server_arguments(argv: list[str] | None = None) -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Run the SiteHarbor FastAPI server with Uvicorn.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--port",
|
||||||
|
type=_port_number,
|
||||||
|
default=settings.port,
|
||||||
|
metavar="PORT",
|
||||||
|
help=f"bind port (default: SITEHARBOR_PORT or {settings.port})",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--workers",
|
||||||
|
type=_positive_integer,
|
||||||
|
default=1,
|
||||||
|
metavar="WORKERS",
|
||||||
|
help="number of Uvicorn worker processes (default: 1)",
|
||||||
|
)
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def run(argv: list[str] | None = None) -> None:
|
||||||
|
arguments = _parse_server_arguments(argv)
|
||||||
|
uvicorn.run(
|
||||||
|
"app.main:app",
|
||||||
|
host=settings.host,
|
||||||
|
port=arguments.port,
|
||||||
|
workers=arguments.workers,
|
||||||
|
reload=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -65,3 +65,26 @@ def test_capture_routes_are_scoped_to_the_browser_session(tmp_path, monkeypatch)
|
|||||||
stats = client.get("/api/stats")
|
stats = client.get("/api/stats")
|
||||||
assert stats.status_code == 200
|
assert stats.status_code == 200
|
||||||
assert stats.json()["active_crawls"] == 1
|
assert stats.json()["active_crawls"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_forwards_server_options_to_uvicorn(monkeypatch) -> None:
|
||||||
|
calls: list[tuple[tuple[object, ...], dict[str, object]]] = []
|
||||||
|
|
||||||
|
def fake_run(*args: object, **kwargs: object) -> None:
|
||||||
|
calls.append((args, kwargs))
|
||||||
|
|
||||||
|
monkeypatch.setattr(main.uvicorn, "run", fake_run)
|
||||||
|
|
||||||
|
main.run(["--port", "9123", "--workers", "2"])
|
||||||
|
|
||||||
|
assert calls == [
|
||||||
|
(
|
||||||
|
("app.main:app",),
|
||||||
|
{
|
||||||
|
"host": settings.host,
|
||||||
|
"port": 9123,
|
||||||
|
"workers": 2,
|
||||||
|
"reload": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user