81 lines
3 KiB
Python
81 lines
3 KiB
Python
import ipaddress
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.config import get_settings
|
|
from app.database import get_postgres_session
|
|
from app.deps.dns_setup_auth import require_dns_setup_auth
|
|
from app.schemas import DnsSetupRequest, DnsSetupResponse
|
|
from app.services.domain_provisioning import (
|
|
build_deployment_dns_records,
|
|
get_domain_by_name,
|
|
validate_domain,
|
|
)
|
|
|
|
router = APIRouter(prefix="/dns-setup", tags=["dns-setup"])
|
|
|
|
|
|
def validate_mail_server_ip(value: str) -> str:
|
|
candidate = value.strip()
|
|
try:
|
|
return str(ipaddress.ip_address(candidate))
|
|
except ValueError as error:
|
|
raise ValueError("Enter a valid IPv4 or IPv6 address.") from error
|
|
|
|
|
|
@router.post("/preview", response_model=DnsSetupResponse)
|
|
async def preview_dns_setup(
|
|
payload: DnsSetupRequest,
|
|
_: None = Depends(require_dns_setup_auth),
|
|
session: AsyncSession = Depends(get_postgres_session),
|
|
) -> DnsSetupResponse:
|
|
settings = get_settings()
|
|
try:
|
|
domain_name = validate_domain(payload.domain)
|
|
mail_server_ip = validate_mail_server_ip(payload.mail_server_ip)
|
|
except ValueError as error:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
detail=str(error),
|
|
) from error
|
|
|
|
domain = await get_domain_by_name(session, domain_name)
|
|
sections = build_deployment_dns_records(
|
|
domain_name=domain_name,
|
|
mail_server_ip=mail_server_ip,
|
|
dkim_selector=settings.dkim_selector,
|
|
dkim_public_key=domain.dkim_public_key if domain else None,
|
|
app_public_host=settings.app_public_host,
|
|
)
|
|
mail_host = f"mail.{domain_name}"
|
|
return DnsSetupResponse(
|
|
domain=domain_name,
|
|
mail_host=mail_host,
|
|
app_public_host=settings.app_public_host,
|
|
mail_server_ip=mail_server_ip,
|
|
is_platform_domain=domain_name == settings.platform_domain,
|
|
sections=sections,
|
|
notes=_build_notes(
|
|
domain_name,
|
|
settings.app_public_host,
|
|
mail_server_ip,
|
|
),
|
|
)
|
|
|
|
|
|
def _build_notes(
|
|
domain_name: str,
|
|
app_public_host: str,
|
|
mail_server_ip: str,
|
|
) -> list[str]:
|
|
return [
|
|
"Add these records in Namecheap Advanced DNS for the zone.",
|
|
f"Point mail.{domain_name} and {app_public_host} A records to {mail_server_ip}.",
|
|
f"Keep the root {domain_name} A record on your main website server if it hosts the marketing site.",
|
|
f"Set {domain_name} MX to mail.{domain_name} (priority 10) so inbound mail reaches Postfix on this host.",
|
|
"Postfix accepts inbound mail on :25 and delivers outbound mail for the app over the Docker network.",
|
|
f"Users open the web UI at https://{app_public_host} (Caddy terminates TLS).",
|
|
f"Ask your VPS provider to set reverse DNS (PTR) for {mail_server_ip} → mail.{domain_name}.",
|
|
"SPF, DKIM, and DMARC authenticate outbound mail signed by Agmail (selector agmail).",
|
|
]
|