The dashboard shots were clipped to `.dashboard-page`, which cropped the
sidebar out and made them the only images in the set without the app chrome
around them — next to Services and Docker in the README it read as a bug.
The clip was there for a reason: `.main-content` owns the scroll, so a plain
shot stops at the viewport and `fullPage` does not reach the bottom widgets.
The fix is a per-section viewport instead — a taller frame captures the whole
board WITH the navigation, and `capture.mjs` now honours `section.viewport`
for any page with the same internal-scroll shape.
Also stops the shots claiming work is in flight. The dashboard fixtures
answered the deploy pill with running jobs, so every capture carried a
"2 deploys running" pill over the sidebar. The pill is correct behaviour; it
just has nothing to do with a screenshot of an idle board, so the fixture now
reports settled jobs rather than the CSS hiding a true statement.
31/31 sections still render.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six new sections cover the pages rebuilt this round — the dashboard as it ships,
its edit mode, the widget library, the widget editor, the databases explorer and
the engine catalog drawer. 31/31 sections render.
The capture is a render smoke test, not just a picture, and it earned that here
by surfacing two real bugs that no test could see:
`SearchField` merges its `className` onto its OWN root, so `.skw-lib__search`
IS the `.sk-searchfield` element rather than a wrapper around one. The rule
styling it as a parent matched nothing, leaving the shared `flex: 1 1 240px`
in force — and inside a column-direction filter bar that 240px basis is a
HEIGHT. The widget library's search box was rendering 240px tall.
`GET /auth/me` answers `{user: {...}}`, but Databases read `.role` straight off
the envelope, so `isAdmin` was always false. Every admin got the read-only
console — and with the DDL builder now shipped, would never have seen the button
that runs a CREATE TABLE.
Also carries `postgresql.yaml`, which I left out of the extensions commit.
Without `engine.extensions: true` stock PostgreSQL is not a host for pgvector at
all; the extension tests passed only because the working tree had the change the
commit did not.
Fixture note for whoever edits these next: `/metrics/history` points are NESTED
(`cpu.percent`, `load['1m']`) and carry no network data, `/servers/available` is
a bare array, and `DeployPill` queries `/deployment-jobs` twice and sums — a
single unfiltered payload makes it claim eight running deploys.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- List all installer-supported distros (Ubuntu 22.04+, Debian 12+,
Fedora, RHEL/Rocky/AlmaLinux 9+) instead of Ubuntu only
- Add Arch row (x86_64, ARM64) matching installer platform detection
- Clarify Docker is optional for the panel itself
- Note the panel's ~180 MB RAM / ~500 MB disk footprint and that it
runs on 1 GB VPSs, spare laptops, and Raspberry Pi homelabs
`contributions.widgets` mounts a component at a host-chosen slot. That is the
wrong shape for the new dashboard, where the user decides what goes on a board
and where. So this adds `dashboard_widgets` alongside it: a registry of
placeable widget TYPES that surface in the widget library, each instance
positioned, sized and configured by the person looking at it.
The two are easy to confuse, so the docstring, the manifest spec and
EXTENSIONS.md all now say plainly which is which, and a test asserts a
dashboard widget never leaks into the fixed-slot list.
SDK_VERSION 1.0.0 -> 1.1.0 (added surface, older extensions unaffected),
bumped in lock-step across the JS export and the backend mirror.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fleet list carried a rail of its own — a health dial, a status nav and a
groups nav — plus a workbar, a command bar, bulk-select and a per-row action
menu. Every number those showed is a column or a filter here, so the page is
one filter row and one table: status segments with counts, a group select,
"4 of 4 servers · 2 online", then Server / Host / Agent / Status / CPU /
Memory / Disk / Last seen. Row actions are gone because ping and delete
already live on the server's own page, which is where rebooting or deleting a
machine belongs.
Offline and pending rows show a dash rather than empty gauges: a 0% bar is a
claim, and those hosts are not reporting.
Add Server was written as a right-side drawer, then pointed at the centred
Dialog primitive while keeping its own `position: fixed; right: 0` geometry.
The primitive's `left: 50%` and -50% transform still applied on top, which is
what rendered the panel half off-screen and clipped. It runs on the shared
Drawer now, so the slide-in, focus trap and overlay come from one place.
`.servers-page { padding: 0 }` was a leftover from the old full-bleed
workspace and was overriding the tab group's padding, running the table under
the right edge of the viewport. Removed, with its layout hook.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The summary band rendered above every section of the group, so "Total
backups" sat on top of Schedules, Storage and Settings, where it answered
nothing you opened those tabs to ask. It belongs to one section, and that
section is now an Overview rather than the archive table.
Overview answers "is my data safe?": protected resources, storage used,
success rate and next scheduled; an 18-week activity heatmap with its failure
days marked; a recent-activity feed; destination bars; latest snapshots. This
needed two endpoints the frontend had never called — /backups/policies and
/backups/runs — which is where the heatmap, the success rate and the
durations come from. A panel that has only ever taken manual backups falls
back to the archive listing instead of showing an empty page.
Schedules gets the mock's columns (frequency, retention, destination, next
fire, an on switch) and only its own action in the top bar; Create Backup was
answering a question nobody on that tab was asking. Snapshots moves its
search to the top bar with every other list page. Storage becomes destination
cards over the provider form.
Two places the mock has data we do not, left out rather than faked: per-policy
size and run-now on Schedules (the schedule API exposes neither), and
duration on Snapshots (filesystem entries do not record it).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fleet Monitor and /monitoring were the same questions at two scopes, so they
were two pages restating each other: both grew an alerts surface and both
grew thresholds. They are now one page with a scope, not two pages with a
gap between them.
The top bar carries a server picker, URL-backed as ?server= so the choice
survives a tab change, a refresh and a pasted link. Overview, Alerts and
Capacity all follow it. The fourth KPI tile is whichever reading the current
scope actually has — load average on the panel host, container count on an
agent — rather than a blank where the other one would be.
The overview is ported from the design mock: KPI band with trends and
sparklines, two trend charts, and a Host health strip below. That strip is
what the standalone heatmap became, except each card is a button that
re-scopes everything above it instead of a cell you can only read.
There is deliberately no second tab strip inside the page. Overview / Alerts
/ Rules / Capacity / Doctor live in the group's own top bar, the way the mock
puts them — a nav under a nav reads as two competing headers.
Also: the sidebar said "Observability" for a page, a route and a title that
all said "Monitoring", and the delivery channels rendered Discord, Slack and
a raw webhook with the same generic glyph. Both fixed.
/fleet-monitor redirects; the Servers tab strip loses the entry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Search now lives in the top bar, right-most, on every list page — Services
kept its copy in the table toolbar, and Domains had none at all. The view
switch claims the free space so it stays right-aligned where a page gave its
toolbar search up; where one is still present it hands the margin back.
Domains loses its four-tile KPI strip. Every number on it was already the
count of a segment you would click to act on, so the segments carry them
instead: All 5 / Expiring SSL 1 / Attention 1. Search narrows the set the
counts come from, so they always describe what a click would show.
Fleet Proxy loses its title, subtitle and five-tile band — the tab strip
names the page and every tile re-counted a column of the table beneath it.
What the page is for now lives in the empty state, where it is needed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Should X be a registry too?" gets re-asked every few months, and the answer is
usually no. This records the rule and the per-catalog verdicts so the assessment
does not get redone from scratch.
A catalog earns a registry only when all three hold: it changes faster than the
panel releases, it is data rather than code (a new entry must work on an
existing panel with no code change), and someone outside the release cycle would
contribute to it. Extensions, themes and app templates pass. Seven other
catalogs were assessed and rejected, each with the reason -- server templates,
notification events, AI providers, DB tuning settings, sanitization profiles,
agent survey probes and build packs.
Also names the failure mode that makes a bad extraction expensive: panels that
have not upgraded read the registry forever, so an entry that needs new panel
code either gets ignored or renders as a broken item. That is what forces a
min_panel_version gate, and why serverkit-extensions has one and the other two
do not need one.
Records one open candidate rather than leaving it implicit. YARA rules pass all
three tests and have no update path -- 16 curated rules frozen at release time.
That got sharper this session: the image now builds without ClamAV by default,
and ClamAV was the component with a self-updating feed via freshclam, so YARA is
now the only malware detection in a default container. Operators can already
drop custom .yar files in CUSTOM_RULES_DIR; what is missing is a curated
upstream. Not built -- noted so the gap is deliberate.
Adds the templates registry and this doc to the README documentation table.
Every claim was checked against the code rather than written from memory: rule
counts (16 in both BUILTIN_RULES and webshells.yar), the CUSTOM_RULES_DIR
default, ARG INSTALL_CLAMAV=false, and that ServerTemplateService already
exposes create_template so its 12-entry library is a seed, not a catalog.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a "By the Numbers" table under the badges in all four READMEs (EN, ES, PT,
zh-CN). Every figure is measured from this repo at this commit, not estimated:
1,519 REST endpoints walked from app.url_map after create_app(); a raw
@route grep undercounts at 1,276 because plugins
register routes dynamically
106 app templates backend/templates/*.yaml, matches index.json
2,633 backend tests pytest tests --collect-only
1.75 MB gzipped UI frontend/dist, gzip -6 over every asset
~180 MB resident gunicorn --workers 1 --threads 100, FLASK_ENV=production
501 MB image docker build . on linux/amd64
Three claims were corrected against the code rather than written from memory:
the OpenAPI docs live at /api/v1/docs (not /api/docs); "no telemetry" was wrong
-- there is a telemetry subsystem, it just writes local SystemEvent rows -- so
it now says nothing phones home, which was verified (no sentry/posthog/segment/
mixpanel, no version ping, and the SPA's only external URLs are documentation
links, not asset loads).
Numbers only ever grow here, so a stale figure undercounts rather than
overstates.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The SDK table listed the surfaces an extension consumes but not the ones it can
now contribute to, and deploys had been missing since it shipped. Adds all six
with one worked example each, plus the rule that ties them together: namespace
what you register, the panel owns the UI and normalises what you hand back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The shared sheet sets padding and a gap on the panel itself, but
.sk-drawer lays out three full-height regions inside it — head, body,
foot — each managing its own padding. The panel's padding therefore inset
all of them: the header's divider stopped short of both edges and the
footer floated above a band of dead space instead of sitting on the
bottom edge.
Zero the panel's padding and gap, and give scrolling to __body alone —
with the panel itself scrollable the footer could not stay put either.
Fixed at .sk-drawer rather than per page, so every drawer gets it: the
template deploy and filter drawers had the same gap.
Measured after the slide-in settles: 0px below the footer, and head/foot
now span the full panel width.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Create WordPress Site" was a centred modal while services and templates
deploy from a right-hand drawer, and its own styling was broken: an icon
stranded on its own line, an unspaced list running straight into the
first field, and a footer that floated over the form.
Two causes, both structural. It rendered legacy .modal-body and
.modal-footer *inside* the shared Modal, which already supplies a body
and takes a footer prop — the chrome was nested twice. And its
.install-warning callout is styled under `.wordpress-page`, while the
dialog renders in a portal, so it is not a descendant of the page that
opened it and none of those rules ever applied.
So the drawer styling moves out of _templates.scss into the design system
as .sk-formdrawer, shared by both pages. That is the fix for the class of
bug, not just this instance: a portalled surface cannot rely on
page-scoped SCSS, so its styles belong somewhere unscoped. The block also
now covers bare <select> and checkbox rows, which otherwise render with
the browser's own white chrome on a dark panel, and the callout.
Verified in a browser: drawer settles at the right edge at its declared
560px, callout renders with its background and spacing, selects pick up
the surface colour, and no .modal-body/.modal-footer/.install-warning
remain in the tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every one of the ~40 loaders went through EmptyState's single hardcoded
shape — an avatar, three cards, three lines — whether the page was a
table, a console, a form or a chart. A skeleton exists to predict the
layout about to arrive so that arrival is a fill-in rather than a jump;
one shape reused everywhere mispredicts on all but the page it was drawn
for, which is worse than honest, because it promises a layout that never
comes.
PageSkeleton provides ten layout archetypes — panel, table, feed, cards,
detail, console, form, chart, tree, split — driven by SCSS so widths stay
responsive instead of being guessed in pixels. Repeated lines use a
deterministic ragged width, so they read as text and don't reshuffle each
render the way Math.random() would.
EmptyState takes `loadingVariant` (defaulting to the old `panel`, so
nothing regresses) and 41 call sites now name their real shape.
ResourceListPage picks table or cards from its own view state.
All ten are on the style guide under Feedback & Loading, and captured as
one screenshot so a regression in any of them is visible in one shot.
Captured bones (npm run capture:skeletons) remain the pixel-exact option
for individual regions; these archetypes are the broad-coverage answer
for the pages that will never be worth capturing one by one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Recaptured against the ported design, plus new shots for the templates
catalog, its filter and deploy drawers, and the Services card view.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
/deployments still reserved a second grid column for the detail pane plan 51
removed — the table sat at half width with its Started column clipped by
overflow:hidden. Single full-width panel now; pruned ~150 lines of dead
detail-pane SCSS; status badge + progress colors move from inline styles to
modifier classes; kinds humanized; skeleton loading rows; removed the topbar
New Service button duplicating the adjacent tab. Console: fills the viewport
(calc-based min-height), wider step rail (248px), flexible log search, denser
toolbar. Adds deploy-console screenshots (4 sections, mock-data captures).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop docs/screenshots/themes/ from the panel repo; THEMING.md and the
README roadmap now link to the registry's gallery instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One dashboard capture per theme (mock-data rig, capture-themes.mjs) under
docs/screenshots/themes/ with a grouped gallery README, linked from
THEMING.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ThemeContext: fetch pre-auth panel default (/public/active) and follow it
until a user picks a skin; ThemeSync bridge loads installed themes after auth
- Theme Gallery admin actions: set panel default, remove installed themes
- Browse modal: install community themes from the registry (offline-tolerant)
- Import theme.json (admin, paste/upload)
- Theme Studio: grouped color editor over the LIVE panel, start-from any
installed theme, Export theme.json, Save-to-panel, and Submit = a prefilled
GitHub new-file PR against serverkit-themes
- docs/THEMING.md (token reference + rules + submit flow); README docs table +
ROADMAP "Recently Completed"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review feedback + follow-up on the review-stamps feature:
- The 409 trust gate now distinguishes reason 'unreviewed' (community
code not vetted) from 'unverified' (no pinned checksum, possible even
for first_party), each with an accurate message; the risk dialog
carries the reason and matches its title/body to the actual case
instead of always claiming 'unreviewed community extension'
- Unreviewed registry entries are now developer-stage content: hidden
from the Marketplace catalog and refused at the install endpoint
(404) unless the panel runs in a development context (Flask debug /
testing config, or the dev_mode setting). Hidden means not
installable; the 409 acknowledgment flow only exists where they list
- dev_mode setting description mentions the Marketplace effect
- tests: reason assertions on both 409 cases; catalog visibility and
404-on-hidden-install coverage (38 passing)
The registry index (schema v3) can now carry a review object per entry
(reviewer, date, sha256, optional commit/notes) asserting that someone
inspected the exact artifact being listed. The stamp is bound to the
artifact hash: review.sha256 must equal the entry sha256, so any change
to the released zip invalidates the review.
- registry_service: keep review through normalization (validated),
derive trust per entry: first_party, reviewed (stamp matches the
pinned hash), or unreviewed
- marketplace install route: unreviewed (or sha256-less) entries return
409 requires_acknowledgment unless the admin posts
acknowledge_risk: true; acknowledged installs record trust in the
audit log. Consent gate, consistent with ADR 0002
- Marketplace UI: Reviewed badge (reviewer + date tooltip) and
Unreviewed warning badge on registry cards, plus a risk confirmation
modal that retries the install with acknowledge_risk
- tests: trust derivation and the install gate (409 + acknowledge path)
- docs: EXTENSIONS_REGISTRY.md documents the field and the flow
The audit overstated the WP frontend as contributed by the extension; in the
tree the extension index.jsx is still a re-export shell over core pages/
components/SCSS. Corrected to match reality before plan 52 Phase 4 moves it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the server half of runtime-frontend delivery: get_active_contributions()
now emits panel sdk_version and a 'frontends' descriptor map {slug: {entry,
hashes, sdk_version}} for every active extension whose frontend_entry is a .mjs
ESM bundle. The bundle sha256 is pinned at install time into the plugin config
under the reserved _frontend_hashes key (no migration — reuses config_json);
update_plugin_config() and the config API preserve/hide reserved keys. Add the
extensions.runtime_frontend kill switch (default on). The client loader already
consumed this shape, so an installed extension's UI now renders on a pre-built
panel with no Vite rebuild. Un-skips the restored plan-25 recovery suites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add docs/DEPLOY_CONSOLE.md (the feature + architecture), an architecture section
in docs/ARCHITECTURE.md, a CHANGELOG (Unreleased) entry, and move the Deploy
Console into the ROADMAP's Recently Completed list with its Active Development
checkboxes ticked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the Trendshift repository badge to the main README and the Spanish, Portuguese, and Simplified Chinese README files so project visibility/trend metrics are shown consistently across all language entry points.
Flip DEFAULT_REGISTRY_URL to https://serverkit.ai/ext/index.json (the
long-planned go-live default: cached proxy of the raw index with logo URL
rewriting). Add logo fields to the bundled offline index so Marketplace
cards get real art even before the first successful fetch, and update the
registry tests + docs for the new default.
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>