157 Commits
Author SHA1 Message Date
Juan Denis b4ff7a07d5 Update login and WordPress screenshots
Refresh the documentation screenshots for the login and WordPress screens to reflect the current UI.
2026-07-20 01:29:14 -04:00
Juan DenisandClaude Opus 4.8 f8ff93a4bd docs(readme): accept crypto donations
Adds USDT (TRC-20 and ERC-20), BTC and SOL to the Support section of all four
READMEs, with the network in its own column — USDT exists on several chains
whose addresses look alike, and the network is what tells a donor which one to
send on.

QR codes come from scripts/generate-funding-qr.mjs rather than a web generator,
since an online tool sits in a position to encode a different address than the
one entered and the resulting image looks perfectly normal. The script holds the
addresses as the single source of truth and checksum-validates each before
encoding — base58check for Tron, EIP-55 casing for Ethereum, bech32 for Bitcoin,
32-byte base58 for Solana — refusing to emit anything if one fails. Every code
was decoded back and diffed against its source string. `--check` re-verifies the
committed PNGs so an address edit with a stale QR cannot pass unnoticed.

These are self-custody wallets rather than exchange deposit addresses, which can
be rotated out from under a published README and turn third-party donations into
an AML review on the receiving account.

Adds CODEOWNERS over the READMEs, the QR script, the funding images,
FUNDING.yml, LICENSE and SECURITY.md. Swapping a donation address through an
innocuous-looking docs PR is a known attack on open-source projects; this
requests maintainer review on every path that carries one. It takes effect once
"Require review from Code Owners" is on in branch protection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:19:54 -04:00
Juan DenisandClaude Opus 4.8 4f08ae47a9 fix(docs): commit the architecture diagrams that .gitignore swallowed
A blanket `*.png` rule meant the eight diagrams added alongside the
ARCHITECTURE.md rewrite were silently skipped — `git add` reported nothing and
the doc landed referencing images that were not in the repo. Every diagram
rendered as a broken image on GitHub.

Force-includes docs/images/ the same way docs/screenshots/ and the extension
cover icons already are, covering both the architecture diagrams and the
funding QR codes that follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:19:30 -04:00
Juan DenisandClaude Opus 4.8 a83dbd3e37 docs(readme): refresh the English README and rebuild all three translations
English: drops the retired Workflow Builder — its screenshot showed a sidebar
entry and canvas that no longer exist — promoting Agent Fleet into that grid
slot, and renames the feature card to Automations with the managed-container
model it actually uses. Template counts go 60+ to 100+, the architecture section
leads with a diagram over an ASCII-only block, and the Tech Stack no longer
claims GeventWebSocket workers. Two links to agent/README.md are repointed at
the serverkit-agent repo; that directory left this tree when the agent was
extracted, so both were 404s.

The translations had drifted a full generation behind. All three pointed their
Screenshots sections at ad-hoc user-attachments URLs instead of the repo's own
files, and their roadmaps still listed Backups, Mail, RBAC and Marketplace as
unshipped. Each is now a faithful mirror of the English: identical section order
and count, 121 table rows, 41 images, both collapsible folds, and nav anchors
that resolve under GitHub's slugger with accents and CJK preserved. The ASCII
architecture diagram is byte-identical across all four — deliberately untouched
in the Chinese file, where double-width characters would break the box drawing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:17:53 -04:00
Juan DenisandClaude Opus 4.8 c6271da13e docs(architecture): rewrite against the current code, add diagrams
The document described a system that partly no longer exists. Removed: the
Workflow Automation section built on a `WorkflowService` that is not in the tree
(Automations replaced it and runs in a managed container), and the claim that
DB credentials are injected as DB_HOST/DB_USER — there is no generic injector,
names are template-authored and the real mechanism is fromSecret/fromService
reference resolution.

Corrected: backups live at /var/backups/serverkit not under /var/serverkit,
/var/serverkit/ssl does not exist, the panel has no config.yaml (only the agent
does), templates merge two directories with backend/templates/ as the source of
truth, and 8000 is the fallback port rather than the start — managed_app_base_port
and the per-template default both take precedence.

Added the three subsystems that were missing entirely — the extension platform,
jobs and scheduling, and the notifications bus — plus a backend layer map and an
agent fleet section absorbing the single-worker constraint.

Every diagram now leads with an image and keeps its ASCII fallback in a
collapsible fold, so the doc still reads as plain text.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:17:06 -04:00
Juan DenisandClaude Opus 4.8 d595ea14ff fix(docs): remove the broken gunicorn worker class from the install guide
The manual-install systemd unit told operators to run
`--worker-class geventwebsocket.gunicorn.workers.GeventWebSocketWorker`. The app
runs `async_mode='threading'`, where engineio serves WebSocket through
simple-websocket; the gevent-websocket worker also answers the upgrade, so the
handshake is answered twice and the browser reports "Invalid frame header".
Anyone following this guide built a panel with broken live metrics, logs and
terminal.

