From b86ba5d994b48035491ad0ebf86bf509a0ef6bb8 Mon Sep 17 00:00:00 2001 From: kappa Date: Sun, 8 Feb 2026 11:32:29 +0900 Subject: [PATCH] Sync servers.json and certificates.json on every change SQLite DB in the K8s pod is ephemeral (no PV). On pod restart, migrate_from_json() re-reads the remote JSON files. Without syncing back, any server/cert changes after migration would be lost. Now every server/cert write to SQLite also writes the corresponding JSON file to the remote host, keeping them in sync. Co-Authored-By: Claude Opus 4.6 --- haproxy_mcp/db.py | 28 ++++++++++++++++++++++++++++ haproxy_mcp/file_ops.py | 18 ++++++++++++------ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/haproxy_mcp/db.py b/haproxy_mcp/db.py index e0d2a77..5dabd99 100644 --- a/haproxy_mcp/db.py +++ b/haproxy_mcp/db.py @@ -580,3 +580,31 @@ def sync_map_files() -> None: logger.debug("Map files synced: %d exact, %d wildcard entries", len(exact_entries), len(wildcard_entries)) + + +def sync_servers_json() -> None: + """Write servers configuration back to servers.json for persistence. + + Ensures the remote JSON file stays in sync with SQLite so that + pod restarts can re-migrate without data loss. + """ + from .file_ops import atomic_write_file + + config = db_load_servers_config() + content = json.dumps(config, indent=2) + atomic_write_file(SERVERS_FILE, content) + logger.debug("Synced servers.json: %d domains", len(config)) + + +def sync_certs_json() -> None: + """Write certificates list back to certificates.json for persistence. + + Ensures the remote JSON file stays in sync with SQLite so that + pod restarts can re-migrate without data loss. + """ + from .file_ops import atomic_write_file + + domains = db_load_certs() + content = json.dumps({"domains": domains}, indent=2) + atomic_write_file(CERTS_FILE, content) + logger.debug("Synced certificates.json: %d domains", len(domains)) diff --git a/haproxy_mcp/file_ops.py b/haproxy_mcp/file_ops.py index 8aacce5..73a4663 100644 --- a/haproxy_mcp/file_ops.py +++ b/haproxy_mcp/file_ops.py @@ -265,8 +265,9 @@ def add_server_to_config(domain: str, slot: int, ip: str, http_port: int) -> Non ip: Server IP address http_port: HTTP port """ - from .db import db_add_server + from .db import db_add_server, sync_servers_json db_add_server(domain, slot, ip, http_port) + sync_servers_json() def remove_server_from_config(domain: str, slot: int) -> None: @@ -276,8 +277,9 @@ def remove_server_from_config(domain: str, slot: int) -> None: domain: Domain name slot: Server slot to remove """ - from .db import db_remove_server + from .db import db_remove_server, sync_servers_json db_remove_server(domain, slot) + sync_servers_json() def remove_domain_from_config(domain: str) -> None: @@ -286,8 +288,9 @@ def remove_domain_from_config(domain: str) -> None: Args: domain: Domain name to remove """ - from .db import db_remove_domain_servers + from .db import db_remove_domain_servers, sync_servers_json db_remove_domain_servers(domain) + sync_servers_json() def get_shared_domain(domain: str) -> Optional[str]: @@ -310,8 +313,9 @@ def add_shared_domain_to_config(domain: str, shares_with: str) -> None: domain: New domain name shares_with: Existing domain to share pool with """ - from .db import db_add_shared_domain + from .db import db_add_shared_domain, sync_servers_json db_add_shared_domain(domain, shares_with) + sync_servers_json() def get_domains_sharing_pool(pool: str) -> list[str]: @@ -358,8 +362,9 @@ def add_cert_to_config(domain: str) -> None: Args: domain: Domain name to add """ - from .db import db_add_cert + from .db import db_add_cert, sync_certs_json db_add_cert(domain) + sync_certs_json() def remove_cert_from_config(domain: str) -> None: @@ -368,8 +373,9 @@ def remove_cert_from_config(domain: str) -> None: Args: domain: Domain name to remove """ - from .db import db_remove_cert + from .db import db_remove_cert, sync_certs_json db_remove_cert(domain) + sync_certs_json() # Domain map helper functions (used by domains.py)