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>
Two fixes for the race where the browser opens report.html before any
VM has registered:
- Orchestrator seeds state.json with {"running":true,"vms":{}} the
instant OutDir is created, so a refresh before the first VM is added
still has a "running" signal.
- report.py treats missing state.json as RUNNING (not FAIL), and a VM
with a non-done stage but no install-status file as RUNNING in its
per-VM badge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
Fixes a cascade of PowerShell + Multipass quirks found during the first
real run:
- Rewrote orchestrator sequentially. Start-Job's separate runspace was
hiding the real errors below behind opaque "method on null" failures.
- Removed `function Mp` helper. PowerShell ships a built-in alias `mp`
for Move-ItemProperty that silently shadows same-named functions and
turned multipass calls into Move-ItemProperty calls. Inline `& $MpExe`
everywhere instead.
- Lowered ErrorActionPreference from Stop to Continue. Multipass writes
harmless "cannot set permissions" warnings to stderr when transferring
to NTFS; Stop turned those into fatal aborts.
- Stopped piping `multipass exec` stdout through Tee-Object. The pipe
could stay open indefinitely after the remote command exited, hanging
the orchestrator for 45+ min on a finished install. Now redirects to a
file on the VM and only checks the exit code.
- Tightened tar excludes. Tarball dropped from 305 MB to 20 MB.
- Added `npm ci` in vm-install.sh after the local-code overlay. The
original installer ran `npm ci` from origin/main, then the overlay
dropped in a dev package.json that needed different deps, causing a
spurious frontend build failure.
- Added -ReuseVm flag for re-running just the harness against an
already-installed VM (skips the 20-min launch cost).
First successful end-to-end run on Ubuntu 24.04: 10/10 tests passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PowerShell 5.1 reads .ps1 files as ANSI by default; em-dashes and check
marks were mangled into bytes that broke the parser. Replaced with ASCII
equivalents so the script runs under stock Windows PowerShell without
needing pwsh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One-click PowerShell entry point that spins up Ubuntu/Debian VMs in
parallel, installs ServerKit from the local working tree (not origin/main),
runs a pytest API harness against the live panel, and aggregates results
into a self-contained HTML report.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Add a unified, cross-platform development launcher and update docs/styles to SCSS.
- Add dev/dev.sh: comprehensive Bash dev launcher with stable non-default ports, port resolution/cleanup, WSL-aware behavior, ngrok support, and validation tooling.
- Rewrite Windows entrypoint (dev.ps1) to require WSL and delegate to dev/dev.sh; make top-level dev.sh a thin wrapper that execs dev/dev.sh.
- Add AGENTS.md with project overview, architecture, and development instructions for assistance tools.
- Update docs (CLAUDE.md, CONTRIBUTING.md) to reflect migration from LESS to SCSS and update style guidance.
- Update .gitignore to ignore backend/.venv-wsl, frontend.env.development.local, and /.agents.
- Frontend: add new pages and page-specific SCSS files, update main.scss, components, pages, client/socket services, and vite.config.js to support the SCSS-based frontend changes.
These changes unify local development workflows (especially via WSL), improve port stability, and complete the frontend styling migration from LESS to SCSS.
Consolidate and expand .empty-state-sm styling: removed the earlier duplicate block and added a single rule that uses a flex column layout with centered alignment, gap, and consistent padding/text color. Also adds specific SVG (color, opacity, no shrink) and paragraph (reset margin) rules to improve alignment, spacing, and icon appearance for empty-state UI.
Refactor: move component styling out of inline Tailwind/CVA classes into SCSS utility classes. Many UI components (dialog, alert-dialog, card, button, badge, checkbox, input, label, select, sheet, switch, table, tabs, textarea, etc.) now use stable .ui-*-prefixed classes and data-slot/data-variant attributes. Removed several class-variance-authority usages and simplified JS by mapping variant/size to SCSS classes (e.g. Button.buttonVariants) and adding a backwards-compat shim for badgeVariants.
Added a new styles/components/_ui.scss containing the style surface for the .ui-* classes and expanded styles/components/_buttons.scss to implement the new button API. main.scss now imports the new _ui.scss. Small behavioral tweaks: Radix primitives keep animation-related attributes, icon sizing cleaned up (removed many explicit size classes), and sheet/dialog close buttons use .ui-* classes. Overall this centralizes geometry/color in SCSS and keeps JSX as thin wrappers.
Normalize button vertical alignment and spacing: add leading-none to root styles and set vertical padding to py-0 for default/sm/lg sizes. Remove conditional has-[>svg] px adjustments and instead set a dedicated icon size variant (size-10) with an explicit SVG sizing rule ([&_svg:not([class*="size-"])]:size-[18px]) to ensure consistent icon rendering and spacing across variants.
SystemStatusCard.jsx: wrap the heading and stale Badge inside a new .system-status-card__title container to better group title and status metadata.
button.jsx: tweak button variant classes to use text-center and increase disabled opacity (50% -> 60%) to adjust visual weight.
_server-packages.scss: comprehensive style refinements — added header bottom padding and border, tightened typographic scale for headings and labels, adjusted probed-at numeric styling, changed rows to remove extra gap and introduced row padding/bottom borders, increased first column width, and updated colors, spacing and alignment for row values and chips. These changes improve spacing, alignment and visual hierarchy in the server packages/system status UI.
Introduce SCSS for alerts and notification UI on the servers page. Adds .alerts-tab layout and .alerts-section (including header, count badge and muted modifier) and .notification rules (head, title, time, actions and button sizing). Uses existing design tokens (spacing, font-size, colors, radii) to match app styling.
Add dev toggle for IPC auth and fix CORS Authorization header; introduce SERVERKIT_IPC_NO_AUTH to skip bearer checks in dev and log a warning. Frontend: wide UI refactor — convert Sheet-based modals to Dialogs, restyle ConfirmDialog, update Badge and Button variants (sizes, rounded tokens, data attributes), improve Tabs overflow trigger handling, and add ARIA labels for process action buttons. Docker page: unify/normalize ports formatting for multiple agent shapes and render safely. ServerDetail: add Alerts tab, load and manage security alerts (acknowledge/resolve), and restructure overview into KPI tiles, info rows, and SystemStatusCard. Replace several ad-hoc modals in Cron/Cloudflared with Dialog + Select forms and tidy Settings/agent sections. Also add UI_REPORT.md to .gitignore.
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.
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>
The current Manager only falls back on hard handshake failures
(RSV1 / engine.io). It doesn't catch the more common CF-tunnel
failure mode the user observed in events.json: WS authenticates
fine, stays alive 2-3 minutes, drops, reconnects, drops again,
forever. Each cycle pays an auth round-trip and burns bandwidth on
retry/backoff loops without ever delivering live streams.
Add a stability monitor that samples IsConnected() at 1Hz, records
disconnect timestamps in a sliding 10-minute window, and triggers
runFallback() permanently after 3 disconnects in window. Once on
poll, no more reconnect churn — heartbeat + buffered capability/
sysinfo get delivered cleanly through the REST endpoint that the
intermediary doesn't mangle. Restart-to-recover semantics preserved
(matches the existing "stay on poll until process restart" rule).
Threshold (3 drops / 10 min) is conservative: a single drop or
two from a brief network blip won't trigger; only sustained
unreliability does. Users with healthy direct WS connections never
hit this path at all.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
The connectionWatcher only fired sendCapabilities() on a single
false→true transition of IsConnected(). When the WS→poll fallback
dance briefly raised IsConnected() against the WS backend right
before it died, the message landed on a transport whose Send()
silently buffered it forever — the panel ended up with an empty
capability map for the lifetime of the agent process.
Add a parallel resend ticker that fires every 5s for the first
minute then backs off to every 60s thereafter. The panel's
update_capabilities is a pure overwrite, so re-shipping the same
payload costs nothing; the worst case is a 60s gap before the next
refresh once the agent has been up for a minute.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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.
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>
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>
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).
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.
Introduce a modular file manager UI: add ContextMenu, FileCard, FileRow, FileIcon, FolderTree, ImageThumb, PreviewDrawer and fileTypes utilities. Refactor FileManager.jsx to use these components and implement selection, multi-select/shift-range, context menu, preview drawer with editable files, image thumbnails (auth download), drag-and-drop and input uploads with progress, pinned quick-access, folder tree with caching/expand behavior, breadcrumbs, persistent view preferences, search, and improved navigation/history. Update styles and persist various UI settings in localStorage. Also add /docs/plans to .gitignore.
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.
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.
Introduce a small agent-side GUI SDK to expose desktop-capture primitives (capabilities + screenshot) with platform implementations (windows, linux, other) and register handlers in the agent; add protocol actions gui:capabilities and gui:screenshot.
Refactor frontend UI for consistency and usability: replace multiple custom banners with a unified alert system, introduce EmptyState component usage across pages, switch Modal implementation to a Sheet (responsive side sheet with size tokens), make StatCard clickable with detail and active states and update Servers to use StatCard/StatsGrid, and standardize error/alert classes in QueryRunner, RemoteTerminal, CompareView, PipelineView, PromoteModal, ResourceLimitsModal, BasicAuthModal, etc. Also adjust badge/sheet sizing, alert & card styles, theme border variables, and small polish in ConfirmDialog, StyleGuide, and other components.
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.