305 Commits
Author SHA1 Message Date
Juan Denis 862b1022c5 Regenerate the migration inventory
The hex colour literal ratchet dropped to 139 when the table work
removed one from the design system.
2026-10-09 00:34:41 -04:00
Juan Denis d2465d9817 Pass error details through and translate last labels
The API client handed translateServerError only the code and message,
so validation.required and conflict.exists could not name the field or
resource and fell back to server text; it now passes data.details.
Also translates the service-type subline, RepoConnectForm's default
labels and a ConnectionsHub fallback that the literal census skipped,
and refreshes the migration inventory counts.
2026-10-04 19:03:49 -04:00
Juan Denis 97a96d963b Give common API errors stable codes and service wording
Toasts showed backend prose verbatim, with no code to translate
against and "application" where the UI says "service". Not-found,
missing-field, permission, conflict and agent-offline errors now come
from factories in app/exceptions.py that pair one fixed message with
a dotted code (not_found.<resource>, validation.required,
permission.denied, conflict.exists, agent.offline), so the client can
translate them.

The shared app-loading helpers and the apps, domains, servers,
databases and backups routes raise those errors instead of hand
shaping bodies, which drops the hand-shaped body count from 1132 to
850. Five routes picked an HTTP status by comparing error text; they
now key off a result code or a sentinel so rewording can't change a
status. Five broad excepts that echoed str(exc) now log the crash and
return the request id instead.
2026-10-04 18:59:25 -04:00
Juan Denis 2f52c87a39 Add /ports/check and /ports/suggest
The deploy paths already know which host ports are taken (app rows,
slot rows, Docker mappings, a bind test) but only used that to pick a
port silently. Exposing it lets the shared port input say "8001 is
used by shop-api" and offer the next free port, instead of failing
at deploy time.
2026-10-03 22:46:54 -04:00
Juan Denis 93a3cd783e Let GET /api/v1/servers accept a scoped API key
Vela holds one ServerKit API key as a host-side secret and polls the
fleet list, so this read route moves from bare @jwt_required() to
auth_required() + require_scope('servers:read') — a deliberate,
per-route jwt_only_census exception, not a sweep. JWT callers are
unchanged; a restricted key without servers:read gets 403 from the
scope middleware, no credentials get 401.

Scope choice: the existing servers:read catalog entry already covers
'list servers, agents, and metrics', and the serverkit-gui read routes
(capabilities/frame/surface, sibling repo) take the same scope, so one
key scope serves the whole Vela polling surface.

The census ceiling ratchets down to 600 and the migration inventory is
regenerated, as both ratchets require. tests/test_servers_api_key_scope.py
pins the four cases: scoped key 200, wrong-scope key 403, JWT 200,
anonymous 401.
2026-10-03 00:31:36 -04:00
Juan Denis c7b6f145d1 Serve the panel through the Cloud relay tunnel
Until now the connect client refused every http and sio stream, so
Open panel in ServerKit Cloud showed a blank page. The client now
bridges them to the loopback listener, the panel serves its page
under the relay's /t/<device>/ prefix, and the SPA prefixes its
router, API and Socket.IO paths from it. The Cloud grant is checked
against Cloud's JWKS before signing in the panel user with the same
email; panel two-factor still applies.
2026-09-29 23:19:34 -04:00
Juan Denis d7f4e6f9b1 Document slot deploys and the schema-change rule 2026-09-28 22:27:14 -04:00
Juan Denis d0b11714e7 Deploy compose apps through A/B slots
Plan 87 phase C. A compose app on slots runs each slot as its own
project, <name>-a / <name>-b, rendered from the merged compose config:
container_name stripped, only the web service published, on the
slot's loopback port, and every named volume pinned to the name it has
today. Without the pin compose prefixes volumes with the project name
and slot B would start on empty ones; the real-Docker leg checks this.

Stateful services (a database, cache or queue with a named volume)
cannot run twice. They move once, on an explicit previewed split, into
a shared <name>-data project that both slots reach by the same names.

The webhook, template-update and rollback paths deploy through the
engine instead of stopping the stack. The --scale web=2 rolling
restart and the Webhook.zero_downtime flag are deleted: it assumed a
service named web, ignored its own health result, and no UI set it.
Compose slot images get immutable per-deployment tags and are pruned
with the rest.
2026-09-28 22:26:44 -04:00
Juan Denis 0749508599 Run release commands and DB snapshots before the switch
Plan 87 phase D. Both slots share the database, so what changes it
runs once, before the switch, while the old release still serves:
the app's managed databases are dumped (a warning, never a blocker),
then the release command runs in a one-off container of the new
image. A failed release aborts the deploy with the live slot
untouched. The command comes from the app's setting, the Procfile
release: line, or serverkit.yaml; a rollback never re-runs it.

