2bd778117f
Replace persistent Cloudflare tunnels (API-based) with temporary tunnels using 'cloudflared tunnel --url'. This removes the need for Cloudflare API tokens, DNS records, and persistent tunnel management. Changes: - Install cloudflared binary in API Dockerfile - Add start_cloudflared_tunnel() and stop_cloudflared_tunnel() to docker.py - Update instance start/stop/restart/delete to use temporary tunnels - Store tunnel PID in tunnel_id field, temporary URL in url/public_url - Remove Cloudflare API service (cloudflare_tunnel.py) - Remove cloudflared container from docker-compose - Remove Cloudflare env vars (CLOUDFLARE_API_TOKEN, ZONE_ID, etc.) - Remove Cloudflare configuration from config.py - Remove Cloudflare startup check from main.py - Remove /health/cloudflare endpoint
144 lines
4.6 KiB
Python
144 lines
4.6 KiB
Python
from pydantic import Field
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
def build_database_url(
|
|
*,
|
|
user: str,
|
|
password: str,
|
|
host: str,
|
|
port: int,
|
|
database: str,
|
|
) -> str:
|
|
return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{database}"
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
app_env: str = "development"
|
|
database_url_override: str | None = Field(default=None, alias="DATABASE_URL")
|
|
postgres_user: str = "headquarter"
|
|
postgres_password: str = "headquarter"
|
|
postgres_host: str = "postgres"
|
|
postgres_port: int = 5432
|
|
postgres_db: str = "headquarter"
|
|
|
|
# Domain configuration
|
|
api_domain: str = "localhost"
|
|
web_domain: str = "localhost"
|
|
authentik_domain: str = "authentik.local"
|
|
|
|
# Public URLs (constructed from domains if not explicitly set)
|
|
api_public_url: str | None = None
|
|
web_public_url: str | None = None
|
|
|
|
# Authentik configuration - no hardcoded URLs
|
|
authentik_client_id: str = "headquarter-web"
|
|
authentik_client_secret: str = "change-me"
|
|
# Authentik application slug used in URLs (e.g., "headquarter-web")
|
|
# This is different from the OAuth client_id which may be a UUID
|
|
authentik_application_slug: str = "headquarter-web"
|
|
authentik_authorize_url: str | None = None
|
|
authentik_token_url: str | None = None
|
|
authentik_jwks_url: str | None = None
|
|
authentik_issuer: str | None = None
|
|
authentik_audience: str = "headquarter-web"
|
|
|
|
# Session configuration
|
|
session_secret: str = "change-me-session-secret"
|
|
session_ttl_hours: int = 24
|
|
|
|
# Repository storage
|
|
repo_base_path: str = "/data/repos"
|
|
|
|
# Tool instance storage
|
|
instance_base_path: str = "/data/instances"
|
|
|
|
|
|
|
|
model_config = SettingsConfigDict(env_file=".env", extra="ignore", populate_by_name=True)
|
|
|
|
@property
|
|
def database_url(self) -> str:
|
|
if self.database_url_override:
|
|
return self.database_url_override
|
|
|
|
return build_database_url(
|
|
user=self.postgres_user,
|
|
password=self.postgres_password,
|
|
host=self.postgres_host,
|
|
port=self.postgres_port,
|
|
database=self.postgres_db,
|
|
)
|
|
|
|
@property
|
|
def api_base_url(self) -> str:
|
|
if self.api_public_url:
|
|
return self.api_public_url
|
|
protocol = "https" if self.app_env == "production" else "http"
|
|
port = "" if self.app_env == "production" else ":8000"
|
|
return f"{protocol}://{self.api_domain}{port}"
|
|
|
|
@property
|
|
def web_base_url(self) -> str:
|
|
if self.web_public_url:
|
|
return self.web_public_url
|
|
protocol = "https" if self.app_env == "production" else "http"
|
|
port = "" if self.app_env == "production" else ":3000"
|
|
return f"{protocol}://{self.web_domain}{port}"
|
|
|
|
@property
|
|
def authentik_base_url(self) -> str:
|
|
protocol = "https" if self.app_env == "production" else "http"
|
|
return f"{protocol}://{self.authentik_domain}"
|
|
|
|
@property
|
|
def resolved_authentik_authorize_url(self) -> str:
|
|
if self.authentik_authorize_url:
|
|
return self.authentik_authorize_url
|
|
return f"{self.authentik_base_url}/application/o/authorize/"
|
|
|
|
@property
|
|
def resolved_authentik_token_url(self) -> str:
|
|
if self.authentik_token_url:
|
|
return self.authentik_token_url
|
|
return f"{self.authentik_base_url}/application/o/token/"
|
|
|
|
@property
|
|
def resolved_authentik_jwks_url(self) -> str:
|
|
if self.authentik_jwks_url:
|
|
return self.authentik_jwks_url
|
|
return f"{self.authentik_base_url}/application/o/{self.authentik_application_slug}/jwks/"
|
|
|
|
@property
|
|
def resolved_authentik_issuer(self) -> str:
|
|
if self.authentik_issuer:
|
|
return self.authentik_issuer
|
|
return f"{self.authentik_base_url}/application/o/{self.authentik_application_slug}/"
|
|
|
|
@property
|
|
def cookie_secure(self) -> bool:
|
|
return self.app_env == "production"
|
|
|
|
@property
|
|
def cookie_samesite(self) -> str:
|
|
if self.app_env == "production":
|
|
return "none"
|
|
|
|
return "lax"
|
|
|
|
@property
|
|
def cookie_domain(self) -> str | None:
|
|
"""Return the parent domain for cross-subdomain cookies.
|
|
|
|
E.g., api.example.com and app.example.com both share .example.com
|
|
"""
|
|
if self.app_env != "production":
|
|
return None
|
|
|
|
# Extract parent domain from api_domain
|
|
# e.g., "api.headquarter.commumedia.org" -> ".headquarter.commumedia.org"
|
|
parts = self.api_domain.split(".")
|
|
if len(parts) >= 3:
|
|
return "." + ".".join(parts[1:])
|
|
return None
|