2318 Commits
Author SHA1 Message Date
Juan Denis 339b5febe3 Merge pull request #124 from jhd3197/dev
Update the panel from the About tab, and stop shipping vite apps that host-block their own visitors
v1.9.17
2026-08-31 09:21:43 -04:00
Juan Denis b966f57533 Merge branch 'main' into dev 2026-08-31 09:06:19 -04:00
github-actions[bot] 846c52d665 chore: bump version to 1.9.17 [skip ci] 2026-08-31 12:41:00 +00:00
Juan Denis d01f7a2ee9 Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-08-31 08:34:51 -04:00
Juan DenisandClaude Opus 5 ebb53d8fcc fix(buildpack): serve vite builds with a host-agnostic static server
`vite preview` host-checks every request against `preview.allowedHosts`
and blocks any domain the repo's vite config doesn't list -- the deployed
container answered localhost probes fine and refused every request the
panel proxy forwarded ("Blocked request. This host ... is not allowed").
Preview is a dev convenience, not a production server, per vite's own docs.

The vite default start command is now `serve -s dist -l tcp://0.0.0.0:4173`
(SPA fallback, no host check); the generator preinstalls serve globally
when the start command needs it, since the repo never declares it. A
repo-authored start script still wins and gets no serve install.

The @docker_builds leg now sends a foreign `Host:` header to every booted
container -- the localhost-only probe was exactly the blind spot that let
this class through.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 08:31:34 -04:00
github-actions[bot] 8c915794c2 chore: bump version to 1.9.16 [skip ci] 2026-08-31 12:27:30 +00:00
Juan DenisandClaude Fable 5 fc89a979ef i18n: translate panel self-update strings in all 15 remaining locales
The self-update feature commit added the 10 new app.aboutTab keys to en
and es only; every other locale fell back to English in an otherwise
fully translated About tab. Add ar, bn, de, fr, id, it, ko, pl, pt, ru,
th, tr, vi, zh-Hans and zh-Hant, matching each file's existing register
(Sie/vous/formal).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 08:17:47 -04:00
Juan DenisandClaude Fable 5 160bf6dab7 feat(panel): one-click self-update from the About tab
The About tab could only detect a new release and link to it; applying the
update meant SSH and scripts/update.sh. Wire that script to the UI:

- panel_update_service launches update.sh via systemd-run in its own
  transient unit — a plain subprocess child would be killed when the
  updater restarts the panel's own systemd unit. Status is stateless
  (transient-unit state + the updater's own update-*.log) so it survives
  the mid-update backend restart.
- Capability gates keep the button honest: root systemd installs only.
  Docker installs get the docker compose pull instructions; dev servers
  and hosts without systemd-run get a reason instead of a button.
- GET/POST /api/v1/system/update (admin_required, confirm-gated,
  audited). API surface doc regenerated.
- About tab gains Update Now with inline confirm, live log tail while
  the update runs, a restarting phase that tolerates the panel being
  briefly unreachable, and success/rollback/timeout outcomes. New
  aboutTab locale keys in en + es.

The heavy lifting (blue/green switch, migrations, health check,
automatic rollback) stays in update.sh, which is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 08:09:52 -04:00
Juan Denis a96fab2f7d Merge pull request #123 from jhd3197/dev
Generated node deploys install their build tooling, and CI now proves it
v1.9.15
2026-08-31 02:27:57 -04:00
github-actions[bot] 4b2e7ecbb5 chore: bump version to 1.9.15 [skip ci] 2026-08-31 06:11:32 +00:00
Juan DenisandClaude Opus 5 20bfcd4947 test(buildpack): run generated Dockerfiles through the real docker daemon
test_buildpacks.py asserts on Dockerfile TEXT, which cannot see a bug that
only fails at build or boot time -- the `--omit=dev` dead fallback shipped
`vite: not found` to a user's deploy. New @docker_builds leg (gated on
SERVERKIT_DOCKER_BUILDS=1, sibling of @real_binaries) runs the full path
per stack: detect() -> generate_dockerfile() -> `docker build` ->
`docker run` -> HTTP 200. Fixtures: vite (the regression), express with no
build step (prod-only install must still boot), flask, static. Wired as a
backend-ci job beside real-binaries.