Now matches what the project actually ships in
templates/serverkit-backend.service.in — `--workers 1 --threads 100`, no worker
class — with a note on why to scale threads rather than workers, since the agent
gateway keeps its registry in one process.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:16:52 -04:00
Juan DenisandClaude Opus 4.8 9639288494 refactor(extensions): extract seven builtins to standalone repos
Analytics, CrowdSec, DNS Server, GPU Monitor, Kubernetes, Mail Server and
Automations (tramo) now live in their own repositories and install from the
registry as versioned, sha256-pinned zips. Their sources leave
builtin-extensions/, their pre-bundled frontends leave frontend/src/plugins/,
and registry_index.json gains a `bundled: false` entry for each so the
Marketplace offers them as normal registry installs.

The panel keeps only the wiring: comments at the former mount points explain
where each surface now comes from, and the gpu auto-install gate in
extension_migration.py is dropped since a host-probe can no longer decide to
re-vendor an extension that is not in this tree.

Also untracks backend/app/plugins/serverkit-{gpu,k8s,tramo}/, which were the
panel's own install artifacts rather than source. Committing those shipped a
second copy of code that now lives in three other repos, guaranteed to drift.
.gitignore now excludes the install path for every extracted slug on both the
backend and frontend side, so a local install can never be committed back.

Four tests from test_tramo_extension.py exercise panel core rather than the
extension (the retired /api/v1/workflows blueprint, the plan-45 event emitters
ported to EventService, and the docker_service double-emit fix); they move to
test_tramo_panel_core.py and stay here. The rest of the extension suites go
with their repos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:16:16 -04:00
Juan DenisandClaude Opus 4.8 bb899522fe docs(analytics): user guide + marketplace recommendations (Phase 7)
- docs/ANALYTICS.md: privacy model, the two data sources, dashboard tour,
  snippet + WordPress mu-plugin + nginx sub_filter install paths, config table,
  geolocation, scale/PostgreSQL guidance, collector protections, and the
  single-worker note. Clarifies the panel-extension vs WP-plugin naming and that
  the Umami/Plausible/PostHog templates remain available.
- Recommend serverkit-analytics for the wordpress + web-apps setup-wizard use
  cases (RECOMMENDED_EXTENSIONS_BY_USE_CASE). Onboarding guard test green.
