Commit Graph
602 Commits
Author SHA1 Message Date
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 a2da251bb8 chore(agent): stop tracking committed MSI build artifacts
serverkit-agent-1.4.17-x64.msi (~26 MB) and its .wixpdb were committed in
4db9300, before the ignore rules for build output existed — tracked files
override .gitignore, so they lingered in the repo. The rest of the agent
artifacts (agent/dist/*.exe ~233 MB, the ~90 other files in
packaging/msi/output/, root dist/) were already untracked/ignored.

Untracks the two stale artifacts (kept on disk) and hardens .gitignore:
add /packages/ (the agent-release.yml output dir) plus global *.msi /
*.wixpdb so stray build output can't be committed again. Release artifacts
belong in GitHub Releases via .github/workflows/agent-release.yml.

The *.syso Windows resource objects under agent/cmd/agent/ are deliberately
left tracked: they're build INPUTS embedded by the Go toolchain at build
time (like the force-included .ico/.png brand assets), not distributables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 15:14:02 -04:00
Juan DenisandClaude Opus 4.8 9de7ecf7c1 fix(agent): Windows file browser — per-entry paths + drive enumeration
The remote file browser was broken because handleFileList returned entries
with only name/is_dir/size/modified and NO `path`. The frontend keys,
selects, and navigates on entry.path, so remote browse was broken on every
OS (entry.path was undefined), and Windows additionally had no way to reach
drives other than the agent process's working drive.

Agent (file_handlers.go + build-tagged helpers):
- Emit an absolute, forward-slash `path` for every entry and for the
  directory itself (filepath.ToSlash), giving the panel one canonical
  separator regardless of the agent OS.
- Normalize the inbound path with filepath.FromSlash before any os call so
  os.ReadDir and the allowlist validation work with Windows separators.
- On Windows, an empty/"/" browse request enumerates logical drives
  (GetLogicalDrives) as virtual directory entries instead of silently
  listing one drive. enumerateDriveRoots() is build-tagged: real on
  windows, nil elsewhere (falls through to "/"). The allowlist still gates
  whether the panel can enter each drive.

Frontend (FileManager.jsx):
- deriveParent and breadcrumbs are drive-aware: "C:/Users" -> "C:/" (not
  "C:", which is Windows' "current dir on C:"), and a drive-rooted path
  builds crumbs from the drive list instead of bogus "/C:" paths. POSIX
  paths are untouched (changes are guarded by drive-letter regexes).

Verified: go vet + go test ./... (host) green incl. new
TestHandleFileListEmitsForwardSlashPaths; cross-builds linux/darwin/
windows-arm64; eslint + vite production build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 15:04:45 -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.8 eab1a35794 docs: sync docs with dev, add CHANGELOG/SECURITY, document Windows agent
dev was ~149 commits ahead of the last release with no changelog and several
stale docs. This brings the user-facing docs in line with what actually ships.

New:
- CHANGELOG.md — curates the unreleased panel work (agent fleet, plugins,
  status pages, cloud provisioning, RHEL support, installer hardening).
- agent/CHANGELOG.md — the agent release workflow links to this on every
  published release; it was a broken link. Now documents the agent's
  independent release line.
- SECURITY.md — vulnerability reporting policy + agent trust model
  (agent.key is host-equivalent; Features.Exec off by default; single-worker).

Fixes:
- Stale version stamps: docs/README.md and docs/API.md said 0.9.0 -> 1.6.7.
- Supported OS: README/INSTALLATION said Ubuntu/Debian only; installer also
  covers Fedora and RHEL/Rocky/AlmaLinux.
- Dead links/placeholders: API.md wiki link, INSTALLATION.md "yourusername".
- Windows agent + short-code pairing were undocumented; added to README,
  agent/README.md, and docs/pairing.md (incl. corrected host-stable
  credential-key derivation — USERNAME was dropped in agent 1.6.14).
- Documented the single gevent-websocket-worker deployment constraint in
  CLAUDE.md and docs/ARCHITECTURE.md.
- CONTRIBUTING.md: removed reference to a non-existent frontend test suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 14:38:15 -04:00
Juan DenisandClaude Opus 4.7 ed68cdccb5 Support Rocky/RHEL + fix Docker install on Fedora
install.sh:
- Detect rocky/almalinux/rhel/centos as OS_FAMILY=rhel.
- The Fedora system-deps block now also runs for rhel; auto-install
  epel-release on RHEL-family.
- Docker install on Fedora/RHEL no longer pipes get.docker.com. That
  script recently added docker-model-plugin to its package list, which
  doesn't exist in docker-ce repos and aborts the install on Fedora 39.
  Replace it with explicit `dnf config-manager --add-repo` +
  `dnf install docker-ce docker-ce-cli containerd.io
  docker-compose-plugin docker-buildx-plugin`.
- Node.js and nginx installs route through dnf for rhel too.
- SELinux setsebool runs for rhel too.

scripts/test/full-stack-test.ps1:
- fedora image bumped from fedora39 (EOL Nov 2024) to fedora40 (current).
- Test-VagrantInstall + Test-VmInstall: defensive read of status file.
  Previously `(Get-Content -Raw).Trim()` crashed with InvokeMethodOnNull
  when the status file was missing/empty (which happens when the VM's
  install script crashed before producing it). Now falls back to 'FAIL'
  cleanly so the run summary still works.

Found by full 5-distro suite: Ubuntu 22/24 + Debian 12 passed; Fedora 39
failed on docker-model-plugin; Rocky 9 failed silently due to apt-vs-dnf
+ status NPE. All three fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:34:50 -04:00
Juan DenisandClaude Opus 4.7 e3337f0550 Stop vagrant up output from corrupting test status
The previous commit added Tee-Object so vagrant up output was visible
in the terminal, but Tee-Object's pipeline output then leaked into
Test-VagrantInstall's return stream — the function returned vagrant's
entire log instead of "OK", so the final summary reported every passing
Vagrant VM as a failure.

Append `| Out-Host` after Tee-Object to consume the pipeline. The
operator still sees the live output; the function's return stays clean.

The previous Debian 12 run was actually all-green (10/10 pytest passed,
0 plugin-loader errors) — only the summary line was wrong.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:59:31 -04:00
Juan DenisandClaude Opus 4.7 18d25c6d03 Stream vagrant up output to terminal so prompts are visible
Previously the orchestrator redirected vagrant up's stdout+stderr to a
log file, which meant the box download progress was invisible and any
interactive prompts (like "choose a Hyper-V switch") were silently
swallowed while vagrant blocked on stdin. The operator had no signal
the run was hung.

Use Tee-Object so the operator sees download progress + prompts live
in the terminal, while the log file still gets a copy for later
inspection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:41:53 -04:00
Juan DenisandClaude Opus 4.7 59e2874d5e Pin Hyper-V switch in Vagrantfile to avoid interactive prompt
Without this, vagrant up halts on first boot asking the operator to
choose a Hyper-V switch from a numbered menu, which breaks unattended
test runs. Hard-code "Default Switch" (Hyper-V's built-in NAT switch,
present on every Hyper-V install), overridable via
VAGRANT_HYPERV_SWITCH_NAME env var for users who want a different
network.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:29:53 -04:00
github-actions[bot] 939575f541 chore: bump version to 1.6.7 [skip ci] 2026-05-17 16:15:59 +00:00
Juan DenisandClaude Opus 4.7 4657483f10 Detect missing admin elevation when Vagrant distros are requested
Vagrant + Hyper-V requires the process itself to run elevated; being in
the Hyper-V Administrators group is necessary but not sufficient. Without
this guard, the script would launch, pack the source, then hit a wall on
'vagrant up' with a permission error mid-run.

Now it checks early: if any requested distro uses the Vagrant backend
and the script isn't running elevated, print exact instructions and exit.
Multipass-only runs (Ubuntu) still work without admin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:15:49 -04:00
Juan DenisandClaude Opus 4.7 3b2974177a Add Vagrant + Hyper-V runner for Debian/Fedora/Rocky coverage
Multipass on Windows only ships Ubuntu images. To cover Debian 12,
Fedora, and Rocky 9 too, add a parallel Vagrant runner that uses the
Hyper-V provider (no VirtualBox conflict with Multipass).

scripts/test/
- vagrant/Vagrantfile: parameterized via SK_BOX, SK_VM_NAME env vars;
  Hyper-V provider with linked clones; bootstraps rsync + curl.
- full-stack-test.ps1:
  - DistroMap replaces ImageMap; each entry tags backend (multipass/
    vagrant), image/box name, login user.
  - Default Distros now: ubuntu22, ubuntu24, debian12, fedora, rocky9.
  - Backend auto-detection: if Vagrant isn't installed, vagrant distros
    silently skip rather than failing. Same for Multipass.
  - Test-VagrantInstall mirrors Test-VmInstall using `vagrant upload` /
    `vagrant ssh -c`, per-VM VAGRANT_CWD under OutDir for state.
  - Teardown dispatches by backend (multipass delete vs vagrant destroy).
- README updated with the new prereqs and distro coverage table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:48:39 -04:00
github-actions[bot] 049e80f812 chore: bump version to 1.6.6 [skip ci] 2026-05-17 15:21:18 +00: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
github-actions[bot] eaad020fab chore: bump version to 1.6.5 [skip ci] 2026-05-17 15:17:37 +00:00
Juan DenisandClaude Opus 4.7 1f7a0a2171 Stop live report flashing FAIL/VMs:0 on first open
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>
2026-05-17 11:17:27 -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 DenisandClaude Opus 4.7 099c61d859 Make E2E test harness actually run end-to-end on Windows
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>
2026-05-17 00:14:59 -04:00
github-actions[bot] 8f523f2de9 chore: bump version to 1.6.4 [skip ci] 2026-05-17 02:32:35 +00:00
Juan DenisandClaude Opus 4.7 37d9c63b9e Strip non-ASCII chars from test harness PS scripts
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>
2026-05-16 22:32:25 -04:00
github-actions[bot] 2a2764deb2 chore: bump version to 1.6.3 [skip ci] 2026-05-17 02:13:33 +00:00
Juan DenisandClaude Opus 4.7 abb8889c83 Add Multipass-based E2E test harness
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>
2026-05-16 22:13:17 -04:00
github-actions[bot] cf88e543de chore: bump version to 1.6.2 [skip ci] 2026-05-05 18:04:18 +00: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 b4414aa5b8 Introduce unified dev launcher and SCSS migration
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.
2026-05-03 04:20:14 -04:00
Juan Denis 4f9d4def18 Enhance .empty-state-sm styles and relocate
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.
2026-05-02 04:56:34 -04:00
Juan Denis 6dc9bd76ae Migrate UI components to SCSS .ui-* classes
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.
2026-05-02 04:22:07 -04:00
Juan Denis cd8c9bd00c Refine Button component sizing and leading
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.
2026-05-02 04:01:01 -04:00
Juan Denis a358300e1f Refine system status card and button styles
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.
2026-05-02 03:56:06 -04:00
Juan Denis fe736fc6d2 Add alerts and notification styles
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.
2026-05-02 03:27:24 -04:00
Juan Denis fea0698f3e UI: revamp dialogs, badges, and alerts tab
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.
2026-05-02 03:20:07 -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 c31da726f6 Detect WS flapping and fall back to polling permanently
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>
2026-05-01 07:02:04 -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