Files
appfactory-portal/app/config.py
T
JiriUhlir a7a53f064c fix
2026-06-22 14:08:37 +02:00

186 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
DEPLOY_SCRIPT = "/tools/deploy-app.sh"
# Holé názvy shell skriptů, které smí Portál zařadit jako Worker run_script job.
# Worker přijímá v poli script_name jen HOLÝ název bez cesty (adresář scripts/ si doplní sám)
# a cokoli s cestou ("scripts/...", "../...", absolutní cesta) odmítne hláškou
# "Invalid script name". Stejné pravidlo má i portál sám v
# app/routes/scheduled_scripts.validate_script_name. Tuto množinu validujeme i tady, ať
# nezaložíme job, který Worker stejně zahodí (přesně na tom padal job 221).
ALLOWED_WORKER_SCRIPTS = {
"deploy-app.sh",
"delete-app.sh",
"deploy-core-service.sh",
"generate-caddyfile.sh",
"redeploy-enabled-apps.sh",
"backup-appfactory.sh",
}
# Mazání aplikace NEPROVÁDÍ portál přímo běží bez práv k workspace, takže rm padá na
# "Permission denied". Portál jen zařadí run_script job a Worker spustí tento skript
# v appfactory-tools/scripts/ s právy serveru.
# POZOR: holý název, NE "scripts/delete-app.sh" cesta v názvu = "Invalid script name".
DELETE_APP_SCRIPT_NAME = "delete-app.sh"
def is_allowed_worker_script(script_name: str) -> bool:
"""True jen pro holý, povolený název skriptu pro Worker run_script job.
Odmítne prázdný název, název nekončící .sh, název s cestou ("/", "\\", "..") i název
mimo allowlist tedy přesně případy, na které Worker hlásí "Invalid script name"."""
if not script_name or not script_name.endswith(".sh"):
return False
if "/" in script_name or "\\" in script_name or ".." in script_name:
return False
return script_name in ALLOWED_WORKER_SCRIPTS
BACKUP_SCRIPT = "/tools/backup-appfactory.sh"
RESTORE_SCRIPT = "/tools/restore-appfactory.sh"
GENERATE_COMPOSE_SCRIPT = "/tools/generate-apps-compose.sh"
CATALOG_FILE = "/opt/appfactory/apps/catalog.yml"
APPFACTORY_ENV = "/opt/appfactory/config/appfactory.env"
DEFAULT_BACKUP_DIR = "/opt/appfactory/backups"
DEFAULT_GITEA_ORG = "appfactory"
INTERNAL_GITEA_URL = "http://appfactory-gitea:3000"
PORTAL_PREFIX = "/portal"
AUTH_MODES = {"google", "mixed", "local"}
def read_env_value(key: str, default: str = "") -> str:
try:
with open(APPFACTORY_ENV, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line.startswith(f"{key}="):
return line.split("=", 1)[1].strip().strip('"')
except FileNotFoundError:
# appfactory.env nemusí existovat (např. lokální vývoj) očekávaný stav, vrátíme default.
pass
except Exception as exc:
# Neočekávaná chyba čtení configu nesmí být tichá.
from app.logging_config import get_logger
get_logger(__name__).warning("Čtení %s (klíč %s) selhalo: %s", APPFACTORY_ENV, key, exc)
return default
def read_env_bool(key: str, default: bool = False) -> bool:
value = read_env_value(key, "")
if value == "":
return default
return value.strip().lower() in {"1", "true", "yes", "on"}
def get_portal_public_url() -> str:
public_url = read_env_value("APPFACTORY_PORTAL_PUBLIC_URL", "").rstrip("/")
if public_url:
return public_url
domain = read_env_value("APPFACTORY_PORTAL_DOMAIN", "").strip()
if read_env_bool("APPFACTORY_ENABLE_HTTPS") and domain:
return f"https://{domain}"
return ""
def get_google_redirect_uri() -> str:
redirect_uri = read_env_value("GOOGLE_REDIRECT_URI", "").strip()
if redirect_uri:
return redirect_uri
public_url = get_portal_public_url()
if public_url:
return f"{public_url}{PORTAL_PREFIX}/auth/google/callback"
return ""
def get_gitea_public_url() -> str:
return (read_env_value("GITEA_URL", "") or read_env_value("GITEA_ROOT_URL", "")).rstrip("/")
def get_gitea_server_url() -> str:
return (
read_env_value("GITEA_SERVER_URL", "")
or read_env_value("GITEA_INTERNAL_URL", "")
or get_gitea_public_url()
or INTERNAL_GITEA_URL
).rstrip("/")
def get_gitea_admin_token() -> str:
return (
read_env_value("GITEA_ADMIN_TOKEN", "")
or read_env_value("GITEA_API_TOKEN", "")
or read_env_value("GITEA_TOKEN", "")
).strip()
def get_gitea_redirect_uri() -> str:
redirect_uri = read_env_value("GITEA_OAUTH_REDIRECT_URI", "").strip()
if redirect_uri:
return redirect_uri
public_url = get_portal_public_url()
if public_url:
return f"{public_url}{PORTAL_PREFIX}/auth/gitea/callback"
return ""
def is_gitea_oauth_button_enabled() -> bool:
return bool(
get_gitea_public_url()
and read_env_value("GITEA_OAUTH_CLIENT_ID", "")
and read_env_value("GITEA_OAUTH_CLIENT_SECRET", "")
and get_gitea_redirect_uri()
)
def get_appfactory_host(request_host: str = "") -> str:
configured_host = read_env_value("APPFACTORY_HOST", "").strip()
if configured_host:
return configured_host
public_url = get_portal_public_url()
if public_url:
return public_url.split("://", 1)[-1].split("/", 1)[0].split(":", 1)[0]
return (request_host or "").split(":", 1)[0]
def get_auth_domain_readiness() -> dict[str, bool]:
google_enabled = read_env_bool("GOOGLE_OAUTH_ENABLED")
google_client_id = read_env_value("GOOGLE_CLIENT_ID", "")
google_client_secret = read_env_value("GOOGLE_CLIENT_SECRET", "")
google_redirect_uri = get_google_redirect_uri()
return {
"portal_public_url_configured": bool(get_portal_public_url()),
"auth_mode_google": get_auth_mode() == "google",
"auth_mode_mixed": get_auth_mode() == "mixed",
"auth_mode_local": get_auth_mode() == "local",
"google_oauth_enabled": google_enabled,
"google_oauth_configured": bool(google_client_id and google_client_secret and google_redirect_uri),
"google_redirect_uri_configured": bool(google_redirect_uri),
"https_enabled": read_env_bool("APPFACTORY_ENABLE_HTTPS"),
}
def get_auth_mode() -> str:
mode = read_env_value("AUTH_MODE", "mixed").strip().lower()
if mode in AUTH_MODES:
return mode
return "mixed"
def is_google_oauth_button_enabled() -> bool:
return bool(
read_env_bool("GOOGLE_OAUTH_ENABLED")
and read_env_value("GOOGLE_CLIENT_ID", "")
and read_env_value("GOOGLE_CLIENT_SECRET", "")
and get_google_redirect_uri()
)