Writing the flask fixture surfaced the same dead-at-boot class one layer
up: detection invents `gunicorn app:app` / `uvicorn main:app` as the start
command but never installed the server, so a repo that doesn't declare
gunicorn built green and died at boot. The build command now installs the
invented server (a no-op when the repo already pins it); Procfile-authored
start commands remain the author's contract.

All four stacks verified locally: build + boot + HTTP 200 in 53s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 01:58:57 -04:00
Juan DenisandClaude Opus 5 4c95a7da9f fix(buildpack): keep devDependencies when a node app has a build step
The generated node Dockerfile ran `npm install --omit=dev || npm install`.
`--omit=dev` exits 0, so the fallback never fired and `npm run build` died
with `vite: not found` -- build tooling (vite, tsc, webpack) is a
devDependency, and so is the server behind preview-style start commands.

Install the full dependency set whenever a build command exists, and put
node_modules/.bin on PATH so locally installed CLIs resolve in both RUN and
CMD. Apps with no build step still install production-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 01:48:02 -04:00
Juan Denis 2ef2d775a0 Merge pull request #122 from jhd3197/dev
The last four bundled flagships move out to their own repos
v1.9.14
2026-08-31 01:25:10 -04:00
github-actions[bot] 696dde66d8 chore: bump version to 1.9.14 [skip ci] 2026-08-31 05:00:01 +00:00
Juan DenisandClaude Opus 5 e181ae7f44 fix(authz census): count extension routes from a source git tracks
a31cc5d3 widened the jwt-only census to app/plugins and shipped a ceiling
of 611 measured on a dev box. CI measured 591 and went red: most
app/plugins/ live copies are GITIGNORED, so the count depended on which
extensions a machine happened to have installed. That is the same
machine-dependence the builtin-copy drift census exists to prevent, and
this walked straight into it.

Count builtin-extensions/ instead -- the source, pinned equal to the live
copy by test_builtin_extension_drift.py, and tracked. Filter to
git-tracked files as well, because extractions leave stale ignored
directories behind (cloudflare-ops, git, localkit after plan 52) that
would otherwise be counted on some working copies but not others.

Stable population: 608 (35 of them extension routes across
cloud-provision, remote-access and status). Also regenerates
docs/MIGRATION_INVENTORY.md, whose row is derived from this ceiling, and
points the CLAUDE.md / AGENTS.md notes at the scope actually scanned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:53:15 -04:00
Juan DenisandClaude Opus 5 b764418b09 fix(status): re-sync the pre-bundled frontend manifest
30b780f4 corrected the serverkit-status description in two of its three
copies. The extension's manifest also exists at
frontend/src/plugins/serverkit-status/plugin.json, which is generated from
the builtin-extensions/ source by scripts/sync-builtin-frontends.mjs --
so Extensions CI went red on the builtin-frontend drift check, and
bump-version failed downstream of it rather than on its own.

Regenerated with the sync script rather than hand-edited; the diff is the
one description line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:44:04 -04:00
Juan Denis 1cc2a1b142 Merge remote-tracking branch 'origin/dev' into dev 2026-08-31 00:42:09 -04:00
Juan DenisandClaude Opus 5 a31cc5d3a8 test(authz): ratchet plugin routes too, and write down why not to sweep them
PR #122 review suggested converting the serverkit-status routes from
@jwt_required() + an inline is_admin check to rbac decorators. Declined:
plan 76's recorded decision (2026-08-19) is that JWT-only is the
deliberate default, because every rbac decorator authenticates through
auth_required() and therefore GRANTS API-key callers access -- an
expansion of the authenticated surface, never a mechanical sweep.

Two gaps let that suggestion look uncontroversial:

1. jwt_only_census.py scanned only app/api, so the extension's 15 bare
   @jwt_required() routes were invisible to the ratchet. It now scans
   app/plugins as well; ceiling 577 -> 611 for the newly visible
   population (a scope widening, not a regression). Only the live copy
   is counted -- builtin-extensions/ is the same code twice and the
   drift guard pins them equal.

