147 Commits
Author SHA1 Message Date
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
Juan Denis 76ded002da Add per-site Fail2ban jail and UI for WP brute-force protection
Introduce a ServerKit-managed per-site Fail2ban layer to protect WordPress login endpoints. Adds Fail2banJailService (write-side) to generate filter and per-site jail configs, enable/disable jails, reload fail2ban, and unban IPs (Linux-only, best-effort, ownership-guarded). Wire it into WordPress service lifecycle (enable on site create, remove on delete) and WpSecurityService wrapper methods. Expose API endpoints for status, toggle and unban in backend API. Add unit tests for Fail2banJailService (config rendering, naming/safety, lifecycle, graceful degradation). Frontend: Security tab loads brute-force status, can toggle protection, shows ban counts and unban action; API client and styles updated. NginxService gains canonical per-site log/error path helpers. Docs updated to reflect shipped brute-force jail feature.
2026-06-23 17:48:59 -04:00
Juan Denis 3f37994f18 feat(installer): atomic blue/green updates, pre-flight checks, offline support, pre-built releases
- Rewrite scripts/update.sh with pre-flight validator (disk, memory, Python,
  deps, network, health) and automatic rollback on failure.
- Implement atomic blue/green install layout (/opt/serverkit-a|b + symlink).
- Add explicit database migration step (flask db upgrade) before switching slots.
- Add --dry-run, --force, --branch, --release flags to update.sh.
- Add SERVERKIT_OFFLINE_TARBALL and SERVERKIT_MIRROR_URL support.
- Verify release tarball SHA-256 checksums on download.
- Ship pre-built frontend/dist and relocatable venv in release tarballs.
- Make serverkit update delegate fully to scripts/update.sh; remove inline
  _update_source fallback.
- Harden install.sh for blue/green, checksums, offline, and pre-built venv.
- Update uninstall to remove both blue/green slots and backups.
- Update docs/INSTALLATION.md and README.md with new options.
2026-06-23 11:02:26 -04:00
Juan DenisandClaude Opus 4.8 6db6680433 docs(backups): backup protection guide
Operator-facing guide to the Protection panel: scheduling, smart/incremental backups, retention, storage cost (free local disk vs real S3/B2), and one-click restore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:53:42 -04:00
Juan Denis f9d0610452 chore: remove agent code now living in serverkit-agent repo
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)
2026-06-21 03:30:57 -04:00
Juan DenisandClaude Opus 4.8 467ebc51b0 chore(docs): track regenerated UI screenshots
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>
2026-06-21 02:21:47 -04:00
Juan DenisandClaude Opus 4.8 6d8d8dfa31 docs(i18n): sync es/zh-CN/pt READMEs with the new meaningful features
The localized READMEs are older, slimmer variants missing several sections the
English README has, so each picked up the feature entries that map to existing
sections (Manual & Zip deploys, Container Lifecycle, SSL/TLS hardening, S3/B2
browsing, Passkeys/WebAuthn, WAF, GPU monitoring) plus the full set of new
completed roadmap items, the Tech Stack security row, and a NEW_FEATURES link.

Features whose parent section doesn't exist in a given translation (Remote
Service Tunnels, Connections, WordPress Publishing, Dynamic DNS, Secrets &
Webhook Gateway, CVE/SBOM, Encrypted Secrets at Rest) are represented in that
file's roadmap rather than fabricating new sections. No Layout Switcher
(UI-only) line added, consistent with the English README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:23:13 -04:00
Juan DenisandClaude Opus 4.8 ae177d789c docs: catch README/CHANGELOG/feature reference up to the unreleased dev work
The dev branch is 23 commits ahead of origin with many user-facing features that
were never reflected in the docs. Bring them current.

- README: add WAF, container CVE/SBOM (grype/syft), passkeys/WebAuthn, dynamic
  DNS, GPU monitoring, container lifecycle (image updates / auto-sleep /
  auto-scale), secrets + inbound webhook gateway, backup encryption, optional
  hardened TLS, encrypted secrets at rest, and the layout switcher to the
  Features list, Roadmap, and Tech Stack; link NEW_FEATURES.md.
- CHANGELOG: add the above to [Unreleased] and a new Security subsection
  (CVE/SBOM, optional/hardened TLS + auto-CAA + HSTS gating, encryption at rest).
- NEW_FEATURES.md: add §6 documenting the SSL/TLS hardening follow-up
  (02afdec..a09bf0f) and renumber Known follow-ups to §7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:08:24 -04:00
Juan DenisandClaude Opus 4.8 1f700cea06 docs: endpoint & page reference for this session's features
Reviewer-facing summary of the new endpoints, pages, models, config flags,
and operational notes for the DDNS / image-update / auto-sleep / auto-scale /
GPU / backup-encryption / WAF features and the layout switcher.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:34:55 -04:00
Juan DenisandClaude Opus 4.8 733f866f1c feat(wordpress): per-site PHP-fatals analytics from the WP_DEBUG log (#25)
Finish #25's deferred PHP-fatals signal, sourced from the WP_DEBUG log
(/tmp/wp-debug.log) that #30's Security toggle writes — not the access log
(which can't carry it):

- get_php_errors: one bounded docker exec (marker-guarded tail -c, 15s single-
  worker timeout) distinguishes logging-off / no-log from on-but-clean, then
  parses [time] PHP <Level>: <msg> into severity counts (fatal/warning/notice/
  deprecated; Parse + Recoverable-fatal fold into fatal) + the 25 most-recent
  entries (messages capped, stack-trace lines skipped).
- Merged into GET /sites/<id>/analytics as php_errors; a PHP errors panel added
  to the Analytics tab with an 'enable WP_DEBUG on the Security tab' empty state.
- Supplies the per-site error signal #27's error-spike rule wanted.

7 new tests (classification, parse/skip-traces, off/empty/unavailable, capping);
full backend suite 218 passed / 12 skipped; frontend lint-clean + production build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:06:58 -04:00
Juan DenisandClaude Opus 4.8 a6a214c245 feat(wordpress): wp-content/full-site zip import with zip-slip-safe extraction (#17)
Finish #17's deferred files half on top of the .sql MVP. After the DB import +
search-replace, an optional wp-content/full-site .zip is extracted and copied
into the container:

- _safe_extract_zip rejects any member escaping the temp dir BEFORE writing
  (zip-slip: ../ traversal, deep traversal, absolute paths) + bad-zip guard.
- _resolve_wp_content_dir handles the 3 layouts: zip OF wp-content, zip WITH
  wp-content/ at root, and full-site zip one wrapper folder deep.
- _import_wp_content_zip docker cp's the resolved tree into
  /var/www/html/wp-content and chowns it to www-data. Non-fatal: DB import still
  succeeds; response carries a warning + wp_content_imported flag.
- Route accepts an optional wp_content multipart file (.zip, own temp, cleaned
  up); client importSite({wpContentFile}) + a file input in the import modal.

11 new tests (zip-slip rejection, layouts, docker cp/chown flow mocked); full
backend suite 211 passed / 12 skipped; frontend lint-clean + production build.

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