1391 Commits
Author SHA1 Message Date
Juan Denis be20b59820 Merge branch 'dev' into main: template bind-mount fix v1.7.47 2026-07-21 23:26:16 -04:00
Juan Denis 465a4ee9a1 fix(templates): only bind-mount absolute file paths; relative files are build-context only
A template file with a relative path (prompture-hub's Dockerfile) was
bind-mounted as './Dockerfile:Dockerfile', failing compose up with
'invalid mount config: mount path must be absolute'.
2026-07-21 23:26:13 -04:00
github-actions[bot] 343073c244 chore: bump version to 1.7.47 [skip ci] 2026-07-22 03:13:17 +00:00
Juan Denis 0e3bddf30b fix(templates): pin prompture-hub to 0.0.1 (0.0.2 was never published to PyPI) 2026-07-21 23:13:08 -04:00
Juan Denis c0c1d36456 Merge branch 'dev' into main: prompture-hub template pin fix 2026-07-21 23:12:42 -04:00
Juan Denis 79420e58ad fix(templates): pin prompture-hub to 0.0.1 (0.0.2 was never published to PyPI) 2026-07-21 23:12:39 -04:00
Juan Denis 9baa8f43ea Merge branch 'dev' into main: fix stuck deployments, repo-app auto-deploy, unified install UX 2026-07-21 22:57:59 -04:00
github-actions[bot] f601782fde chore: bump version to 1.7.46 [skip ci] 2026-07-22 02:57:45 +00:00
Juan Denis 98ad0481a5 fix(deployments): stop stuck 0% installs, deploy repo apps on create, unify install UX
- deployment_job_logs.id: BigInteger -> with_variant(Integer, 'sqlite');
  BIGINT PK never autoincrements on SQLite, so the first log insert crashed
  every fresh install (jobs stuck 'running' at step 0 with no logs)
- migration 076 rebuilds the table on existing SQLite databases
- runner: first status/log/telemetry inside try, non-raising log(),
  rollback-safe failure marking — any failure now fails the job visibly
- service: guard enqueue, crash-proof run_job, fail jobs left 'running'
  across restarts at boot
- repo-based services (POST /apps/from-repository) now enqueue a real
  'app_deploy' DeploymentJob (build + start via DeploymentService.deploy)
  and return deploy_job_id
- compose override injects restart: unless-stopped for services without a
  policy, so apps come back after reboot
- GET /deployment-jobs gains an app_id filter
- frontend: shared DeploymentJobProgress component (timeout + visible poll
  errors) used by the Templates InstallModal and the service Logs tab;
  repo wizard navigates to the Logs tab to watch the deploy live; install
  success lands on the Logs tab; failure toasts surface real server errors
2026-07-21 22:57:33 -04:00
Juan DenisandClaude Opus 4.8 59a64e4337 serverkit-localkit: advertise sync kinds + site kind (plan 26)
The LocalKit contract for per-kind sync (plan 26): /pair now returns a `kinds`
array (site kinds this extension can sync) and every /sites entry carries a
`kind`. Both keep old-server ↔ new-client safe — a LocalKit client defaults an
absent kind to "wordpress" and gates its php sync/import UI on the `kinds`
advertisement.

KINDS is ['wordpress'] today: serverkit-wordpress is this extension's only site
backend, so advertising php would be untrue. Add 'php' here in lockstep with a
php-stack backend that _resolve_site / _install_code / _import_db can dispatch
to; the client already speaks the php protocol (engine-native db, app/ code).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 22:57:33 -04:00
Juan Denis f1c5ff1688 Merge pull request #77 from jhd3197/dev
Test Sandbox, resumable LocalKit sync, and installer hardening
v1.7.45
2026-07-21 00:24:07 -04:00
github-actions[bot] db8d3126a1 chore: bump version to 1.7.45 [skip ci] 2026-07-21 03:52:15 +00:00
Juan Denis baebb92d3a feat(registry): default to serverkit.ai index and ship logo fields
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.
2026-07-20 23:46:02 -04:00
Juan Denis 99ba978731 fix(deployments): add target_server_name property to DeploymentJob
DeploymentRunner.run() logged self.job.target_server_name, but the model
only exposed that value as a to_dict() key, so every deploy job crashed
with AttributeError at its first step and was marked failed before any
work ran. Add a target_server_name property and reuse it in to_dict().
2026-07-20 22:38:30 -04:00
github-actions[bot] e2887342a8 chore: bump version to 1.7.44 [skip ci] 2026-07-21 02:29:07 +00:00
Juan Denis 5e8012de61 Add test sandbox and harden install scripts
Introduces a full-stack Test Sandbox feature to run distro matrix tests in Docker from the UI: new backend API/service, sandbox_runs model + migration, run/log endpoints, and a new frontend page with mode selection, run history, live polling, cancel, and per-distro logs.

Also adds ARM64 scripts CI coverage (ubuntu-24.04-arm) and strengthens install/test automation for minimal images and overlay rebuilds: early curl/bootstrap index refresh, stricter venv validation, release-venv glibc fallback, Node reprovisioning on source fallback, rsync --delete overlay behavior, npm bootstrap in VM install, Windows tar pinning in PowerShell harness, plus updated test docs and distro coverage notes.
2026-07-20 22:26:19 -04:00
Juan Denis e79fb1cf11 Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-07-20 22:25:56 -04:00
Juan DenisandClaude Opus 4.8 4411a3595f localkit ext: sync v2 chunked push + resumable ranged pulls
Sync v1 POSTs the whole payload in one request, so it is capped by the
panel's 100MB MAX_CONTENT_LENGTH and a drop at 99% means starting over.
v2 makes every request one 8 MiB chunk, which lifts the cap and makes a
retry cost only what was actually lost.

- POST /push/<kind>/init: describes the transfer, reaps stale ones, and
  returns the offsets already confirmed for an identical payload — that
  array IS resume.
- PUT /push/<kind>/chunk: per-chunk sha256, idempotent re-sends, marker
  written after the bytes so a crash costs one chunk, not a corrupt file.
- POST /push/<kind>/finish: verifies the whole-file sha256, THEN runs the
  processing. Missing chunks are a 409 with the received set (resumable);
  a hash mismatch discards. Because processing only ever runs on verified
  bytes, an abandoned transfer cannot leave half-applied state.
- push/code and push/db v1 now delegate to _install_code/_import_db, so
  v1 and v2 share exactly one processing path and the safe-extract policy
  stays mandatory in both.
- Pulls accept ?session= and serve with conditional=True, giving Range /
  If-Range. The session pins one materialized export: pull/db and
  pull/code build their payload per request, and ranges taken from two
  different mysqldump/tar runs would assemble into neither. No session =
  v1 export-and-delete, unchanged.

A transfer is a directory under the temp dir with per-chunk marker files
rather than a shared JSON list, so concurrent chunk writes need no lock.
Transfer ids are validated against uuid4().hex before touching a path.

FEATURES gains 'sync-v2'; older clients keep the v1 routes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 15:35:06 -04:00
Juan DenisandClaude Opus 4.8 8670ebae45 localkit ext: GET /pull/code, pair features, php_version on /sites
The download half of code sync — the missing piece for LocalKit's
"import this remote site as a new local site" flow (LocalKit plan 18).

- GET /pull/code streams a tar.gz of the site's wp-content, built inside
  the container with `docker exec tar czf -` so permissions and structure
  survive. Entries are prefixed `wp-content/`, the same format POST
  /push/code accepts, so both directions share one archive shape. GNU
  tar's exit 1 ("file changed as we read it") is tolerated — a live site
  writing uploads mid-archive is normal.
- GET /pair now reports a `features` array so clients gate their UI on
  capability instead of discovering a 404 mid-operation.
- GET /sites gains `php_version` (regexed off the compose image tag, not
  a per-site container shell) and an explicit `site_url`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:39:12 -04:00
Juan Denis 4d52f8476e Merge pull request #76 from jhd3197/dev
Harden the auth layer and slim the core down to standalone extensions
v1.7.43
2026-07-20 02:44:23 -04:00
github-actions[bot] aa4fb76b9e chore: bump version to 1.7.43 [skip ci] 2026-07-20 06:16:58 +00:00
Juan Denis 7104713b8e Add pip-audit CI gate and bump click
Make dependency scanning reliable in CI by adding a blocking pip-audit job for the backend and test harness requirements, while keeping Safety as a non-blocking advisory check because third-party service failures should not block merges. Also bump `click` to `8.3.3` to address the reported vulnerability in a directly pinned dependency.
2026-07-20 02:16:47 -04:00
github-actions[bot] d42bd91ea8 chore: bump version to 1.7.42 [skip ci] 2026-07-20 05:58:40 +00:00
Juan Denis 82f88b964f Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-07-20 01:58:27 -04:00
Juan DenisandClaude Opus 4.8 6ac61a5e28 test: lower the test-count ratchet for the extension extraction
Extracting the seven builtins to their own repos took their suites with them —
2722 collected tests down to 2430. The ratchet fired exactly as designed, since
a 292-test drop is indistinguishable from the silent loss it was built to catch;
lowering the floor is the explicit statement that this removal was intentional.

Those tests are not gone, they moved: each extension's suite now lives in its
own repo and runs in that repo's CI. The four panel-core cases rescued from
test_tramo_extension.py stay here in test_tramo_panel_core.py.

This should have been part of the extraction commit, which is where the script's
own guidance points. It lands separately because the extraction was already
pushed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:56:17 -04:00
github-actions[bot] 195dd5a8ed chore: bump version to 1.7.41 [skip ci] 2026-07-20 05:44:52 +00:00
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 adf70b4df0 docs: correct stale architecture facts in the agent instructions
File counts were roughly a third of reality (api 36 vs 90, services 48 vs 165,
models 15 vs 80, pages 29 vs 60), so both files understated the codebase enough
to mislead anyone sizing a change.

Two claims were wrong rather than stale. `db.create_all()` is never called —
schema is 74 Alembic migrations, and describing tables as auto-created invites a
model change that ships without one. And the Production Build section
recommended GeventWebSocket workers while the deployment note 25 lines above
warned against exactly that; the Dockerfile and systemd unit both use a plain
threaded worker.

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 9156b88097 docs: add the missing MIT LICENSE file
The README has carried an MIT badge and a link to ./LICENSE since the first
commit, but the file was never added — the badge 404'd and the project had no
license text, which legally means all rights reserved regardless of intent.

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 f4c93e8fe3 docs(roadmap): mark the core feature-complete
Every planned core phase has shipped. New functionality now arrives as
installable extensions through the Marketplace, and core work is limited to
stability fixes and UI polish. Reframes the "what's next" list accordingly and
points feature requests at the extension SDK with an `extension-idea` label,
keeping the phase history as the record of how the core was built.

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 7d22c2cbcf feat(ui): move env var creation into a modal, always store as secret
Adding a variable happened through an inline row that competed with the list
for attention; it now opens a dedicated modal that resets its own state on open.

Drops the is_secret toggle. Env vars are stored as secrets unconditionally, so
offering a choice implied a public mode that does not exist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:16:39 -04:00
Juan DenisandClaude Opus 4.8 bc3711f32d fix(ui): stop detail-page tabs jumping, unify them as segmented bars
PageTopbar measured overflow against a region that grew and shrank with the
page title, so a title change shoved the tabs sideways on first paint. The nav
is now the flex-fill measurement region, right-aligned so the title can resize
without moving the tabs, with overflow hidden to suppress the pre-measure flash.

The tab strips themselves were a transparent underline that dissolved into the
page background. Service detail, applications and WordPress detail now share one
treatment — pills inside a bordered surface, active pill filled — so every
detail page's nav reads the same. Service detail also gets a consistent vertical
rhythm between the KPI band, the info grid and the full-width panels, fixing the
KPI strip being glued to the section below it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:16:39 -04:00
Juan DenisandClaude Opus 4.8 a6867c8757 feat(scripts): scaffold sub-path routes in new-extension
Generated manifests now contribute both `<slug>` and `<slug>/:tab`, forwarding
every sub-path to the same component. Adding a tab to a scaffolded extension
becomes a one-line change to TABS in frontend/index.jsx instead of a manifest
edit plus a route entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:16:25 -04:00
Juan DenisandClaude Opus 4.8 0e7bf511f6 feat(extensions): add serverkit-localkit builtin
Backend-only builtin that pairs the panel with LocalKit, the local WordPress
companion app. Ships as a marketplace one-click builtin (no frontend surface),
so it appears in the Marketplace but adds nothing to the sidebar until a user
installs it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:16:25 -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 8a9aa03d8f chore(frontend): drop unused eslint-disable in capture-skeletons script
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:23:59 -04:00
Juan DenisandClaude Opus 4.8 3333ede87b feat(frontend): runtime self-captured skeleton bones (plan 50 phase 3)
Add useSelfCapturedBones(key, {ready}): after a page's real content renders it
snapshots the region into bones (shared src/utils/snapshotBones.js) and caches
them in localStorage under sk-bones:<key>, versioned so stale layouts are purged;
the next load replays a pixel-accurate skeleton. First-ever visit has no cache and
falls back to the composed skeleton — no regression.

SkeletonBoundary now forwards its ref to the measured container, and the SSL page
is wired as the demonstration. The capture script (phase 2) is DRY'd to inject the
same shared snapshotBones source rather than an inline copy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:20:19 -04:00
Juan DenisandClaude Opus 4.8 67142efd13 feat(frontend): dev-time skeleton bone capture + bones replay (plan 50 phase 2)
Add an accuracy path for skeletons: a dev-only `npm run capture:skeletons` tool
(scripts/capture-skeletons.mjs) drives a headless, logged-in dev session, measures
designated page regions into boneyard-format bones ({x,y,w,h,r}; x/w %, y/h px),
and bakes them to frontend/src/skeletons/*.json as static assets. Nothing browser-
or capture-related ships in the product.

SkeletonBoundary gains an optional `bones` prop that replays a captured layout via
a new `renderBones` mapper — SCSS-classed absolutely-positioned placeholders reusing
the shared .skeleton look (shimmer + reduced-motion). boneyard-js is added as a
devDependency for parity/format reference (never imported by shipped code). Ships a
README runbook and an example.json documenting the format.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:15:46 -04:00
Juan DenisandClaude Opus 4.8 c687ea4dc4 feat(frontend): consolidate skeletons + overlay SkeletonBoundary (plan 50 phase 1)
Deprecate the Tailwind `ui/skeleton` shim (deleted; its two call sites moved to
the SCSS `Skeleton` primitive with no raw Tailwind sizing classes) and add
`SkeletonBoundary`, which keeps real content in flow and paints the skeleton over
its exact box while loading — inheriting the real dimensions so there is no
cumulative layout shift when data arrives. Falls back to intrinsic size when the
data-gated children are still absent (first load), overlays once content is
present (refresh).

Migrated the worst hand-guessed loaders through the boundary: SSL certificates,
WordPress list + detail page, the WordPress activity feed, and all 11 WordPress
detail tabs. Skeleton shimmer now honors prefers-reduced-motion across the legacy
wp-* skeleton families too (specificity-proof override), and loading regions set
aria-busy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:11:55 -04:00
Juan DenisandClaude Opus 4.8 d1589024e3 test(analytics): bump test-count ratchet for the analytics extension
Full backend suite green (2669 passed, 53 skipped). serverkit-analytics adds 83
proving tests (+ 2 parametrized manifest-hygiene cases). Ratchet 2637 -> 2722.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 03:15:06 -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 7bc323b41b feat(analytics): log ingestion + optional geo (Phase 6)
- log_ingest_service.py: generalized apache/nginx combined-log parser (skips
  assets, non-GET, and bots so log hits approximate real pageviews). Incremental
  per-site cursors — file sources use (inode, byte_offset) with rotation/
  truncation reset; docker sources use `docker logs --since <last_ts>` with a
  ts-based boundary dedup. Events tagged source='log' so JS vs log stay
  comparable. run_log_tail (scheduled) + ingest_site.
- geo.py: optional country-only GeoLite2 lookup, off by default, cached reader,
  returns None on any miss (no package / no DB / private IP). Wired into JS
  build_event and log ingestion; no IP is ever stored.
- analytics.py: admin POST /sites/<id>/ingest-logs (ops/testing trigger).
- 12 new tests (parser, asset/bot/non-GET filter, log events, since-dedup, file
  cursor advance, disabled skip, run_log_tail + global toggle, endpoint, geo
  off/no-db, country null default). 83 total.

The realtime Socket.IO namespace is intentionally deferred (a plan fast-follow);
the Realtime tab already polls.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 02:44:58 -04:00
Juan DenisandClaude Opus 4.8 d6e8c737dc feat(analytics): WordPress + nginx tracker wiring (Phase 5)
- wp_integration.py: writes a ServerKit Analytics mu-plugin into a managed WP
  site. Docker sites (the default template uses a named volume, not a bind mount)
  get the file via `docker exec tee` into /var/www/html/wp-content/mu-plugins;
  bare-metal sites via a host write + www-data chown. Idempotent, remove-on-
  disable, feature-detects serverkit-wordpress, remove_all_injections on
  uninstall.
- nginx_integration.py: injects an idempotent, marker-guarded sub_filter block
  (rewrites </body>) into a managed app's vhost, following the WAF include-
  injection precedent. Validates with nginx -t and REVERTS the file if the test
  fails (never leaves nginx broken); reloads (never restarts).
- analytics.py: admin POST/DELETE /sites/<id>/inject/{wordpress,nginx}.
- AnalyticsTab.jsx: feature-detects the extension and shows a banner linking to
  the persistent dashboard, keeping the on-demand log view as a complement.
- 18 new tests (mu-plugin PHP, WP inject/idempotent/docker-exec/remove/
  remove-all, wordpress_available, nginx block insert/strip/idempotent/revert-
  on-invalid/remove, admin-gated endpoints). 71 total. lint + build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 02:38:39 -04:00
Juan DenisandClaude Opus 4.8 0def464703 feat(analytics): dashboard frontend, six tabs (Phase 4)
- AnalyticsPage: route-driven tab shell (Overview/Pages/Referrers/Devices/
  Realtime/Sites), loads the site list, topbar site picker for report tabs,
  zero-sites empty state.
- OverviewTab: KPI tiles + recharts area chart (visitors+pageviews) + top
  pages/referrers DataTables + live 30-min counter.
- PagesTab / ReferrersTab: range picker + sorted DataTable.
- DevicesTab: device/browser/OS/country tables in a responsive grid.
- RealtimeTab: active-visitor + pageview counters, minutes selector, recent-hit
  table, 15s polling.
- SitesTab: site CRUD (Modal add form, ConfirmDialog rotate/delete, enable
  toggle) + tracking-snippet modal (copy + outbound-link opt-in).
- RangePicker + format utils; isolated analytics.scss (CSS custom props +
  neutral rgba, namespaced under .analytics-page).
- Synced into frontend/src/plugins/serverkit-analytics. npm run lint (0 errors)
  and npm run build both green; sync --check clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 02:25:41 -04:00
Juan DenisandClaude Opus 4.8 b61e1d5c18 feat(analytics): tracker JS + snippet generator (Phase 3)
- backend/tracker/sk.js: real cookieless tracker (~3.1 KB min) — pageview,
  external referrer, Navigation-Timing load ms, screen-class bucket, language;
  SPA nav via history patch + popstate; opt-in outbound/download click tracking;
  honors DNT; navigator.sendBeacon (text/plain, no preflight) with fetch
  keepalive fallback; fails silently.
- scripts/build-analytics-tracker.mjs: dependency-free conservative minifier
  (strip comments, trim, keep newlines) → backend/tracker/sk.min.js, with a
  --check drift gate. Built artifact checked in (3147 bytes, under the 4 KB cap).
- analytics.py: GET /sites/<id>/snippet returns the ready-to-paste <script>
  snippet (site_key + panel tracker URL, optional data-outlinks).
- dev/test-tracker.html: manual smoke page (load tracker, fire SPA nav/outlinks).
- 6 new tests (artifact bounded + comment-free, real build served, snippet +
  outlinks flag + 404). 59 total.

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