2. Neither CLAUDE.md nor AGENTS.md said the inline shape is already a
   valid explicit decision (test_route_authz_static counts .is_admin via
   ATTRIBUTE_GATES), or that ~91 routes across 21 files use it. Both now
   do, with the plan 76 quote and a note that automated reviewers raise
   this routinely.

Audited 2026-08-31 by AST over app/api + app/plugins + builtin-extensions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:41:54 -04:00
Juan DenisandClaude Opus 5 30b780f4e4 fix(status): a null JSON body no longer 500s the status-page routes
PR #122 review. Four routes passed request.get_json() straight into the
service, which does `if field in data` and raises TypeError on None.

Copilot reported the trigger as "empty/invalid JSON bodies"; measured on
Flask 3.1.3 that is wrong — an empty body is already a 400 and a wrong
content-type a 415, both raised by Flask before the view runs. The body
that actually reaches the view is literal `null`, which is valid JSON and
decodes to None. Same fix, accurate reason.

Also corrects plugin.json, which claimed installing the extension serves
the public /status/<slug> pages; the registry catalog says that page
stays core, and the two contradicted each other.

Both copies edited — app/plugins/ is the live copy the loader imports,
builtin-extensions/ is the source — and a test pins that they agree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:34:20 -04:00
github-actions[bot] 41405c1257 chore: bump version to 1.9.13 [skip ci] 2026-08-31 04:21:47 +00:00
Juan Denis bcf53f3e84 Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-08-31 00:11:38 -04:00
Juan DenisandClaude Opus 5 f724a1fbca fix(notifications): escape interpolated values in Telegram alerts
send_telegram posts with parse_mode='HTML' but interpolated the hostname,
alert type, message, value and threshold straight into the body. Telegram
then parses those as markup, so a message as ordinary as

    Disk usage > 90% on /var & /tmp

is rejected with "can't parse entities" and the entire notification is
dropped — precisely when something is already wrong on the box. A value
containing <b> could also inject markup into the rendered alert.

Escape every substituted value with html.escape(quote=False); the
template's own tags are untouched.

Proving tests pinned against the unfixed service first: the three
escaping cases fail without this change, the literal-markup case passes,
so they test the behaviour and not the implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:07:47 -04:00
Juan DenisandClaude Opus 5 48871b07e6 fix(dev): dev.sh notices a new requirement instead of booting into ImportError
The venv check probed four imports (dotenv, flask, flask_socketio,
sqlalchemy). Those catch a missing or broken venv, but they are blind to
the common case: a dependency is added to requirements.txt, all four
imports still succeed, dev.sh reports nothing, and the backend dies on
the fifth import at startup. That has derailed several sessions.

Stamp a sha256 of requirements.txt into the venv after a successful
install and compare on each start, so drift triggers a sync. The import
probe stays as the broken-venv check; the hash covers what it cannot see.

Guards: the hash is computed with the venv's own python (no dependency on
sha256sum being installed), an unreadable requirements.txt yields an
empty hash and leaves a working venv alone rather than reinstalling every
start, and the marker is written only after a successful install so a
failed one retries.

Verified by driving the decision logic with pip stubbed: no marker →
install, marker current → skip, requirement appended → install, settled →
skip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:05:47 -04:00
github-actions[bot] d39f2a8d9d chore: bump version to 1.9.12 [skip ci] 2026-08-31 03:54:38 +00:00
Juan DenisandClaude Opus 5 12c863b230 fix(templates): repo deploys send the build inputs the template declares
The deploy drawer posted only url/branch/app_type/build_method/port, so a
repo template's `build_command`, `start_command` and `dockerfile_path`
were decorative — declared in the YAML, read by nobody. The endpoint has
always accepted custom_build_cmd / custom_start_cmd / dockerfile_path;
only the caller was dropping them.

The visible cost was nixpacks builds: with no start command it falls back
to `npm start`, so any repo whose package.json has no `start` script
produced an image that built fine and could not boot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 23:47:14 -04:00
Juan Denis 37cf8b6e84 Merge plan 52 cutover: the published extensions leave the tree
localkit, email, cloudflare-ops and git are now published, signed and in
the registry index, so the panel no longer needs in-tree copies of them.
This removes ~29k lines that existed in two places at once and flips the
bundled registry so they install from the catalog like any other
extension.

