feat: add .env based configuration for API, Web, and Auth options

This commit is contained in:
2026-04-08 01:24:49 -04:00
parent 31e0c5151b
commit 32b06afef6
76 changed files with 600 additions and 64 deletions

View File

@@ -15,6 +15,13 @@ import typer
from rich.console import Console
from rich.table import Table
from decnet.env import (
DECNET_API_HOST,
DECNET_API_PORT,
DECNET_INGEST_LOG_FILE,
DECNET_WEB_HOST,
DECNET_WEB_PORT,
)
from decnet.archetypes import Archetype, all_archetypes, get_archetype
from decnet.config import (
DeckyConfig,
@@ -199,22 +206,24 @@ def _build_deckies_from_ini(
return deckies
@app.command()
def api(
port: int = typer.Option(8000, "--port", help="Port for the backend API"),
log_file: str = typer.Option("/var/log/decnet/decnet.log", "--log-file", help="Path to the DECNET log file to monitor"),
port: int = typer.Option(DECNET_API_PORT, "--port", help="Port for the backend API"),
host: str = typer.Option(DECNET_API_HOST, "--host", help="Host IP for the backend API"),
log_file: str = typer.Option(DECNET_INGEST_LOG_FILE, "--log-file", help="Path to the DECNET log file to monitor"),
) -> None:
"""Run the DECNET API and Web Dashboard in standalone mode."""
import subprocess
import sys
import os
console.print(f"[green]Starting DECNET API on port {port}...[/]")
console.print(f"[green]Starting DECNET API on {host}:{port}...[/]")
_env: dict[str, str] = os.environ.copy()
_env["DECNET_INGEST_LOG_FILE"] = str(log_file)
try:
subprocess.run(
[sys.executable, "-m", "uvicorn", "decnet.web.api:app", "--host", "0.0.0.0", "--port", str(port)],
[sys.executable, "-m", "uvicorn", "decnet.web.api:app", "--host", host, "--port", str(port)],
env=_env
)
except KeyboardInterrupt:
@@ -555,20 +564,21 @@ def list_archetypes() -> None:
@app.command(name="web")
def serve_web(
web_port: int = typer.Option(5173, "--web-port", help="Port to serve the DECNET Web Dashboard"),
web_port: int = typer.Option(DECNET_WEB_PORT, "--web-port", help="Port to serve the DECNET Web Dashboard"),
host: str = typer.Option(DECNET_WEB_HOST, "--host", help="Host IP to serve the Web Dashboard"),
) -> None:
"""Serve the DECNET Web Dashboard frontend."""
import http.server
import socketserver
from pathlib import Path
# Assuming decnet_web/dist is relative to the project root
dist_dir = Path(__file__).parent.parent / "decnet_web" / "dist"
if not dist_dir.exists():
console.print(f"[red]Frontend build not found at {dist_dir}. Make sure you run 'npm run build' inside 'decnet_web'.[/]")
raise typer.Exit(1)
class SPAHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
# Try to serve the requested file
@@ -577,12 +587,12 @@ def serve_web(
# If not found or is a directory, serve index.html (for React Router)
self.path = "/index.html"
return super().do_GET()
import os
os.chdir(dist_dir)
with socketserver.TCPServer(("", web_port), SPAHTTPRequestHandler) as httpd:
console.print(f"[green]Serving DECNET Web Dashboard on http://0.0.0.0:{web_port}[/]")
with socketserver.TCPServer((host, web_port), SPAHTTPRequestHandler) as httpd:
console.print(f"[green]Serving DECNET Web Dashboard on http://{host}:{web_port}[/]")
try:
httpd.serve_forever()
except KeyboardInterrupt:

22
decnet/env.py Normal file
View File

@@ -0,0 +1,22 @@
import os
from pathlib import Path
from dotenv import load_dotenv
# Calculate absolute path to the project root
_ROOT: Path = Path(__file__).parent.parent.absolute()
# Load .env.local first, then fallback to .env
load_dotenv(_ROOT / ".env.local")
load_dotenv(_ROOT / ".env")
# API Options
DECNET_API_HOST: str = os.environ.get("DECNET_API_HOST", "0.0.0.0")
DECNET_API_PORT: int = int(os.environ.get("DECNET_API_PORT", "8000"))
DECNET_JWT_SECRET: str = os.environ.get("DECNET_JWT_SECRET", "fallback-secret-key-change-me")
DECNET_INGEST_LOG_FILE: str | None = os.environ.get("DECNET_INGEST_LOG_FILE", "/var/log/decnet/decnet.log")
# Web Dashboard Options
DECNET_WEB_HOST: str = os.environ.get("DECNET_WEB_HOST", "0.0.0.0")
DECNET_WEB_PORT: int = int(os.environ.get("DECNET_WEB_PORT", "8080"))
DECNET_ADMIN_USER: str = os.environ.get("DECNET_ADMIN_USER", "admin")
DECNET_ADMIN_PASSWORD: str = os.environ.get("DECNET_ADMIN_PASSWORD", "admin")

View File

@@ -20,6 +20,7 @@ from decnet.web.auth import (
)
from decnet.web.sqlite_repository import SQLiteRepository
from decnet.web.ingester import log_ingestion_worker
from decnet.env import DECNET_ADMIN_USER, DECNET_ADMIN_PASSWORD
import asyncio
repo: SQLiteRepository = SQLiteRepository()
@@ -31,13 +32,13 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
global ingestion_task
await repo.initialize()
# Create default admin if no users exist
_admin_user: Optional[dict[str, Any]] = await repo.get_user_by_username("admin")
_admin_user: Optional[dict[str, Any]] = await repo.get_user_by_username(DECNET_ADMIN_USER)
if not _admin_user:
await repo.create_user(
{
"uuid": str(uuid.uuid4()),
"username": "admin",
"password_hash": get_password_hash("admin"),
"username": DECNET_ADMIN_USER,
"password_hash": get_password_hash(DECNET_ADMIN_PASSWORD),
"role": "admin",
"must_change_password": True
}

View File

@@ -1,10 +1,11 @@
import os
from datetime import datetime, timedelta, timezone
from typing import Optional, Any
import jwt
import bcrypt
SECRET_KEY: str = os.environ.get("DECNET_SECRET_KEY", "super-secret-key-change-me")
from decnet.env import DECNET_JWT_SECRET
SECRET_KEY: str = DECNET_JWT_SECRET
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440