- CHANGELOG (Unreleased): Web Analytics feature entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 02:58:11 -04:00
Juan DenisandClaude Opus 4.8 176141295f docs(plan47): sync extension manifests, raise ratchet, changelog + docs
Plan 47 Phase 6 (for shipped work). Sync the pre-bundled frontend plugin.json
copies for the four extracted builtins (entry_point/url_prefix + status
description) so builtin-extensions/ and frontend/src/plugins/ agree
(sync-builtin-frontends --check + test_extension_manifests green). Raise the
test-count ratchet 2588 -> 2623. Document the second-speed backend-extraction
recipe in EXTENSIONS.md, add CHANGELOG entries (wizard installs + opt-in
FTP/cloud/remote-access/status), and a README "lean by default" bullet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 23:05:06 -04:00
Juan DenisandClaude Opus 4.8 89af823b39 docs: use new poster as README banner and update tagline
Replace the logo image at the top of the English and translated READMEs with the new docs/screenshots/poster.png banner, and change the tagline to "Deploy, manage, and monitor servers." (translated per language).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 06:14:37 -04: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
Juan DenisandClaude Opus 4.8 0b21ef93f5 feat(manifest): network identity — fromServer, SERVER_PUBLIC_IP, shared net (plan 35 ph5)
Add a fromServer env binder ({property: publicIp|hostname}) resolved against the
target server or panel host, with a fromserver_no_ip plan-time blocker. Add the
${SERVER_PUBLIC_IP} template magic variable. Attach manifest-generated app
projects to a shared external `serverkit` docker network (DockerService.
ensure_network + networks param on create_docker_app/buildpack generate_compose)
so a fromService host reference resolves cross-app by container name at runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 03:28:08 -04:00
Juan DenisandClaude Opus 4.8 619b2215da feat(manifest): BYO image + host requirements (plan 35 ph4)
Add first-class `image:`/`registry:` (apply stamps Application.docker_image +
registry_id, resolved by registry name; registry_credential blocker on
unknown/uncredentialed registries, ECR accepted) and `hostRequirements`
(privileged/capAdd/sysctls/devices/kernelModules). create_docker_app emits the
host-requirement fields; every requirement is listed in plain words as a plan
issue and audited via manifest.host_requirements on apply. kernelModules get an
advisory /proc/modules check that warns but never silently passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 03:21:14 -04:00
Juan DenisandClaude Opus 4.8 fdc3d45749 feat(manifest): multi-container units (plan 35 ph3)
Add a `containers:` map that turns one service into one Application rendered as
one compose project. Normalizer validates mutual-exclusion with buildpack keys,
dependsOn references, and rejects cycles (DFS). New UnitComposeService renders
container_name prefixing, healthcheck blocks (cmd->CMD-SHELL, httpPath->wget),
depends_on.condition mapping, raw port publishes, and per-container synthetic
named volumes. resolve_service flattens container ports/bootstraps/host-reqs/env
to the unit (unified `bootstraps` list under one bootstrap_done flag); unit disk
backups route at the docker-volume name. Seams: unit_compose/unit_container_names.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 03:13:54 -04:00
Juan DenisandClaude Opus 4.8 be3a9f9bcd feat(manifest): finish appliance volumes + one-shot bootstrap (plan 35 ph2)
Persist manifest disk `size` onto AppVolume.declared_size; route disk backups to
the docker volume's live host mountpoint (VolumeService.host_path_for_mount +
volume_mount meta in BackupPolicyService._resolve_target). Add `bootstrap`
schema + BootstrapService.run_once (docker compose run --rm, injectable runner
seam), a bootstrap apply step that stamps applications.bootstrap_done once, and
POST /manifests/bootstrap/reset to re-arm it. Scaffold round-trips disk size.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 03:05:08 -04:00
Juan DenisandClaude Opus 4.8 06c3fcf28c feat(manifest): appliance-tier typed L4 ports + plan-time blockers rail (plan 35 ph1)
Rebuild of plan 35 Phase 1 against the surviving migrations (ports as a JSON
column on applications, not a separate table). Adds typed `ports[]` to the
serverkit.yaml schema/normalizer, AppPortService (persist/compose-emit/firewall),
open_port apply step + firewall open on apply / close on app delete, and the
blockers engine (port_conflict/remote_target/observed_server/firewall_undetected)
that refuses apply instead of half-deploying.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 02:57:23 -04:00
Juan DenisandClaude Opus 4.8 9062f1d47a docs(extensions): document the installable-straight-from-GitHub contract
EXTENSIONS.md gains an 'Installable straight from GitHub' section: plugin.json
at archive root, release .zip preferred over zipball fallback, accepted URL
forms incl. owner/repo[@tag], and how the preview/consent flow presents your
declared permissions (checksum-pinned install). Keeps the GitHub docs and the
serverkit.ai Installing page telling the same story.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 02:05:32 -04:00
Juan DenisandClaude Opus 4.8 dc628aabba docs(manifest): point serverkit.yaml $schema at serverkit.ai
Single home for the schema on the product domain (serverkit.dev -> serverkit.ai
in SERVERKIT_YAML.md examples and the schema $id), matching the new
serverkit.ai/serverkit-yaml.schema.json endpoint. Also documents the deferred
registry-URL flip (plan 40 task 11): DEFAULT_REGISTRY_URL stays raw-GitHub
until the serverkit.ai /ext endpoints are deployed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:57:11 -04:00
Juan DenisandClaude Opus 4.8 bd6b8eff33 feat(plugins): GitHub-native install with preview + permissions consent
Add POST /api/v1/plugins/preview: resolve a GitHub/zip source, download,
read plugin.json, and return slug/version/permissions/panel-gate + the
resolved URL, sha256, and warnings — without installing. The manual-install
URL flow becomes two-step (Resolve -> consent card -> Install), pinning the
install to the previewed sha256 so installed bytes match previewed bytes.

- owner/repo and owner/repo@tag shorthand normalization (install accepts it too).
- Actionable resolve errors: rate limit (with reset ETA), 404/private, no
  plugin.json at archive root.
- Optional SERVERKIT_GITHUB_TOKEN: Bearer auth on GitHub hosts only (never
  leaked to third-party mirrors, never logged); lifts the anon rate limit and
  enables private-repo installs.
- ManualInstallModal: PreviewConsent card with permission chips + warnings +
  'GitHub · unverified' badge; SCSS added.

Tests (test_plugin_preview.py, 15): shorthand, happy/warning preview paths,
token header injection + host scoping, preview->install checksum continuity +
tamper rejection. Full plugin suites green; frontend lint 0 errors, build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:52:38 -04:00
Juan DenisandClaude Opus 4.8 c7b12b3095 feat(marketplace): surface registry v2 logo/repo/bundled fields
Stop stripping the v2 fields in registry_service normalization: add repo,
logo, bundled to _FIELDS. Repo-relative logos (assets/<slug>/<file>) are
resolved to absolute URLs against the index URL they came from, so one
relative path works for both the raw-GitHub index and the serverkit.ai
/ext endpoint. Bundled entries are excluded from the Browse catalog by
default (they duplicate builtin cards) and reachable via
?include_bundled=true. Detail modal gains a Source repo link.