Unblocked by the operator publishing all nine repos (2026-08-30); the two
registry-mirror tests that plan 83 expected to stay red now pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

# Conflicts:
#	builtin-extensions/serverkit-cloudflare-ops/frontend/components/StoragePanel.jsx
#	frontend/src/plugins/serverkit-cloudflare-ops/components/StoragePanel.jsx
2026-08-30 22:13:01 -04:00
Juan DenisandClaude Opus 5 7b9eb43a9a test: lower the count floor for the extensions that left the tree
The cutover removes four builtin extensions, so collection legitimately
drops. Verified against dev by diffing collected node ids rather than
trusting the delta:

  -7  test_extension_manifests  the 7 plugin.json files of the four
                                departing extensions (builtin + frontend)
  -4  test_builtin_extension_drift  anchor swap, email (7 py files) ->
                                    status (3)
   0  test_files_rbac / *_extraction  param-id churn only

No feature lost its proving tests. 4799 -> 4791.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 22:12:05 -04:00
Juan DenisandClaude Opus 5 18767c2244 test(drift): re-anchor the builtin-copy guard on serverkit-status
serverkit-email's live copy under app/plugins/ was the only git-TRACKED
one, which is what kept test_builtin_extension_drift non-vacuous on a
fresh clone. Plan 52's cutover deletes it, so on CI the guard had zero
pairs to compare and test_drift_check_is_not_vacuous fired -- correctly.
It still passed on a dev box, where the gitignored live copies exist, so
the failure only reproduces in a clean checkout.

Track serverkit-status's live copy instead (verified byte-identical to
builtin-extensions/serverkit-status/backend), un-ignore just that path,
and update both docstrings to name the new anchor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 22:07:53 -04:00
Juan Denis d597173ed0 Merge remote-tracking branch 'origin/main' into dev 2026-08-30 21:48:27 -04:00
Juan Denis 3ff85b1598 Merge pull request #121 from jhd3197/dev
Split the security suite and the last built-ins into extensions
v1.9.11
2026-08-30 21:30:45 -04:00
Juan DenisandClaude Fable 5 cea1770562 fix(review): posture installs survive unchecked overlaps; translatable storage empty-states
PR #121 review follow-ups. The setup summary de-duplicated the install
queue with recommendations winning, so a security-posture extension
that also appeared as an unchecked recommendation was silently dropped
— the posture half now wins regardless of checkbox state. The
Cloudflare storage panel templated English around a translated title
(`No ${title.toLowerCase()} yet.`); each section now passes a fully
translated emptyHint instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 20:25:17 -04:00
Juan Denis 4b65c1e638 Merge remote-tracking branch 'origin/main' into dev 2026-08-30 20:22:18 -04:00
github-actions[bot] 3df4fb8446 chore: bump version to 1.9.11 [skip ci] 2026-08-30 20:32:52 +00:00
Juan Denis d764f50809 Merge branch 'dev' into feature/plan52-cutover
# Conflicts:
#	backend/app/data/registry_index.json
#	frontend/src/plugins/serverkit-git/plugin.json
2026-08-30 16:24:27 -04:00
Juan DenisandClaude Fable 5 2c3b63b1f6 chore: re-vendor the bundled registry index from the published catalog
The nine extracted extensions are now published, signed, and merged
into the serverkit-extensions main catalog; the bundled offline copy
picks up the five new security-suite entries and the source/sha256/
signature fields on localkit, email, cloudflare-ops, and git.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 16:23:48 -04:00
Juan DenisandClaude Fable 5 6234832c46 chore(preflight): gate builtin-frontend drift locally
The Extensions CI drift check (sync-builtin-frontends.mjs --check) had
no local counterpart, so a builtin-extensions/ manifest edit without a
re-sync reached CI unrun — exactly how the serverkit-git split went red
on the next PR. Same contract as the migration-inventory gate: a stale
copy is re-synced on the spot and flagged so the diff gets committed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 14:08:09 -04:00
Juan DenisandClaude Fable 5 91d464f871 chore: re-sync the pre-bundled serverkit-git plugin.json
The plan 52 Phase 6 git split updated the source manifest under
builtin-extensions/ (shell permission, entry_point, url_prefix,
min_panel_version) without running sync-builtin-frontends.mjs, so the
Extensions CI drift check went red on the next PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 14:00:50 -04:00
Juan DenisandClaude Fable 5 1b9f0061c7 fix(apps): name-clash errors point at the Recycle Bin for tombstones
A deleted app deliberately keeps reserving its name (Plan 70: a restore
must never collide), but every clash site said only "already exists" —
pointing the operator at an app that is in no list (GH report: delete an
app, then a same-name deploy fails with no way forward).

