Commit Graph
202 Commits
Author SHA1 Message Date
Juan DenisandClaude Opus 4.8 9e511caba7 fix(deps): bump Authlib 1.6.11 -> 1.6.12 (CVE-2026-44681)
Safety flagged Authlib 1.6.11 for CVE-2026-44681 (MEDIUM): an open-redirect
via insufficient redirect_uri validation in the OAuth flow — directly
relevant since Authlib backs the SSO/OAuth login. 1.6.12 is the upstream fix
(patch bump, same 1.6.x line, lowest risk to the SSO code).

Authlib was pinned twice in requirements.txt; bumped both so they don't drift
into conflicting exact pins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:06:58 -04:00
Juan DenisandClaude Opus 4.8 6525e66a83 fix(backend): auto-allow SERVERKIT_PUBLIC_URL in CORS; bump Flask-SocketIO
When the panel sits behind a reverse proxy / tunnel, browsers and agents
arrive from SERVERKIT_PUBLIC_URL. Without that origin in CORS_ORIGINS the
engine.io WS handshake rejects it and every Socket.IO connection 400s with
"not an accepted origin". Auto-append SERVERKIT_PUBLIC_URL (deduped,
whitespace-stripped) so operators configure the public URL in one place.
Also bumps Flask-SocketIO 5.4.1 -> 5.6.1. Recovered from a local stash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:44:47 -04:00
Juan DenisandClaude Opus 4.8 c4ddc6096d fix(db): migration 008 — fix server_metrics.id type for SQLite autoincrement
The id column was declared BigInteger, which on SQLite is NOT a rowid alias
so it never autoincrements — every metrics insert failed with "NOT NULL
constraint failed: server_metrics.id" and the dashboard silently sat at zero
once an agent sent heartbeats. The model was already fixed to Integer, but
db.create_all() doesn't ALTER existing tables, so deployed SQLite installs
stayed broken. This migration detects the wrong column type on SQLite and
rebuilds the table (historical samples are disposable); Postgres/MySQL are
left untouched where BIGINT autoincrement works. Chains off 007. Recovered
from a local stash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:44:45 -04:00
Juan DenisandClaude Opus 4.8 8aa760bf02 chore: normalize line endings to LF per .gitattributes
Four files were stored with CRLF in git while .gitattributes mandates
`* text=auto eol=lf`, so git reported them as perpetually "modified":
  - ROADMAP.md
  - backend/app/__init__.py
  - backend/app/models/__init__.py
  - frontend/src/App.jsx