Rolling code back never rolls the database back: after a switch
back the slot card offers the pre-deploy snapshot as an explicit
second step with a data-loss warning. stop_old_before_release is
the announced-downtime fallback for schema changes that cannot stay
backward-compatible for one deploy.
2026-09-28 21:41:52 -04:00
Juan Denis c697e7247b Show slot deploys in the console and app detail
Plan 87 phase F. A slot deploy job lists its real stages (Preflight,
Build, Boot slot X, Health gate, Switch, Watch) and advances through
them as the engine reaches each one, so PipelineStrip renders them
unchanged. The overview gets a slot card: which release is live,
which is the standby and how long it stays warm, with Switch back;
when slots are off it lists why the app does or does not qualify.
Settings gains the opt-in, watch window, standby minutes and the
shared-volume confirmation.
2026-09-28 21:33:45 -04:00
Juan Denis f77aaa1f4d Deploy single-container apps through A/B slots
Plan 87 phase B. An app that opts in keeps two slots, like the
panel updater's two installs. A deploy boots the new release in the
idle slot on its own loopback port while the live one serves, holds
it to the health gate, then switches nginx by moving app.port to the
new slot and rewriting the vhost, so every app.port reader follows
without edits. A watch window switches back on its own if the
release falls over; the old slot stays warm for an instant switch
back, then is stopped (not removed) by a standby sweep.

Opting in adopts the running container as slot a with no restart.
Eligibility is shown with reasons and re-checked on every deploy;
opting out folds the slots back on the next in-place deploy. Slot
images are never pruned and slot ports stay reserved.
2026-09-28 21:29:33 -04:00
Juan Denis c81d452f4a Note traffic, edge caching and attachments in docs
The roadmap gains the per-app traffic and bottleneck hints, the
page cache and compression work, and attaching services to apps.
The removed scaling pieces are simply no longer listed.
2026-09-28 09:07:07 -04:00
Juan Denis acdf6b878d Remove auto-scaling and the load-balancer builder
Replica counts could be changed, but nginx only proxied to one port
and the auto-scale sweep was never scheduled, so neither the
Auto-scale section nor its five routes could work; the nginx
upstream builder had no UI at all. With one live copy per app they
are gone, and the scale-policy table is dropped. Running containers
are not touched. The scaling spec now says replicas are not
pursued.
2026-09-28 09:03:34 -04:00
Juan Denis e688b7b5c3 Let proxied sites long-cache fingerprinted assets
The Docker vhost set no caching at all. An opt-in rule serves files
whose name carries a content hash with a one-year immutable
Cache-Control, proxying exactly like location /. A name only counts
as hashed with 8+ characters and a digit, so app-settings.js is never
pinned. Off by default: the panel cannot know an app's asset layout.
2026-09-27 22:45:53 -04:00
Juan Denis 3feba262cb Name an app's likely bottleneck on its Overview
A rule-based check reads the app's request metrics, its container
CPU and the CPU and top queries of the databases it references, and
names what fits: page cache for a busy app over an idle database,
a cache or index when one query dominates a busy database, slot
deploys for errors during deploys, and TTL for a low hit ratio. Each
hint links to the fix and states its failure mode; nothing is added
automatically.
2026-09-27 22:36:14 -04:00
Juan Denis 45f7100d93 Offer an opt-in PgBouncer beside PostgreSQL
An installed PostgreSQL can switch on a transaction-mode PgBouncer
sidecar through its managed override, with no host port, on the
shared network. fromService gains pooledUrl; apps keep the direct
connectionString unless they switch, because transaction pooling
breaks prepared statements in some drivers, LISTEN and advisory
locks, and the card says so.
2026-09-27 22:27:32 -04:00
Juan Denis dc08f6975c Show top queries and hit ratios per database
The explorer showed live sessions but nothing about which queries
cost the most or whether the cache was working. A container's new
Insights tab lists top queries by total time (pg_stat_statements,
performance_schema digests) with connections in use and the buffer
cache hit ratio, and says why a list is empty. PostgreSQL's
pg_stat_statements is switched on through the config tuner, keeping
existing preloads, so its rollback undoes it.
2026-09-27 21:30:07 -04:00
Juan Denis 6986f497f3 Attach a cache, storage or queue from the app page
Installed Redis and a running app were unrelated until someone wrote
the env by hand. The Overview's Attached services card reuses an
installed service or installs one, writes the fromService references
(REDIS_URL, AMQP_URL/BROKER_URL, the S3_* set), shows that kind's
failure mode, and offers a redeploy. Detach removes exactly what the
attach wrote.
2026-09-27 21:16:58 -04:00
Juan Denis f23c7a5009 Give each app its own bucket and a scoped key
MinIO's images no longer pull, so object storage is now the Garage
template. Attach storage creates a bucket named for the app and a key
that can read and write only that bucket, keeps the secret in an
internal vault, and wires S3_* and AWS_* env through fromService and
fromSecret. Detach revokes the key and removes the env but keeps the
bucket and its objects. A docker leg proves the scope against a
real Garage.
2026-09-27 20:36:38 -04:00
Juan Denis d2fd3b38b0 Show per-app request rate, errors and latency
A one-minute builtin tick tails each app's timed access log from a
stored byte offset and rolls it into minute and hour rows: status
classes, cache HIT/MISS/BYPASS/STALE and a latency histogram, so
p50/p95/p99 merge exactly across ticks and into hours. Minute rows
go after 48 h and hour rows after 30 days on the same tick. The
Overview tab gets a Traffic card.
2026-09-27 18:18:30 -04:00
Juan Denis d3baa27b72 Regenerate inventories for the Storage work
Adds GET /system/storage to the API surface, records the Storage
tab's form block in the migration inventory, and seeds the history
retention test through make_application.
2026-09-27 17:04:36 -04:00
Juan Denis 45bc3c70bd Guard first admin with a setup code, fix Git deploys
A fresh panel no longer lets whoever reaches it first become admin:
registering the first account needs a one-time code the installer
prints (`serverkit setup-code` repeats it). SERVERKIT_ADMIN_EMAIL and
SERVERKIT_ADMIN_PASSWORD create the admin unattended instead.