app_name_clash_error() now distinguishes a tombstone from a live row and
names the release path (restore or purge from Settings -> Recycle Bin);
all five clash sites go through it. Live-row wording is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 03:20:34 -04:00
Juan DenisandClaude Fable 5 d3e3564c7d fix(templates): surface validate-install errors in the deploy drawer
The API client throws on any non-2xx, so a 400 from validate-install
never reached the `!validation.valid` branch — and the catch only read
`err.data.error` (singular), while this endpoint returns an `errors`
array. The operator saw "Request failed (400)" instead of the actual
reason (e.g. the app name being reserved by the Recycle Bin).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 03:20:27 -04:00
Juan DenisandClaude Fable 5 aee50fea0c cutover: contracts follow the flagship departure
API surface regenerated (the flagship-seeded cloudflare zone routes no
longer boot with a lean panel); the frontend route contract gains an
explicit EXTRACTED_EXTENSION_PREFIXES excuse list for the /git, /email and
/cloudflare prefixes core clients probe fail-soft.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:57:57 -04:00
Juan DenisandClaude Fable 5 02967b0547 cutover(git): serverkit-git leaves the tree (plan 52 Phase 6)
builtin-extensions/serverkit-git + the tracked frontend live copy removed;
the bundled registry entry flips to bundled:false at v1.1.0 (the version
that carries the Gitea backend) with the local zip's sha256; the gated
auto-install (_gitea_was_used) moves to REGISTRY_GATED_SLUGS, leaving
GATED_BUILTIN_SLUGS empty with the machinery intact.

i18n: en.json regenerated (the app.git/app.email/app.cloudflare* namespaces
ride with their extensions, which carry en+es bundles; other languages are
recoverable from git history), all locales pruned, intl-door ratchet
tightens 123 -> 115.

Expected red on this branch: the two registry-mirror tests, until the four
repos are published + signed + re-vendored (see docs/plans/83).

OPERATOR: publish jhd3197/serverkit-git, sign, sync-registry-index.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:53:27 -04:00
Juan DenisandClaude Fable 5 665d6775e4 cutover(cloudflare-ops): the last in-place flagship leaves the tree (plan 52 Phase 2)
builtin-extensions/serverkit-cloudflare-ops + the tracked frontend live
copy removed; FLAGSHIP_SLUGS is now empty (machinery kept);
plugins-manifest.json empties; the bundled registry entry flips to
bundled:false with the local zip's sha256. cloudflare_ops_bridge resolves
the copy-installed package FIRST so a registry install works without the
builtin registration path.

Tests: new conftest.mount_sibling_extension mirrors a real install from
the sibling checkout (the _wp_support.ensure_plugin analogue) and a
cf_extension fixture forces a private app; the zone-settings and round2
suites mount it autouse (74/74 green), the extraction suite pins the
lean-panel shape, and the flagship-era onboarding tests prove the
machinery with a fabricated member instead.

OPERATOR: publish jhd3197/serverkit-cloudflare-ops, sign,
sync-registry-index.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:51:17 -04:00
Juan DenisandClaude Fable 5 43fec8030c cutover(email): serverkit-email leaves the tree (plan 52 Phase 2, D8)
builtin-extensions/serverkit-email + both tracked live copies removed; the
bundled registry entry flips to bundled:false with the local zip's sha256;
the gated auto-install (_email_was_configured) moves from
GATED_BUILTIN_SLUGS to REGISTRY_GATED_SLUGS so mail users still get it back
once the entry is published.

Panel-core tests re-home onto the sibling checkout via the new
conftest.sibling_extension_dir helper (skip cleanly when absent): the
extraction suite installs from the serverkit-email repo, the dovecot-probe
suite loads its module from there. All 39 affected tests green with the
sibling present.