One-time `git add --renormalize` rewriting the stored blobs to LF. Pure
line-ending change — `git diff --ignore-cr-at-eol` is empty, i.e. zero
content was altered. Windows scripts (*.ps1/*.bat/*.cmd) are untouched;
.gitattributes pins them to CRLF and they were correctly skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 16:29:05 -04:00
Juan DenisandClaude Opus 4.8 f295103f29 fix(pairing): verify Ed25519 proof-of-possession on pairing poll
During short-code pairing the agent submitted its Ed25519 public key at
enroll time, but the panel never checked that the poller actually held the
matching private key -- identity rested entirely on the bearer
enrollment_secret. Anyone who captured that secret (e.g. from a log or proxy)
could claim the device's credentials.

The agent now signs the enrollment_id with its pairing keypair and sends the
base64 signature as X-Enrollment-Signature on each poll. pairing_service.poll
verifies it against the enrolled hex pubkey (cryptography Ed25519) and rejects
a present-but-invalid or wrong-key signature with InvalidEnrollmentError,
before touching any state. So a stolen enrollment_secret alone no longer
suffices -- the attacker also needs the private key.

Verification is best-effort for backward compatibility: a poll with no
signature still works (older agents), and becomes mandatory once signing
agents have rolled out (tracked in SECURITY.md).

Agent side: KeyPair.Sign(); pairing.Client gains an optional keypair via
SetKeyPair(), wired in at both call sites (cmd/agent/pair.go and
internal/pairdriver/driver.go); Poll attaches the signature header.

Verified: backend 89 passed / 12 skipped (4 new PoP tests: valid sig accepted,
invalid rejected, wrong-key rejected, no-sig backward-compatible); go build
./... + go vet + go test ./... all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 16:08:32 -04:00
Juan DenisandClaude Opus 4.8 a01ccc4aee fix(agent): fail-closed agent registration + surface stuck-command errors
Two correctness defects from the deep-dive review.

B. register_agent left a stale in-memory "online" entry when the DB write
   failed. The except block rolled back the session but kept the
   ConnectedAgent in _agents, so the gateway still emitted auth_ok and
   command routing targeted an agent with no backing AgentSession row
   (online/offline desync). register_agent now returns Optional[str]: on DB
   failure it removes its own in-memory entry (guarded on identity so a
   concurrent reconnect isn't clobbered) and returns None. Both callers —
   the WS gateway (on_auth) and the poll /connect endpoint — now emit
   auth_fail / HTTP 500 instead of reporting a connected agent. A stale
   'online' server row with no in-memory agent is self-corrected by the
   heartbeat reaper.

C. send_command swallowed the final command-record DB update on the
   timeout/result path: a failed commit left the row stuck in 'running'
   with no log line, making it invisible. Now logs the exception (the row
   is still left for a future sweep, but it's now diagnosable).

Adds a regression test that register_agent returns None and rolls back the
in-memory registration when the session write fails.

Full backend suite: 89 passed, 12 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 16:08:13 -04:00
Juan DenisandClaude Opus 4.8 9867380737 fix(agent): harden gateway — locked rate limiter, safe stream/ack, nonce ordering
Cluster of lower-severity agent-gateway defects from the deep-dive review.

agent_gateway.py:
- _check_auth_rate_limit was an unlocked read-modify-write of a module dict
  under async_mode='threading' (on_auth + poll /connect race it), and the
  dict grew without bound from one-off IPs. Now guarded by a threading.Lock
  with a periodic sweep that drops aged-out buckets.
- on_stream crashed with AttributeError on a malformed/partial frame with a
  null/non-string channel (precisely what the poll fallback exists to
  tolerate). Guard the channel type and the container_id split; drop the
  bad frame instead of killing the handler.
- on_credential_update_ack swallowed exceptions without db.session.rollback(),
  leaving a poisoned SQLAlchemy session for the next user on the worker.
- Replaced all print() with module logging (info/warning/exception), so
  auth/IP-block/rotation events are level-controllable and structured.

agent_registry.verify_agent_auth:
- Consume the replay nonce ONLY after the HMAC signature is verified.
  Previously the nonce was recorded first, so a forged request (valid
  agent_id+prefix+nonce, bogus signature) burned the nonce and locked the
  real agent out when it next presented it. Reordered; added a regression
  test (test_failed_signature_does_not_consume_nonce).
- Fixed the misleading "5 minute window" comment (the check is 60s) and
  documented that the 60s window is the only replay guard for the nonce-less
  HTTP-poll transport.

NOT changed (deliberately, to avoid breaking agents):
- Nonce is left optional. The HTTP-poll transport signs agent_id:timestamp
  with no nonce (auth.Sign, "nonce":""); requiring it would lock out every
  poll-fallback agent. Making it mandatory needs a coordinated Go-agent
  change and is tracked in SECURITY.md.
- Per-message session-token validation remains a documented TODO; it
  requires a protocol change on both the Go agent and backend.

Full backend suite: 84 passed, 12 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 15:34:50 -04:00
Juan DenisandClaude Opus 4.8 e9172f7af6 fix(agent): drop stale socket and fail in-flight commands on reconnect
When an agent reconnected, register_agent overwrote the in-memory
ConnectedAgent for the server but never disconnected the old socket or
drained its pending commands. Callers blocked in send_command() on the old
agent's result_queue were never signalled — they waited out the full
per-command timeout and returned a misleading "Command timeout" even though
the agent was back online (the late result would resolve against the new
agent and be unreachable). The agent was also briefly left holding two live
sockets.

On replacement, register_agent now (after releasing the lock to avoid
deadlocking with the disconnect handler):
- puts an AGENT_RECONNECTED error on each orphaned pending command's
  result_queue so blocked callers return immediately, and
- disconnects the old WS socket (guarded: only for ws transport, only when
  the socket id actually changed, only when socketio is initialized).

The old socket id is already removed from _socket_to_server before the
disconnect, so the resulting disconnect handler is a no-op for the freshly
registered agent. Adds two regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:46:56 -04:00
Juan DenisandClaude Opus 4.8 5598a1c6e2 fix(agent): rate-limit the poll /connect auth path
The WebSocket auth path (agent_gateway.on_auth) throttles auth attempts
per-IP via _check_auth_rate_limit, but the REST long-poll equivalent,
POST /api/v1/agent/connect, did full HMAC auth with no throttle. That left an
unthrottled credential-stuffing surface against agent auth while the WS path
stayed protected.

connect() now calls the same shared limiter before verify_agent_auth and
returns 429 when the per-IP window is exhausted. Adds a regression test that
drives the real endpoint through the limit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:46:47 -04:00
Juan DenisandClaude Opus 4.8 749152c09e fix(agent): fail-closed auth + heartbeat reaper reconnect race
Two defects in the agent control plane found during the deep-dive review.

1. verify_agent_auth() failed OPEN on a missing secret (critical). The HMAC
   check was wrapped in `if api_secret:`, so when get_api_secret() returned
   None (NULL ciphertext, or a decryption error it swallows) the signature
   block was skipped and execution fell through to "authenticated" — reducing
   agent auth to agent_id + api_key_prefix, both non-secret observable values.
   Now mandatory: no decryptable secret => log + track_auth_attempt(False) =>
   return None. Also added Server.get_pending_api_secret() and select the
   secret matching whichever prefix matched, so signatures actually verify
   during a key-rotation window instead of hitting the old bypass.

2. Heartbeat reaper evicted freshly-reconnected agents (TOCTOU race).
   _check_heartbeats snapshotted dead server_ids under the lock then released
   it; _handle_agent_timeout popped unconditionally. An agent that reconnected
   in that window (new ConnectedAgent, new socket_id) got clobbered and flipped
   offline right after reconnecting. The snapshot now carries socket_id, and
   _handle_agent_timeout re-validates under the lock (same socket AND still
   stale) before evicting. Extracted HEARTBEAT_TIMEOUT so the scan and the
   re-check can't drift; added the missing rollback on error.

Adds tests/test_agent_registry_security.py (5 tests). Full suite: 80 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:38:30 -04:00
Juan DenisandClaude Opus 4.7 5a5e5e2002 Address Copilot review: plugin security + correctness
backend/app/services/plugin_service.py:
- Zip Slip defense: new _safe_extract_path() validates every archive
  member against the destination root (rejects absolute paths, ../
  segments, and entries that resolve outside backend/frontend dest).
  All zip extract paths route through it.
- Plugin requirements.txt no longer auto-installs. Running pip on an
  untrusted plugin gives setup.py hooks code-exec at the backend's
  privilege level. Operators must opt in with SERVERKIT_ALLOW_PLUGIN_PIP=1;
  otherwise the file is saved to the plugin dir for manual review.
- New _attach_status_guard() registers a per-blueprint before_request
  that returns 503 if the plugin's DB status is not 'active'. Flask
  can't unregister blueprints from a running app, so the in-DB status
  is now authoritative at request time — disabling a plugin actually
  stops serving its routes.
- New _update_plugin_metadata() syncs all mutable manifest fields
  (display_name, description, author, homepage, repository, license,
  category) on reinstall. Previously these went stale after the first
  install.

backend/app/api/plugins.py:
- /install endpoints no longer echo raw exception strings to clients
  (could leak paths, lib versions, SQL fragments). Log full traceback
  server-side with a short correlation ref returned to the client.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:21:08 -04:00
Juan DenisandClaude Opus 4.7 b0f31ac083 Export InstalledPlugin from app/models so create_all sees it
Without this import, SQLAlchemy's db.metadata doesn't know about the
installed_plugins table, so MigrationService.check_and_prepare's
db.create_all() never creates it on fresh installs. The plugin loader
then fails on cold start with "no such table: installed_plugins".

Pairs with the previous fix that moved the plugin loader to run after
the migration step. Both are required; this one is what Copilot's PR
review meant by "export the model from app/models/__init__.py".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:10:25 -04:00
Juan DenisandClaude Opus 4.7 191c6fde94 Fix plugin loader ordering + add live test report
backend: move load_all_plugins(app) inside the with app.app_context()
block, after MigrationService.check_and_prepare(app). The loader queries
the installed_plugins table; previously it ran before migrations created
that table, causing "no such table: installed_plugins" errors in journal
on every cold start. Surfaced by the new E2E harness (visible in the
journalctl pane of the report).

scripts/test: live HTML report.
- Orchestrator writes state.json after each phase + regenerates
  report.html, so opening the page shows real-time progress.
- Browser auto-opens the report when the run starts; page auto-refreshes
  every 8s while a run is in progress.
- Added per-VM 'Stage' field (pending -> transferring source ->
  installing -> running pytest -> done).
- Fixed empty "Details" cells rendering as black boxes; now show "-".
- Pulsing dot indicator in the live banner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:04:04 -04:00
Juan Denis 4db9300001 Introduce sk1 connection string format and GUI plugin
Switch connection string encoding from the old base64 blob to a readable sk1:// URL format (host/token[?exp&insecure]) across agent, backend, and frontend. Update encode/decode logic and extensive tests to validate port preservation, insecure/http handling, expiry parsing, and input validation; adjust agent pairing UI to accept the new scheme. Add ServerKit Agent GUI plugin: backend blueprint endpoints for capabilities, frame capture and synthetic desktop, plugin metadata; frontend plugin components (ServerGui, Launcher, SyntheticDesktop), styles, and manifest entry to enable streaming screenshots with a synthetic fallback. Also add Windows service stop waiting to avoid start races after pairing, and document SERVERKIT_PUBLIC_URL in env examples.
2026-05-05 14:03:31 -04:00
Juan Denis 3c4a2c0c96 Make Git extension canonical at /git
Promote the serverkit-git extension to own the canonical /git route and migrate legacy /git-ext usage. Adds an Alembic migration that updates the installed_plugins manifest for serverkit-git to the canonical /git contributions. Frontend changes: plugin manifests and build-time plugin list use the new display name and /git routes; plugin entry exports now expose GitExtensionPage (and keep GitExtPage as a compatibility alias); contribution normalization rewrites older plugin contributions that referenced /git-ext to the canonical /git and provides fallback normalization for build-time contributions; App routing adds redirects for legacy git-ext routes to /git and removes the host's hardcoded /git page from core routes; Command Palette and Sidebar code updated to remove the static core Git item and to respect plugin-contributed nav items (including new hidden-item handling and Set-based sidebar helpers). Small backend docstring wording tweak.
2026-05-03 21:03:48 -04:00
Juan Denis 35f46073d4 Monitoring UI, public page & slug validation
Backend: accept and persist an 'enabled' flag when updating monitoring config; add slug normalization and validation in StatusPageService to enforce lowercase alphanumeric-hyphen slugs.

Frontend: add PublicStatusPage component and route (/status/:slug). Major overhaul of Monitoring.jsx: introduce defaults for thresholds, improved state handling (loading/saving/checking flags), reworked UI and tabs (Alert Rules, Delivery, History), add Check Now, Refresh, toasts, and Switch UI for enabling checks; persist and validate thresholds on save. App.jsx imports the public page and registers the route. StatusPages.jsx refactor: slug normalization, autosync slug from name, improved page/component/incident flows (create, load, run checks, copy public URL, delete handling), UI/ux improvements and additional metadata. Overall implements better validation, UX, and public status page support.
2026-05-03 20:10:18 -04:00
Juan Denis 9f7bd23f3c Improve plugin loading, API client & marketplace UI
Several changes to plugin handling, API client behavior, and the marketplace UI:

- Backend: added explicit '' route decorators for marketplace and plugins endpoints (GET/POST) to ensure routes mount at both '' and '/'.
- Frontend: make Git page accept a basePath prop and update serverkit-git plugin export to render GitPage at /git-ext.
- Auth/API: normalize API base URL, update token-expiry handling to dispatch a global serverkit:auth-expired event (client) and listen for it in AuthContext to clear user state; refresh token logic preserved on JWT 401s.
- Plugins/contributions: add build-time plugin manifest support and fallback for contributions when backend endpoint is unavailable; parse local plugin manifests and tag contributions with plugin slugs.
- Plugin loader: internalize getInstalledPlugins function signature.
- Marketplace UI: remove the submit-extension modal and form, replace with an Import ZIP flow and make tabs controlled via state; remove related SCSS styles.
- API & sockets: fix plugin API URL encoding/format and compute socket URL robustly for dev/prod.

These changes improve offline/dev experience (Vite dev server), make plugin contributions resilient when the backend is absent, and simplify the marketplace extension import workflow.
2026-05-03 17:17:32 -04:00
Juan Denis b4f5a466ce Add audit fallback, builtin extensions, and ports
Introduce fallback audit logging and enrich audit tooling; add builtin extension discovery/install; and update default launcher ports and related docs/config.

Key changes:
- New middleware app.middleware.audit registers an after-request fallback that records authenticated mutating API requests when routes don't emit explicit audit logs.
- Audit model/action constants extended and AuditService enhanced (request context handling, redaction for sensitive settings, optional commit flag, sets g.audit_logged, emits events).
- Routes/services updated to create explicit audit entries for marketplace and plugin operations (create/update/publish/install/uninstall/enable/disable) and SSO/settings flows switched to use AuditService.
- Added tests for audit logging behavior and redaction (backend/tests/test_audit_log.py).
- Plugin service: added BUILTIN_EXTENSIONS_DIR, list_builtin_extensions and install_builtin_extension; plugin API endpoints to list/install builtin extensions.
- Contribution system extended to support layouts and route layout metadata; frontend wiring updated to surface standalone/custom layouts and extension routes.
- Launcher port defaults changed (backend -> 47927, frontend -> 41921). Updated backend .env.example, config CORS defaults, Dockerfiles and docker-compose to use SERVERKIT_BACKEND_PORT, and many docs/README files and frontend envs to reflect new ports.
- Minor frontend cleanups and new builtin extension example (serverkit-git) plus associated manifest and frontend re-export.

These changes improve observability/security for mutating APIs, enable opt-in builtin plugins, and align local dev/run ports with the launcher defaults.
2026-05-03 15:57:02 -04:00
Juan Denis ddcf0b2f5f Add plugin contributions system, SDK, and integration
Introduce a first-class plugin contribution system and SDK, plus frontend integration and installer enhancements.

Backend:
- Add /plugins/manifest-spec and /plugins/contributions endpoints to expose the plugin.json contract and merged active-plugin UI contributions.
- Add app/plugins_sdk for plugin authors (current_user, logger, audit, jwt helpers).
- Add ContributionService to aggregate active plugin contributions (nav, routes, page_titles, command_palette, widgets).
- Marketplace now hands off to plugin installer when an extension has a source URL and enforces admin-only installs in the marketplace API.
- PluginService: run lifecycle hooks (install/uninstall) and install declared template dependencies (best-effort, recorded/logged) during plugin installs; surface template/manifest fields on InstalledPlugin.to_dict; surface linked InstalledPlugin status on ExtensionInstall.to_dict.

Frontend:
- Add runtime contributions loader (plugins/contributions.js) with useContributions hook and resolveComponent/getPluginModule helpers.
- Add SDK re-exports for plugin frontends (plugins/sdk/index.js) and Vite alias 'serverkit-sdk'.
- Integrate contributions across UI: Sidebar, CommandPalette, PluginLoader (declared widgets + legacy fallback), PageTitleUpdater, DashboardLayout (refreshContributions), and route injection via new ExtensionRoutes hook loaded in App.jsx.
- Add API client getPluginContributions and adjust PluginLoader discovery patterns.

Other:
- Add .extension-platform-plan/ to .gitignore.

Overall this enables declarative plugin-driven UI integration (sidebar items, SPA routes, page titles, command palette entries, and widgets), lifecycle hooks, and template dependency installs while keeping legacy behavior for older plugins.
2026-05-03 13:54:40 -04:00
Juan Denis 99abf8f147 Add repository manifest detection and templates
Introduce RepositoryManifestService to detect deployment manifests (ServerKit, Render, Railway, Compose, app.json, Dockerfile, Nixpacks) and recommend app/build settings. Add backend integration: analyze repo during import (apps.create_app_from_repository), new GitHub manifest API route, and SourceConnectionService helpers to list root files and fetch file contents from GitHub. Pass resolved dockerfile_path/custom build/start commands into build configuration. Add unit tests for the manifest service. Frontend: add template support and manifest UI in NewService.jsx, new API call inspectGithubRepositoryManifest, and related SCSS styles for templates/manifest display. Misc: updated imports and minor UX messaging changes.
2026-05-03 07:07:57 -04:00
Juan Denis 96db8df5ad Add GitHub source connections & repo picker
Introduce source provider connections to allow OAuth-backed GitHub repository selection and authenticated cloning. Backend: add SourceConnection model, SettingsService keys for GitHub OAuth, and a SourceConnectionService that handles OAuth flow, token storage (encrypted), repo/branch listing, and authenticated clone URL generation. Expose new API blueprint under /api/v1/source-connections with endpoints for status, authorize, callback, repos, branches, disconnect, and admin config. Update app creation flow to accept source_connection_id and repository_full_name, use authenticated clone URLs for cloning/deploy, and sanitize clone errors to show public repo URLs. Frontend: add SourceConnectionsTab UI, source connection callback route/component, extend App routes, and revamp New Service page to support connecting GitHub, picking repositories/branches, or using a manual git remote; update API client and styles accordingly. Note: database migration will be required to create the source_connections table and admins must configure GitHub client ID/secret.
2026-05-03 06:54:29 -04:00
Juan Denis c18d0d8ae5 Add New Service (repo) creation and UI
Introduce creating services from Git repositories and related UI changes.

Backend: add /apps/from-repository endpoint to validate input, clone repositories, detect build/app type (via BuildService), configure build and deploy (via GitService), persist Application records, and perform cleanup on failure. Add helper utilities for slugifying, safe repo URLs, path validation, and attaching deploy configs to app responses. Make git clone command branch optional.

Frontend: add NewService page, route (/services/new), sidebar entry and buttons for creating a new repo-based service, API client method createAppFromRepository, and import new styles. Update Services, ServiceDetail, Deployments, and Servers pages with UI/UX improvements (deploy actions, repo display sanitization, new icons, deployment CTA, and extensive servers rail/refactor). Add _new-service.scss and include it in main styles.
2026-05-03 06:28:21 -04:00
Juan Denis 31dd984970 Add bulk container stats and UI workspace revamp
Backend: add POST /docker/containers/stats and DockerService.get_containers_stats to fetch resource metrics for multiple containers in a single docker stats call (parses JSON lines and returns a map keyed by ID/Name). Validates input and logs errors.

Frontend: refactor Docker page into a two-column workspace with a left sidebar for targets, resources and maintenance, and a main area with workbar and panel. Add support for multiple targets (default local), fetch available servers, and expose selection UI. Implement bulk stats fetching via api.getContainersStats for local hosts, periodic refresh, per-container stats caching, sorting controls for the container list (status/name/image/cpu/memory/created), and improvements to the container inspector (drawer behavior, loading timer, close action). Update API client with getContainersStats and adjust container stats parsing to handle multiple shapes.

Styles: add comprehensive SCSS for the new workspace, sidebar, inspector drawer, inventory and sort control, and responsive behavior.
2026-05-03 06:12:39 -04:00
Juan Denis 85fb3b5bb6 Add compose list API and overhaul Docker UI
Add backend support for listing Docker Compose projects: new /docker/compose/list endpoint and DockerService.compose_list with a JSON-parsing path and a fallback that builds projects from container labels for older compose binaries. Fix RemoteDockerService to use DockerService.get_container for remote/local inspection. Large frontend updates: DashboardLayout supports full-page routes; Databases page received a visual refresh with engine status badges and a summary grid; Docker page refactored extensively — normalize container fields, consistent ID/name/image helpers, table-based resource list, inspector sidebar (ports/mounts/env), resource bars, improved logs/exec handling, and disabled actions for remote targets. Compose tab now uses the new compose list API and streamlines compose actions. File Manager now keeps current path in the URL (search params), tracks previous target changes, and manages selection state more robustly. Miscellaneous UX fixes and icon updates across pages.
2026-05-03 05:10:01 -04:00
Juan Denis a79bbc27f7 Add connstring, file handlers, and security tests
Introduce connstring package and tests to decode panel "connection strings" and add UI/CLI hooks to accept them. Move file access handlers into a dedicated file_handlers.go with strict allowlist checks that resolve symlinks and validate parent dirs to prevent escapes; add unit tests covering symlink and allowlist behaviour. Add credential-rotation HMAC verification, command blocking and configurable exec timeout resolution, gate system:exec registration by feature flag, and fix Docker logs reading to use io.ReadAll with a size cap. Misc: buffer restart channel to avoid IPC hangs, improve UDP discovery error handling, expose IPC token via a local endpoint and add IPCTokenPath, and wire the pairer to handle connection-string pairing and persist credentials. Overall changes focus on security hardening, UX for single-string pairing, and reliability fixes.
2026-05-02 00:51:47 -04:00
Juan DenisandClaude Opus 4.7 5541504ef8 Fix the systemic silent failures: empty log, dead WS, locked-out agents
Four real root causes that produced the symptoms we've been chasing:

1. agent.log was 0 bytes because io.MultiWriter aborts the entire
   write chain on the first writer error. On Windows services where
   os.Stdout writes can transiently fail, the lumberjack file writer
   never got the slog records — events.json (direct os.File) worked
   fine while agent.log silently stayed empty for the entire process
   lifetime. Replace io.MultiWriter with resilientWriter that
   independently writes to each underlying writer and swallows
   per-writer errors. Also surface logger init failures (MkdirAll,
   OpenFile) instead of silently falling back to stdout-only, and
   emit an explicit "logger initialized" startup line so an empty
   file is now an obvious bug rather than ambiguous.

2. WS connections appeared to "drop every 2-3 minutes" but actually
   the read loop had no ReadDeadline at all — ReadMessage() blocked
   forever on a network-killed connection until the OS-level TCP
   timeout fired, which is exactly what produced the
   ws_connected → ws_disconnected pattern in events.json. Add
   readWait = pingInterval*2.5 + 5s, reset it on every successful
   read AND in SetPongHandler. Now CF/ngrok killing the conn at the
   network layer (without sending a WS close frame) is detected
   within seconds, the read errors out cleanly, the Manager's flap
   detector counts it, and we fall back to polling far faster.

3. Newly-registered servers defaulted to permissions=[] which made
   every action 403. The user hit "Permission denied" on
   docker:image:list, system:info, agent:recapabilities, and probably
   half the rest. Default to ['*'] when no profile or list is
   specified — single-tenant deployments shouldn't need to manually
   open a profile editor before anything works.

4. Migration 006 retroactively fixes already-stuck servers: any row
   with permissions=[] gets ['*']. Non-empty admin-defined ACLs are
   left alone. Solves "I registered before this fix and now I can't
   do anything" without bulldozing legitimate configurations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 07:06:58 -04:00
Juan DenisandClaude Opus 4.7 e99e65cff6 Auto-correct stale 'online' status when no in-memory agent
When the panel restarts or the agent process crashes without a clean
disconnect, Server.status stays 'online' in the DB even though
agent_registry has no in-memory record. The UI then shows a
contradiction: "online" pill in the header, "Agent not connected"
on the Docker tab.

Reconcile in get_server: if there's no in-memory agent and the row
says online, flip to offline and persist. The capabilities cache +
last_seen still surface so the user sees the last known state, but
the UI now correctly reflects that the agent isn't reachable right
now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 06:52:15 -04:00
Juan DenisandClaude Opus 4.7 19907dec43 Stop dropping capability/sysinfo payloads on transient /poll failures
pollOnce was clearing c.caps and c.sysInfo *before* postJSON ran,
so any failed /poll request (tunnel reconnect, transient 5xx,
timeout) silently lost the buffered payload. The agent's periodic
resend would only repopulate it 5 seconds later, and if the panel
was checking just before that tick the capability map looked empty.

Plus the very first /poll after auth happens before the
connectionWatcher's 1Hz tick gets a chance to fire sendCapabilities,
so /poll #1 had nothing to ship.

Fix: don't clear the buffers on /poll. The agent re-populates them
on every 5–60s tick, the panel's update_capabilities is a pure
overwrite, and re-sending the same payload twice is free. This
trades a few unnecessary writes for "your capabilities never go
missing because of a flaky network."

Also harden update_system_info: only overwrite a column when the new
value is truthy. Previously a single probe failure (Docker briefly
unreachable, gopsutil WMI hiccup) would wipe a healthy value to
empty/0 and leave it that way until the next successful probe — see
the regression where Docker Version went 27.3.1 → N/A across builds.

And: log the top-level keys + inner caps count + sudo mode in
update_capabilities so we can diagnose the "panel says empty"
failure mode from the panel logs without instrumenting further.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 06:46:13 -04:00
Juan DenisandClaude Opus 4.7 6e5bccda01 Permission expansion + Go probe + persisted system info fallback
Three wins exposed by the redeployed panel showing real data:

1. Permission expansion now covers every namespace, not just docker
   and system. Extend _expand_permission_scope so any action with a
   read-style verb (list, list_installed, list_units, status, info,
   inspect, search, logs, stats, available, current) accepts a
   `<ns>:read` or `<ns>:<sub>:read` candidate. Mutating verbs add a
   `<ns>:write` candidate. This unblocks docker:image:list,
   system:info, packages:list_installed, systemd:list_units, and the
   pyenv read paths for users on read-style profiles.

2. Go runtime probe was using `--version` (a flag), but Go takes
   `version` as a subcommand. The agent's runtime detector tries
   each form and falls back gracefully; Go is now correctly
   identified by `go version` and the panel shows a real version
   string instead of the "go flag provided but not defined" error
   text.

3. SystemInfo gains Platform + CPUModel fields (pushed alongside
   the existing hostname/cores/memory/disk). OverviewTab now reads
   from the persisted server row first and falls back to the
   synchronous getRemoteSystemInfo response — so CPU and memory
   render even when the system:info action is permission-denied or
   the agent is offline.

   metrics.GetSystemInfo also gets a runtime.NumCPU() fallback so
   CPU cores never reports 0 on Windows hosts where gopsutil's WMI
   queries flake.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 06:34:20 -04:00
Juan DenisandClaude Opus 4.7 d528d27296 Bypass per-server ACL for control-plane actions; add diagnostic logs
The Refresh button on the System Status card was returning 403
because the per-server permissions allowlist (e.g. 'docker_manager'
profile) doesn't include 'agent:recapabilities' and the scope
expander has no rule for the agent: namespace. The action is purely
panel-driven discovery — it doesn't mutate the host — so it
shouldn't be subject to the same ACL as install/exec actions.

- Add Server.CONTROL_PLANE_ACTIONS bypass set: agent:recapabilities,
  agent:update. has_permission() returns True for these unconditionally;
  JWT auth at the API layer is what gates access.
- /poll endpoint now logs the keys it received in metrics / system_info
  / capabilities so we can debug "panel says N/A" failure modes from
  the panel logs without instrumenting the agent.
- Agent's sendCapabilities now logs at INFO with sudo mode + capability
  count + runtime count so the agent.log shows whether the periodic
  resend is firing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 06:17:31 -04:00
Juan DenisandClaude Opus 4.7 7d55a9c05d Add panel-driven cloudflared login flow
Users had to SSH in to run `cloudflared tunnel login` before any of
the Tunnels tab worked. Wire up the OAuth flow so the panel can
trigger it and surface the auth URL for the user to open from their
own browser.

Agent:
- New cloudflared.LoginEvent + Manager.Login() that spawns the
  process, regex-matches the dash.cloudflare.com/argotunnel URL on
  stdout/stderr, and watches cert.pem in parallel as a reliable
  completion signal. 15-minute hard ceiling.
- New cloudflared:login action returns {job_id, channel} immediately;
  the goroutine emits start → status (with auth_url) → done events
  on the job channel.
- Non-linux stub returns "unsupported".
- jobs.HasTerminated() helper so handler shutdown paths can avoid
  double-emitting the terminator.

Panel:
- POST /servers/<id>/cloudflared/login + RemoteCloudflaredService.login
- ApiService.startRemoteCloudflaredLogin

Frontend:
- CloudflaredTab gains a "Login to Cloudflare" CTA when not authed.
  Click → starts the agent flow, joins the job's Socket.IO room,
  listens for the auth_url event, and renders an Open-in-Browser
  button. Once the agent emits done, refreshes capabilities + status
  + tunnel list automatically — the rest of the tab unlocks itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 05:52:11 -04:00
Juan DenisandClaude Opus 4.7 244eef80c6 Push system_info from agent and persist server IP
The Server.ip_address column was never written: register_polling
captured the source IP into the in-memory ConnectedAgent but didn't
copy it to the Server row, so the Overview tab showed "IP Address:
N/A" for every connected agent forever. Persist it on agent
registration; existing rows backfill on the next reconnect.

The agent never actually pushed a system_info message either —
it only answered system:info synchronously on demand, so update_
system_info was never called from the request/response path. CPU,
memory, disk, kernel version, and docker version stayed N/A on the
Server row no matter how long the agent had been online.

- New sendSystemInfo() in agent.go builds a typed SystemInfoMessage
  and pushes via the transport. Wired into connectionWatcher
  alongside sendCapabilities so the data flows on connect and on
  the periodic resend cadence (5s for the first minute, 60s
  thereafter).
- poll.Send() gains a typed branch for SystemInfoMessage that
  marshals to a map and stashes for the next /poll body, mirroring
  the CapabilitiesMessage path.
- agent_registry.register_polling now writes ip_address to the
  Server row when present.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 05:36:16 -04:00
Juan DenisandClaude Opus 4.7 32bf2b8081 Cache agent capabilities to DB and surface them in System Status card
The agent reports a rich capability payload (sudo mode, runtime
managers, detected runtimes, allowed paths, systemd JSON support)
that the panel was discarding once the WebSocket disconnected. The
Overview tab also gave no visibility into what the agent had
discovered, which left users guessing why a feature tab was missing
or a Docker call returned empty.

Backend:
- Server model gains seven nullable columns: cached_capabilities,
  cached_runtimes, cached_runtime_managers, cached_allowed_paths,
  cached_sudo, cached_systemd_json, capabilities_at. Migration 005
  is idempotent (checks existing columns before adding) so it's safe
  on already-bootstrapped databases.
- agent_registry.update_capabilities() now persists a snapshot to the
  Server row whenever the agent reports state. Wrapped in try/except
  so a DB hiccup doesn't drop the in-memory update.
- /servers/<id> falls back to the cached snapshot when the agent is
  offline and exposes capabilities_stale + capabilities_at so the UI
  can badge stale data.

Frontend:
- New SystemStatusCard renders sudo mode, package manager, runtime
  managers, detected runtime versions, capability map (as colored
  chips), and file-access roots. "Stale" badge + relative timestamp
  + Refresh button (calls agent:recapabilities).
- DockerTab now distinguishes "agent online but no docker daemon"
  from "no containers found", with concrete remediation hints. Load
  errors surface as a banner instead of being swallowed to console.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 05:16:42 -04:00
Juan Denis 930705fa3b Mirror agent version; plugin install & UI improvements
Sync build Version into ws and poll transport packages and use it in User-Agent headers to avoid mismatched version strings. Improve plugin installer robustness: support SERVERKIT_BACKEND_PLUGINS_DIR and SERVERKIT_FRONTEND_PLUGINS_DIR env overrides, add clearer Docker-aware error messages, lazy-create frontend plugin dir (with helpful guidance for Docker), validate local paths (reject Windows paths on non-Windows hosts), preflight zip contents to detect frontend assets before extraction, and skip frontend manifest regeneration when frontend dir is absent. Frontend UI updates: redesign Docker page layout and stats, integrate TargetPicker, introduce container cards with richer actions and resource indicators, wire in LogToolbar/LogContent for log viewing (download, auto-refresh, search, line numbers, wrap), and update Logs modal to a drawer-style preview. Marketplace text clarified that install-from-path refers to the panel host. Terminal/Journal improvements: new journal UI and controls, persistent prefs, auto-refresh, download support, and remote-target handling.
2026-05-01 05:12:23 -04:00
Juan DenisandClaude Opus 4.7 a534fbf002 Add Packages/Services tabs, JobProgressModal, pyenv runtime mgmt and template library
Phase 3 (frontend), Phase 4 (template library), Phase 5 (pyenv runtimes).

Frontend:
- New Packages tab on ServerDetail: search, install/remove, upgrade-all,
  update-cache, quick-install presets (nginx, redis, mariadb, postgres,
  docker, fail2ban, certbot, htop). Capability-gated on packages.
- New Services tab: list systemd units with state filter, start/stop/
  restart/logs per row, daemon-reload at top. Capability-gated on systemd.
- JobProgressModal subscribes to a server-stream Socket.IO room and
  renders live install/build progress until the agent emits phase=done.
- ApiService: 13 packages/services methods + 9 runtime methods +
  refresh-capabilities helper.
- LESS partials for both new tabs imported in main.scss.

Template library: 9 new one-click stacks added to ServerTemplate
TEMPLATE_LIBRARY: lemp, lamp, docker-host, node-host, python-host, redis,
postgres, cloudflared, fail2ban. Reuses existing packages/services/
firewall_rules JSON columns; the workflow engine's agent_command +
capability_gate nodes drive execution against the new agent surface.

Phase 5 — pyenv / pyenv-win runtime management:
- Agent: 9 new runtimes:* actions (list, pyenv:bootstrap, python:installed,
  python:available, python:install, python:uninstall, python:set_global,
  python:set_local, python:current). Bootstrap and install stream via
  the job-channel pattern. pyenvLock mutex serializes mutations.
  Capability probe surfaces RuntimeManagers["python"] = pyenv | pyenv-win.
- Panel: remote_runtimes_service.py + 9 routes under /servers/<id>/runtimes.
- Frontend: ApiService methods to drive the new endpoints.
  (Dedicated RuntimesTab component left as a follow-up; the API
   surface is the foundation.)

Build, vet, and tests pass across all three layers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 04:42:30 -04:00
Juan DenisandClaude Opus 4.7 e71099a4e9 Add remote packages, services, sudo, job streaming and capability re-probe
Foundation for the remote server setup experience: agent gains
auto-sudo, package and systemd handler additions, missing docker
verbs, and a job-channel registry for streaming long-running
operations. Panel exposes the new agent surface as /servers/<id>/
packages, /services, and /refresh-capabilities routes.

Agent:
- New protocol actions: agent:recapabilities, packages:update_cache/
  install_async/upgrade/search/info, systemd:daemon_reload/list_units/
  logs/logs_follow, docker:container:create/exec, docker:image:build,
  docker:volume:create, docker:network:create.
- Auto-sudo helper (sudo -n) probed at boot, surfaced as a capability;
  apt invocations get DEBIAN_FRONTEND=noninteractive + dpkg force-confold.
- jobs registry with ring-buffer replay so panels that subscribe to a
  job:<id> channel mid-flight still see prior progress events.
- Capability re-probe is mutex-guarded and additive (transient probe
  failures don't drop features the panel already saw).
- Existing systemd handlers now sudo and use 2-min timeouts for cold
  service starts (postgresql etc.).

Panel:
- remote_packages_service.py and remote_systemd_service.py mirror the
  cron service shape (thin send_command dispatcher).
- 13 new routes on servers.py covering packages CRUD + search/info,
  systemd unit control + logs + daemon-reload, and capability refresh.
- Long-running install/upgrade return {job_id, channel} for the
  frontend to subscribe to via Socket.IO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 04:33:08 -04:00
Juan Denis 07a778d2bb Add log viewer UI and plugin local/upload install
Introduce a full log viewer feature and add backend/frontend support for installing plugins from local folders or uploaded zips. Backend: add /plugins/install-local and /plugins/install-upload endpoints (admin-only, 50MB cap for uploads) that reuse existing install pipeline and emit audit logs. Frontend: add LogContent, LogFileList, LogToolbar and logHelpers, integrate into Terminal.jsx with target picker, search, auto-refresh, wrap/line-number prefs and remote-target handling; add styles for the viewer and marketplace install tabs. API: detect FormData in the client to avoid forced JSON Content-Type and add installPluginFromPath/installPluginFromZip helpers. Misc: marketplace UI now supports URL / path / upload install modes and minor SCSS fixes (font-size variable adjustments).
2026-05-01 03:48:27 -04:00
Juan Denis 1f61513c26 Add agent Phase4 primitives, remote files & UI
Add Phase 4 agent primitives (package management, systemd control, bounded exec) and advertise file-access roots so the panel can target agents safely. Introduces agent/internal/agent/workflow_handlers.go with handlers for packages, systemd and system:exec, registers those actions, and extends capabilities.Probe and capability messages to include runtimes and allowed_paths.

Backend: add RemoteFileService and new /servers/<id>/files/* endpoints, store advertised allowed_paths in agent_registry and expose a getter, normalize agent system metrics shape in remote_docker_service, and wire capabilities/allowed_paths into the servers list. Extend workflow_engine with agent_command, capability_gate and runtime_gate nodes and basic runtime-version comparison.

Frontend: add TargetPicker component and update FileManager to support remote targets (browse/read/write via agent), guard unsupported remote ops, and normalize agent file responses for the UI. Overall this enables remote file browsing and Phase 4 workflows against connected agents.
2026-05-01 03:37:33 -04:00
Juan Denis 8ddd60f83d Add Cloudflared tunnel management
Introduce Cloudflare Tunnel management end-to-end.

Highlights:
- Add agent/internal/cloudflared package (Manager interface, Linux impl, non-Linux stub) to shell out to cloudflared and detect auth via cert.pem.
- Wire cloudflared Manager into the agent and register cloudflared:* handlers for status/list/create/route/delete.
- Extend protocol.Capabilities and agent payloads with runtimes detection and a cloudflared capability; implement runtime version probes.
- Add backend API endpoints and RemoteCloudflaredService that forward cloudflared actions to agents; update agent registry to store runtimes.
- Frontend: new CloudflaredTab UI, API client methods for remote cloudflared operations, styles, and UX tweaks (plus minor component/class refinements in LogViewer, ProcessTable, FileManager, StyleGuide, terminal styles).

Purpose: allow the panel to manage Cloudflare named tunnels by delegating commands to the agent's cloudflared CLI, while surfacing binary/auth state (install vs authenticated) and detected language runtimes.
2026-05-01 02:26:15 -04:00
Juan Denis dca5c3af67 Add remote cron management (agent, API, UI)
Introduce remote cron management across agent, backend and frontend.

- Agent: add a cron.Manager to agent, register handlers (cron:status,list,add,remove,toggle) and implement thin JSON handlers. Add agent/pkg/protocol message constants.
- Cron package: new platform-agnostic interface with Linux implementation (crontab-driven) and non-Linux stub. Includes content-based stable IDs, validation, list/add/remove/toggle and human-friendly schedule descriptions.
- Backend: expose /servers/<id>/cron/* endpoints and add RemoteCronService to proxy requests to agents via agent_registry, mapping agent responses to HTTP codes.
- Frontend: enhance Tabs component with overflow "More" popover; add API calls for remote cron ops; wire a Cron tab in ServerDetail (guarded by server.capabilities.cron) with UI for listing, adding, toggling and removing jobs; add Cron UI in Git page by switching to the new Tabs component.

Notes: cron functionality is Linux-only; non-Linux agents return an explicit unsupported status. Validation prevents unsafe shell operators and requires absolute command paths.
2026-05-01 01:54:17 -04:00
Juan Denis a93697c7f6 Add agent capability probing and reporting
Probe and report agent feature capabilities (cron, docker, systemd, nginx, etc.) so the panel can gate target pickers without extra round-trips.

Key changes:
- Add agent/internal/capabilities: probes host for features and platform/distro, returns protocol.CapabilitiesMessage cached at agent startup.
- Agent: store probed capabilities, send them on every transport reconnect (sendCapabilities) and include them when using poll transport.
- Protocol: introduce TypeCapabilities and CapabilitiesMessage (map[string]bool + platform/distro fields).
- Poll transport: accept CapabilitiesMessage in Send(), serialize to raw payload and queue for next /poll; support one-shot capabilities in poll body.
- Backend: handle capabilities from socket (on_capabilities) and poll endpoint; persist capabilities in in-memory ConnectedAgent (agent_registry) with helpers get_capabilities/has_capability; include capabilities/platform info in servers API.
- New fleet API (GET /api/v1/fleet/targets): list runnable targets (local + connected agents) and optionally filter agents by a requested feature using the advertised capability map.

Rationale: make agent feature availability explicit and discoverable to the panel so UI pickers can show only applicable agents without asking agents on every UI interaction.
2026-05-01 00:37:48 -04:00
Juan Denis 8ea0586d88 Agent key migration, DB fix, UI tabs/page cleanup
Add robust credential handling and migration for agent encryption keys: surface LoadError on startup, log clear guidance when creds fail to decrypt, try legacy (pre-1.6.14) machine key fallback and transparently re-encrypt under the new host-stable key. Fix machine-key derivation (make host-stable instead of user-stable) and encapsulate decryption/migration helpers.

Fix SQLite autoincrement issue: change ServerMetrics.id from BigInteger to Integer so SQLite will autoincrement rows and metrics are recorded.

Ensure background heartbeat checker can touch the DB by capturing the Flask app in AgentRegistry and entering an app_context in the heartbeat thread (with a test-friendly fallback for when no app is available).

Frontend UI: simplify Tabs component classes and update pages to use a unified .page-container wrapper; replace many manual tab button implementations with Tabs/TabsList/TabsTrigger and add improved tab styles (disabled/focus states). Misc: add docs/PLAN_AGENT_FLEET.md to .gitignore.
2026-05-01 00:27:37 -04:00
Juan Denis 96a6f31d75 Add poll transport; Windows UI & service fixes
Introduce a fallback polling transport and manager: add internal/transport (Manager + poll client) so the agent can fall back from WebSocket to REST long-poll when tunnels mangle WS frames. Wire the Manager into the Agent and expose transport mode in IPC status.

Windows/service fixes: add service-scoped mutex to prevent concurrent service starts, probe IPC port on start to fail fast if occupied, and mirror main.Version into agent package at init. Fix spawned wizard visibility by setting HideWindow=false and add focus helpers that force foreground on Windows.

UI/asset changes: enable DevTools by default (env to disable), inject a small JS shim to forward console errors/navigation readiness to Go logs, hide WebView host until bundle ready (with timeout), and wrap static assets with no-store headers to avoid stale cached bundles. Add local /local/status endpoint so the UI can detect registration when IPC is unreachable.

Pairing/service handling: ensure service stop/start errors are surfaced, wait for service to reach RUNNING after pairing and surface helpful error messages if it doesn't. Implement platform-specific waitForServiceRunning and service control stubs.

Auth/backend alignment: expand API key prefix from 8 to 12 chars to match backend panel expectations and avoid authentication mismatches.

Misc: add focus_windows.go (Windows-only), various small refactors and logging improvements to make blank-page and pairing failures debuggable.
2026-04-30 22:31:04 -04:00
Juan DenisandCopilot 05a70f1a3b Bump vulnerable backend deps flagged by Safety
- python-dotenv 1.0.0 -> 1.2.2 (CVE-2026-28684, arbitrary file overwrite via symlinks)
- cryptography 46.0.5 -> 46.0.7 (CVE-2026-39892 buffer overflow, CVE-2026-34073 cert validation)
- requests 2.32.5 -> 2.33.1 (CVE-2026-25645, insecure temp file reuse)
- Authlib 1.6.9 -> 1.6.11 (CVE: missing CSRF protection)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-29 03:14:25 -04:00
Juan Denis e5a90a3085 Add short-code agent pairing flow
Implements a RustDesk-style short-code pairing feature across agent, backend, frontend and docs.

Key changes:
- Agent CLI: adds `serverkit-agent pair` command (agent/cmd/agent/pair.go) that generates/loads an Ed25519 keypair, enrolls with the panel, shows a rotating pair code, and saves credentials on claim. Adds passphrase marker handling and interactive/headless modes.
- Agent internals: new pairing package with client and keypair (agent/internal/pairing/*.go). Keypair files are encrypted using new exported config EncryptBytes/DecryptBytes utilities and MachineID() helper in agent/internal/config/config.go. go.mod/go.sum updated (Go version and x/term, x/sys bumps).
- Backend API: new pairing blueprint exposing endpoints for enroll, poll, code refresh/freeze, lookup and claim (backend/app/api/pairing.py). Adds PendingAgent model (backend/app/models/pending_agent.py) to track enrollments and their lifecycle.
- Backend service: pairing_service (backend/app/services/pairing_service.py) implements enrollment, rotation, long-polling, claim handling, credential minting and one-shot delivery. Adds a background pairing pruner thread to remove expired pending enrollments (registered in backend/app/__init__.py).
- Frontend: add API client support and page changes to integrate pairing flow (frontend/src/services/api/pairing.js, modifications to Servers.jsx and api index).
- Docs: adds pairing.md explaining the pairing flow.

Why: provide a secure, user-friendly mechanism for operators to claim new agents via short rotating codes and a passphrase, simplifying server onboarding while protecting credentials and preventing brute-force attempts.
2026-04-29 03:08:21 -04:00
Juan Denis cc1151d0ce Add deployment jobs and remote deployment runner
Introduce a deployment jobs system to support cross-server template installations and remote execution. Adds DeploymentJob and DeploymentJobLog models, DeploymentJobService for job lifecycle, and DeploymentPlanRunner to execute plans locally or via agents. TemplateService gains build_install_plan and related helpers to produce executable plans; Template installs can run async (202) or synchronously. Backend API/CLI updated: new deployment-jobs endpoints, apps API uses RemoteDockerService for remote compose actions, and CLI commands (deploy-template, deployment-status, list-servers) were added. Agent and config changes: file access validation with allowed_paths, file write create_dirs option, default allowed paths, and install script/systemd read-write paths updated to permit /var/lib/serverkit and /var/serverkit. Migration file for deployment_jobs schema included.
2026-04-29 00:39:16 -04:00
Juan Denis 313f1a8f6b Remote Docker remove, normalize API & permissions
Add end-to-end support for removing remote Docker networks and volumes and normalize remote API responses. Server: new Flask endpoints to DELETE remote networks/volumes and helpers to compute external base/ws URLs and v1 API paths. Agent: register new handler docker:network:remove and Client.RemoveNetwork implementation. Backend service: RemoteDockerService.remove_network uses agent registry for remote servers and falls back to local DockerService. Models: expand Server permission logic to map agent actions to profile/legacy scopes (DOCKER_READ_ACTIONS, scope expansion and wildcard matching) and add unit tests covering permission mappings. Frontend: handle agent-backed response shapes (unwrapRemoteData, normalizeListResponse), call new removeRemoteNetwork/removeRemoteVolume API methods, update download/install URLs to /api/v1, and tweak several UI texts. Install scripts: change config/log directories, pass --config when registering/starting agent, and update systemd/service/unit and Windows service install paths. README: small wording updates about fleet/remote deployment and monitoring.
2026-04-29 00:11:46 -04:00
Juan Denis 71b341ef77 Add plugin system (backend + frontend)
Introduce a plugin installation and management system.

Backend: add InstalledPlugin model, plugin_service with download/install/enable/disable/uninstall, dynamic blueprint registration and frontend manifest generation, and plugins API endpoints (list/get/install/enable/disable/uninstall). Register plugins blueprint and attempt to hot-load installed plugin blueprints at app startup.

Frontend: add PluginLoader component (Vite import.meta.glob) to render plugin widgets, extend Marketplace UI to install/manage plugins, add API client methods for plugins, and update styles for the marketplace plugin UI. This enables installing plugins from GitHub/repos/zips and managing them from the app.
2026-03-28 15:58:34 -04:00
Juan Denis e0492f54f0 Move nginx private URLs to location snippets
Generate private URL configs as location-only snippets in /etc/nginx/serverkit-locations instead of full server blocks. nginx_service.py: write location snippets (.conf) to LOCATIONS_DIR, ensure the locations directory, remove legacy separate server blocks, and add get_location_config to read these snippets (used for private URLs, Gitea, WordPress). install.sh & serverkit: only remove conflicting catch-all (server_name _) configs that are not ServerKit-managed (skip serverkit.conf and serverkit-*), strengthen the server_name regex, and create the serverkit-locations dir. cmd_doctor: add docker presence checks and avoid frontend checks when docker is missing; require root in --fix mode. Misc cleanup and minor refactors around enabling/disabling configs and reload handling.
2026-03-28 13:47:43 -04:00
Juan Denis 39ac49609e Use nginx location snippets and add doctor
Switch Gitea/WordPress to location-only snippets included into serverkit.conf and introduce /etc/nginx/serverkit-locations. NginxService now writes snippets to LOCATIONS_DIR, ensures the directory exists and removes legacy full-server configs to avoid server_name _ conflicts; validation blocks use of the reserved catch-all name. install.sh and the update path create the locations dir and remove any conflicting catch-all site configs. serverkit.conf proxy port updated (8080 -> 3847) and now includes the snippets directory. Add a new serverkit doctor command (with --fix) and help entries to run diagnostics and auto-fix common issues (nginx, docker, service health, ports, disk, DB, etc.).
2026-03-28 12:46:33 -04:00