plan 75 §B1. Two helpers did overlapping jobs with different semantics, and the
one that says nothing about privilege sat one autocomplete from the one that
does. nginx_advanced_service reached for it, so `nginx -t` ran unprivileged and
by bare name — it could neither read root-owned config nor find /usr/sbin/nginx,
and reported every config as invalid.
Renamed rather than folded. Folding into run_privileged(privileged=False) would
change sudo semantics at all six call sites — `sudo -n dig` fails where plain
dig worked, if sudoers does not permit it — and the failure this fixes was
choosing the wrong helper, not the helper's behaviour. The rename removes the
ambiguity with zero behavioural risk; the return type (dict, not
CompletedProcess) stays as callers expect.
`run_unprivileged(['nginx', '-t'])` reads as obviously wrong. `run_command` did
not. The old name was ambiguous in a second way: PythonService has an unrelated
method of the same name, which keeps it.
Six call sites, three test modules. Two patch targets were written as strings
('app.utils.system.run_command', setattr(mod, 'run_command')) so a word-boundary
rename skipped them and they would have failed at runtime, not import.
No alias: an alias leaves the second door open, which is the thing being closed.
run_command was never exported through plugins_sdk, so there is no public SDK
surface to break; a third-party extension importing it gets a loud ImportError
rather than silent misbehaviour. A guard test asserts the name stays gone.
417 tests green across monitor, ssl, dns, nginx, firewall and the guards.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The largest single move in the plan (#38). The entire WordPress backend leaves
core for the bundled, default-installed serverkit-wordpress extension (D4).
Moved (12 files → builtin-extensions/serverkit-wordpress/backend/, intra-extension
imports made relative, core imports stay absolute):
api/wordpress.py, api/wordpress_sites.py, api/environment_pipeline.py,
wordpress_service.py (2676 lines), git_wordpress_service, wordpress_env_service,
wordpress_plugin_library_service, wp_analytics/reports/security/update/vulnerability.
Wiring:
- Blueprints keep /api/v1/wordpress, /wordpress/projects and the /wordpress/pipelines
alias (D9) via new manifest `extra_blueprints` support (3 blueprints / 4 mounts).
- Flagship (D4): seeded active on every panel (fresh + upgrade), uninstallable,
never deletes tracked builtin/pre-bundled files; the `wordpress` module toggle is
retired (MODULES now empty) — the plugin status guard is the gate.
- Loadability: the dashed package app.plugins.serverkit-wordpress is registered
in-place via spec_from_file_location (no copy) — uniform across prod/dev/test.
- The 11 WP test files re-pointed through app.services.wordpress_bridge; the 6 lazy
core callers were re-pointed in the prior commit.
- _regenerate_frontend_manifest() is a no-op under testing so the flagship boot
seed can't clobber the checked-in plugins-manifest.json.
Deviations (documented, matching the Email precedent):
- WordPress *models* stay core — status_page/environment_activity/promotion_job hold
FKs into wordpress_sites, so relocation would be a breaking data migration.
- Event catalog + Fail2ban WP filter stay core as two-speed items (extension calls
them via core APIs); inverting adds risk without blocking the move.
- Frontend UI stays core this session (it lives in a TabGroupLayout — the tab-grouped
conversion Phase 1 deliberately deferred); the extension is backend-only, so there
is no route duplication. Frontend contribution is the scoped follow-up.
Verified: clean collection across all 1223 tests; 199 passed / 2 skipped / 0 failed
across the WP + plugin/extension + module + backup + core-caller parity set, incl.
the new tests/test_wordpress_extraction.py proving the move.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One-time operator setup that makes every managed subdomain HTTPS. New
SitesHttpsService.setup creates the *.<base> + <base> A records via a connected
DNS provider and issues a wildcard Let's Encrypt certificate over DNS-01,
reusing that provider's credentials — then flips sites_https_enabled on.
- AdvancedSSLService.issue_wildcard_cert now supports route53 (AWS env creds) as
well as cloudflare, takes an email, installs the matching certbot DNS plugin
best-effort, and returns the cert/key paths.
- SiteDomainService gains https_enabled / wildcard_cert_paths / covers. create_site
installs new subdomain sites at https when enabled, and _write_app_vhost serves
the wildcard cert on vhosts whose every domain it covers (custom domains keep
their own cert).
- NginxService.create_site takes optional ssl_cert/ssl_key and bakes a 443 block +
HTTP->HTTPS redirect (idempotent via full regen).
- sites_base_domain / server_public_ip / sites_https_enabled are now discoverable
settings. Admin endpoints GET/POST /admin/sites-https/{status,setup}, surfaced
in Settings → Site as a "Managed Sites Domain & HTTPS" panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>