feat(runner): tgit-runner — сервисы в песочнице на хосте, деплой после push (#13)
CI / ruff · mypy · pytest (pull_request) Successful in 4m3s
CI / ruff · mypy · pytest (pull_request) Successful in 4m3s
- infra/runner/tgit-runner: deploy/restart/remove/list по SSH forced command; аргументы не исполняются и не глобятся; архив — только файлы и папки, без «..», ≤ 20 МБ, есть main.py; health_url — только localhost - песочница: uid 10001, read-only, tmpfs noexec, cap-drop ALL, no-new-privileges, 128 МБ без swap, 0.25 CPU, 64 PID, --network none (сеть — только по egress-allow админа), лимит 10 сервисов, 2 образа - новая версия стартует рядом (-next) и заменяет старую, только став healthy; иначе удаляется, старая работает, exit 3 - бот: после push git archive изменённых services/<name>/ → runner; автору — «🚀 работает» или «⛔ не развёрнут» с логом, команде — алерт ⛔; удалённая папка → remove; «♻️ Перезапустить» в «Статусе» для админа (S-5) - examples/escape_attempts: все попытки выбраться — BLOCKED на живом Docker - install.sh, authorized_keys.example; CD бота обновляет runner на хосте Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+11
-3
@@ -29,6 +29,8 @@ from app.logs_setup import setup_logging
|
||||
from app.repo.drafts import DraftService
|
||||
from app.repo.git import UploadsRepo
|
||||
from app.repo.publisher import Publisher
|
||||
from app.runner.client import RunnerClient
|
||||
from app.runner.deployer import Deployer
|
||||
from app.runtime.checks import Checker, load_checks
|
||||
from app.runtime.docker import DockerClient
|
||||
from app.runtime.snapshots import SnapshotStore, snapshot_loop
|
||||
@@ -47,6 +49,7 @@ def build_dispatcher(
|
||||
checker: Checker | None = None,
|
||||
agent: Agent | None = None,
|
||||
alerts: AlertService | None = None,
|
||||
runner: RunnerClient | None = None,
|
||||
) -> Dispatcher:
|
||||
dp = Dispatcher(storage=make_storage(sessions))
|
||||
gate = AccessMiddleware(access, texts)
|
||||
@@ -54,10 +57,13 @@ def build_dispatcher(
|
||||
dp.message.outer_middleware(gate)
|
||||
dp.callback_query.outer_middleware(gate)
|
||||
dp.include_router(members.build_router(texts, access))
|
||||
deployer = Deployer(repo, runner, alerts) if runner else None
|
||||
dp.include_router(
|
||||
commit.build_router(texts, repo, DraftService(sessions), Publisher(repo, sessions), commit_url_template)
|
||||
commit.build_router(
|
||||
texts, repo, DraftService(sessions), Publisher(repo, sessions), commit_url_template, deployer
|
||||
)
|
||||
)
|
||||
dp.include_router(status.build_router(texts, docker, checker))
|
||||
dp.include_router(status.build_router(texts, docker, checker, runner))
|
||||
dp.include_router(logs.build_router(texts, docker, LogJournal(sessions), agent, alerts))
|
||||
dp.include_router(alerts_router.build_router(texts, alerts, docker))
|
||||
dp.include_router(common.build_router(texts, access))
|
||||
@@ -99,6 +105,7 @@ async def run(settings: Settings) -> None:
|
||||
checker,
|
||||
agent,
|
||||
alerts,
|
||||
RunnerClient(settings.runner) if settings.runner.host else None,
|
||||
)
|
||||
rt = settings.runtime
|
||||
background = [
|
||||
@@ -111,12 +118,13 @@ async def run(settings: Settings) -> None:
|
||||
asyncio.create_task(snapshot_loop(docker, store, settings.docker.snapshot_interval, monitor.on_round))
|
||||
)
|
||||
log.info(
|
||||
"tgit %s starting, %d bootstrap admin(s), telegram %s, docker %s, agent %s",
|
||||
"tgit %s starting, %d bootstrap admin(s), telegram %s, docker %s, agent %s, runner %s",
|
||||
__version__,
|
||||
len(settings.access.admin_ids),
|
||||
"via proxy" if proxy else "direct",
|
||||
"on" if docker else "off",
|
||||
settings.llm.provider if agent else "off",
|
||||
settings.runner.host or "off",
|
||||
)
|
||||
try:
|
||||
await dp.start_polling(bot, handle_signals=True)
|
||||
|
||||
Reference in New Issue
Block a user