Tests: v2 normalization, relative-logo resolution, v1 unaffected, bundled
exclusion + include_bundled endpoint flag. Full test_registry.py green (19).
Docs: EXTENSIONS_REGISTRY.md documents schema_version 2 + v2 fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 01:43:34 -04:00
Juan Denis d8bfb5cf41 docs: recovery record + recovered original commit messages 2026-07-07 20:32:54 -04:00
Juan Denis 6e32519f7e restore: remaining reconstructed July 5-7 changes (clean recovered files not attributable to one plan) 2026-07-07 20:29:31 -04:00
Juan Denis 28129e7848 feat(cloudflare): round 2 — DNSSEC, Origin CA, rules, R2 preset (plan 36) 2026-07-07 20:29:31 -04:00
Juan Denis 59e92c64a6 feat(manifest): appliance tier — ports/volumes/multi-container units (plan 35) 2026-07-07 20:29:30 -04:00
Juan Denis 45942339eb feat(notifications): polish — digests, prefs, deep-links (plans 24/33) 2026-07-07 20:29:29 -04:00
Juan Denis c96a476a74 feat(extensions): runtime-frontend hardening + GPU core-slim (plan 32) 2026-07-07 20:29:28 -04:00
Juan Denis a882d35db9 feat(adoption): DNS cutover+verify, snapshots, SSH import (plan 31) 2026-07-07 20:29:26 -04:00
Juan Denis 7d045f32af feat(backup): trust round 2 — restore drills, offsite/verify (plan 30) 2026-07-07 20:29:25 -04:00
Juan Denis 44892d3314 feat(agent): V2 capability pack — survey/doctor/fleet sweep+repair (plan 28) 2026-07-07 20:29:24 -04:00
Juan Denis 782ea70c8b feat(workspace): scoping audit + per-scope matrix (plan 19) 2026-07-07 20:29:22 -04:00
Juan DenisandClaude Opus 4.8 8c5ede0cfd feat(manifest): v1 serverkit.yaml spec, normalizer, scaffold (Phase 0)
Add the declarative serverkit.yaml v1 spec: JSON Schema + reference doc, a pure
parse/validate/normalize service, version:1 recognition in the repo manifest
detector (legacy flat files unchanged), and a scaffold endpoint that renders a
manifest from a live app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 01:11:50 -04:00
Juan Denis fe9e924626 polish(marketplace): extension copy sweep, publisher filter chips, docs + screenshot 2026-07-03 04:20:53 -04:00
Juan DenisandClaude Fable 5 af777a87b3 feat(marketplace): default the registry to the public serverkit-extensions index
SERVERKIT_REGISTRY_URL unset now resolves to the curated raw index at
jhd3197/serverkit-extensions (the go-live step for the registry infra);
set-but-empty explicitly disables remote fetching (air-gapped panels,
and how the test suite stays offline — pinned in conftest). URL is
resolved per fetch so env changes apply without a restart. Cache ->
bundled fallback chain unchanged; the Marketplace never blanks.