OPERATOR: publish jhd3197/serverkit-email, sign, sync-registry-index.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:45:22 -04:00
Juan DenisandClaude Fable 5 4eda066264 cutover(localkit): serverkit-localkit leaves the tree (plan 52 Phase 2)
builtin-extensions/serverkit-localkit removed; the bundled registry entry
flips to bundled:false pointing at the (to-be-published) v0.2.0 release
asset with the sha256 of the locally built zip.

OPERATOR: publish jhd3197/serverkit-localkit, let its release workflow (or
update-registry.mjs) upsert + sign the registry entry, then run
scripts/sync-registry-index.sh - which replaces this hand-staged entry with
the published, signed one. Until then the two registry-mirror tests
(signature pin + published-index match) are red BY DESIGN on this branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:43:36 -04:00
Juan DenisandClaude Fable 5 b72c1d0014 docs: check off the extraction + security-suite roadmap items
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:40:10 -04:00
Juan DenisandClaude Fable 5 4be264ccd5 chore: migration inventory picks up the 511 controller-boundary count
Second regeneration - the first ran before the boundary baseline rewrite
landed, so it still measured 514.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:37:51 -04:00
Juan DenisandClaude Fable 5 2bdddd4d77 chore: regenerate ratchet inventories after the git split
Controller-boundary baseline drops the three api/security.py crossings that
moved to serverkit-image-scan; API surface sheds the 18 Gitea operations;
migration inventory regenerated by preflight; test-count floor raised
4735 -> 4799 (SERVERKIT_CLEAN_COLLECT).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:35:38 -04:00
Juan DenisandClaude Fable 5 db59ad2da6 feat(git)!: split the Gitea self-host half into the serverkit-git extension (plan 52 Phase 6)
The split point was clean: git_service.py L602-881 (Gitea install/
lifecycle/config) and 18 of git_bp's 32 routes (lifecycle + repo browsing
via GiteaAPIService) move into builtin-extensions/serverkit-git/backend/,
mounted back at the same /api/v1/git prefix when the extension is
installed. GiteaAPIService moves wholesale (its only consumer).

The deploy pipeline stays core untouched: clone/pull/deploy/webhook-verify
on GitService (all 5 core importers keep every method they call), the
webhook routes including the unauthenticated signature-verified receiver,
and the deployment routes. NginxService's gitea-config helpers stay core
as a deliberate two-speed seam the extension calls.

Upgrade parity: serverkit-git joins GATED_BUILTIN_SLUGS (auto-install only
when the panel-managed serverkit-gitea application exists) and
run_backend_acquisition now sweeps gated slugs too, so a panel that
installed the frontend-only extension picks the backend up once. The core
repo pickers keep three fail-soft getGitea* aliases that 404 into a clean
'not installed' state.

Proving suite test_git_split.py (7 tests); live smoke: builtin install
mounts all 18 routes. Render smoke green for the Git + Security pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:30:07 -04:00
Juan DenisandClaude Fable 5 53757bf96b test: drop re-homed security suites from the seed one-door baselines
test_malware_scan_job.py / test_security_scan_jobs.py moved to their
extension repos with the security split; the staleness guard rightly
flagged them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:27:10 -04:00
Juan DenisandClaude Fable 5 2e0330d84d refactor(git): relocate the Git page into the serverkit-git extension (plan 52 Phase 6, frontend half)
Same relocation pattern as email/cloudflare-ops: pages/Git.jsx (1920 lines),
its SCSS partial (with the %sk-state placeholder inlined - @extend cannot
cross compile units) and a new Gitea-only API client move into
builtin-extensions/serverkit-git/frontend/. The page's two halves now show
their seam: Gitea self-host calls go through the extension's own client;
webhook + deployment calls stay on the core api object (they ARE the deploy
pipeline and exist on every panel).

Core files.js sheds the Gitea-half methods but keeps the three fail-soft
getGitea* aliases the app-creation repo pickers probe (they 404 and degrade
to 'not installed' when the extension is absent). serverkit-sdk adds the
ds/status helpers (statusKind & co). pages/ ratchets tighten: raw api calls
431->405, toast extractions 264->218.

Frontend build green, lint fully green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 14:04:30 -04:00