mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
feat(services): sleep mode — scale-to-zero on idle, wake on traffic
Services gain sleep_enabled + sleep_idle_minutes. A 30s sweeper tracks container NetworkIO deltas; a flat counter for the configured window scales the service to zero (status=sleeping, workload containers removed). While slept, a busybox wake container takes over the service's Traefik host rule at priority 100: it serves a self-reloading "waking" page and calls the unauthenticated idempotent GET/POST /api/v1/internal/wake/:id, which reconciles the service back to running. Wake placeholders carry a dedicated label (containr.wake-for) so runtime state never counts them; the sweeper prunes them once the workload is up. The wake URL resolves the API via its container name when containerized, else the edge network's IPAM gateway — bare-metal installs need HOST=0.0.0.0 for the callback. Surfaces: PUT fields on services, POST /services/:id/sleep|wake, CLI --sleep/--sleep-idle + services sleep|wake, 2 MCP tools (89 total), OpenAPI paths/schema, and a Settings > Sleep card with toggle, idle minutes, and manual sleep/wake plus a Sleeping badge. Verified live: idle sweep scaled whoami-web to zero; the wake container served the page on the edge network and its callback woke the service (container recreated, status running, placeholder pruned). Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
b4baa853b2
commit
dbc87bf34c
15 files changed
+957
-49
No files matched your search
@@ -98,6 +98,9 @@ func SetupRoutes(router *gin.Engine, db *database.DB, redis *database.Redis, cfg
|
||||
// Start the cron scheduler — executes due cron_jobs via docker exec.
|
||||
StartCronScheduler(context.Background(), db, dockerClient)
|
||||
|
||||
// Sleep sweeper — scales idle services to zero and prunes wake containers.
|
||||
StartSleepSweeper(context.Background(), db, dockerClient, deploymentEngine, cfg.Port)
|
||||
|
||||
// Initialize database handler
|
||||
databaseHandler := NewDatabaseHandler(db.DB, dockerClient)
|
||||
|
||||
@@ -158,6 +161,7 @@ func SetupRoutes(router *gin.Engine, db *database.DB, redis *database.Redis, cfg
|
||||
c.Set("deployment_engine", deploymentEngine)
|
||||
}
|
||||
c.Set("deploy_queue", deployQueue)
|
||||
c.Set("api_port", cfg.Port)
|
||||
c.Set("scheduler", scheduler)
|
||||
c.Set("metrics_collector", metricsCollector)
|
||||
c.Set("auto_scaler", autoScaler)
|
||||
@@ -249,6 +253,10 @@ func SetupRoutes(router *gin.Engine, db *database.DB, redis *database.Redis, cfg
|
||||
public.POST("/auth/login", handleLogin)
|
||||
public.POST("/auth/register", handleRegister)
|
||||
public.GET("/maintenance", handleMaintenancePage)
|
||||
|
||||
// Wake callback for sleep-mode placeholders. Unauthenticated but
|
||||
// non-destructive: it only reconciles the service back to running.
|
||||
public.Any("/internal/wake/:id", handleServiceWake)
|
||||
}
|
||||
|
||||
// Read routes — public browsing. OptionalAuth resolves a session when
|
||||
@@ -335,6 +343,8 @@ func SetupRoutes(router *gin.Engine, db *database.DB, redis *database.Redis, cfg
|
||||
authed.POST("/services/:id/start", handleServiceStart)
|
||||
authed.POST("/services/:id/stop", handleServiceStop)
|
||||
authed.POST("/services/:id/restart", handleServiceRestart)
|
||||
authed.POST("/services/:id/sleep", handleServiceSleep)
|
||||
authed.POST("/services/:id/wake", handleServiceWakeNow)
|
||||
authed.POST("/services/:id/redeploy", handleServiceRedeploy)
|
||||
authed.POST("/services/:id/clone", handleCloneService)
|
||||
authed.POST("/services/:id/move", handleMoveService)
|
||||
|
||||
Reference in new issue
Block a user