acme.sh --list failed in uv/systemd context. Now scans certs directory
directly and uses openssl for cert details + HAProxy show ssl cert API
for loaded status.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add X-Forwarded-For support for real client IP detection (BunnyCDN)
- Change MCP domain from mcp.inouter.com to haproxy.inouter.com
- Remove unused wildcard domains (bench, mcp, ssh)
- Add nocodb.inouter.com wildcard mapping
- Ignore runtime files (*.db, cdn-ips.lst) in .gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New db_remove_wildcard() for surgical wildcard-only deletion.
New haproxy_cleanup_wildcards tool scans all wildcard entries and removes
those belonging to subdomains (e.g., *.nocodb.inouter.com) while keeping
base domain wildcards (e.g., *.anvil.it.com).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add CUSTOM_TLDS config (HAPROXY_CUSTOM_TLDS env, default: "it.com")
and _get_base_domain() for eTLD+1 detection. _check_subdomain now uses
three layers: registered domains, certificate domains, and structural
analysis. This ensures nocodb.inouter.com never gets a *.nocodb wildcard
entry even when inouter.com has no cert or registration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_check_subdomain now also checks certificate domains from DB, not just
registered domains. This prevents adding useless wildcard map entries
like *.nocodb.inouter.com when inouter.com already has a wildcard cert
that only covers one level deep (*.inouter.com).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
acme.sh default --days is 30, which triggers renewal 60 days before
expiry. Setting --days 60 aligns with the common practice of renewing
30 days before the 90-day certificate expires.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduces MCP response from ~12.3k tokens (~1000 lines) to only active servers.
Empty pool slots (MAINT with no health check) are filtered by default.
Added show_all parameter to optionally show all slots.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only show servers with actual IP addresses. Empty slots are
HAProxy pre-allocated placeholders that add noise to the output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reflects the JSON → SQLite migration: architecture diagram now shows
K8s pod + SSH/SCP remote sync, data flow documentation, updated
file descriptions, startup sequence, and write flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of syncing JSON files back, the SQLite DB itself is now
the persistent store on the remote HAProxy host:
- Startup: download remote DB via SCP (skip migration if exists)
- After writes: upload local DB via SCP (WAL checkpoint first)
- JSON sync removed (sync_servers_json, sync_certs_json deleted)
New functions:
- ssh_ops: remote_download_file(), remote_upload_file() via SCP
- db: sync_db_to_remote(), _try_download_remote_db()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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 <noreply@anthropic.com>
In REMOTE_MODE the SQLite database lives inside the MCP container,
not on the remote HAProxy host. Check db_file with os.path.exists
instead of remote_file_exists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
init_db() now creates the parent directory for the SQLite database
file before connecting. Also sets HAPROXY_DB_FILE in the K8s
deployment to /app/data/ since the container doesn't have
/opt/haproxy/conf/.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace servers.json, certificates.json, and map file parsing with
SQLite (WAL mode) as single source of truth. HAProxy map files are
now generated from SQLite via sync_map_files().
Key changes:
- Add db.py with schema, connection management, and JSON migration
- Add DB_FILE config constant
- Delegate file_ops.py functions to db.py
- Refactor domains.py to use file_ops instead of direct list manipulation
- Fix subprocess.TimeoutExpired not caught (doesn't inherit TimeoutError)
- Add DB health check in health.py
- Init DB on startup in server.py and __main__.py
- Update all 359 tests to use SQLite-backed functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously issue_cert saved a reloadcmd that only combined PEM files,
so cron renewals would not reload certs into HAProxy. Now the reloadcmd
uses the Runtime API (set/commit ssl cert) for zero-downtime reload,
matching the existing domain reloadcmd configuration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shared domains (with _shares key) have no server slots to restore,
so they should be skipped early in the restore loop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove bash -c from remote_exec (pass command as single SSH arg)
- Fix remote_write_file to pass bash -c script as single quoted string
- Add LogLevel=ERROR to suppress SSH warning messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add UserKnownHostsFile=/dev/null to prevent write errors on read-only .ssh
- Wrap all SSH commands with 'bash -c' for fish shell compatibility on remote
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MCP server can now manage HAProxy running on a remote host via SSH.
When SSH_HOST env var is set, all file I/O and subprocess commands
(podman, acme.sh, openssl) are routed through SSH instead of local exec.
- Add ssh_ops.py module with remote_exec, run_command, file I/O helpers
- Modify file_ops.py to support remote reads/writes via SSH
- Update all tools (domains, certificates, health, configuration) for SSH
- Fix domains.py: replace direct fcntl usage with file_lock context manager
- Add openssh-client to Docker image for SSH connectivity
- Update k8s deployment with SSH env vars and SSH key secret mount
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove buildx action and test job, use direct docker build/push
and mounted kubectl for k8s deployment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MCP streamable-http transport returns 406 on GET requests.
Switch to tcpSocket probe for readiness/liveness checks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Dockerfile (multi-stage, python:3.11-slim + uv), K8s manifests
(Deployment + Service), and extend CI workflow with build-push-deploy
stages targeting Gitea registry and K3s.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add real client IP detection (CF-Connecting-IP / src fallback) to both frontends
- Add per-IP rate limiting (429) using real IP for Cloudflare compatibility
- Add CrowdSec syslog forwarding with custom log format
- Add httplog option for detailed HTTP logging
- Fix Python-level defaults on MCP tool parameters to match Field(default=X)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow multiple domains to share the same backend pool using share_with parameter.
This saves pool slots when domains point to the same servers.
- Add share_with parameter to haproxy_add_domain
- Add helper functions for shared domain management
- Protect shared pools from being cleared on domain removal
- Update documentation with pool sharing examples
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Split domain routing into two stages for improved performance:
- Stage 1: map_str for exact domains (O(log n) using ebtree)
- Stage 2: map_dom for wildcards only (O(n) but small set)
Wildcards now stored in separate wildcards.map file.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix haproxy_cmd_batch to send each command on separate connection
(HAProxy Runtime API only processes first command on single connection)
- HTTP frontend now routes to backends instead of redirecting to HTTPS
- Add subdomain detection to avoid duplicate wildcard entries
- Add reload verification with retry logic
- Optimize SSL: TLS 1.3 ciphersuites, extended session lifetime
- Add CPU steal monitoring script
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove legacy restore-state.sh (MCP server handles state via servers.json)
- api/ was separate git repo, already removed from filesystem
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>