Telemetry: ensure sanitized payloads bigger than the 32KiB threshold are replaced with a {_truncated: True} marker before storage, and add a unit test covering that behavior.
Backend API: adjust imports in secrets_webhooks to use WorkspaceService and include User model.
Frontend: make Sidebar resilient to malformed active_workspace JSON in localStorage (try/catch), and ensure socketService.disconnect() is called when user becomes unauthenticated in NotificationsProvider to clear notifications and unread count.
Files: backend/services/telemetry_service.py, backend/tests/test_telemetry.py, backend/app/api/secrets_webhooks.py, frontend/src/components/Sidebar.jsx, frontend/src/contexts/NotificationsContext.jsx
Rename service settings 'repository' → 'git' (routes, labels, and links) and update App route comment. Reorder sidebar footer so user mini and notification bell share a single row and add accessible user-mini button. Adjust notification panel positioning, add show animation, and responsive layout tweaks. Replace Workspaces card grid with a dense table view: add search, status segment control, bulk select checkboxes, and selection state. Minor SCSS tweaks for sidebar button sizing and WordPress action width. These changes improve discoverability and bulk management UX.
- Rename service Environment tab to Env Vars and settings Environment to Environment Type
- Add Domain & SSL section to service settings with attach domain + Let's Encrypt flow
- Surface primary domain from domains array in service subtitle
- Add prominent repo connection pill to WordPress detail header
- Apply WordPress list layout to Services page (drop KPIs, tabs+search toolbar)
WorkflowEventBus.emit no longer spawns a per-event daemon thread to match +
trigger workflows. It now enqueues a single 'workflow.dispatch' job; the new
WorkflowEventBus.dispatch_event handler does the matching (active event-trigger
workflows whose eventType matches, honoring the 60s cooldown) and enqueues a
workflow.execute job per match. emit is a non-blocking enqueue, best-effort
(never raises into the caller), and the whole event -> dispatch -> execute path
now flows through the bus (observable, persistent across restart). Removes the
last ad-hoc thread on the workflow path.
Registered alongside workflow.execute in WorkflowEngine.register_jobs.
tests: dispatch handler registered; emit enqueues a dispatch job (not a thread);
dispatch fans out only to the matching event workflow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire scheduled backups onto the unified job system and retire the orphaned
backup scheduler thread.
- BackupService.start_scheduler/_scheduler_loop were defined but NEVER called,
so scheduled backups never actually ran. Replace with check_backup_schedules()
(enqueues a backup.run job per due schedule) driven by a new
builtin.backup_scheduler tick, plus a run_backup_job handler that executes one
schedule via the existing _run_scheduled_backup. max_attempts=1 (backups
aren't idempotent); a failed run marks the job failed. This activates
scheduled backups, but stays gated by the existing backup config.enabled
(default False), so installs without configured backups are unaffected.
- Fix a latent bug in _send_backup_notification: it called
NotificationService.send_all(title=, message=, severity=) but send_all takes
a list of alert dicts — corrected (faithful: still routes to system channels
via the queue-backed bus).
- Monitoring intentionally left as-is: a user-toggled continuous stream whose
alert delivery already goes through the queued send_all. Phase 8 orchestration
is already met — send_all is a non-blocking shim over broadcast_system.
- tests: backup.run register/handler success+failure; scheduler tick enqueues
only due+enabled schedules and is inert when disabled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add /jobs — the single pane to observe and manage all background work, built
on the /api/v1/jobs API. Mirrors the DeliveryLog/Telemetry admin pages.
- Jobs table polls every 5s: status Pills (succeeded/running/pending/failed/
cancelled), kind, owner, attempts, age. Status/kind filters. Per-row Cancel
(pending/running) and Retry (failed/cancelled) with toast feedback. Row
expands to a detail view (payload/result/error/correlation/timing) via getJob.
- Scheduled-jobs table: name, kind, schedule (cron or "every Nh"), next/last run,
with Run-now and Pause/Enable.
- Stat cards (total/running/pending/succeeded/failed) from /jobs/stats.
- Wiring: services/api/jobs.js module bound into ApiService; route + page title
in App.jsx; "Jobs" item in the sidebar "system" group; _jobs.scss imported
in main.scss. Admin-gated client-side via useAuth().isAdmin.
Lint clean (0 errors) and production build passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate workflow executions onto the unified job system (single-job wrap).
WorkflowEngine is split into _create_execution (validate graph + persist the
running WorkflowExecution row) + run_execution (the synchronous DAG core) +
enqueue_execution (create row, enqueue a 'workflow.execute' job, return the
execution id immediately). execute_workflow stays as the synchronous primitive.
- All four triggers route through enqueue_execution: manual API, webhook,
cron tick (builtin.workflow_schedules), and the WorkflowEventBus. The manual
and webhook endpoints no longer block the HTTP request on the whole DAG; the
cron tick enqueues one job per due workflow instead of running them serially
inside the tick; the event bus thread now only matches + enqueues.
- max_attempts=1 (workflows deploy/script/notify — not idempotent). A failed
run raises so the unified job is marked failed; the WorkflowExecution row
keeps the detailed per-node status/logs. Graph validation still happens
synchronously at enqueue, so an invalid workflow surfaces immediately.
- tests: register, enqueue-creates-row+job, empty workflow runs to success,
failure path marks the job failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The release workflow was publishing Docker assets and source but never
the serverkit-vX.Y.Z-linux-{amd64,arm64}.tar.gz files that install.sh,
update.sh, and 'serverkit update' expect to download. This caused release
updates to fail with 'Failed to download the release tarball.'
- Make scripts/build-release.sh honor BUILD_ARCH for CI matrix builds.
- Add a build-release job that builds tarballs on native amd64 and arm64
runners and uploads them as artifacts.
- Wire the release job to download those artifacts, include them in the
GitHub Release, and checksum them.
- Add the one-line installer to the release notes body.
Add docs/NOTIFICATIONS_ROADMAP.md and docs/ORCHESTRATION_ROADMAP.md to .gitignore so newly added roadmap documents are not tracked by Git. This keeps in-progress planning files out of commits until ready for publishing.
Migrate deployment installs off their ad-hoc daemon thread onto the unified
job system. DeploymentJobService.install_template now enqueues a
'deploy.install' Job instead of spawning a thread via _start_background_job
(retired); the JobConsumer runs it by calling the existing run_job.
- max_attempts=1: installs create containers/files/an app row and are not
idempotent, so no auto-retry (preserves prior single-shot behavior).
- The unified Job shares the DeploymentJob's correlation_id and links back via
owner_type='deployment_job'/owner_id; the DeploymentJob row stays the rich
record (plan/steps/logs/progress). A failed run raises so both rows show failed.
- Bonus: CLI `deploy-template` (no --wait) now persists to the queue and runs
when a consumer is available, instead of dying with the CLI process.
- tests: handler success/failure mapping + install_template enqueues-not-threads.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalize the Notification Bus pattern (thin {id} message -> fat DB row
-> single consumer) into a Job/WorkUnit layer on top of the Queue Bus, so
background work flows through one pipe instead of many ad-hoc threads.
- app/jobs: Job + ScheduledJob models, handler registry, JobService +
ScheduledJobService, one generic JobConsumer, one JobScheduler ticker,
and a plugin SDK surface (app.plugins_sdk.jobs).
- Migrate the 8 per-domain daemon scheduler threads (auto-sync,
snapshot-retention, workflow, health-check, wp-update, api-background,
pairing-prune, registrar-expiry) to ScheduledJob rows + built-in
handlers that reuse the exact prior check functions: cadence and
per-task de-dup unchanged, only the trigger moved.
- Retire the orphaned in-memory BackgroundJobService; repoint
/api/v1/performance/jobs at JobService.
- Add /api/v1/jobs (list/get/cancel/retry/stats; scheduled list/run/
toggle) as a single pane over all background work.
- tests/test_jobs.py: enqueue->run->succeed, dead-letter->failed,
unknown-kind, cancel, retry, scheduler tick/advance, built-ins.
No Alembic migration (tables created via create_all), avoiding a
multi-head clash with the in-flight telemetry work. Plan lives in
docs/ORCHESTRATION_ROADMAP.md (local/git-ignored).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add SystemEvent model, TelemetryService, /api/v1 telemetry endpoints, and
a /telemetry UI page. Instrument audit, notification, webhook, backup,
deployment, and monitoring paths. Add correlation_id to Notification,
EventDelivery, and DeploymentJob so related events can be grouped.
- 18 backend tests added and passing
- Frontend production build clean
- NotificationsContext + header bell/dropdown (sidebar + mobile top bar)
with a live unread badge over Socket.IO and a /notifications history page.
- Admin /admin/notifications delivery log (stat cards, status/channel
filters, retry) and an email-provider management UI (add/test/
set-default/delete) for SMTP/SendGrid/Postmark/SES/Mailgun.
- api/notifications.js methods, socket 'notification' forwarding, a timeAgo
util, and themed sk-notif* / sk-dlog / sk-eprov styles.
Builds clean (vite) and lints clean (eslint exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reusable, non-blocking notification layer on top of the Queue Bus:
notify.send(event, to, data) records a Notification + per-channel
NotificationDelivery, queues them, and a daemon consumer delivers via
pluggable channel adapters (in-app, email, chat) inheriting retry,
backoff and dead-letter.
- Jinja2 email system: branded, dark-mode, Outlook-safe base layout +
per-event templates (security/backup/monitoring/welcome/invitation).
The legacy inline f-string email was removed and now renders here.
- In-app center API (inbox/unread-count/mark-read) + per-user Socket.IO push.
- Admin delivery log (stats/filter/retry).
- Email providers (SMTP/SendGrid/Postmark/SES/Mailgun) with encrypted
credentials + test; the email adapter prefers the default provider and
falls back to the legacy SMTP config; surfaced in ConnectionRegistry.
- NotificationService.send_all() is now a non-blocking shim that routes
system alerts through the bus. Migrations 023 + 024.
Tests: backend/tests/test_notification_bus.py (20). Full suite: 391 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Enforce max_servers quota on server creation and workspace moves
- Add my_role/my_effective_role to single workspace GET response
- Add Navigation Permissions editor to Workspace Settings
- Store full active workspace object for nav/brand settings
- Update workspace API and service for workspace context handling
- Refine Sidebar and WorkspaceSwitcher components
- Update sidebar navigation items for workspace-aware routing
- Enhance SecretsWebhooks page and API client integration
Mirror the Mapanare release pattern for the ServerKit panel:
- Trigger release on push to main (PR merge), v* tags, and workflow_dispatch
- Add workflow_call to backend-ci.yml so release can use it as a CI gate
- Read VERSION file, create/push v{VERSION} tag when releasing from main
- Build and push multi-arch Docker image jhd3197/serverkit:{VERSION} + latest
- Create GitHub Release with Docker install instructions
The Go agent has moved to its own repository:
https://github.com/jhd3197/serverkit-agent
Changes:
- Delete agent/ directory and all source, build, and packaging files
- Delete .github/workflows/agent-ci.yml and agent-release.yml
- Delete scripts/test/agent-test.ps1 (agent now builds in separate repo)
- Remove committed dist/serverkit-agent-windows-amd64.exe build artifact
- Update docs/INSTALLATION.md and docs/README.md links to point to the new repo
- Add migration notes to docs/AGENT_AUDIT.md, docs/REMOTE_ACCESS_ROADMAP.md,
SECURITY_AUDIT.md, and docs/plans/AGENT_FLEET_PHASES_3-4.md
Panel agent API endpoints under /api/v1/servers/agent/ and /api/v1/agent/
are retained — they are the control-plane contract with the standalone agent.
Verified:
- backend: pytest (371 passed, 12 skipped)
- frontend: npm run lint (0 errors)
Sidebar — leaner default + setup wiring:
- Add a "Recommended" preset (Full minus Marketplace, Queue Bus, and
Secrets & Webhooks) and make it the default for unconfigured users, so
fresh installs aren't maximalist. The three demoted items stay fully
routable and one click away via the "Full" view or Customize Sidebar.
- Factor the three into a shared ADVANCED_ITEM_IDS list so every curated
preset hides them consistently.
- Wire the Setup wizard's use-case answer to an initial preset
(presetForUseCases): a single WordPress intent -> Web Hosting, a single
DevOps intent -> Docker/DevOps, anything mixed/general -> Recommended.
Settings — split the 17-item rail:
- Add a two-way "My Account" / "Admin" segmented switch (admin-only) so
personal preferences aren't interleaved with server-wide system
controls. The active group is derived from the tab, so deep links
(e.g. /settings/users) open the right group. About moves into My
Account; the dev/local-dev tools consolidate under Admin.
Verified: eslint (0 errors) and vite build both pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expose Remote Access as a per-server tab and adapt the fleet page for server-scoped views. RemoteAccess now accepts serverId, filters/labels tunnels involving that server, renders inline action button when embedded, disables private-server select for scoped views, and links nodes to per-server Remote Access pages from fleet view. Added tab to ServerDetail when wireguard is supported and updated styles for responsive intro, node links, and a “this server” tag. Small refactors: useMemo/useCallback optimizations and cleaner topbar action handling.
- TargetPicker now renders a static label when only one target option is available,
removing the pointless/disabled dropdown on single-server setups.
- Change Terminal tab route from /terminal/terminal to /terminal/shell and add
a redirect for the old URL.
- Terminal shell tab only lists agents advertising terminal capability, preventing
'Agent not connected' errors on local/WSL dev installs.
- Add per-server creation guard in RemoteTerminal to avoid duplicate session
attempts on remounts or rapid server switches.
Drop the stray /docs/screenshots ignore that overrode the existing negation (.gitignore:117-118), so the 23 mock-data README screenshots are committed assets again, and add the freshly regenerated set (ServiceDetail header selector updated in the local capture skill).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add app/utils/formatting.py with format_bytes() and delegate 9 duplicated
human-readable byte formatters across services.
- Import admin_required from app.middleware.rbac in 15 API modules instead of
redefining it locally.
- Extend app/utils/slug.py with validate_app_name() and use it for template
install/validation and apps slug normalization.
Add a POST /security/clamav/start endpoint and SecurityService.start_clamav() to enable+start the ClamAV daemon (tries clamav-daemon and clamd). Add unit tests that mock ServiceControl to verify success/failure and auth. Wire a startClamAV API call into the frontend and extend the Security Overview: richer posture UI, one-click fixes (install, start service, init integrity, update defs), KPIs, icons, and accessibility improvements. Update related styles and file-manager minor styles. This improves operator workflows by allowing a one-click "Start service" posture fix and provides immediate UI feedback when fixes run.
- Standardize toast usage in AgentFleet and FleetMonitor (toast.success/error/info)
- Replace inline full-page spinners with PageLoader in CloudProvision, DNSZones, DynamicDns, Marketplace, ServerTemplates, StatusPages
- Migrate Servers and Domains tables to DataTable
- Migrate DynamicDns create form to FormField
- Adopt SearchFilterBar in Applications
- Adopt CopyButton in DynamicDns token callout
- Replace raw buttons with Button in Servers, Terminal, Docker
- Move Domains error-banner close button inline styles to SCSS class
- New ErrorState, SearchFilterBar, CopyButton utility components
- Add ESLint rules warning on inline styles, raw <button>, and legacy .card divs
- New useOverflowItems hook for tab overflow menus
- Refactor ui/tabs and ds/PageTopbar to share overflow logic
- New PageLoader component for full-page loading states
- New DataTable component with sorting, empty/loading states, custom rows
- New ResourceCard / ResourceList for servers/apps/domains/containers
- New FormField / FormRow wrappers for consistent forms
- New IconButton wrapper for accessible icon-only actions
- Add supporting styles in _design-system.scss and _forms.scss
Bring the Service detail page to parity with the WordPress detail page:
- Routes: add services/:id/:tab and services/:id/:tab/:section so the active
tab and the Settings sub-section live in the URL (shareable, survive refresh).
ServiceDetail drives its tab via useTabParam; SettingsTab reads/writes the
section via useParams + navigate.
- Repository: the Settings -> Repository section now renders the shared
RepoConnectForm (the same component the WordPress Git settings use) instead
of the bespoke GitConnectModal — provider picker (GitHub/GitLab/Bitbucket/
Gitea) with URL fallback and a connected summary + Disconnect. Existing
deploy scripts are preserved on connect. The top 'connect a repository' pill
now navigates to .../settings/repository, and GitConnectModal is removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A flex <button> keeps its shrink-to-fit intrinsic width, so each settings
nav item's box was sized to its label (e.g. the active 'Git' box only as wide
as the word) instead of the 200px column. Add width:100% (box-sizing is
border-box) so every item's box is the same full width, text still left-aligned.
Applies to both the WordPress (.wp-settings__navitem) and Service
(.svc-settings__navitem) detail settings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds source-connection support for browsing repositories on GitLab, Gitea, and
Bitbucket (new *RepoPicker components) plus a PathSelector, wired through
RepoConnectForm / GitProviders / ConnectionsHub / providerCatalog and the
sourceConnections + files APIs, backed by additions to the source_connections
API and service.
Bundled from in-progress work in a concurrent session at the user's request.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restructure the service Settings tab from a flat stack into a grouped left
sub-nav + content pane (.svc-settings grid), mirroring the WordPress detail
page: General -> Environment, Connections -> Repository, Advanced -> Danger
Zone, each with an icon. Section is local state; the existing panels are
unchanged, only reorganized.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The empty-state used text-align:center, but svgs are display:block (Tailwind
preflight) so the clock glyph left-aligned while the text centered. Flex-center
the container so the icon and text share the same axis.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give each Service detail tab a lucide glyph (TAB_ICONS) and pin the Settings
tab to the right of the strip via app-detail-tab--end, matching the WordPress
detail tab bar. The strip wraps (flex-wrap + overflow-x:visible) so the
right-pinned Settings tab can never get pushed off-screen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reuse the WordPress detail page's shell so /services/:id matches /wordpress/🆔
a full-bleed PageTopbar (.sk-topbar) with the breadcrumb in the title slot and
the Deploy/Restart/more actions on the right, above a centered .app-detail-body
holding a flat .app-detail-header (ServiceTile + name + status Pill + type
badge + mono subtitle), the repo-connection pill, the versions panel, the
shared .app-detail-tabs underline strip, and .app-detail-content.
Drops the bespoke .svc-detail__{breadcrumb,header,title-row,tabs,content}
chrome in favour of the shared app-detail-* base classes, and runs the page
full-bleed via .main-content:has(.svc-detail).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace per-page Tabs with a shared PageTopbar tab group that supports overflow and a "More" popover. Adds BACKUP_TABS and SECURITY_TABS components and updates Backups, Security and Git pages to render sections via the topbar tabs (useTabParam routing). Implements responsive/overflow measurement logic in PageTopbar (TopbarTabs) and related styles; removes old page-local tab styles and shadcn Tabs usage. Improves header UX so active tab stays visible when space is limited.
- Redesign QueueOperations to match the Servers page layout:
left overview/status rail, main workbar + command bar + queue table
- Replace tabbed UI with a single queues table filtered by group/status/search
- Add per-queue Send and Messages actions; send is now a clear modal
pre-filled with the destination group and queue
- Fix confusing send flow that made it look like messages couldn't be sent
- Message detail and create group/queue modals preserved
- Frontend production build passes; backend tests pass
- Extend app.utils.slug with unique_slug(default=...) and validate_slug helpers
- Migrate workspace, secret-vault, webhook-gateway, site-domain, upload,
environment-domain, environment-pipeline, marketplace, and status-page
services to use the shared helpers
- Removes ~8 duplicate _slugify/_unique_slug implementations
- Marketplace create_extension now resolves duplicate slugs instead of raising
- Backend tests and frontend production build pass