feat(worker): add Docker Compose discovery and management
Некоторые проверки не удались
CI / test (push) Failing after 6s
Docker / Build and publish worker image (push) Failing after 8s

Add an internal/compose package that discovers Compose projects via
`docker compose ls` + `docker ps` labels (grouped by
com.docker.compose.project/service) and enriches each container with
`docker inspect` ports/mounts and Traefik router labels. Management
runs `docker compose` in each project's working directory for
up/down/stop/restart/pull plus per-service variants and log tails.

Wire it into the webapp: a 60s ComposeRefresher (constructed in New,
started in Start, stopped in Close), a /compose list + detail + logs
HTML surface, and /web/api/compose/* JSON endpoints (list, detail,
logs, project/service lifecycle). Browser lifecycle POSTs are
session+CSRF protected; the /web/api/* variants accept HTTP basic auth.
WORKER_COMPOSE_ENABLED defaults on (false to disable).

Tests cover discovery parsing/grouping/traefik/summary, the action
allowlists, and the full handler surface (list/detail/logs HTML+API,
CSRF enforcement, disabled/unknown-action rejection, audit writes,
success+failure exec paths) via a stub Docker binary.
Этот коммит содержится в:
root
2026-07-29 21:31:52 +03:00
родитель e987f24903
Коммит ff0d2f088f
19 изменённых файлов: 2115 добавлений и 0 удалений

Просмотреть файл

@@ -51,6 +51,23 @@ func (s *Server) routes() {
s.mux.Handle("POST /settings/rotate-token", s.requireSession(s.handleRotateToken))
s.mux.Handle("GET /updates", s.requireSession(s.handleUpdates))
// Docker Compose discovery + management. The list/detail/logs pages
// are session-protected HTML; the lifecycle endpoints accept either a
// session (browser, CSRF-checked in the handler) or HTTP basic auth
// (scripting, on the /web/api/compose/* prefix). Compose is disabled
// per host via WORKER_COMPOSE_ENABLED=false; when off, the management
// handlers return 503 and the list page renders a banner.
s.mux.Handle("GET /compose", s.requireSession(s.handleComposeList))
s.mux.Handle("GET /compose/{project}", s.requireSession(s.handleComposeDetail))
s.mux.Handle("GET /compose/{project}/logs", s.requireSession(s.handleComposeLogsPage))
s.mux.Handle("POST /compose/{project}/{action}", s.requireSession(s.handleComposeProjectAction))
s.mux.Handle("POST /compose/{project}/service/{service}/{action}", s.requireSession(s.handleComposeServiceAction))
s.mux.Handle("GET /web/api/compose", s.requireSession(s.handleComposeAPIList))
s.mux.Handle("GET /web/api/compose/{project}", s.requireSession(s.handleComposeAPIDetail))
s.mux.Handle("GET /web/api/compose/{project}/logs", s.requireSession(s.handleComposeLogsAPI))
s.mux.Handle("POST /web/api/compose/{project}/{action}", s.requireSession(s.handleComposeProjectAction))
s.mux.Handle("POST /web/api/compose/{project}/service/{service}/{action}", s.requireSession(s.handleComposeServiceAction))
// Health endpoint for the cmd health subcommand and for the
// operator to confirm the listener is up without going through the
// login form. Returns 200 with a tiny body.