feat: manual Proxmox re-sync button + make connection config env-only

Add a 'Re-sync now' button to the Proxmox auto-sync settings section that
triggers an immediate inventory import (POST /proxmox/sync-now) using the
server env config — the manual counterpart to scheduled auto-sync.

Also fix a dual-source-of-truth bug: Proxmox connection config (host, port,
token, verify_tls) is now env-only and never persisted to scan_config.json.
Previously save_overrides() dumped host/port/verify alongside scan settings,
so saving an unrelated setting wrote an empty proxmox_host that load_overrides
then clobbered PROXMOX_HOST with on every boot. Only the auto-sync activation
(sync_enabled + sync_interval) stays user-editable and persisted.

ha-relevant: no
This commit is contained in:
Pouzor
2026-07-07 21:20:13 +02:00
parent 4206d50c70
commit 9437a74147
9 changed files with 263 additions and 28 deletions
+48 -8
View File
@@ -29,6 +29,7 @@ from app.schemas.proxmox import (
ProxmoxImportPendingResponse,
ProxmoxImportResponse,
ProxmoxNodeOut,
ProxmoxSyncConfig,
ProxmoxTestConnectionResponse,
)
from app.schemas.scan import ScanRunResponse
@@ -141,6 +142,43 @@ async def import_proxmox_to_pending(
return run
@router.post("/sync-now", response_model=ScanRunResponse)
async def sync_proxmox_now(
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db),
_: str = Depends(get_current_user),
) -> ScanRun:
"""Trigger an immediate Proxmox inventory sync using the server env config.
Same background flow as ``/import-pending`` but sources host + token from
``settings`` (env) rather than the request body — the manual counterpart to
the scheduled auto-sync job. Requires the env token to be configured.
"""
if not (settings.proxmox_host and settings.proxmox_token_id and settings.proxmox_token_secret):
raise HTTPException(
status_code=400,
detail="Cannot sync: no Proxmox host/token configured on the server.",
)
run = ScanRun(
status="running",
kind="proxmox",
ranges=[f"{settings.proxmox_host}:{settings.proxmox_port}"],
)
db.add(run)
await db.commit()
await db.refresh(run)
background_tasks.add_task(
_background_proxmox_import,
run.id,
settings.proxmox_host,
settings.proxmox_port,
settings.proxmox_token_id,
settings.proxmox_token_secret,
settings.proxmox_verify_tls,
)
return run
async def _background_proxmox_import(
run_id: str,
host: str,
@@ -442,22 +480,24 @@ async def get_proxmox_config(_: str = Depends(get_current_user)) -> ProxmoxConfi
@router.post("/config", response_model=ProxmoxConfig)
async def save_proxmox_config(
payload: ProxmoxConfig,
payload: ProxmoxSyncConfig,
_: str = Depends(get_current_user),
) -> ProxmoxConfig:
"""Persist non-secret Proxmox config and apply the auto-sync schedule live.
"""Persist the auto-sync activation (enabled + interval) and apply it live.
The token is NOT accepted here — it is env-only by design.
This is the ONLY Proxmox config the app writes. Connection settings
(host, port, token, verify_tls) are env-only and are never accepted or
persisted here — enabling auto-sync requires host + token already set in the
server env, since the scheduled job reads them from there.
"""
if payload.sync_enabled and not (settings.proxmox_token_id and settings.proxmox_token_secret):
if payload.sync_enabled and not (
settings.proxmox_host and settings.proxmox_token_id and settings.proxmox_token_secret
):
raise HTTPException(
status_code=400,
detail="Cannot enable auto-sync: no Proxmox API token configured on the server.",
detail="Cannot enable auto-sync: no Proxmox host/token configured in the server env.",
)
try:
settings.proxmox_host = payload.host
settings.proxmox_port = payload.port
settings.proxmox_verify_tls = payload.verify_tls
settings.proxmox_sync_enabled = payload.sync_enabled
settings.proxmox_sync_interval = payload.sync_interval
settings.save_overrides()
+7 -12
View File
@@ -121,13 +121,10 @@ class Settings(BaseSettings):
self.scanner_http_probe_enabled = bool(data["scanner_http_probe_enabled"])
if "scanner_http_verify_tls" in data:
self.scanner_http_verify_tls = bool(data["scanner_http_verify_tls"])
# Proxmox non-secret config (token stays env-only, never here).
if "proxmox_host" in data:
self.proxmox_host = str(data["proxmox_host"])
if "proxmox_port" in data:
self.proxmox_port = int(data["proxmox_port"])
if "proxmox_verify_tls" in data:
self.proxmox_verify_tls = bool(data["proxmox_verify_tls"])
# Proxmox auto-sync activation only. Connection config (host, port,
# token, verify_tls) is env-only by design — never read from or
# written to this file. Persisting host here previously created a
# dual source of truth that silently clobbered PROXMOX_HOST.
if "proxmox_sync_enabled" in data:
self.proxmox_sync_enabled = bool(data["proxmox_sync_enabled"])
if "proxmox_sync_interval" in data:
@@ -146,11 +143,9 @@ class Settings(BaseSettings):
"scanner_http_ranges": self.scanner_http_ranges,
"scanner_http_probe_enabled": self.scanner_http_probe_enabled,
"scanner_http_verify_tls": self.scanner_http_verify_tls,
# Proxmox: only non-secret config. Token fields are intentionally
# excluded — they must never be written to disk by the app.
"proxmox_host": self.proxmox_host,
"proxmox_port": self.proxmox_port,
"proxmox_verify_tls": self.proxmox_verify_tls,
# Proxmox: only the auto-sync activation is persisted. Connection
# config (host, port, token, verify_tls) is env-only and must never
# be written to disk — that is the single source of truth.
"proxmox_sync_enabled": self.proxmox_sync_enabled,
"proxmox_sync_interval": self.proxmox_sync_interval,
}))
+14 -2
View File
@@ -64,8 +64,11 @@ class ProxmoxImportPendingResponse(BaseModel):
class ProxmoxConfig(BaseModel):
"""Non-secret Proxmox connection + auto-sync config. Never carries a token —
``token_configured`` reflects whether a server-side token is present."""
"""Non-secret Proxmox connection + auto-sync config (GET response).
Connection fields (host/port/verify_tls) are env-only and read-only here —
surfaced for display. ``token_configured`` reflects whether a server-side
token is present. Never carries the token itself."""
host: str = ""
port: int = Field(8006, ge=1, le=65535)
@@ -73,3 +76,12 @@ class ProxmoxConfig(BaseModel):
sync_enabled: bool = False
sync_interval: int = Field(3600, ge=300)
token_configured: bool = False
class ProxmoxSyncConfig(BaseModel):
"""User-editable auto-sync config (POST body). The ONLY persisted Proxmox
settings. Connection fields (host/port/token/verify_tls) are env-only and
are deliberately not accepted here."""
sync_enabled: bool = False
sync_interval: int = Field(3600, ge=300)