Git apps now deploy what users expect: a repository with a compose
file deploys every service it declares, "Deploy latest" builds the
branch's newest commit, and push webhooks rebuild the containers.
Also fixes branch switching on single-branch clones, uploads of a
lone file, compose v1 port preflight, and CLI commands that ignored
their flags.
2026-09-24 00:50:02 -04:00
Juan Denis 04eecd5eb3 docs(api): regenerate API_SURFACE.md for the managed-profile route
The connect managed presentation profile (d866de54) added
GET /connect/managed-profile without regenerating the committed
inventory, failing test_committed_surface_matches_served_surface.
2026-09-19 05:09:53 -04:00
Juan Denis ff3c928917 feat(test): passive cgroup-v2 runtime baselines for full installs
Full VM installs and Test Sandbox full mode now record a short
post-install runtime observation (60s settle + 120s sampling) with an
explicit measured/unavailable/failed/interrupted status that never
rewrites install success. Adds the standalone scripts/measure-runtime.py
sampler, the measure-installed-runtime.py harness wrapper, E2E report
rendering, results-API summaries, CI coverage and METRICS.md accounting
boundaries.
2026-09-19 04:52:24 -04:00
Juan Denis 41c079a874 docs(readme): add Vela to Related Projects in the translated READMEs 2026-09-18 01:52:07 -04:00
Juan Denis 693ba8e322 docs: update demo links in all README translations [skip version] 2026-09-10 22:05:14 -04:00
Juan Denis 172e410ce3 chore: prepare 1.11.0 and refresh release inventories [skip version] 2026-09-08 09:31:21 -04:00
Juan Denis 8468ab79f2 test(ai): verify management UI and document completed integration 2026-09-08 04:10:21 -04:00
Juan Denis 9bb474e111 docs(ai): document settings standards and Prompture integration review 2026-09-08 03:03:58 -04:00
Juan Denis fde0acee6d fix(ai): satisfy release gates and prepare 1.10.0 [skip version]
Keep connection persistence behind the service boundary, use typed errors and shared JSON parsing, and refresh reviewed inventories. The new GET connection catalog accepts API keys so API clients managing connections can discover IDs without exposing credentials; existing JWT-only routes stay unchanged. Add regression coverage and release notes.
2026-09-08 01:30:03 -04:00
Juan Denis add2f713a5 feat(ai): use Prompture 1.11 native gateway streaming 2026-09-08 00:47:58 -04:00
Juan Denis 569b052659 feat(ai): expose Prompture providers and saved connections 2026-09-08 00:42:54 -04:00
Juan Denis 7dfc0c8b68 docs: align award badge heights across README languages 2026-09-07 02:36:37 -04:00
Juan Denis 63d1fe8e4e docs: show deployment videos side by side in all languages 2026-09-07 02:35:15 -04:00
Juan Denis cacd3a2625 docs: add deployment video walkthrough in all README languages 2026-09-07 02:32:59 -04:00
Juan Denis ccba314042 docs: unify README asset size into one total 2026-09-05 14:57:27 -04:00
Juan Denis 2de3a971c5 docs: publish reproducible README measurements
Generate all four README tables from reviewed source and build snapshots, distinguish collection and asset sizes from runtime results, and remove unsupported footprint claims. Add a consistency check in CI and document how to reproduce profiling and measurements.
2026-09-05 14:42:28 -04:00
Juan Denis d67e5d07d0 fix: complete audit follow-ups and add regression coverage
Centralize backup schedule calculations and shared request/form flows, encode query values, and move repository finalization into its service. Add browser CI coverage, repair stale test fixtures, and provide opt-in request and build measurement tools.
2026-09-05 14:41:15 -04:00
Juan Denis aae180aaef docs: refresh migration inventory after reuse cleanup 2026-09-05 13:55:48 -04:00
Juan Denis 3068fc81f0 docs: record security review and remediation handoff
Preserve synthetic historical probes, document fixes and validation, and list upgrade behavior and remaining hardening work with the local implementation commits.
2026-09-05 04:30:47 -04:00
Juan Denis c8c93a8e0c fix(auth): enforce MFA, scoped keys, and revocable sessions
Add session-family revocation and migration 097, require current credentials for password changes, constrain API keys, and require verified WebAuthn authentication. Update browser token handling and add boundary regressions. Existing JWTs require fresh login; restricted keys deny undeclared endpoints.
2026-09-05 04:29:16 -04:00
Rebertt RodriguesandJuan Denis a092ba1afe fix(dashboard): avoid content blockers on metrics history requests (#133)
Co-authored-by: Juan Denis <jhd3197@gmail.com>
2026-09-04 22:20:22 -04:00
Juan DenisandClaude Opus 5 4dfb468f77 chore(connect): regenerate the surface, inventory and locale files
Generated files the connect work moved and nobody re-ran:

- docs/API_SURFACE.md gains GET /connect/status
- docs/MIGRATION_INVENTORY.md: the hand-shaped error bodies count is 1145
  (ceiling 1150) for the 409 the backups PUT now answers when Cloud owns
  the destination
- en.json gains app.backups.storageManagedByCloud, so the sentence the
  read-only backup form shows is translatable like every other string

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q56AfqpgKjio4n9KYubvZ
2026-09-03 00:19:35 -04:00
Juan DenisandClaude Opus 5 503dedd95c fix(apps): start/stop/restart a docker app that has no compose project
`app_type == 'docker'` was being read as "compose-managed", but the build-pack
path deploys by building an image and running a single container
(DeploymentService._deploy_docker, named serverkit-app-<id>) and never writes
a compose file. start/stop/restart called `docker compose` anyway, which -- in
a directory with nothing to read -- fails with:

    no configuration file provided: not found

So every build-pack app returned 400 from the panel's start, stop and restart
buttons and could not be run at all. There was already a materialize-compose
guard for BYO-image apps, but it only fires when root_path is NULL; a
build-pack app has source on disk, so it fell straight through to compose.

Decide on the artifact rather than the type: `_has_local_compose_project()`
looks for a compose file on disk (the app's own `compose_file`, else the four
default names). When there is one, nothing changes. When there isn't, drive
the container the deploy actually created.

Stop treats an already-gone container as stopped -- reporting that as a
failure would strand the app in `running` forever. Start and restart explain
that the app has not been deployed yet rather than surfacing a docker error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 01:34:30 -04:00
Juan DenisandClaude Opus 5 332ad73770 fix(walkthroughs): accept the step-level shorthand in extension manifests
Core walkthroughs author single-condition steps with `signal:`, `check:` or
`route:` directly on the step -- 24 sites in frontend/src/data/walkthroughs.js
do it -- and the frontend registry's completionFor() has always desugared
them. The backend validator did not: `_STEP_FIELDS` rejected all three, so an
extension that copied the pattern passed `new-extension --validate` and was
then refused at install with an "unsupported fields" error.

Teach the backend the same shorthand rather than take it away from core.
`_completion_for()` mirrors completionFor() exactly (an explicit `completion`
block still wins) and returns which spelling supplied the value, so problems
are reported against the key the author wrote -- `steps[0].signal`, not
`steps[0].completion.signal`. The shorthand is a spelling, not an escape
hatch: the same token regexes and length bounds apply either way.

The published schema gains the three properties so the contract, the
validator and the frontend now agree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 00:46:35 -04:00
Juan DenisandClaude Opus 5 c8ae7f35f2 chore(docs): regenerate the API surface and migration inventory
Both committed inventories went stale behind the walkthrough work, so the
two ratchets that pin them failed on dev:

  test_api_surface_inventory::test_committed_surface_matches_served_surface
  test_migration_inventory::test_the_committed_inventory_matches_the_measured_state

API_SURFACE picks up GET and PUT /walkthroughs/definitions, the endpoints the
Walkthrough Studio contribution contract added. MIGRATION_INVENTORY moves the
hand-shaped {'error': ...} census 1141 -> 1142 against an unchanged ceiling of
1150; the routes above are the new sites and the ceiling still holds.

Regenerated, not hand-edited:
  SERVERKIT_UPDATE_API_SURFACE=1 pytest tests/test_api_surface_inventory.py
  python scripts/generate-migration-inventory.py

Version Bump failed only as a dependent of Backend CI ("Sibling CI failed;
VERSION will not advance") and needs no change of its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 00:21:38 -04:00
Juan Denis cfcf965e24 feat: add declarative walkthrough contribution contract
Extensions can now contribute walkthroughs via contributions.walkthroughs
in plugin.json. The backend validates definitions against a shared
contract (route, signal, manual, and check completion types), transports
them through the dynamic contribution envelope, and persists custom
walkthroughs from the Studio. Includes the JSON Schema, SDK version gate,
and extension scaffold support.
2026-08-31 23:14:29 -04:00
Juan DenisandClaude Fable 5 5417c39100 perf(models): index every foreign-key column
An audit found 102 FK columns with no index — every join, per-parent
listing, and the delete cascades from _delete_cascade_policy scanned
whole tables on them (deployments.app_id, audit_logs.user_id,
dns_records.zone_id, webhook_logs.webhook_id, ...). All FK columns now
declare index=True; migration 096 creates the same indexes on existing
installs, deriving the list from the live schema at upgrade time (so
it is idempotent and cannot drift from what an install actually has).

Ratchet: test_every_fk_column_is_indexed pins the rule with no
allowlist — a new FK column is born indexed or the build fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:42:53 -04:00
Juan DenisandClaude Fable 5 5bfb0f6747 refactor(recycle-bin): drop the page-local header
The tab-local title, retention blurb and standalone Refresh button
duplicated chrome the grid already provides (Refresh data lives in the
grid tools menu; per-row 'purges in Xd' carries the retention signal).
The page now leads with the view picker and table like other grids.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:35:53 -04:00
Juan DenisandClaude Fable 5 160bf6dab7 feat(panel): one-click self-update from the About tab
The About tab could only detect a new release and link to it; applying the
update meant SSH and scripts/update.sh. Wire that script to the UI:

- panel_update_service launches update.sh via systemd-run in its own
  transient unit — a plain subprocess child would be killed when the
  updater restarts the panel's own systemd unit. Status is stateless
  (transient-unit state + the updater's own update-*.log) so it survives
  the mid-update backend restart.
- Capability gates keep the button honest: root systemd installs only.
  Docker installs get the docker compose pull instructions; dev servers
  and hosts without systemd-run get a reason instead of a button.
- GET/POST /api/v1/system/update (admin_required, confirm-gated,
  audited). API surface doc regenerated.
- About tab gains Update Now with inline confirm, live log tail while
  the update runs, a restarting phase that tolerates the panel being
  briefly unreachable, and success/rollback/timeout outcomes. New
  aboutTab locale keys in en + es.

The heavy lifting (blue/green switch, migrations, health check,
automatic rollback) stays in update.sh, which is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 08:09:52 -04:00