3 proving tests (default, empty-disables, override); registry/plugin
selection 76 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 03:57:23 -04:00
Juan DenisandClaude Fable 5 751dde7a7d fix(deploy): threaded gunicorn worker — gevent-websocket class broke WebSocket
The app runs flask-socketio with async_mode='threading' (the backend
is real-thread based: jobs scheduler, queue bus, agent registry), where
engineio serves WebSocket via simple-websocket on the raw socket. The
systemd unit template and Dockerfile still launched gunicorn with the
GeventWebSocketWorker, which ALSO answers the upgrade — the client got
HTTP/1.1 101 twice and read the second one as a frame ('Invalid frame
header'), so every panel silently degraded to polling. Proven by
hexdump (double 101, first 'frame' byte 0x48 'H') and fixed live:
single 101 + 0x81 text frame, socket.io connects through Cloudflare.

- unit template + Dockerfile: gunicorn -w 1 --threads 100 (single
  PROCESS stays mandatory — in-memory agent gateway)
- requirements: pin simple-websocket explicitly (was transitive-only)
- CLAUDE.md / ARCHITECTURE.md / SECURITY.md single-worker notes updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 00:14:49 -04:00
Juan DenisandClaude Fable 5 0ec55accbe feat(extensions): settings.section slot + manifest shape lint (#52)
Extensions can now contribute Settings sections: a settings.section
PluginSlot renders below the active Settings tab with the tab id as
context. _validate_manifest grew shape checks for everything the
platform consumes declaratively — module:attr refs (entry_point,
socket_entry, models, lifecycle hooks, job handlers), jobs/schedules
shapes, and per-kind required contribution keys (nav/routes/tabs/
palette/widgets/layouts) — so a malformed block fails the install with
a message naming each problem instead of being silently dropped at
runtime. Unknown contribution kinds only warn (forward compat). The
scaffolder gained --validate <path> mirroring the same rules for
authors, wired into the conversion recipe in docs/EXTENSIONS.md.

3 new lint tests incl. one asserting every shipped builtin manifest
passes; full backend suite 1227 green; lint 0 errors; build clean;
--validate smoke-tested on a good and a bad manifest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 02:36:46 -04:00
Juan DenisandClaude Fable 5 4ca07fb71c feat(extensions): per-plugin config store + Configure form (#49)
Completes the deferred half of #30. InstalledPlugin gains config_json
(migration 045); admin-gated GET/PUT /api/v1/plugins/<id>/config
persists values; plugins read them via the new plugins_sdk.config(slug)
accessor. The Marketplace Installed tab shows a Configure button for
plugins declaring a config_schema and renders a real form
(string/number/integer/boolean/enum/secret fields, defaults, hints).
Values may hold secrets so they are excluded from to_dict() and only
served by the admin endpoint. Documented in docs/EXTENSIONS.md.

2 new tests (config round-trip + SDK accessor + secret non-leak;
non-object rejection); pipeline suite 18 green; lint 0 errors; build
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 02:09:39 -04:00
Juan DenisandClaude Fable 5 50166b8f78 feat(extensions): tab-group contributions (#43) + FTP as serverkit-ftp (#44)
Extensions can now contribute a real tab to a core-owned tab group:
a new `tabs` contribution kind ({group, to, label, icon?, end?, order?},
group id == sidebar item id) is merged into the strip by TabGroupLayout
(new groupId prop; core wins `to` collisions), and route contributions
gained a `group` field so ExtensionRoutes collects them per group and
App.jsx mounts them INSIDE that group''s TabGroupLayout — the shared
PageTopbar chrome stays and the Sidebar keeps the group item lit via
contributed matchPrefixes. Wired for files/servers/monitoring.

First consumer: the FTP Server page moves to the serverkit-ftp builtin
extension (tab in the Files group; backend /api/v1/ftp stays core per
D2). Added to CONVERTED_BUILTIN_SLUGS so upgraded panels auto-install
it once; fresh installs find it in the Marketplace.

Proven by the extended contributions-envelope test (26 plugin/platform
tests green; full backend suite 1215 passed), lint 0 errors, build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 01:39:37 -04:00
Juan DenisandClaude Opus 4.8 39d21f7a46 feat(extensions): Phase 5 — WordPress UI contributed by serverkit-wordpress
Completes #38: the WordPress frontend now belongs to the extension too, not just
the backend. The tab-group constraint (contributed routes are flat — there's no
way to contribute a shared TabGroupLayout parent inside the dashboard) is solved
by contributing a SINGLE splat route `wordpress/*` whose component
(WordPressExtension) self-renders the whole WordPress sub-router — reproducing the
exact structure core App.jsx used to own:
  - TabGroupLayout + WORDPRESS_TABS over the 3 list pages (sites / plugin library /
    pipelines),
  - full-bleed site + pipeline detail routes as siblings,
  - the legacy /wordpress/projects → /wordpress/pipelines deprecation redirects.

The 5 page components stay in core pages/ and are re-used (Pattern A, same as
serverkit-git/email/workflows); only the routing + nav/palette/title ownership
moves into the manifest.

- builtin-extensions/serverkit-wordpress/frontend/index.jsx (source) → synced to
  frontend/src/plugins/serverkit-wordpress/ (pre-bundled, D5) via
  scripts/sync-builtin-frontends.mjs (drift check green).
- plugin.json contributions: nav (WordPress), routes ([{path:"wordpress/*",
  component:"WordPressExtension"}]), page_titles, command_palette.
- Core App.jsx: removed the WordPress imports, the TabGroupLayout WORDPRESS_TABS
  route block, the detail routes, LegacyWpPipelineRedirect, and the now-unused
  ModuleRoute + WORDPRESS_TABS imports (kept the dynamic /wordpress/ title
  fallbacks). Removed the sidebar nav item and the two command-palette entries.
  WordPress now disappears cleanly (nav+routes+API) when the extension is
  uninstalled.

Verified: npm run lint (0 errors) + npm run build clean; the screenshots skill
renders /wordpress/42 correctly (visually confirmed — full detail page, Overview
tab, sidebar item active); backend flagship still seeds/loads (has_frontend now
true) with tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 00:50:10 -04:00
Juan DenisandClaude Opus 4.8 a55e50bc16 feat(extensions): Phase 5 — extract WordPress into serverkit-wordpress (backend)
The largest single move in the plan (#38). The entire WordPress backend leaves
core for the bundled, default-installed serverkit-wordpress extension (D4).

Moved (12 files → builtin-extensions/serverkit-wordpress/backend/, intra-extension
imports made relative, core imports stay absolute):
  api/wordpress.py, api/wordpress_sites.py, api/environment_pipeline.py,
  wordpress_service.py (2676 lines), git_wordpress_service, wordpress_env_service,
  wordpress_plugin_library_service, wp_analytics/reports/security/update/vulnerability.

Wiring:
- Blueprints keep /api/v1/wordpress, /wordpress/projects and the /wordpress/pipelines
  alias (D9) via new manifest `extra_blueprints` support (3 blueprints / 4 mounts).
- Flagship (D4): seeded active on every panel (fresh + upgrade), uninstallable,
  never deletes tracked builtin/pre-bundled files; the `wordpress` module toggle is
  retired (MODULES now empty) — the plugin status guard is the gate.
- Loadability: the dashed package app.plugins.serverkit-wordpress is registered
  in-place via spec_from_file_location (no copy) — uniform across prod/dev/test.
- The 11 WP test files re-pointed through app.services.wordpress_bridge; the 6 lazy
  core callers were re-pointed in the prior commit.
- _regenerate_frontend_manifest() is a no-op under testing so the flagship boot
  seed can't clobber the checked-in plugins-manifest.json.

Deviations (documented, matching the Email precedent):
- WordPress *models* stay core — status_page/environment_activity/promotion_job hold
  FKs into wordpress_sites, so relocation would be a breaking data migration.
- Event catalog + Fail2ban WP filter stay core as two-speed items (extension calls
  them via core APIs); inverting adds risk without blocking the move.
- Frontend UI stays core this session (it lives in a TabGroupLayout — the tab-grouped
  conversion Phase 1 deliberately deferred); the extension is backend-only, so there
  is no route duplication. Frontend contribution is the scoped follow-up.

Verified: clean collection across all 1223 tests; 199 passed / 2 skipped / 0 failed
across the WP + plugin/extension + module + backup + core-caller parity set, incl.
the new tests/test_wordpress_extraction.py proving the move.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 00:15:10 -04:00
Juan DenisandClaude Opus 4.8 b59a57430e feat(extensions): Phase 4 — extract Email into serverkit-email extension
Moves the mail-server stack out of core into builtin-extensions/serverkit-email/:
- api/email.py + email_service/dovecot/dkim/spamassassin/roundcube → extension
  backend (relative intra-extension imports; core imports absolute); blueprint
  keeps /api/v1/email (D9). Core __init__ no longer registers email.
- Stays core (deviations from the audit, documented): postfix_service (the
  outbound relay actively uses it) + all email models (no external importers,
  so no DNSProviderConfig relocation / data migration needed).
- Frontend: serverkit-email manifest contributes nav/route/title/palette; core
  App.jsx/sidebar/palette entries removed; Email page re-exported (D2 pattern).
- #34 auto-install gated on real mail usage (EmailDomain/EmailAccount rows);
  the core Email module toggle is retired in favor of the extension.
- Tests: test_email_extraction.py proves core has no /api/v1/email, install
  registers+responds (exercises relative-import rewiring), uninstall clean.
  Full backend suite: 1200 passed. Frontend lint 0 errors + build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:39:18 -04:00
Juan DenisandClaude Opus 4.8 81d070b1d8 docs(extensions): Phases 4-5 boundary audits (Email + WordPress)
Grounded coupling maps + cut lists ("stays core" vs "moves") that gate the
actual extractions (#32/#38):
- EXTRACTION_EMAIL_AUDIT.md: mail-server stack cleanly extractable; only pre-req
  is relocating DNSProviderConfig + lazy postfix import; notifications SMTP is
  independent.
- EXTRACTION_WORDPRESS_AUDIT.md: extractable but 7 core hooks to invert first
  (eager import, backup-engine branch, Fail2ban WP filter, DatabaseSnapshot/SyncJob
  relocation, event catalog, lazy cross-imports). Ships bundled default-installed
  per D4; run after Email proves the machinery.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:13:51 -04:00
Juan DenisandClaude Opus 4.8 cdcfba5980 feat(extensions): Phase 3 — platform primitives (models, jobs, sockets, perms)
Backend:
- Plugin data models (manifest `models` → ext_<slug>_ tables; upgrade hook;
  purge on uninstall) via extension_lifecycle.py
- Declarative jobs/schedules wired into the Jobs SDK; pause on disable,
  resume on enable, removed on uninstall
- Socket.IO extension point (plugins_sdk.sockets → status-guarded /ext/<slug>)
- Permission capability gate (plugins_sdk.permissions.require_permission)
- Version compat gates enforced at every install source + panel_version() SDK;
  app/utils/version.py
- Keep-data vs purge on uninstall (lifecycle.uninstall(plugin, purge=))
- Plugin-asset serving route (delivery foundation, ADR 0001); manifest-spec
  documents all new fields

Frontend:
- Generic PluginSlot; mounted dashboard.top / service.detail.tab /
  domain.drawer.panel
- Uninstall keep/purge dialog; permission consent list + config_schema preview
  on the extension detail modal

Docs: ADR 0001 (frontend delivery), ADR 0002 (sandboxing note), EXTENSIONS.md
updates; scripts/new-extension.mjs scaffolder (#40).
Tests: test_extension_platform.py (11). Full backend suite: 1197 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:11:09 -04:00
Juan DenisandClaude Opus 4.8 6f5f2b1047 feat(extensions): Phase 2 backend — remote registry, checksum install, updates
- RegistryService: configurable SERVERKIT_REGISTRY_URL, TTL cache, offline
  fallback (last-good → bundled app/data/registry_index.json); read-only, never
  auto-installs. GET /api/v1/marketplace/registry + registry install endpoint.
- Checksum-verified install: install_from_url(expected_sha256) hard-fails on
  mismatch; force= path enables in-place updates.
- Update flow v1: check_for_updates() + GET /plugins/updates + POST
  /plugins/<id>/update; respects min/max_panel_version via new
  app/utils/version.py (get_panel_version + semver compare).
- serverkit-gui listed as the first registry entry (bundled index).
- Docs: EXTENSIONS_REGISTRY.md (index format + publisher/review guide).
- Tests: test_registry.py (fetch/cache, checksum, compat gate, update flow).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:41:50 -04:00
Juan DenisandClaude Opus 4.8 74c5e0a885 feat(extensions): Phase 0 hygiene — author guide, sync gate, truth pass, tests
- docs/EXTENSIONS.md: honest author guide (manifest, contributions, SDK,
  lifecycle, install sources, production frontend constraint, conversion recipe)
- Retire the serverkit-git compat shim in contributions.js (manifest is canonical)
- scripts/sync-builtin-frontends.mjs + extensions-ci.yml: builtin-frontend
  drift gate (single source of truth per D5)
- Marketplace UI truth pass: relabel bundled entries "Built-in"
- backend/tests/test_plugins_pipeline.py: first coverage for the install
  pipeline (builtin install, contributions envelope, disable 503, reinstall
  refresh, zip-slip)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:13:04 -04:00
Juan DenisandClaude Opus 4.8 45ba40a7ad feat(wordpress): surface the live site URL on create (closes roadmap #21)
After creating a WordPress site the post-create banner showed only the admin
credentials, so users had no signal that the site was already reachable at
<slug>.<base> — the exact "preview URL before DNS" gap roadmap #21 tracked,
now unblocked by the base-domain + Phase-5 wildcard-cert stack.

The banner now surfaces the live link on every successful create: a clickable
<slug>.<base> (noting HTTPS when the wildcard cert is set up), or, when the
site only came up on localhost, a nudge to set a managed-sites base domain in
Settings to publish it publicly. Shown even when the automated install did not
finalize (no captured credentials).

Mark #21 done in the roadmap: the wildcard base subdomain is the working,
TLS-valid preview a user relies on while pointing a custom domain, so the
separate *.preview.<panel-domain> scheme is unnecessary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 23:35:25 -04:00
Juan Denis 96d3094c52 Add 'Faro' related-projects note to README
Add a 'Related Projects' section linking Faro (desktop SFTP/SSH/S3 client) to the main README and translated docs (Spanish, Portuguese, Chinese). Explains Faro's features and relationship to ServerKit, and adds a link to the latest releases. This provides users a recommended desktop companion for file transfers and agent bridge functionality.
2026-06-30 23:03:47 -04:00
Juan DenisandClaude Opus 4.8 ca66629780 feat(deploy): serve panel frontend from host nginx, retire the container
The panel is now fully off Docker: the React SPA is served as static
files directly by host nginx from frontend/dist, eliminating the
serverkit-frontend container and the double nginx hop (host nginx ->
container nginx -> dist). Docker is now used ONLY for managed app
workloads.

- nginx sites (serverkit.conf + serverkit-insecure.conf): `location /`
  now roots at /opt/serverkit/frontend/dist with a try_files SPA
  fallback and a long-cache /assets/ block, instead of proxying
  127.0.0.1:3847. Root tracks the /opt/serverkit symlink so blue/green
  updates swap assets atomically.
- install.sh: launch_services no longer builds/starts a frontend
  container; adds apply_frontend_root (rewrites the dist root for a
  custom SERVERKIT_DIR) and selinux_label_frontend_dist (httpd_sys_-
  content_t so SELinux hosts can read the bundle from /opt).
- update.sh: refresh_config carries the same root rewrite across
  upgrades; selinux_label_dist relabels the switched-in slot; dropped
  the `docker compose up -d --force-recreate frontend` from both the
  forward and rollback paths.
- docker-compose.yml: frontend service moved behind a `legacy-frontend`
  profile (opt-in escape hatch); default `up -d` starts nothing.
- docs/DEPLOYMENT.md: documents the off-Docker panel model.

Tests: T14 (refresh_config rewrites dist root for custom install dir),
T15 (shipped nginx configs serve static, never proxy :3847). 16 passed.

Note: live nginx -t / SELinux file-serving not validated on a RHEL box
from this dev host; logic is best-effort guarded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 02:24:22 -04:00
Juan DenisandClaude Opus 4.8 31330a09be fix(deploy): make docker-compose.yml frontend-only (backend runs on host)
ServerKit's backend manages the host it runs on -- it creates app dirs under
/var/serverkit/apps, drives the host Docker daemon (one container per app),
and reloads nginx. The default compose containerized the backend, so it ran
as a non-root user into a root-owned /var/serverkit/apps with no host
docker.sock: WordPress/app creation failed with
`Permission denied: /var/serverkit/apps/...`. It also contradicted install.sh,
which runs the backend on the host via systemd and expects compose to bring
up only the frontend.

- docker-compose.yml: frontend-only; the frontend reaches the host backend via
  extra_hosts ["backend:host-gateway"].
- backend/Dockerfile: chown /var/serverkit to appuser (latent EACCES on the
  apps dir for dev/demo container use) + header noting it cannot manage host.
- frontend/nginx.conf: document the backend:host-gateway upstream.
- docs/DEPLOYMENT.md: add a Deployment model note; fix build/start + data paths.
- scripts/test/test_install.sh: T12 guards against re-adding a backend service.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:20:32 -04:00
Juan DenisandClaude Opus 4.8 c4f3b4fdac feat: Phase 4 polish & fleet — onboarding completion, fleet proxy, shared-resource depth, docs
- Server onboarding completion (C8): a pure pre-flight compatibility check
  (arch/distro/kernel/cpu/memory) gating validate; real-but-guarded
  distro-aware install_prerequisites/install_docker/pair_agent steps that
  no-op safely on dev/Windows and never raise; audit + best-effort
  notifications on each transition; and a live auto-scrolling log trail +
  elapsed timer in the onboarding wizard.

- Fleet-wide proxy dashboard (C6): ProxyStackService.fleet_overview aggregates
  every server's proxy posture (configured stack vs host Nginx default) behind
  GET /api/v1/servers/proxy/overview, surfaced as a new Fleet Proxy page in the
  Servers tab group with summary tiles + click-through to each server's proxy.

- Polymorphic shared resources depth (C9): hierarchical variable resolution
  (workspace < project < environment < direct attachment < resource) with
  source provenance and {{group.KEY}} interpolation, an audit trail on every
  tag/group/variable/attachment mutation, GET /api/v1/shared/resolved/
  hierarchical, and the reusable Tags/Environment-Variables panels embedded on
  the application and server detail pages.

- Docs & coverage: docs/ENHANCEMENTS.md (brand-neutral guide to all ten
  capabilities + verified endpoints), README/CHANGELOG updates, and a
  cross-cutting integration test asserting the new endpoints are wired.

Full backend suite: 924 passed, 14 skipped; frontend builds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:04:04 -04:00
Juan DenisandClaude Opus 4.8 f47d0e1428 feat: Phase 1 foundation — status aggregator, API scopes, onboarding, template magic vars
Four foundational enhancements toward the deploy-code developer experience:

- Container status aggregator (C7): new ContainerStatusService applies a
  deterministic priority hierarchy (degraded > restarting > running:unhealthy >
  starting > running:healthy > exited > unknown) over an app's containers,
  cached with a short TTL. Exposed at /api/v1/status and pushed over a new
  container_status Socket.IO channel; Applications page now renders an
  aggregated ContainerStatusPill with sub-status tooltip.

- API token scopes + sensitive data masking (C10): require_scope() decorator
  enforces fine-grained scopes for API-key requests (pass-through for JWT),
  a canonical SCOPES catalog at /api/v1/api-keys/scopes, and a reusable
  sensitive_data_filter (mask_sensitive) honoring secrets:read. Frontend gains
  a grouped ApiKeyScopesModal picker and per-key scope pills.

- Server onboarding state machine (C8): new onboarding lifecycle
  (pending -> validating -> installing_prerequisites -> installing_docker ->
  pairing_agent -> ready/failed) with ServerOnboardingService, a
  server_onboarding_logs table, servers.onboarding_* columns, REST endpoints,
  and an OnboardingWizard surfaced on the server detail overview.

- Declarative template catalog (C5): documented catalog schema and
  magic-variable resolution (SERVICE_PASSWORD_/USER_/FQDN_/URL_/BASE64_*)
  layered onto the existing YAML template loader without changing behavior for
  templates that don't use them; GET /api/v1/templates/catalog/schema.

Migration 029 adds the onboarding table + columns. 60 new tests pass; frontend
builds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:23:47 -04:00