186 lines
6.3 KiB
Python
186 lines
6.3 KiB
Python
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()
|
||
)
|