Commit Graph
1245 Commits
Author SHA1 Message Date
Juan DenisandClaude Opus 4.8 483f1fa4c0 feat(automations): side-nav Settings layout (like core/WordPress settings)
Replace the single long scroll of stacked cards with the core settings-layout:
a left sub-nav (Engine / Events & port / Pack secrets) and a content pane that
shows only the selected section. Reuses the global .settings-layout/.settings-nav
classes so it matches core Settings and WordPress. Drops the redundant per-card
Refresh (the topbar Refresh covers it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:10:19 -04:00
Juan DenisandClaude Opus 4.8 5fce53f779 feat(automations): Templates marketplace tab (search, favorites, import)
Promote 'New from template' from a popup to its own Templates tab, built to grow
like a marketplace:
- Responsive card grid (icon, name, description, integration tags) with a
  'Use template' action per card.
- Search box to filter.
- Per-card favorite star; favorites persist in localStorage and float to the
  top (server-side store can replace this later without UI changes).
- 'Import template' action: paste a tramo workflow JSON or load a .json file to
  create it as a new automation.
Removes the old modal picker and its topbar button.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:01:03 -04:00
Juan DenisandClaude Opus 4.8 049235b12d feat(automations): two-column New automation dialog with trigger picker
Replace the name-only New workflow modal with a two-column dialog: an explainer
panel (badge, mini flow diagram, use-case examples) on the left and, on the
right, the name field plus a 'How should it start?' trigger picker. Choices seed
a valid builtin trigger node (manual / cron / panel-event via webhook on
/sk/events / incoming webhook) so the editor opens with the trigger already
placed. Own copy/wording; collapses to a single column on narrow screens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:51:19 -04:00
Juan DenisandClaude Opus 4.8 f4b8630e3b feat(automations): richer template picker
Replace the three plain text rows with proper cards: a brand-tinted icon tile
(Telegram/GitHub/Discord/generic), bold name, muted description, integration
tags, and a hover chevron. Wider modal (size lg) with an intro line, and the
Close button moves to the modal footer slot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:42:56 -04:00
Juan DenisandClaude Opus 4.8 6f4246bf0e fix(automations): un-nest template-picker styles from .tramo-page
The 'New from template' picker renders inside a <Modal>, which portals to
<body> -- outside the .tramo-page subtree -- so the .tramo-template rules
nested under .tramo-page never matched it. The name/description spans fell back
to inline defaults and ran together. Move .tramo-template-list/.tramo-template
to top-level scope so the portaled modal picks them up (name bold on its own
line, muted description below).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:33:25 -04:00
Juan DenisandClaude Opus 4.8 52ea07426f fix(automations): center content in sk-tabgroup__inner (match WordPress)
Add the .sk-tabgroup__inner wrapper WordPress/Domains use inside
.sk-tabgroup__content so the content is padded + max-width centered under the
full-bleed header, instead of sitting edge-to-edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:23:09 -04:00
Juan DenisandClaude Opus 4.8 801fe3bbf6 fix(automations): full-bleed tab-group header to match WordPress/Domains
The topbar was rendered inside a plain padded page-container, so it floated
inset with no header band. Switch the wrapper to the same classes TabGroupLayout
uses for WordPress/Domains -- 'page-container--full-bleed sk-tabgroup' with the
content in 'sk-tabgroup__content' -- so Automations gets the identical
full-width header strip (bordered band, icon+title+tabs+actions) over centered
content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:20:08 -04:00
Juan DenisandClaude Opus 4.8 18a8248fc1 fix(automations): match the clean Domains/WordPress header layout
Drop the '<icon> tramo . workflows . triggers . integrations' topbar subtext
and the redundant inner card-header on the Workflows/Runs tabs. Actions
(Deploy, New from template, New workflow, Refresh) now live in the PageTopbar
and are contextual to the active tab, so each tab shows a single clean header
over its content -- the same pattern as Domains and WordPress.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:13:23 -04:00
Juan DenisandClaude Opus 4.8 bd8842faf4 fix(routing): show a Not Found page for unknown/uninstalled-extension routes
Navigating straight to an extension route whose extension isn't installed
(or any unknown URL) rendered a blank page -- the route was never registered
and there was no catch-all. Add a catch-all '*' route inside the dashboard
shell that renders a NotFound page (icon, the missing path, links to the
dashboard + Marketplace).

Contributed routes load async, so on a hard refresh onto an installed
extension route the catch-all can mount for a beat before the route
registers; NotFound keys off a new contributions __ready flag to show a
loading skeleton until the route set is known, avoiding a 'Not found' flash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:06:03 -04:00
Juan DenisandClaude Opus 4.8 4f448de660 docs(automations): changelog + raise test-count baseline to 2588
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:39:56 -04:00
Juan DenisandClaude Opus 4.8 e62d969a8b feat(automations): retire React-Flow Workflow Builder UI (Phase 4 frontend)
The Automations extension (tramo) replaces the drag-and-drop builder.

- Delete builtin-extensions/serverkit-workflows + its mirror, pages/
  WorkflowBuilder.jsx, and all of components/workflow/ (its @xyflow nodes,
  edges, and config panels).
- KEEP @xyflow/react (vendor-flow chunk): pages/AppMap.jsx still uses it.
- Redirect /workflow -> /automations in App.jsx (retired-route pattern).
- Drop the pages/_workflow SCSS partial + its main.scss import.
- Refresh the stale serverkit-workflows comments in DashboardLayout +
  sidebarItems to point at the Automations extension.

Build + lint clean; sync-builtin-frontends --check green (14 managed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:27:20 -04:00
Juan DenisandClaude Opus 4.8 8eaf13f2ed build(frontend): add tramo@^0.1.0 dependency for Automations editor
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:24:31 -04:00
Juan DenisandClaude Opus 4.8 79be6e082d test(automations): tramo extension suite + host/model refinements
- test_tramo_extension.py: 40 tests (manifest, models+purge, workflow store
  CRUD/materialize/deploy, host argv/control/status/nt-guard, run_sync,
  jobs harvest+health, events bridge + call-back key, blueprint auth/run
  proxy/settings mask/auth-exempt hooks passthrough, Phase 4 ported emitters +
  legacy export + old routes gone).
- host_service: add _is_windows() seam (test platform without touching global
  os.name, which would break pathlib).
- models: dirty detection via deployed_version counter (race-free vs the
  deploy commit's own updated_at onupdate bump).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:23:53 -04:00
Juan DenisandClaude Opus 4.8 531fdf3d42 refactor(automations): retire Workflow Builder engine, port emitters (Phase 4 backend)
The React-Flow Workflow Builder is replaced by the Automations extension
(tramo). Remove its core Python engine and port its four event emitters to
the panel event bus so tramo workflows can trigger on them via the events
bridge.

- event_service.py: new EVENT_CATALOG entries health.check_failed, git.push,
  monitor.high_cpu, monitor.high_memory.
- docker_service.py: drop the app_stopped emit (app.stopped already fires via
  the audit trail -- no double-emit).
- environment_health_service/git_service/monitoring_service: emit the new
  panel events via EventService instead of WorkflowEventBus.
- Delete app/api/workflows.py, app/services/workflow_engine.py,
  app/services/workflow_service.py; drop the blueprint + startup job
  registration; neutralize the cron workflow-scheduler handler to a no-op.
- KEEP app/models/workflow.py + its tables (read-only legacy export lives in
  the tramo blueprint; table drop is a future cleanup).
- Remove the two workflow-engine test classes from test_jobs.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:15:10 -04:00
Juan DenisandClaude Opus 4.8 675543541b feat(automations): starter workflow templates (Phase 3)
Three seed WorkflowDocs (backup-failed->Telegram, nightly health->GitHub
issue, panel event->Discord) served at GET /tramo/templates, plus
POST /tramo/workflows/from-template/<id> to instantiate one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:09:10 -04:00
Juan DenisandClaude Opus 4.8 38c1e33097 feat(automations): serverkit-tramo extension backend (Phase 1)
Embed tramo as the panel's automation surface. Managed @tramo/server
container is the executor; the panel owns workflow docs.

- plugin.json: opt-in builtin, /api/v1/tramo, Automations nav -> /automations,
  harvest+health schedules, ext_serverkit_tramo_* models.
- models.py: TramoWorkflow (doc/slug/enabled/dirty) + TramoRun (harvested).
- host_service.py: TramoHostService, stalwart_service mirror -- _docker/_api
  choke-points, loopback API, encrypted Bearer key + pack secrets in config.
- workflow_store.py: CRUD + materialize enabled docs + deploy (restart).
- run_sync.py: defensive run parser + upsert-by-run_id.
- events_bridge.py: scoped call-back ApiKey + managed EventSubscription.
- lifecycle.py / jobs.py: install/uninstall hooks, harvest + health jobs.
- tramo.py: JWT blueprint (workflows/runs/approvals/host/settings) + one
  auth-exempt /hooks/<path> inbound-webhook passthrough + legacy export.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:04:12 -04:00
Juan Denis 570a3c7152 Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-07-11 21:49:33 -04:00
Juan DenisandClaude Opus 4.8 bde4647fdc feat(k8s): serverkit-k8s extension — manage remote clusters via kubectl
Opt-in builtin extension. Add a cluster by pasting its kubeconfig (stored
encrypted, one row per cluster); the engine shells out to kubectl against a
per-call temp kubeconfig to show cluster health, nodes, namespaces, workloads,
pods with live logs, services, and ingresses, plus scale/restart/delete and
apply-from-paste. Reads need viewer, mutations need admin. Not platform-gated
(kubectl runs anywhere); no cluster needed on the panel host.

Backend: models (ext_serverkit_k8s_clusters), kubectl_service (argv build +
temp-file materialize/cleanup), cluster_service (CRUD + normalizers), k8s
blueprint (/api/v1/k8s), lifecycle. Frontend: K8sPage (7 tabs) mirrored to the
baked plugin tree via sync-builtin-frontends. 30 extension tests green;
manifest/platform suites green; frontend builds; sync-drift clean.

Plan: docs/plans/44_KUBERNETES_EXTENSION_PLAN.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 03:41:34 -04:00
github-actions[bot] dc575542f9 chore: bump version to 1.7.29 [skip ci] 2026-07-11 05:34:27 +00:00
Juan DenisandClaude Fable 5 1face0702f fix(cli): wrapper delegates forward flags verbatim to cli.py
Live test on the dev box caught 'serverkit list-apps --json' printing
the table: cmd_list_apps' hand-rolled flag mapping dropped everything
but --all, and cmd_list_users/cmd_list_servers dropped all args. All
three now forward "$@" (with -a kept as the legacy --all alias), and
test_cli.sh T20 drives the real executed-mode dispatch through a fake
venv with a recording python stub so any wrapper verb that eats flags
fails the suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 01:34:12 -04:00
github-actions[bot] be83e38c51 chore: bump version to 1.7.28 [skip ci] 2026-07-11 05:25:00 +00:00
Juan Denis cb30b93d6f Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-07-11 01:24:43 -04:00
Juan DenisandClaude Fable 5 ad73f0c581 fix(cli): wrapper parity with backend/cli.py + bash tab-completion
serverkit migrate-db forwarded the wrapper's own name to cli.py, which
only defines db-migrate — the command has failed with 'No such command'
since the rename. Fix the forward and stop the drift class entirely:

- cmd_panel_cli forwards Click-only verbs verbatim (manifest, services,
  apps, repair, login-url, support-bundle, db-migrate/status/history;
  panel-status/panel-doctor where a native bash name collides)
- serverkit completion prints a bash completion script; install.sh and
  update.sh drop it into /etc/bash_completion.d (best-effort), the
  uninstall lib removes it
- test_cli.sh T17-T19: migrate-db forwarding, dispatch reachability,
  and a two-way parity guard (cli.py verbs <-> dispatch <-> completion);
  test_update.sh T29 covers refresh_bash_completion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 01:23:00 -04:00
Juan DenisandClaude Fable 5 9be1ebf8bb feat(cli): --json output on read-only admin CLI verbs
status, doctor, list-users, list-apps, list-servers, db-status,
deployment-status, services list, apps list, and manifest plan gain a
--json flag emitting machine-readable output, so the CLI can feed
scripts, cron checks, and external monitors. Table-only extras refuse
the combination loudly (--all, --repair).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 01:22:49 -04:00
Juan DenisandClaude Opus 4.8 d482c6d261 feat(email): commit serverkit-email extension backend
The Email Server builtin extension's backend package was present on disk (and
exercised by the green test suite) but never tracked. Adds it alongside the
already-tracked serverkit-gpu / serverkit-gui backend plugins: the Flask
blueprint (email.py) plus the Postfix/Dovecot/DKIM/SpamAssassin/Roundcube
service modules. __pycache__ stays gitignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 01:10:30 -04:00
github-actions[bot] 620fdb992a chore: bump version to 1.7.27 [skip ci] 2026-07-11 04:54:20 +00:00
Juan DenisandClaude Opus 4.8 e674a6b6d2 chore(deps): apply frontend security bumps (dependabot #62-67)
Clears the frontend Dependabot security alerts, minus the vite chain:

- react-router-dom ^6.22.3 -> ^7.18.1 (direct; fixes the // open-redirect,
  alert #17). v7 keeps the react-router-dom API surface — lint + build green.
- overrides pin the transitive fixes: lodash 4.18.1 (prototype pollution +
  _.template code injection), js-yaml 4.3.0 (merge-key DoS), postcss 8.5.16
  (stringify XSS), @babel/core 7.29.7 (sourceMappingURL file read), ws 8.21.0
  (memory-exhaustion DoS + uninitialized memory), engine.io-client 6.6.6.
- npm audit fix cleared the remaining non-breaking transitive alerts
  (flatted, minimatch, brace-expansion, ajv).

Down from 8 to 2 npm-audit findings; the remaining two (esbuild + rollup via
vite, both dev-server-only) need the vite 5->8 major upgrade, tracked
separately as #68.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 00:43:43 -04:00
Juan DenisandClaude Opus 4.8 2dfe0d7d54 feat(search): unified entity omnisearch endpoint for the palette
GET /api/v1/search?q= (JWT, 2-char minimum) fans a term out across the core
entity types — services, servers, domains, databases, WordPress sites, cron
jobs (admin-only), installed extensions, and vaults (names only, never secret
values) — reusing each domain's existing access helpers so a member only sees
what their grants allow. Per-type cap of 5; each entity block is isolated so one
failing type degrades gracefully instead of 500-ing the search.

Wires the frontend api.search() async provider (200ms debounce) into the
palette's entity group, replacing the fetch-everything-on-open prefetch. Adds
tests/test_search.py covering shape, min-length, cap, member-vs-admin filtering,
cron admin-only, and the vaults names-only rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 00:26:40 -04:00
Juan DenisandClaude Opus 4.8 86bd8d70a9 feat(palette): wrap settings cards for deep-link focus across all tabs
Mechanical wrap-the-card pass: each remaining settings tab component now spreads
useSettingFocus's register(id, class) onto its identifiable cards so a palette
settings result scrolls to and flashes the exact card. Covers Profile,
Appearance, Sidebar, White Label, About, Users, Activity, Site, Modules,
Migrations, SSO, API, AI, System, and Connections. Cards whose roots live in
non-editable subcomponents (login links, invitations, provider cards) register
at the nearest reachable section or fall back to landing on the tab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 23:47:36 -04:00
Juan DenisandClaude Opus 4.8 e0a47bbb9d feat(palette): settings search — deep-link focus + completeness guard
useSettingFocus rides the useFocusParam rail with a new `setting` kind: a
palette result navigates to /settings/<tab>?focus=setting:<id>, and the target
card scrolls into view and flashes a token-based highlight for ~2s. Wires the
headline Security (2FA card + admin require-2FA policy, now mounted) and
Notifications (email/SMTP, channels, severity, categories, quiet hours) cards.

Adds scripts/check-settings-index.mjs (dependency-free) to the lint step so
every settings tab must carry at least one index entry — a new tab can't ship
invisible to search.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 23:42:57 -04:00
Juan DenisandClaude Opus 4.8 3e0fbd2f81 feat(palette): omnisearch core — providers, ranking, VS Code keys
Rebuild CommandPalette around scored providers (pages, settings, actions,
plugin contributions, docs, async entities) with a single subsequence fuzzy
scorer and shouldFilter={false} so cmdk no longer double-filters. Adds:

- data/palettePages.js, data/settingsIndex.js (84 cards), data/commandActions.js
- utils/paletteScore.js (word-start bonus + category weight) and
  utils/paletteFrecency.js (localStorage frecency: count + time decay)
- hooks/usePaletteAuthz.js — mirrors the sidebar's admin + workspace-nav gate
  so a member's palette never surfaces admin surface
- F1 and Ctrl/Cmd+Shift+P open aliases alongside Ctrl/Cmd+K
- prefix modes (`>` actions, `?` docs), Recently used empty state, kbd footer

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 23:31:25 -04:00
Juan DenisandClaude Opus 4.8 5fc0b881b4 feat(connections): one-click GitHub setup via the GitHub App manifest flow
Connecting GitHub required hand-registering an OAuth app and pasting a client
id + secret — friction, and an open-source panel can't ship a secret anyway.
Now an admin sets it up in one click.

- backend: build_github_app_manifest / complete_github_app_manifest — hand
  GitHub an app manifest, convert the returned code into credentials stored
  locally (client id/secret in settings; PEM + webhook secret encrypted). No
  shared secret in the repo or build.
- connect flow: when an app slug exists, generate_github_authorize_url points at
  the app's install screen (grants repos + authorizes in one hop); repositories
  are listed per-installation since GitHub Apps don't use /user/repos.
- get_github_config surfaces provider_kind/app_slug/app_name/install_url.
- routes: GET/POST /source-connections/admin/github/app-manifest[/complete].
- frontend: Settings -> Connections leads with "Set up in one click" (auto-POSTs
  the manifest form to github.com); a new /connections/github-app/callback page
  finishes setup and chains into connect. Manual OAuth app moves under
  "Advanced". 7 proving tests (manifest shape, conversion+storage, install-url
  routing, app-mode repo listing); ratchet 2498->2505.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 23:06:09 -04:00
Juan DenisandClaude Opus 4.8 4bc5e1af9a feat(new-service): template step is a mini app gallery, not one card
Feedback: the template step showed only AgentSite in a stacked card. Make it a
real gallery of the whole catalog.

- pull the FULL template catalog into the picker (repo + one-click compose), not
  just repo-kind; icon + title tiles ~4 across
- collapsed to two rows; "Show all N" expands into a scrollable grid with a
  simple search box
- curated ordering floats AgentSite + popular apps first; AgentSite badged
  "Featured"
- picking a repo template continues in the wizard; picking a one-click compose
  template routes to its install flow (its real deploy path)
- keep the inline "Bring your own manifest" escape hatch

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 09:32:05 -04:00
Juan DenisandClaude Opus 4.8 48cdd159f3 fix(new-service): center the stepper, richer template step + bring-your-own
Feedback on the template step: it read as sparse (one bare row), the "Browse all
templates" button punted to another page, and the stepper hugged the left.

- center the Source/Connect/Review stepper
- render templates as featured cards: icon (with fallback), name, a "Featured"
  badge, version, description, category chips, and the repo URL — a real preview
  instead of a one-line row
- add an inline "Bring your own manifest" card: deploy any Git repo shipping a
  serverkit.yml / Docker Compose / Dockerfile by switching to the Git-remote
  input in place, no page jump
- demote "Browse all templates" to a subtle text link

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 09:08:48 -04:00
Juan DenisandClaude Opus 4.8 bd87b06a60 feat(new-service): a real three-step wizard, not a form-wall
/services/new rendered everything at once regardless of state — a "Ready to
Import" rail of placeholder rows, a decorative Connect->Detect->Deploy strip, a
numbered guide, and two boilerplate note boxes. It is now a slim stepper over
one centered column.

- three explicit steps: Source (radio-cards) -> Connect (source-specific input
  + API-driven template picker) -> Review (manifest detection, buildpack, env
  list, core fields; nothing renders without real data)
- decomposed the 1,327-line monolith into pages/new-service/: a
  useNewServiceForm hook (step state + submit) with presentational SourceStep /
  ConnectStep / ReviewStep components
- ?template=/?source= deep links land preloaded on the Connect step; deploy
  templates load from the backend catalog (kind: repo) instead of a JS constant
- submit payloads are byte-identical to before (flow re-skin, not a backend
  change); _new-service.scss rewritten for the stepper layout
- deletes the hardcoded frontend catalog (data/deployTemplates.js) now that its
  last consumer is gone, and the old single-file NewService.jsx

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 02:39:23 -04:00
Juan DenisandClaude Opus 4.8 b91b28ae4f feat(templates): one catalog grid with the Marketplace filter treatment
Templates was two pages stacked: a one-row "Deploy templates" section above the
real 106-template grid, plus a category chip wall with a "More +95" spillover.
Now it is one browsing surface.

- repo-kind templates render in the main grid as normal cards with a kind badge
  (Badge info "Git repo" vs outline "One-click")
- topbar SearchField + FilterButton/FilterDrawer (category, kind, sort) with a
  single row of quick category chips; the chip wall and spillover are gone
- repo cards open the same detail shell with a repo-specific body (repo URL,
  detected manifests, env preview from the /manifest endpoint), primary action
  "Deploy" -> /services/new?template=<id>
- `#deploy-templates` anchor redirects to the ?kind=repo view; kind joins
  category/search/sort in the URL params

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 02:39:02 -04:00
Juan DenisandClaude Opus 4.8 abe0fbece9 feat(templates): deploy templates as a backend repo-kind catalog entry
Deploy templates were a hardcoded frontend constant (AgentSite, with a
hand-fabricated manifest and an EXAMPLE badge). They now live in the backend
catalog as `kind: repo` YAML entries that blend with the 105 compose templates.

- template schema gains `kind: compose | repo` (absent = compose, so every
  existing template is untouched); repo templates carry a `repo` block with a
  `url` instead of a compose stack
- backend/templates/agentsite.yaml (kind: repo) replaces the JS constant
- list + detail payloads serve `kind`/`repo`; category derivation picks up
  repo templates automatically
- GET /api/v1/templates/<id>/manifest inspects a PUBLIC repo via
  RepositoryManifestService.analyze_public_repo (raw host, no clone, no OAuth)
  and falls back to the template's declared hints, labeled `template-hints`
- index.json + catalog-cert test recognize repo-kind; ratchet floor 2475->2498

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 02:38:46 -04:00
github-actions[bot] cdf8ea09e6 chore: bump version to 1.7.26 [skip ci] 2026-07-10 05:25:44 +00:00
Juan DenisandClaude Fable 5 284bc02dc1 feat(manifest): dockerfilePath — build services from a repo Dockerfile
A v1 manifest service gains a third image source next to buildpacks and
BYO image: — `dockerfilePath: services/api/Dockerfile`. This is the
monorepo path: one repository, several services, each built from its own
Dockerfile with the repo root as the shared build context.

- spec: new service key (camel + snake alias), validated relative-only
  (no `..`/absolute), mutually exclusive with `image` and `containers`,
  rejected on db services; schema docs copy kept in sync.
- resolve/plan: the path rides the create_app payload (with autoDeploy)
  and shows in the step description; a changed path plans as an app
  update; drift_pair tracks it against the stored build config.
- apply: create clones the project's repository into the managed apps
  dir (idempotent — an existing checkout is reused) and writes the same
  git-deployment + build config the import wizard does, so the existing
  deploy pipeline (push webhook, per-service autoDeploy) takes over
  unchanged. Source resolves from the stored manifest's provenance, else
  a sibling app's git deployment; neither is a plan-time blocker
  (dockerfile_no_source). Remote targets refuse like the other appliance
  features ('a Dockerfile build' joins _service_features).
- scaffold: a dockerfile-built app round-trips its build config instead
  of leaking the built image as a BYO `image:` declaration.
- import wizard: a v1 manifest with dockerfilePath seeds
  build_method=dockerfile + the path.

Tests: 10 new (spec exclusivity/traversal + plan/apply/blocker wiring);
manifest suite 96/96, full backend suite green except 3 pre-existing
test_server_time_timezone failures also present on a clean tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 01:13:14 -04:00
github-actions[bot] 33b21c8ba6 chore: bump version to 1.7.25 [skip ci] 2026-07-09 13:33:06 +00:00
Juan Denis d96acd24e9 Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-07-09 09:32:41 -04:00
Juan DenisandClaude Fable 5 4420c406f8 fix(release): stop blanket-excluding images from the release tarball
build-release.sh excluded *.png/*.jpg/*.jpeg globally — fine when the only
images were screenshots, fatal once ExtensionIcons.js (8bd9c1a) started
importing frontend/src/assets/extension-icons/*.png as build inputs: the
/tmp build tree lost them and rollup failed to resolve. The blanket rule
was also silently stripping frontend/public/favicon-*.png from every
release build.

Replace with targeted excludes: docs/screenshots, frontend/dist,
.codex-screenshots, template-icons, and (via --no-wildcards-match-slash)
root-level ./*.png|jpg|jpeg strays + dev/*.png. Verified by listing the
tar: 14 extension icons + 4 favicons included, zero stray images, *.log/
*.tmp/*.pyc still excluded; shellcheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 09:32:09 -04:00
github-actions[bot] e03795b707 chore: bump version to 1.7.24 [skip ci] 2026-07-09 13:07:35 +00:00
Juan Denis b46b86505d Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-07-09 09:05:53 -04:00
Juan DenisandClaude Fable 5 a7ddb1423e fix(plugins): re-sync baked builtin manifests through the canonicalizer
The plan-39 em-dash purge hand-wrote six baked plugin.json copies without
the trailing newline that sync-builtin-frontends.mjs emits, tripping the
CI drift gate (--check). Regenerated via the script; content unchanged.
Guard suite test_extension_manifests.py: 28 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 09:04:11 -04:00
github-actions[bot] 01c1fba433 chore: bump version to 1.7.23 [skip ci] 2026-07-09 12:49:07 +00:00
Juan Denis 326079f21c Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-07-09 08:47:35 -04:00
Juan Denis 4e1b7da5f6 Add NVIDIA GPU monitor plugin (backend + manifests)
Introduce serverkit-gpu extension: backend plugin with GPU API and service (nvidia-smi based) providing per-GPU metrics and best-effort container resolution for GPU processes. Files added: backend/app/plugins/serverkit-gpu/{__init__.py,gpu.py,gpu_service.py,plugin.json}. Also update several frontend plugin.json files (including serverkit-gpu) to match manifest formatting (trailing newline adjustments). The GPU service degrades gracefully when nvidia-smi or Docker info is unavailable.
2026-07-09 08:47:28 -04:00
Juan DenisandClaude Opus 4.8 297c1e5864 ci(tests): add test-count ratchet + changelog recovery-parity (plan 42 ph6)
Install the guard the data loss taught us to want: code and its proving tests
died together, so the suite stayed green (2572->2209 passing) while whole
features silently vanished — nobody diffs a green number.

- backend/tests/BASELINE_COUNT (2475) + check_test_count.py: compares pytest's
  collected count to the floor; CI (backend-ci.yml new step) fails if it drops.
  Lowering the floor requires editing BASELINE_COUNT in the same commit, so a
  silent test loss surfaces as a red X instead of a still-green suite. Verified:
  passes at baseline, exits 1 on a simulated drop.
- CHANGELOG: document the recovery-parity restore (Cloudflare R2 engine,
  email-bounce, DNS-cutover staging, survey routes, cron CSS, 18 test suites)
  and the ratchet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 05:27:22 -04:00
Juan DenisandClaude Opus 4.8 b9ee243ba2 feat(cloudflare): restore Round 2 engine — DNSSEC, Origin CA, rules, activity (plan 42 ph2)
Restore the plan-36 Cloudflare Round 2 backend lost in the rebuild (migrations
073/074 survived; the engine + test_cloudflare_round2.py did not):

- CfOpsChange model (verbatim from clean pyc dump, matches mig 074) +
  CfOpsChangeService.record (never-raises) / list.
- Core client (dns/cloudflare.py): get/set_dnssec; create/list/revoke Origin
  certificates; X-Auth-User-Service-Key header mode (origin_ca_key, mig 073).
- Extension service: DNSSEC get/set; Origin CA issue (panel-side CSR, key never
  leaves the box, proxied-hostname warning, scoped-cred 400 not 502) /list/revoke
  + Linux-guarded install via upload_custom_cert; Redirect+Transform rules
  (RULE_PHASES/ACTIONS/PRESETS reusing _find_custom_ruleset + injection guard);
  best-effort _record wired into every mutating method; scope_check probe;
  list_activity.
- Extension routes: /zones/<id>/dnssec, origin-ca, /rules/<slug>, /activity,
  /scope-check (admin-gated, CloudflareError->400, provider->502).
- ssl_service.list_certificates walks /etc/ssl/serverkit + "Origin CA
  (proxy-only)" badge; fixed certbot-branch early-return hiding custom certs.

Also registers EmailBounceState (plan 42 ph3) in models/__init__ (shared file).
Deviation: origin_ca_key implemented as the Fernet Origin CA *service key* (plan
Decision 3), not mig-073's docstring "generated private key" — the generated key
is used inline for install and never persisted. R2 preset backend-untouched (it
lives in the storage-provider + frontend layers). Verified: 83 passed
(round2 30 + zone_settings 42 + extraction 8 + ssl 3), no regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 05:02:54 -04:00