Add SSL certificate mode: Let's Encrypt or Wildcard per NPM
Settings > NPM Integration now allows choosing between per-customer Let's Encrypt certificates (default) or a shared wildcard certificate already uploaded in NPM. Includes backend, frontend UI, and i18n support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,8 @@ class AppConfig:
|
||||
docker_network: str
|
||||
relay_base_port: int
|
||||
dashboard_base_port: int
|
||||
ssl_mode: str
|
||||
wildcard_cert_id: int | None
|
||||
|
||||
|
||||
# Environment-level settings (not stored in DB)
|
||||
@@ -79,4 +81,6 @@ def get_system_config(db: Session) -> Optional[AppConfig]:
|
||||
docker_network=row.docker_network,
|
||||
relay_base_port=row.relay_base_port,
|
||||
dashboard_base_port=getattr(row, "dashboard_base_port", 9000) or 9000,
|
||||
ssl_mode=getattr(row, "ssl_mode", "letsencrypt") or "letsencrypt",
|
||||
wildcard_cert_id=getattr(row, "wildcard_cert_id", None),
|
||||
)
|
||||
|
||||
@@ -126,12 +126,25 @@ class SystemConfigUpdate(BaseModel):
|
||||
branding_name: Optional[str] = Field(None, max_length=255)
|
||||
branding_subtitle: Optional[str] = Field(None, max_length=255)
|
||||
default_language: Optional[str] = Field(None, max_length=10)
|
||||
ssl_mode: Optional[str] = Field(None, max_length=20)
|
||||
wildcard_cert_id: Optional[int] = Field(None, ge=0)
|
||||
mfa_enabled: Optional[bool] = None
|
||||
azure_enabled: Optional[bool] = None
|
||||
azure_tenant_id: Optional[str] = Field(None, max_length=255)
|
||||
azure_client_id: Optional[str] = Field(None, max_length=255)
|
||||
azure_client_secret: Optional[str] = None # encrypted before storage
|
||||
|
||||
@field_validator("ssl_mode")
|
||||
@classmethod
|
||||
def validate_ssl_mode(cls, v: Optional[str]) -> Optional[str]:
|
||||
"""SSL mode must be 'letsencrypt' or 'wildcard'."""
|
||||
if v is None:
|
||||
return v
|
||||
allowed = {"letsencrypt", "wildcard"}
|
||||
if v not in allowed:
|
||||
raise ValueError(f"ssl_mode must be one of: {', '.join(sorted(allowed))}")
|
||||
return v
|
||||
|
||||
@field_validator("base_domain")
|
||||
@classmethod
|
||||
def validate_domain(cls, v: Optional[str]) -> Optional[str]:
|
||||
|
||||
Reference in New Issue
Block a user