- Config writes to DATA_DIR/state/instance.yaml (writable) instead of
CONFIG_DIR (read-only :ro in Docker)
- instance_config.py checks DATA_DIR/state/ first, then falls back to
CONFIG_DIR for backward compat
- CalVer counter is now global across channels (*-YYYY.MM.*) per spec
- Keboola error messages sanitized — log full error, return generic msg
- chmod in secrets.py wrapped in try/except for Windows compat
- Setup wizard JS handles 401 (expired JWT) with user-facing message
- deploy.yml changed to workflow_dispatch only (no duplicate test runs)
- Smoke test uses docker-compose.prod.yml + AGNES_TAG instead of sed
- docker-compose.prod.yml uses ${AGNES_TAG:-stable} env var
663 tests pass. 8 E2E verification tests pass.
40 lines
1.3 KiB
Python
40 lines
1.3 KiB
Python
"""Auto-generate and persist secrets that survive container restarts."""
|
|
import logging
|
|
import os
|
|
import secrets
|
|
from pathlib import Path
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _load_or_generate(env_var: str, file_name: str) -> str:
|
|
"""Load secret from env var, or from file, or generate and persist."""
|
|
val = os.environ.get(env_var, "")
|
|
if val:
|
|
return val
|
|
data_dir = Path(os.environ.get("DATA_DIR", "./data"))
|
|
secret_path = data_dir / "state" / file_name
|
|
if secret_path.exists():
|
|
return secret_path.read_text().strip()
|
|
secret_path.parent.mkdir(parents=True, exist_ok=True)
|
|
val = secrets.token_hex(32)
|
|
secret_path.write_text(val)
|
|
try:
|
|
secret_path.chmod(0o600)
|
|
except OSError:
|
|
pass # chmod not supported on all platforms (e.g., Windows)
|
|
logger.info(
|
|
"Auto-generated %s -> %s (set %s in .env to use a fixed value)",
|
|
file_name, secret_path, env_var,
|
|
)
|
|
return val
|
|
|
|
|
|
def get_jwt_secret() -> str:
|
|
"""Get JWT secret key from env, file, or auto-generate."""
|
|
return _load_or_generate("JWT_SECRET_KEY", ".jwt_secret")
|
|
|
|
|
|
def get_session_secret() -> str:
|
|
"""Get session secret from env, file, or auto-generate."""
|
|
return _load_or_generate("SESSION_SECRET", ".session_secret")
|