Commit Graph
73 Commits
Author SHA1 Message Date
Tomas DvorakandDevin 3bf44dbcd5 Add global recent-deployments feed for the projects dashboard
The RECENT DEPLOYMENTS section queried the builds table, so
image-sourced services — which deploy without building — never
appeared and the dashboard reported "no deployments yet" while
containers were running.

- GET /api/v1/deployments lists latest deployments across the
  caller's services, joined with service + project names, ordered
  newest-first with a bounded limit
- ProjectsPage consumes it via listRecentDeployments; the DEPLOYS
  stat card now counts real deployments
- is_admin is now projected in register/upsert user responses, and
  the Better Auth mirror grants admin to the first mirrored account
  (previously only the legacy register path did)

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 21:07:01 +02:00
Tomas DvorakandDevin ff356230fe feat: real service runtime — private networks, replicas, lifecycle, rollback
Services now reconcile into actual Docker containers instead of
bookkeeping rows:

- Per-project bridge network with service-name DNS aliases and
  ${{service.KEY}} env expansion (HOST/PORT builtins)
- Replicas, published ports, Traefik domain routing, health checks,
  restart policies persisted on the service spec; spec-hash drift
  recreates containers on redeploy
- Lifecycle API: start/stop/restart/redeploy, live runtime state,
  real image rollback (replaces the fake sleep)
- Manual scale and PUT replicas reconcile live containers; service
  and project delete remove their containers and the project network
- Canvas: real URLs, replica badges, env filter, right-click
  deploy/restart/stop/delete menu, empty state
- Service detail: real lifecycle buttons, live replica/URL strip,
  Networking editor, ${{ }} variable helper
- Fix prebuilt-image pulls racing the tag (drain the pull stream)

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 19:31:19 +02:00
Tomas DvorakandDevin c16b32f56a feat: owner accounts and in-app platform settings
- First registered account becomes admin (users.is_admin, backfilled
  to earliest account); better-auth mirror path does the same
- Public registration closes after bootstrap; admin can reopen it via
  GET/PUT /api/v1/settings (admin-gated) or SIGNUP_ENABLED env
- In-app settings override env vars; secrets stored in app_settings,
  token values never returned by the API
- Saving a Cloudflare Tunnel token pulls and runs a managed
  cloudflared sidecar on the backend's networks; clearing removes it;
  startup restores it from saved settings
- group_add DOCKER_GID grants the non-root backend user Docker socket
  access; install.sh probes the in-container socket gid
- Settings page gains an owner-only Platform section (registration
  toggle, tunnel token, link to Cloudflare dashboard); sign-up page
  stays reachable while registration is open
- OpenAPI spec + generated types updated; manual user creation now
  requires admin

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 18:05:22 +02:00
Tomas DvorakandDevin 060038cb6f feat: swap Redis for Dragonfly, fix remote installs, self-hosted email-only auth
- Replace redis:7-alpine with Dragonfly (Redis-protocol compatible; go-redis
  client unchanged) in root, infra, and backend-dev compose files. Bounded
  defaults --proactor_threads/--maxmemory so it boots on small VPSs.
