Skip to content

Commit 2841782

Browse files
feat: add POST /system/service/{name}/recreate for Docker Compose service recreation
Add endpoint for recreating Docker Compose services via `docker compose up -d`. Used by API for Pi-hole password sync — the API container lacks the compose plugin, so it delegates to HAL which runs on the host. Allowlisted services: pihole. Gated by requireFullTier. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 29d8fa8 commit 2841782

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

internal/handlers/routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func SetupRoutes(r chi.Router, h *HALHandler) {
4343
r.Post("/service/{name}/start", h.StartService)
4444
r.Post("/service/{name}/stop", h.StopService)
4545
r.Post("/service/{name}/restart", h.RestartService)
46+
r.Post("/service/{name}/recreate", h.requireFullTier(h.RecreateComposeService))
4647
})
4748

4849
// Power (Battery, UPS, RTC, Watchdog)

internal/handlers/system.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,50 @@ func (h *HALHandler) StopService(w http.ResponseWriter, r *http.Request) {
753753
successResponse(w, fmt.Sprintf("service %s stopped", name))
754754
}
755755

756+
// ============================================================================
757+
// Docker Compose Service Recreate
758+
// ============================================================================
759+
760+
// composeServiceAllowlist restricts which Docker Compose services can be recreated.
761+
var composeServiceAllowlist = map[string]string{
762+
"pihole": "/cubeos/coreapps/pihole/appconfig",
763+
}
764+
765+
// RecreateComposeService runs `docker compose up -d` for a whitelisted service.
766+
// @Summary Recreate Docker Compose service
767+
// @Description Recreates a Docker Compose service by running `docker compose up -d` in its appconfig directory. Used when environment changes require container recreation (e.g. Pi-hole password sync).
768+
// @Tags System
769+
// @Accept json
770+
// @Produce json
771+
// @Param name path string true "Service name (must be in allowlist)" example(pihole)
772+
// @Success 200 {object} SuccessResponse
773+
// @Failure 400 {object} ErrorResponse "Invalid service name"
774+
// @Failure 403 {object} ErrorResponse "Service not in allowlist"
775+
// @Failure 500 {object} ErrorResponse
776+
// @Router /system/service/{name}/recreate [post]
777+
func (h *HALHandler) RecreateComposeService(w http.ResponseWriter, r *http.Request) {
778+
name := chi.URLParam(r, "name")
779+
if name == "" {
780+
errorResponse(w, http.StatusBadRequest, "service name is required")
781+
return
782+
}
783+
784+
composeDir, ok := composeServiceAllowlist[name]
785+
if !ok {
786+
errorResponse(w, http.StatusForbidden, fmt.Sprintf("service %q is not in the compose recreate allowlist", name))
787+
return
788+
}
789+
790+
output, err := execWithTimeout(r.Context(), "docker", "compose", "-f", composeDir+"/docker-compose.yml", "up", "-d")
791+
if err != nil {
792+
log.Printf("RecreateComposeService: docker compose up -d failed for %s: %v (output: %s)", name, err, output)
793+
errorResponse(w, http.StatusInternalServerError, sanitizeExecError("recreate compose service", err))
794+
return
795+
}
796+
797+
successResponse(w, fmt.Sprintf("service %s recreated via docker compose", name))
798+
}
799+
756800
// ============================================================================
757801
// Extended System Information Handlers (Fix #13)
758802
// ============================================================================

0 commit comments

Comments
 (0)