Skip to content

Setup & Configuration

Last update: June 4, 2026


This page covers every configuration topic for AI-Bridge for Cisco UC β€” environment variables, authentication modes, client management, RBAC profiles, TLS configuration, and backup settings.


The .env File

All runtime configuration lives in a single .env file at the project root. It is created automatically by the setup wizard and preserved intact across upgrades.

Protect your .env

The .env file contains RSA key paths, client definitions, and other security-sensitive values. Ensure it is readable only by the service account running AI-Bridge (chmod 600 .env).

A commented template with all available keys is provided in .env.example.

Only .env is read β€” os.environ overrides are NOT honoured

The server loads its configuration exclusively from the .env file at the project root (via python-dotenv's dotenv_values). Process-level environment variables exported by the shell (e.g. export AUTH_ISSUER=…) or injected by Docker (-e AUTH_ISSUER=…, environment: block in docker-compose.yml) are deliberately ignored.

Rationale: single source of truth, full auditability (one file on disk, captured by the integrity manifest, the backups, and show_tech reports), restart parity between bare-metal and Docker deployments. To override a value, edit .env (or template it from your CI/CD) β€” never rely on os.environ injection.


Environment Variables Reference

Project Metadata

These keys identify the installation. They are managed automatically by the upgrade runner β€” do not edit them manually.

Key Description Example
PROJECT_NAME Internal identifier ai-bridge-for-cisco-uc
PROJECT_CREATOR Publisher Pierre SOURDEAU
PROJECT_VERSION Build version YYYYMM 202605

Auto-managed keys

PROJECT_NAME, PROJECT_CREATOR, and PROJECT_VERSION are overwritten automatically during every upgrade. Any manual edits will be lost.


MCP Server

Key Description Default
MCP_SERVER_NAME Human-readable name shown to AI agents AI-Bridge for Cisco UC
MCP_SERVER_INSTRUCTIONS Description injected into agent context MCP SERVER for Cisco UC
MCP_SERVER_TRANSPORT Transport type streamable-http
MCP_SERVER_HOST Bind IP address inside the container 0.0.0.0
MCP_SERVER_PORT HTTPS listening port 8443
MCP_SERVER_URL_PATH MCP endpoint path /mcp

Docker deployment

MCP_SERVER_HOST must remain 0.0.0.0 for Docker port mapping to work. The container's network namespace is isolated β€” binding to a host IP address would fail. Control external access with your host firewall and MCP_SERVER_SECURITY_ALLOWED_HOSTS.


Docker Settings

Key Description Default
UID UID of the host user β€” the container process runs as this UID 1000
GID GID of the host user β€” the container process runs as this GID 1000

Run id -u && id -g on the Docker host to find your values. These must match the owner of the bind-mounted volume directories (secrets/, clients/, logs/, etc.).

Transport

streamable-http is currently the only supported transport. SSE transport is not offered (legacy).


Authentication & Cryptography

Key Description Default
AUTH_RSA_PUBLIC_KEY_FILE Path to RSA public key (PEM) β€” used for JWT signature verification ./secrets/rsa_public.pem
AUTH_RSA_PRIVATE_KEY_FILE Path to RSA private key (PEM) β€” used for JWT signing and backup decryption. ./secrets/rsa_private.pem
AUTH_ISSUER JWT issuer claim and OAuth 2.1 Authorization Server base URL β€” must be the publicly reachable HTTPS URL of the server https://mcp.domain.com:8443
AUTH_AUDIENCE Expected JWT aud claim β€” must match the value configured in AI agent clients mcp-clients
AUTH_CLIENTS Client definitions (see Client Management section below) (must be configured)
CRYPTO_SECRETS_DIR Directory for auto-generated secrets (tokens, keys, bcrypt hashes) ./secrets/
AUTH_MODE Global authentication mode jwt
AUTH_RSA_KEY_SIZE RSA key size in bits used when generating a new key pair 4096
AUTH_TOKEN_EXPIRY_DAYS JWT token validity in days 365
AUTH_OAUTH_TOKEN_EXPIRY_SECONDS OAuth 2.1 access token validity in seconds 3600
AUTH_FAIL_LIMIT Failed authentication attempts before IP is blocked 10
AUTH_FAIL_WINDOW_SECONDS Sliding window duration for failure counting (seconds) 300
AUTH_BLACKLIST_DURATION_SECONDS IP block duration after exceeding fail limit (seconds) 900
AUTH_OAUTH_REDIRECT_URIS JSON dict {"client": ["uri", ...]} β€” per-client allowlist of OAuth 2.1 redirect_uri values (wildcard * allowed) (empty β€” no whitelist)
AUTH_OAUTH_REDIRECT_URI_ENFORCEMENT off (default, backwards-compat) or strict (recommended in production β€” rejects unknown redirect_uri) off
INTEGRITY_CHECK_INTERVAL_HOURS Interval between two integrity-manifest sweeps performed by the watchdog 24
BACKUP_SFTP_KNOWN_HOSTS OpenSSH known_hosts pinning the SFTP destination hostkey (see Backup Configuration below) ./secrets/sftp_known_hosts

Valid AUTH_MODE values: jwt / oauth2 / jwt+oauth2

Valid AUTH_RSA_KEY_SIZE values: 2048 / 3072 / 4096


Logging

Key Description Default
LOGGING_LEVEL Log verbosity info
LOGGING_DIR Log file directory ./logs/
LOGGING_ROTATE_SIZE Max log file size before rotation (bytes) 5000000 (~5 MB)
LOGGING_ROTATE_FILES Number of rotated archives to keep 3
LOGGING_CONSOLE_ENABLED Enable colored console output 1

Valid LOGGING_LEVEL values: debug / info / warning / error / critical


Report Charts

Key Default
REPORT_CHARTS_COLORS PASS=#78F5AE,WARN=#FFBE54,FAIL=#FF6F61,INFO=#69C2FF,N/A=#BACCCC

CUCM Server

Key Default
SERVER_CUCM_AXL_PORT 8443
SERVER_CUCM_RIS_PORT 8443
SERVER_CUCM_SSH_PORT 22
SERVER_CUCM_SSH_TIMEOUT 30
SERVER_CUCM_ZEEP_TIMEOUT 30
SERVER_CUCM_AXL_SCHEMAS_DIR ./servers/cucm/axl-schemas/
SERVER_CUCM_RIS_SCHEMAS_DIR ./servers/cucm/ris-schemas/
SERVER_CUCM_AXL_WSDL_TIMEOUT 10
SERVER_CUCM_RIS_WSDL_TIMEOUT 15
SERVER_CUCM_SSH_TERM dumb
SERVER_CUCM_SSH_TERM_WIDTH 220
SERVER_CUCM_SSH_TERM_HEIGHT 50
SERVER_CUCM_PHONE_SCREENSHOT_TIMEOUT 10
SERVER_CUCM_ZEEP_CACHE_MAX 64
SERVER_CUCM_WSDL_CACHE_MAX 32
SERVER_IMP_ZEEP_CACHE_MAX 64
SERVER_IMP_WSDL_CACHE_MAX 32
PROFILE_SCHEMA_STRICT 1

Phone SSL verification

SSL certificate verification is automatically disabled for phone web connections. Cisco IP Phone certificates (MIC/LSC) are not compliant with Python 3.13+ strict X.509 validation: they lack the Authority Key Identifier (AKI) extension and use MAC-based CN (e.g. SEP001122334455) instead of the IP address. Security for phone connections relies on managed VLAN isolation, HTTP Basic Auth credentials, and the phone IP being obtained from a trusted source (CUCM RIS/AXL).


IMP Server

Key Default
SERVER_IMP_AXL_PORT 8443
SERVER_IMP_SSH_PORT 22
SERVER_IMP_SSH_TIMEOUT 120
SERVER_IMP_ZEEP_TIMEOUT 30
SERVER_IMP_AXL_SCHEMAS_DIR ./servers/imp/axl-schemas/
SERVER_IMP_AXL_WSDL_TIMEOUT 10
SERVER_IMP_SSH_TERM dumb

IMP SSH timeout

IMP SSH sessions may run longer diagnostics than CUCM. The default SERVER_IMP_SSH_TIMEOUT=120 is intentionally higher.


CUC Server

Key Default
SERVER_CUC_CUPI_PORT 8443
SERVER_CUC_CUPI_TIMEOUT 30
SERVER_CUC_SSH_PORT 22
SERVER_CUC_SSH_TIMEOUT 60
SERVER_CUC_SSH_TERM dumb
SERVER_CUC_SSH_TERM_WIDTH 220
SERVER_CUC_SSH_TERM_HEIGHT 50

No AXL / RIS / cluster on CUC

CUC exposes only CUPI (REST on tcp/8443) and SSH (tcp/22). There is no AXL toolkit, no RIS, and no cluster concept β€” credentials and operations are scoped per host.


Transport Security

Key Description Default
MCP_SERVER_SECURITY_TRANSPORT_ENABLED Enable DNS rebinding protection 1
MCP_SERVER_SECURITY_ALLOWED_HOSTS Comma-separated allowed Host header values localhost:*,127.0.0.1:*,mcp.domain.com:*
MCP_SERVER_SECURITY_ALLOWED_ORIGINS Comma-separated allowed Origin header values https://localhost:8443,https://127.0.0.1:8443,https://mcp.domain.com:8443

The * wildcard in ALLOWED_HOSTS matches any port on that hostname (e.g., localhost:* allows localhost:8443, localhost:4433, etc.).


SSL Verification per Product

The same pattern applies to CUCM, IMP, and CUC:

Key Description Default
SERVER_CUCM_SSL_VERIFY Verify TLS certificate of CUCM nodes True
SERVER_CUCM_SSL_CA_BUNDLE Path to CA bundle for CUCM TLS verification (empty = system trusted CA), or product certificates bundle (empty)
Key Description Default
SERVER_IMP_SSL_VERIFY Verify TLS certificate of IMP nodes True
SERVER_IMP_SSL_CA_BUNDLE Path to CA bundle for IMP TLS verification (empty = system trusted CA), or product certificates bundle (empty)
Key Description Default
SERVER_CUC_SSL_VERIFY Verify TLS certificate of CUC nodes (CUPI/REST) True
SERVER_CUC_SSL_CA_BUNDLE Path to CA bundle for CUC TLS verification (empty = system trusted CA), or product certificates bundle (empty)

Custom CA for Cisco UC connections

To trust an internal CA for outbound AXL/CUPI/HTTPS connections to Cisco UC nodes, specify the CA bundle path in SERVER_CUCM_SSL_CA_BUNDLE / SERVER_IMP_SSL_CA_BUNDLE / SERVER_CUC_SSL_CA_BUNDLE. Files placed in certs-trust-store/ are not automatically applied to these connections.


Backup Configuration

Key Description Default
BACKUP_ENABLED Enable scheduled backups (0 / 1) 0
BACKUP_DIR Local backup directory ./backups/
BACKUP_EXCLUDE_DIRS Comma-separated directories excluded from the archive ./backups/,./.git/,./__pycache__/
BACKUP_RETENTION Number of local archives to retain 4
BACKUP_INTERVAL_HOURS Backup interval (hours) 168
BACKUP_SFTP_HOST SFTP server FQDN or IP (optional) (empty)
BACKUP_SFTP_PORT SFTP server TCP port 22
BACKUP_SFTP_USER SFTP username (optional) (empty)
BACKUP_SFTP_AUTH SFTP auth method: key or password key
BACKUP_SFTP_KEY_FILE Path to the SSH private key (when BACKUP_SFTP_AUTH=key) ./secrets/backup_ssh_key
BACKUP_SFTP_KNOWN_HOSTS OpenSSH known_hosts file used to verify the SFTP server hostkey ./secrets/sftp_known_hosts
BACKUP_SFTP_PASSWORD_ENC Encrypted SFTP password (generated by encrypt_sftp_password.py) (empty)
BACKUP_SFTP_REMOTE_PATH Remote directory for SFTP uploads /backups/ai-bridge/
BACKUP_SFTP_RETENTION Number of remote archives to retain 52

!!! "SFTP hostkey verification is mandatory" Before the very first SFTP transfer, populate BACKUP_SFTP_KNOWN_HOSTS with the trusted hostkey of the remote server. If the file is missing or contains no entry for the host, the backup transfer aborts immediately β€” credentials are never sent to an unverified server (anti-MITM).

```bash
# Default location: ./secrets/sftp_known_hosts
ssh-keyscan -p 22 sftp.example.com >> ./secrets/sftp_known_hosts
# Non-default port: the entry is stored as "[host]:port"
ssh-keyscan -p 2222 sftp.example.com >> ./secrets/sftp_known_hosts
```

**Verify the fingerprint out-of-band** (phone call to the SFTP admin, internal wiki, etc.) before trusting the file. The format is standard OpenSSH `known_hosts`.

SFTP password encryption

When using BACKUP_SFTP_AUTH=password, the password must be encrypted before being stored in .env. Use the dedicated script:

python scripts/encrypt_sftp_password.py
The resulting token is automatically copied into .env as BACKUP_SFTP_PASSWORD_ENC. It is never stored in plaintext. See the Operations page for details.


Authentication Modes

JWT Mode (Default)

JWT is the simplest deployment mode. Tokens are pre-generated at startup and stored on disk.

How it works:

  • A signed RS256 JWT is generated for each client at startup
  • Tokens are written to clients/<name>/secrets/.token
  • Token lifetime is controlled by AUTH_TOKEN_EXPIRY_DAYS (default: 365 days)
  • Tokens are automatically renewed at startup when: missing, corrupted, invalid signature, claims changed, or subject changed

JWT claims structure:

Claim Description
sub Client subject (from AUTH_CLIENTS)
iss Issuer (AUTH_ISSUER)
aud Audience (AUTH_AUDIENCE)
iat Issued-at timestamp
exp Expiration timestamp
jti Unique token ID (for revocation)
client_id Client name

Revoking a JWT token:

curl -sk -X POST https://mcp.domain.com:8443/revoke \
  -H "Authorization: Bearer <token>"

OAuth 2.1 Mode

OAuth 2.1 uses short-lived dynamic tokens, better suited for automated pipelines and environments with stricter credential hygiene.

How it works:

  • Two grant types are supported:
    • authorization_code + PKCE (RFC 7636) β€” for interactive clients such as AI IDEs (OpenCode, Claude Desktop). The client opens a browser login page, the user authenticates, and a short-lived token is issued automatically.
    • client_credentials (RFC 6749 Β§4.4) β€” for headless M2M clients using curl or scripts.
  • Token lifetime is controlled by AUTH_OAUTH_TOKEN_EXPIRY_SECONDS (default: 3600 s)
  • client_id = client name; client_secret is stored as a bcrypt hash in .secret_hash
  • The plaintext .secret file is automatically deleted after the first successful authentication
  • Token revocation: POST /revoke (RFC 7009)
  • Authorization Server Metadata: GET /.well-known/oauth-authorization-server (RFC 8414)
  • Protected Resource Metadata: GET /.well-known/oauth-protected-resource (RFC 9728) β€” required by MCP 2025-03-26 compliant clients

Interactive login flow (AI IDEs β€” authorization_code + PKCE):

When a compliant MCP client (OpenCode, etc.) connects to the MCP endpoint, it automatically:

  1. Detects the 401 response and performs OAuth discovery
  2. Opens the AI Bridge login page in your browser (GET /authorize)
  3. You enter your client_id and client_secret
  4. The browser is redirected back to the client with an authorization code
  5. The client exchanges the code for a Bearer token (POST /token)

No manual steps required after initial configuration.

M2M token request (client_credentials grant):

curl -sk -X POST https://mcp.domain.com:8443/token \
  -u "<client_id>:<client_secret>" \
  -d "grant_type=client_credentials"

One-time secret

The plaintext .secret file is a one-time credential. Copy it before the client's first authentication β€” it will be deleted automatically on first use. Only the bcrypt hash (.secret_hash) is retained.


Hybrid JWT + OAuth 2.1 Mode

Setting AUTH_MODE=jwt+oauth2 enables both modes simultaneously. Each client independently declares its own auth mode as the 4th field in AUTH_CLIENTS.

AUTH_CLIENTS='alice:sub-alice:admin:jwt,bob:sub-bob:operator:oauth2'

This allows mixed environments where some clients use static JWT tokens (e.g., interactive AI agents) while others use dynamic OAuth tokens (e.g., automated pipelines).


Client Management

AUTH_CLIENTS Syntax

Key Description Default
CLIENTS_DIR Base directory for client data ./clients/
# JWT or OAuth 2.1 mode (3 fields)
AUTH_CLIENTS='name:subject:profile'

# Hybrid mode (4 fields β€” only valid when AUTH_MODE=jwt+oauth2)
AUTH_CLIENTS='name:subject:profile:auth_mode'

# Multiple clients (comma-separated)
AUTH_CLIENTS='alice:sub-alice:admin:jwt,bob:sub-bob:operator:oauth2,carol:sub-carol:auditor:jwt+oauth2'

Field definitions:

Field Rules
name Unique identifier; used as the client directory name. Alphanumeric + hyphens.
subject JWT sub claim. Recommended format: sub-<name>-YYYYMMDD. Immutable once tokens have been distributed β€” changing it invalidates all issued tokens.
profile RBAC profile name. Must match a .json file in profiles/.
auth_mode jwt / oauth2 / jwt+oauth2. Only valid in hybrid global mode.

Subject immutability

The subject field is embedded in every issued JWT token. Changing a client's subject invalidates all previously issued tokens and forces re-issuance. Plan subject values carefully before distributing tokens.


Client Directory Structure

Each client gets a dedicated directory under CLIENTS_DIR:

clients/<name>/
β”œβ”€β”€ secrets/
β”‚   β”œβ”€β”€ .uuid           # Stable UUID used for credential encryption key derivation
β”‚   β”œβ”€β”€ .token          # JWT bearer token (jwt mode)
β”‚   β”œβ”€β”€ .secret         # OAuth client secret β€” plaintext, one-time use
β”‚   β”œβ”€β”€ .secret_hash    # OAuth client secret β€” bcrypt hash (permanent)
β”‚   └── ...
β”œβ”€β”€ reports/            # Markdown reports and PDF exports
└── custom/
    └── logo.png        # Custom logo embedded in PDF reports

Client Lifecycle at Startup

At every startup, the server synchronizes the clients/ directory against AUTH_CLIENTS:

Condition Action
Client in AUTH_CLIENTS but no directory Directory created, credentials generated
Client exists, token valid No action
Client exists, token invalid (expired, bad sig, claims changed) Token renewed automatically
Client in directory but not in AUTH_CLIENTS Directory purged

Automatic purge

Removing a client from AUTH_CLIENTS and restarting the server permanently deletes the client's directory, including all reports and credentials. Export any needed data before removing a client.


RBAC Profiles

Predefined Profiles

Three profiles are shipped in the profiles/_default/ directory:

Profile Product Scope AXL Operations SSH Access RIS Access Phone CUPI Access (CUC) Rate Limits (req/min)
admin All All prefixes + SQL Update All commands All device classes Screenshot All verbs (GET/POST/PUT/DELETE) 60 / 5 / 20 / 10 / 60
operator cucm, imp, cuc get, list + phone write + SQLQuery show only Phone only Screenshot GET + targeted PUT (users, callhandlers) 30 / 5 / 20 / 10 / 30
auditor cucm, imp, cuc get, list + SQLQuery show + audit utils All device classes Screenshot GET /vmrest/ only (read-only) 30 / 5 / 20 / 5 / 30

Rate limit columns

Rate limit columns represent: AXL / SSH / RIS / Phone / Common (report) requests per minute, per client.

Default profile sync

At startup, the three default profiles are automatically copied from profiles/_default/ into profiles/. This ensures they stay up-to-date after an upgrade. Custom profiles created in profiles/ are never overwritten.


Custom Profile JSON Structure

Create a .json file in profiles/ with the following structure:

{
  "description": "Profile description",
  "product_scope": ["cucm", "imp", "all"],
  "cucm": {
    "axl": {
      "rate_limit": 30,
      "allowed_prefixes": ["get", "list"],
      "allowed_exact": ["executeSQLQuery"]
    },
    "ris": {
      "rate_limit": 10,
      "allowed_device_classes": ["Phone"],
      "allowed_statuses": ["Any"]
    },
    "ssh": {
      "rate_limit": 20,
      "allowed_prefixes": ["show"]
    }
  },
  "imp": {
    "axl": { "rate_limit": 30, "allowed_prefixes": ["get", "list"] },
    "ssh": { "rate_limit": 20, "allowed_prefixes": ["show"] }
  },
  "cuc": {
    "cupi": {
      "rate_limit": 30,
      "allowed_prefixes": ["GET /vmrest/", "PUT /vmrest/users"]
    },
    "ssh": {
      "rate_limit": 5,
      "allowed_prefixes": ["show"]
    }
  },
  "common": {
    "report": { "rate_limit": 30 }
  }
}

Matching rules:

Scope Rule Example
AXL prefix Any operation starting with the prefix is allowed list β†’ allows listPhone, listLine, listUser, …
AXL exact Exact operation name match only executeSQLQuery β†’ allows only that exact call
SSH prefix Command must equal the prefix or start with <prefix> (space-separated) show β†’ allows show version, show interface, …
RIS filters allowed_device_classes and allowed_statuses are optional; if absent, no filter is applied ["Phone"] β†’ restricts to phone device registrations
CUPI prefix Allowed CUPI calls β€” full <METHOD> <path> prefix match (CUC only) GET /vmrest/ β†’ allows any GET under /vmrest/

SSH section omission

If the ssh section is absent from a product in the profile, SSH access is denied for that product.

Deploying a custom profile:

  1. Create profiles/my_profile.json
  2. Assign it in AUTH_CLIENTS: name:subject:my_profile
  3. Profiles are hot-reloaded on file change β€” no server restart required

TLS & Transport Security

Self-Signed Certificate (Default)

At first startup, if no CA-signed certificate is already deployed (TLS_CA_SIGNED_CERT_FILE / TLS_CA_SIGNED_KEY_FILE), AI-Bridge auto-generates a self-signed TLS certificate with SAN entries derived from MCP_SERVER_SECURITY_ALLOWED_HOSTS. The certificate is stored in secrets/server.crt and secrets/server.key.

Midlife renewal: the certificate is automatically renewed when less than 50% of its validity period remains.


CA-Signed Certificate

To replace the self-signed certificate with a CA-issued one:

# Place your certificate and private key:
secrets/server.crt   # PEM-encoded certificate (include full chain)
secrets/server.key   # PEM-encoded private key

If both files exist at startup, the server uses them instead of the auto-generated certificate.

Recommended for production

Using a CA-signed certificate eliminates TLS trust warnings on AI agent clients and is strongly recommended for any production deployment.


Transport Security Settings

Key Description Example / Default
MCP_SERVER_SECURITY_TRANSPORT_ENABLED Enable DNS rebinding protection (validates Host and Origin headers) 1
MCP_SERVER_SECURITY_ALLOWED_HOSTS Comma-separated allowed Host header values. Supports * as port wildcard mcp.domain.com:8443,localhost:*
MCP_SERVER_SECURITY_ALLOWED_ORIGINS Comma-separated allowed Origin header values https://mcp.domain.com:8443

DNS rebinding protection

Setting MCP_SERVER_SECURITY_TRANSPORT_ENABLED=0 disables Host/Origin header validation. Only disable this in fully isolated network environments where DNS rebinding attacks are not a concern.


Backup Configuration

Encryption Model

AI-Bridge backups use hybrid encryption:

  1. A random AES-256-GCM session key encrypts the backup archive
  2. The session key is RSA-4096 OAEP wrapped using the server's public key
  3. An SHA-512 sidecar ensures integrity before restore

This means backups can be created by any process with access to the public key, but decryption requires the RSA private key.

Enabling Backups

BACKUP_ENABLED=1
BACKUP_DIR=./backups/
BACKUP_RETENTION=4
BACKUP_INTERVAL_HOURS=168

SFTP Export (Optional)

Push backups to a remote SFTP server automatically after each local backup:

BACKUP_SFTP_HOST=sftp.domain.com
BACKUP_SFTP_PORT=22
BACKUP_SFTP_USER=aibridge
BACKUP_SFTP_AUTH=key          # or: password
BACKUP_SFTP_KEY_FILE=./secrets/backup_ssh_key
BACKUP_SFTP_KNOWN_HOSTS=./secrets/sftp_known_hosts
BACKUP_SFTP_REMOTE_PATH=/backups/ai-bridge/
BACKUP_SFTP_RETENTION=52

Trust the SFTP server hostkey first

The very first thing to do is to register the remote SFTP server hostkey in BACKUP_SFTP_KNOWN_HOSTS. Without this entry, no transfer will ever happen β€” the server aborts before sending any credential (anti-MITM protection).

ssh-keyscan -p ${BACKUP_SFTP_PORT:-22} ${BACKUP_SFTP_HOST} >> ./secrets/sftp_known_hosts
chmod 600 ./secrets/sftp_known_hosts

Compare the printed fingerprint with the one published by the SFTP administrator (out-of-band) before keeping the file.

Place the SSH private key at secrets/backup_ssh_key (or override the path with BACKUP_SFTP_KEY_FILE). The corresponding public key must be authorized on the SFTP server.

BACKUP_SFTP_AUTH=key
BACKUP_SFTP_KEY_FILE=./secrets/backup_ssh_key

Set BACKUP_SFTP_AUTH=password. The password must be encrypted before storage using:

python scripts/encrypt_sftp_password.py

The resulting token is automatically copied into .env as BACKUP_SFTP_PASSWORD_ENC. It is never stored in plaintext. To verify an existing encrypted token without storing a new one:

python scripts/encrypt_sftp_password.py --verify <token>

See the Operations page for details.

BACKUP_SFTP_AUTH=password

SFTP key permissions

The secrets/backup_ssh_key private key file should be chmod 600 and owned by the service account.