- Frontend now defaults to same-origin API calls; nginx proxies /api/* to the
  backend (WebSocket-aware, docker DNS resolver for upstream re-resolution).
  Removes the baked localhost:8082 that broke installs reached by IP/domain.
- /auth/bootstrap returns configured OAuth providers; the UI hides
  GitHub/Google buttons unless real credentials exist. Self-hosted stays
  email/password only - OAuth reserved for a future managed offering.
- Sidecar ignores PLACEHOLDER_* OAuth credentials and trusts same-host
  origins dynamically (Origin host == request Host) so remote installs pass
  origin checks without config; cross-origin requests still rejected.
- install.sh writes ENVIRONMENT=production; COOKIE_SECURE=false in
  production is now a startup warning instead of fatal (plain-HTTP installs).
- Bootstrap failure surfaces an error notice instead of silently rendering
  the sign-in page.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 17:24:43 +02:00
Tomas DvorakandDevin 430370dbc7 docs: add app screenshots, default compose to published :latest images
- docker-compose.yml now references ghcr.io/dvorinka/containr-*:latest with
  pull_policy: missing, so `docker compose up` pulls published images by
  default; CONTAINR_VERSION pins a release tag, build config retained for
  source builds
- install.sh pulls instead of always building (CONTAINR_BUILD=1 or a failed
  pull falls back to a source build)
- README gains a Screenshots section (dashboard, canvas, service detail,
  databases) captured from a live local stack, plus updated install docs

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-21 16:20:16 +02:00
Tomas DvorakandDevin cec7d92e83 docs: sync guides to verified behavior
Rewrites AUTOSCALING.md — it was generic Cloudflare/Swarm advice that
predated the platform autoscaler. Now documents the real /scaling API,
per-service policy fields, manual scale, and the in-memory caveat.

Adds OPERATIONS.md covering cron jobs, exec console, managed databases,
preview environments (bookkeeping-only caveat), security scans
(heuristic, not Trivy), HA, and node-agent onboarding.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 18:37:26 +02:00
Tomas DvorakandDevin f357c7adee feat: service exec console
POST /services/:id/exec runs a one-off sh -c command inside the
service's container via ExecRun (30s ceiling, 64KB output cap), with
the standard service→project ownership check. Console section on the
service detail page submits commands and renders captured output with
exit codes.

Verified live: real exec output, stderr + exit code propagation, 404 on
foreign service, 401 unauthenticated, 400 on empty command.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 18:34:58 +02:00
Tomas Dvorak 07ac54aa05 feat(notifications): persistent notification center
- notifications table (goose migration) + sqlc queries; list with unread
  count, mark-one, mark-all endpoints under /notifications.
- Deployment success/failure now writes a notification row for the owner
  (insertUserNotification — best-effort, never breaks the deploy flow).
- Shell bell reads the real persisted feed: unread badge, click-to-read,
  mark-all, 30s polling. Replaces the transient builds+audit derivation.

Verified e2e: migration applies clean, list/read/read-all round-trips.

Authored By: TDvorak <[email protected]>
2026-09-17 17:52:37 +02:00
Tomas Dvorak 08d92fb10f feat(backups): scheduled database backups via cron tick
- database_services gains backup_schedule + next_backup_at (goose migration,
  sqlc schema, new queries).
- PUT /databases/:id accepts backup_schedule — validated cron expression,
  empty string disables; invalid returns 400.
- Cron scheduler tick now also folds due databases into the existing
  snapshot pipeline (createBackupProcess).
- Databases page backups section gets a schedule row showing current expr
  and next run.
- resolveBackupConfig reports real next_backup/enabled instead of a fake
  last+24h estimate.
- Verified e2e: '* * * * *' produced three consecutive completed archives
  containing the seeded payload; invalid schedule rejected.

Authored By: TDvorak <[email protected]>
2026-09-17 17:46:02 +02:00
Tomas Dvorak bcd3a83aa4 feat(backups): archive download endpoint + GitHub App install surfacing
- GET /databases/:id/backups/:bid/download streams the archive out of the
  containr-db-backups volume via a stopped utility container and
  CopyFromContainer; daemon logs sanitize non-UTF-8 so binary through
  container logs was not viable. Verified e2e: byte-identical payload,
  404 missing/cross-user, 401 unauthenticated.
- Settings Git Providers gains an Install GitHub App action that opens the
  install_url and completes the installation_id return via connect endpoint.
- ROADMAP: mark both items done with verification evidence.

Authored By: TDvorak <[email protected]>
2026-09-17 17:30:30 +02:00
Tomas DvorakandDevin a1e43524f5 Build real cron engine + service-detail Cron UI
The cron backend was a stub: calculateNextRun returned now+1h for any
input and executeCronJob slept 2s then wrote "success" without running
the command. Now robfig/cron parses and validates schedules (400 on
invalid), a 60s scheduler goroutine executes due enabled jobs, and
commands run via docker exec sh -c inside the service's container with
stdout+stderr captured into cron_executions. Added Client.ExecRun for
attached output capture. UI: Cron tab on the service detail page with
create/edit/enable-toggle, manual Run, and execution history with logs.
Verified e2e against real Postgres + Docker (trigger and scheduler tick
both produce real captured output).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 17:01:16 +02:00
Tomas DvorakandDevin aab785afc1 Add agent onboarding tokens with Usage-page issuance UI
Per-node onboarding tokens replace the placeholder <token> in the agent
connect command: raw cagt_ tokens are returned once at issuance, only
sha256 hashes persist in agent_auth_tokens, and listing exposes metadata
only. Public agent routes accept env-configured shared tokens or any
active DB-issued token; successful validation stamps last_used_at and
revoked tokens are rejected — verified end-to-end against a real stack
(register, heartbeat, revoke → 401, bogus → 401).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 16:46:26 +02:00
Tomas DvorakandDevin 2bfa683084 feat(audit): filterable audit log page under Settings
The audit trail existed only as a summary card — no table, no filters.
The list endpoint was also self-scoped and never returned user_email
(the field existed on the struct but was never selected).

Backend: GET /audit-logs now joins users for email and accepts
user_id / actor (email ILIKE) / since (RFC3339) filters alongside the
existing resource/action/page/limit. Widening past self-scope matches
the platform's no-roles trust model — every authed user can already
create users. RLS on audit_logs is dormant (superuser connection,
app.current_user_id never set).

Frontend: /settings/audit-logs page with deferred filter inputs, time
range select, paginated table (time, actor, resource, action, id, IP,
details), linked from a Settings card. Spec updated and types
regenerated.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 16:36:05 +02:00
Tomas Dvorak 25703592c6 feat(git): push webhook receiver + repo connect on service create
New public POST /api/git/webhooks/🆔 verifies provider signatures
(GitHub/Gitea HMAC-SHA256, GitLab token), filters branch pushes, matches
connected repo to services by clone_url/full_name + branch, inserts
deployment rows and enqueues runDeploymentAndSync (trigger=webhook).
Service-create dialog now connects the selected provider repo and
registers a push webhook (409-tolerant via connected-repo lookup).

api-client: connectGitRepository, listConnectedGitRepositories,
createGitWebhook typed from generated schemas. GitWebhook spec schema
corrected (events array + branch field).

Authored By: TDvorak <[email protected]>
2026-09-17 10:30:36 +02:00
Tomas Dvorak 93e3074477 docs(api): bring OpenAPI spec to route parity, dedupe client types
openapi.yaml: 29 -> 114 paths / 154 operations covering every mounted
route, including root probes (/live, /health, /ready) and /api/agents/*
token routes via per-path servers overrides. Schemas corrected against
the real Go structs (Service, GitProvider/GitRepository/GitBranch,
NodeAgent capabilities/resources, HostMonitoring, UpgradeStatus,
service metrics, project stats); stale source/build_config contract and
phantom ServiceSource/BuildConfig/ResourceConfig schemas removed.
Proxmox handlers are never registered - intentionally undocumented.

api-client.ts: raw wire types now alias generated schemas
(ServiceVariable, UpgradeStatus, HostMonitoring, NodeAgent, Service,
ServiceMetrics, Git*); CreateServiceInput/CreateUserInput use the
generated request types. CamelCase view models and normalizers remain -
they are the frontend contract, not duplication.

Frontend gate green: tsc, eslint, vitest, vite build.

Authored By: TDvorak <[email protected]>
2026-09-17 10:19:27 +02:00
Tomas Dvorak 9e3aa3aa17 Consolidate backend: drop vendored APwhy, converge on goose + sqlc
Phase 0 cleanup verified end-to-end:

- Delete vendored APwhy runtime (cmd/apwhy, internal/apwhy) and the
  unwired copy (internal/api/server.go + static bundle + cloned
  internal/{storage,analytics,auth,rbac}) — ~7.5k lines of dead code.
- Keep Containr's native internal/gateway; move APwhy management
  routes under /api/v1/gateway/* so they stop colliding with the
  native /services paths. Fix handlers against the real schema:
  UUID ids via RETURNING, integer enabled flags, plan validation,
  service validate endpoint, unique-violation mapping.
- Port agents.go off GORM onto sqlc (sqlc/queries/agents.sql);
  drop gorm dependencies; fix sqlc schema drift (services.environment_id).
- Replace the SELECT 1 goose baseline with the real consolidated
  schema (52 tables) minus APwhy's parallel auth/RBAC objects;
  idempotent via IF NOT EXISTS / OR REPLACE / guarded DO blocks so it
  replays cleanly over legacy installs. Add a drop migration for the
  dead objects and delete the legacy filename-tracked migrations/
  loader, directory, and Dockerfile copy. Verified via cmd/migrate:
  fresh install 53 tables, legacy upgrade clean to 20260917000000.
- Canvas: add invisible React Flow handles to ServiceNode so inferred
  auto-connections stop emitting warning #008.
- Remove reactflow dep (superseded by @xyflow/react), test harness
  cmds, and legacy 0001_init.sql backups.

Authored By: TDvorak <[email protected]>
2026-09-17 09:40:08 +02:00
Tomas DvorakandDevin 37a56dc6b6 Vertice restyle: fullscreen layout, lime/gray palette, mini-canvas project cards
- Token swap to near-black grays + lime accent + yellow secondary across
  index.css, all shared chart/card components, and every feature page
- PlatformShell: 62px icon rail, 46px telemetry ticker topbar, containr
  wordmark, env pill; mobile header preserved
- ProjectsPage rebuilt: cursor heading, host stat row (CPU sparkline,
  MEM/DISK meters, DEPLOYS), Railway-style mini-canvas project cards,
  mono deployment feed table
- All layout-level max-w-[1400px] caps removed -> w-full px-8
- New lime containr.svg synced to public/, root, and dist
- Auth glow, chart series colors, service-type colors retuned to palette
- Demo mode now seeds representative host telemetry
- Smoke test updated for new heading/copy

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-15 21:03:07 +02:00
Tomas DvorakandDevin 9f3b196032 Overhaul: real metrics, Git integration UI, open-source hygiene, root cleanup
Backend:
- GET /services/:id/metrics with real Docker container stats
- DELETE /git/providers/:id and GET repo branches endpoint

Frontend:
- Service detail shows real telemetry or honest empty states
- Live status distinguishes idle/polling/reconnecting
- Git providers section in Settings; repo+branch pickers in Add Service
- Real logo, compact Projects header, canvas fitView fix
- index.css deduplicated (1725 -> 1384 lines)
- Fixed smoke tests, lint errors, ToastProvider/context unification

Repo hygiene:
- Added LICENSE (MIT), CONTRIBUTING.md, SECURITY.md; rewrote README
- Untracked .env.prod and data/letsencrypt secrets; fixed .gitignore
- Removed legacy root src/internal/cmd and stale configs/docs/artifacts
- Moved guides and design references under docs/

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-15 18:59:40 +02:00
Tomas Dvorak 355a97bab4 overhaul 2026-04-14 18:04:48 +02:00
Tomas Dvorak 94f7302972 docs: specify compose template catalog 2026-04-14 17:02:10 +02:00
Tomas Dvorak cdf8000185 docs: specify railway workspace slice 2026-04-14 16:23:28 +02:00
Tomas Dvorak 08cb5754f3 small fix, don't worry about it 2026-04-10 12:02:36 +02:00
Tomas Dvorak ffa5489dc1 feat: initial implementation of container management platform 2026-02-16 10:18:05 +01:00