Publish curated Unraid-AppOps-Gateway source from a506550ef83ef2d81651e37c98a89f7ee330c730
This commit is contained in:
1 parent
2facb8de34
commit
767e66921e
68 files changed
+15924
-313
No files matched your search
@@ -2,6 +2,8 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
import stat
|
||||
from typing import Any
|
||||
|
||||
|
||||
@@ -34,6 +36,38 @@ def _secret_value(name: str, file_name: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _private_secret_file(path: str | None) -> str | None:
|
||||
"""Read a small owner-private regular secret file without following links."""
|
||||
if not path:
|
||||
return None
|
||||
descriptor: int | None = None
|
||||
try:
|
||||
source = Path(path)
|
||||
before = source.lstat()
|
||||
if not source.is_absolute() or source.is_symlink() or not stat.S_ISREG(before.st_mode) or before.st_size > 512:
|
||||
return None
|
||||
descriptor = os.open(
|
||||
source,
|
||||
os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_CLOEXEC", 0),
|
||||
)
|
||||
opened = os.fstat(descriptor)
|
||||
if not stat.S_ISREG(opened.st_mode) or opened.st_size > 512:
|
||||
return None
|
||||
if os.name != "nt" and (
|
||||
opened.st_uid != os.geteuid() or stat.S_IMODE(opened.st_mode) & 0o077
|
||||
):
|
||||
return None
|
||||
value = os.read(descriptor, 513)
|
||||
if len(value) > 512:
|
||||
return None
|
||||
return value.decode("utf-8").strip() or None
|
||||
except (OSError, UnicodeDecodeError):
|
||||
return None
|
||||
finally:
|
||||
if descriptor is not None:
|
||||
os.close(descriptor)
|
||||
|
||||
|
||||
def _env_list(name: str, default: list[str] | None = None) -> list[str]:
|
||||
value = os.getenv(name)
|
||||
if value is None:
|
||||
@@ -86,6 +120,7 @@ class Settings:
|
||||
mcp_token: str | None = None
|
||||
mcp_token_file: str | None = None
|
||||
mcp_allowed_hosts: list[str] | None = None
|
||||
runner_statistics_token_file: str | None = None
|
||||
n8n_enabled: bool = False
|
||||
n8n_token: str | None = None
|
||||
n8n_token_file: str | None = None
|
||||
@@ -120,6 +155,11 @@ class Settings:
|
||||
self.mcp_token = token_file.read().strip() or None
|
||||
except OSError:
|
||||
self.mcp_token = None
|
||||
default_runner_statistics_token_file = str(Path(self.data_dir) / "runner-statistics-token")
|
||||
self.runner_statistics_token_file = _env_value(
|
||||
"APPOPS_RUNNER_STATISTICS_TOKEN_FILE",
|
||||
self.runner_statistics_token_file or default_runner_statistics_token_file,
|
||||
)
|
||||
allowed_hosts = _env_value("APPOPS_MCP_ALLOWED_HOSTS", "localhost:*,127.0.0.1:*,198.18.10.150:*") or ""
|
||||
self.mcp_allowed_hosts = [item.strip() for item in allowed_hosts.split(",") if item.strip()]
|
||||
self.n8n_enabled = _env_bool("APPOPS_ENABLE_N8N", self.n8n_enabled)
|
||||
@@ -267,6 +307,10 @@ class Settings:
|
||||
"APPOPS_VERIFY_TLS": self.verify_tls,
|
||||
}
|
||||
|
||||
def read_runner_statistics_token(self) -> str | None:
|
||||
"""Read the fixed private credential at request time for no-restart provisioning."""
|
||||
return _private_secret_file(self.runner_statistics_token_file)
|
||||
|
||||
def redact_env_preview(self) -> dict[str, Any]:
|
||||
return {
|
||||
"APPOPS_BIND_HOST": self.bind_host,
|
||||
@@ -291,6 +335,8 @@ class Settings:
|
||||
"APPOPS_MCP_TOKEN_FILE": bool(self.mcp_token_file),
|
||||
"APPOPS_MCP_TOKEN_CONFIGURED": bool(self.mcp_token),
|
||||
"APPOPS_MCP_ALLOWED_HOSTS": list(self.mcp_allowed_hosts or []),
|
||||
"APPOPS_RUNNER_STATISTICS_TOKEN_FILE": bool(self.runner_statistics_token_file),
|
||||
"APPOPS_RUNNER_STATISTICS_TOKEN_CONFIGURED": bool(self.read_runner_statistics_token()),
|
||||
"APPOPS_ENABLE_N8N": self.n8n_enabled,
|
||||
"APPOPS_N8N_TOKEN_FILE": bool(self.n8n_token_file),
|
||||
"APPOPS_N8N_TOKEN_CONFIGURED": bool(self.n8n_token),
|
||||
|
||||
@@ -53,6 +53,15 @@ def require_service_request(request: Request, service: str) -> str:
|
||||
enabled, expected = settings.mcp_enabled, settings.mcp_token
|
||||
elif service == "n8n":
|
||||
enabled, expected = settings.n8n_enabled, settings.n8n_token
|
||||
elif service == "mcp_runner_statistics":
|
||||
expected = settings.read_runner_statistics_token()
|
||||
if not expected:
|
||||
raise HTTPException(404, "runner statistics MCP integration is disabled")
|
||||
if not token_is_strong(expected):
|
||||
raise HTTPException(503, "runner statistics MCP authentication is not safely configured")
|
||||
if settings.mcp_token and hmac.compare_digest(expected, settings.mcp_token):
|
||||
raise HTTPException(503, "runner statistics MCP token must be separate from the governed MCP token")
|
||||
enabled = True
|
||||
elif service == "voice":
|
||||
enabled, expected = True, settings.voice_token or os.getenv("APP_OPS_VOICE_TOKEN")
|
||||
else:
|
||||
|
||||
Reference in new issue
Block a user