serverkit update (update.sh) assumed the hybrid install shape (backend in a
host venv under systemd, frontend in Docker) and unconditionally ran
`source $INSTALL_DIR/venv/bin/activate`. On an all-Docker deployment (backend
AND frontend as compose services, no host venv) that crashes with
"venv/bin/activate: No such file or directory" -- and the preceding "stop
services" step leaves the box down with no restart.
Add is_docker_deployment() (compose file present + no host venv + a
serverkit-backend container exists) that routes to update_docker_compose():
in-place git sync, optional host frontend build when ./frontend/dist is
bind-mounted, `docker compose build` (the running version keeps serving until
the build succeeds), `up -d` (backend auto-migrates on boot), then a
container-health check. The hybrid venv/systemd/blue-green path is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replace rsync with tar-based copy to avoid missing rsync on Windows/Git Bash
- Exclude backend/venv, backend/.venv, backend/.venv-wsl, and broken WSL symlinks
- Exclude ./backups, backend/dev-data/backups, backend/instance/backups, and scripts/test/output
- Exclude scratch images (PNG/JPEG/JPG), logs, and tmp files from the tarball
- Pack with tar --transform so /opt/serverkit prefix is created without cross-device moves
- Reduce release tarball from 13 GB back to ~6 MB
- Fall back to cp + manual cleanup when rsync is unavailable
- Exclude .venv-wsl, test output, scratch images, and logs from the tarball
- Use tar --transform to avoid moving directories across filesystems
- Stop including root-level PNG/JPEG/JPG/log/tmp artifacts in releases
Make email handling case-insensitive (queries + model normalization) to prevent duplicate accounts. Add SiteDomainService.panel_origin() helper and use it across services to build public URLs (Gitea, WordPress, Roundcube) and Django ALLOWED_HOSTS. Add optional custom-domain support when creating WordPress sites and propagate domain to Roundcube install. Improve environment domain fallback to prefer panel/base domains over .localhost. Small Dockerfile healthcheck tweak (use 127.0.0.1). Update install/update/build scripts to always (re)create virtualenv locally for release installs. Remove unused frontend CreateSiteModal and expose domain field in WordPress page.
Introduce a GitHub Actions workflow (release-smoke.yml) that builds the release tarball on pushes/PRs to dev/main for relevant paths, catching release-script issues early. Adjust scripts/build-release.sh rsync excludes to explicitly target backups directories (root/backups and backend instance/dev-data backups) instead of a broader exclude, preventing accidental omission of needed files during release packaging.
The release workflow was publishing Docker assets and source but never
the serverkit-vX.Y.Z-linux-{amd64,arm64}.tar.gz files that install.sh,
update.sh, and 'serverkit update' expect to download. This caused release
updates to fail with 'Failed to download the release tarball.'
- Make scripts/build-release.sh honor BUILD_ARCH for CI matrix builds.
- Add a build-release job that builds tarballs on native amd64 and arm64
runners and uploads them as artifacts.
- Wire the release job to download those artifacts, include them in the
GitHub Release, and checksum them.
- Add the one-line installer to the release notes body.
The Go agent has moved to its own repository:
https://github.com/jhd3197/serverkit-agent
Changes:
- Delete agent/ directory and all source, build, and packaging files
- Delete .github/workflows/agent-ci.yml and agent-release.yml
- Delete scripts/test/agent-test.ps1 (agent now builds in separate repo)
- Remove committed dist/serverkit-agent-windows-amd64.exe build artifact
- Update docs/INSTALLATION.md and docs/README.md links to point to the new repo
- Add migration notes to docs/AGENT_AUDIT.md, docs/REMOTE_ACCESS_ROADMAP.md,
SECURITY_AUDIT.md, and docs/plans/AGENT_FLEET_PHASES_3-4.md
Panel agent API endpoints under /api/v1/servers/agent/ and /api/v1/agent/
are retained — they are the control-plane contract with the standalone agent.
Verified:
- backend: pytest (371 passed, 12 skipped)
- frontend: npm run lint (0 errors)
KIMI's optional-SSL commit hardened ssl_protocols/ssl_ciphers per-vhost but
left the system nginx.conf http{} defaults untouched, so the default server
and any non-ServerKit vhost could still negotiate TLS 1.0/1.1 and weak ciphers
(the scan's "insecure TLS versions" / "weak ciphers in TLS 1.2" findings).
- install.sh: harden_global_tls() rewrites (or injects) ssl_protocols and
ssl_ciphers in /etc/nginx/nginx.conf to TLS1.2/1.3 + AEAD-only. Editing the
line in place avoids nginx's duplicate-directive error on Debian/Ubuntu,
which already set ssl_protocols in http{}.
- update.sh: re-applies the floor so existing installs harden on upgrade too.
- Persist the panel domain to /etc/serverkit/panel-domain; update.sh recovers
it from there (or the live cert path) instead of the commented-out .env
SERVERKIT_PUBLIC_URL grep, which never matched -> secure installs were
getting the YOUR_DOMAIN placeholder re-applied and nginx -t failing on update.
- update.sh now degrades to the HTTP config when a secure cert is missing,
rather than leaving nginx unable to reload.
Global TLS settings are inert in HTTP-only mode, so optional SSL is unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add secure nginx config with TLS 1.2+, HSTS preload, CSP, and security headers
- Add insecure HTTP fallback config for users who cannot or do not want HTTPS
- Make installer best-effort SSL: try Let's Encrypt, fall back to HTTP without failing
- Preserve secure/insecure SSL choice across updates via /etc/serverkit/ssl-mode
- Support SERVERKIT_SKIP_SSL=1 to disable HTTPS entirely
- Add CAA record to default DNS zone template
- Harden generated per-app nginx configs with HSTS preload, CSP, and security headers
- Align Flask security middleware headers with nginx edge config
- Vagrantfile: don't `dnf install rsync/curl` on RHEL boxes — it upgrades
openssl-libs and breaks the box's sshd (same ABI issue as install.sh).
rsync/curl ship preinstalled on generic/rocky9; fail loudly if a future
box lacks them rather than silently corrupting the run.
- full-stack-test.ps1: drop fedora from the default distro set (Vagrant
Cloud's generic/fedora is EOL at f39; opt in via -Only fedora).
- vm-install.sh (new): run install.sh from uploaded local source, overlay
the working tree, rebuild, and probe health — so the harness tests the
exact local code (incl. uncommitted) with sshd/firewall snapshots on every
exit path for post-mortems. Recovered from a local stash.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
install.sh:
- Detect rocky/almalinux/rhel/centos as OS_FAMILY=rhel.
- The Fedora system-deps block now also runs for rhel; auto-install
epel-release on RHEL-family.
- Docker install on Fedora/RHEL no longer pipes get.docker.com. That
script recently added docker-model-plugin to its package list, which
doesn't exist in docker-ce repos and aborts the install on Fedora 39.
Replace it with explicit `dnf config-manager --add-repo` +
`dnf install docker-ce docker-ce-cli containerd.io
docker-compose-plugin docker-buildx-plugin`.
- Node.js and nginx installs route through dnf for rhel too.
- SELinux setsebool runs for rhel too.
scripts/test/full-stack-test.ps1:
- fedora image bumped from fedora39 (EOL Nov 2024) to fedora40 (current).
- Test-VagrantInstall + Test-VmInstall: defensive read of status file.
Previously `(Get-Content -Raw).Trim()` crashed with InvokeMethodOnNull
when the status file was missing/empty (which happens when the VM's
install script crashed before producing it). Now falls back to 'FAIL'
cleanly so the run summary still works.
Found by full 5-distro suite: Ubuntu 22/24 + Debian 12 passed; Fedora 39
failed on docker-model-plugin; Rocky 9 failed silently due to apt-vs-dnf
+ status NPE. All three fixed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit added Tee-Object so vagrant up output was visible
in the terminal, but Tee-Object's pipeline output then leaked into
Test-VagrantInstall's return stream — the function returned vagrant's
entire log instead of "OK", so the final summary reported every passing
Vagrant VM as a failure.
Append `| Out-Host` after Tee-Object to consume the pipeline. The
operator still sees the live output; the function's return stays clean.
The previous Debian 12 run was actually all-green (10/10 pytest passed,
0 plugin-loader errors) — only the summary line was wrong.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the orchestrator redirected vagrant up's stdout+stderr to a
log file, which meant the box download progress was invisible and any
interactive prompts (like "choose a Hyper-V switch") were silently
swallowed while vagrant blocked on stdin. The operator had no signal
the run was hung.
Use Tee-Object so the operator sees download progress + prompts live
in the terminal, while the log file still gets a copy for later
inspection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this, vagrant up halts on first boot asking the operator to
choose a Hyper-V switch from a numbered menu, which breaks unattended
test runs. Hard-code "Default Switch" (Hyper-V's built-in NAT switch,
present on every Hyper-V install), overridable via
VAGRANT_HYPERV_SWITCH_NAME env var for users who want a different
network.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vagrant + Hyper-V requires the process itself to run elevated; being in
the Hyper-V Administrators group is necessary but not sufficient. Without
this guard, the script would launch, pack the source, then hit a wall on
'vagrant up' with a permission error mid-run.
Now it checks early: if any requested distro uses the Vagrant backend
and the script isn't running elevated, print exact instructions and exit.
Multipass-only runs (Ubuntu) still work without admin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multipass on Windows only ships Ubuntu images. To cover Debian 12,
Fedora, and Rocky 9 too, add a parallel Vagrant runner that uses the
Hyper-V provider (no VirtualBox conflict with Multipass).
scripts/test/
- vagrant/Vagrantfile: parameterized via SK_BOX, SK_VM_NAME env vars;
Hyper-V provider with linked clones; bootstraps rsync + curl.
- full-stack-test.ps1:
- DistroMap replaces ImageMap; each entry tags backend (multipass/
vagrant), image/box name, login user.
- Default Distros now: ubuntu22, ubuntu24, debian12, fedora, rocky9.
- Backend auto-detection: if Vagrant isn't installed, vagrant distros
silently skip rather than failing. Same for Multipass.
- Test-VagrantInstall mirrors Test-VmInstall using `vagrant upload` /
`vagrant ssh -c`, per-VM VAGRANT_CWD under OutDir for state.
- Teardown dispatches by backend (multipass delete vs vagrant destroy).
- README updated with the new prereqs and distro coverage table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes for the race where the browser opens report.html before any
VM has registered:
- Orchestrator seeds state.json with {"running":true,"vms":{}} the
instant OutDir is created, so a refresh before the first VM is added
still has a "running" signal.
- report.py treats missing state.json as RUNNING (not FAIL), and a VM
with a non-done stage but no install-status file as RUNNING in its
per-VM badge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
backend: move load_all_plugins(app) inside the with app.app_context()
block, after MigrationService.check_and_prepare(app). The loader queries
the installed_plugins table; previously it ran before migrations created
that table, causing "no such table: installed_plugins" errors in journal
on every cold start. Surfaced by the new E2E harness (visible in the
journalctl pane of the report).
scripts/test: live HTML report.
- Orchestrator writes state.json after each phase + regenerates
report.html, so opening the page shows real-time progress.
- Browser auto-opens the report when the run starts; page auto-refreshes
every 8s while a run is in progress.
- Added per-VM 'Stage' field (pending -> transferring source ->
installing -> running pytest -> done).
- Fixed empty "Details" cells rendering as black boxes; now show "-".
- Pulsing dot indicator in the live banner.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes a cascade of PowerShell + Multipass quirks found during the first
real run:
- Rewrote orchestrator sequentially. Start-Job's separate runspace was
hiding the real errors below behind opaque "method on null" failures.
- Removed `function Mp` helper. PowerShell ships a built-in alias `mp`
for Move-ItemProperty that silently shadows same-named functions and
turned multipass calls into Move-ItemProperty calls. Inline `& $MpExe`
everywhere instead.
- Lowered ErrorActionPreference from Stop to Continue. Multipass writes
harmless "cannot set permissions" warnings to stderr when transferring
to NTFS; Stop turned those into fatal aborts.
- Stopped piping `multipass exec` stdout through Tee-Object. The pipe
could stay open indefinitely after the remote command exited, hanging
the orchestrator for 45+ min on a finished install. Now redirects to a
file on the VM and only checks the exit code.
- Tightened tar excludes. Tarball dropped from 305 MB to 20 MB.
- Added `npm ci` in vm-install.sh after the local-code overlay. The
original installer ran `npm ci` from origin/main, then the overlay
dropped in a dev package.json that needed different deps, causing a
spurious frontend build failure.
- Added -ReuseVm flag for re-running just the harness against an
already-installed VM (skips the 20-min launch cost).
First successful end-to-end run on Ubuntu 24.04: 10/10 tests passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PowerShell 5.1 reads .ps1 files as ANSI by default; em-dashes and check
marks were mangled into bytes that broke the parser. Replaced with ASCII
equivalents so the script runs under stock Windows PowerShell without
needing pwsh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One-click PowerShell entry point that spins up Ubuntu/Debian VMs in
parallel, installs ServerKit from the local working tree (not origin/main),
runs a pytest API harness against the live panel, and aggregates results
into a self-contained HTML report.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce fallback audit logging and enrich audit tooling; add builtin extension discovery/install; and update default launcher ports and related docs/config.
Key changes:
- New middleware app.middleware.audit registers an after-request fallback that records authenticated mutating API requests when routes don't emit explicit audit logs.
- Audit model/action constants extended and AuditService enhanced (request context handling, redaction for sensitive settings, optional commit flag, sets g.audit_logged, emits events).
- Routes/services updated to create explicit audit entries for marketplace and plugin operations (create/update/publish/install/uninstall/enable/disable) and SSO/settings flows switched to use AuditService.
- Added tests for audit logging behavior and redaction (backend/tests/test_audit_log.py).
- Plugin service: added BUILTIN_EXTENSIONS_DIR, list_builtin_extensions and install_builtin_extension; plugin API endpoints to list/install builtin extensions.
- Contribution system extended to support layouts and route layout metadata; frontend wiring updated to surface standalone/custom layouts and extension routes.
- Launcher port defaults changed (backend -> 47927, frontend -> 41921). Updated backend .env.example, config CORS defaults, Dockerfiles and docker-compose to use SERVERKIT_BACKEND_PORT, and many docs/README files and frontend envs to reflect new ports.
- Minor frontend cleanups and new builtin extension example (serverkit-git) plus associated manifest and frontend re-export.
These changes improve observability/security for mutating APIs, enable opt-in builtin plugins, and align local dev/run ports with the launcher defaults.
Add ngrok 'tunnel' mode to dev scripts and documentation, plus a large UI overhaul for the Servers pages.
- Add .gitattributes and new scripts/setup-wsl.sh helper.
- Extend dev.ps1 and dev.sh with a new 'tunnel' mode that starts backend+frontend and launches an ngrok tunnel (supports NGROK_DOMAIN, NGROK_AUTHTOKEN, and sets sensible CORS_ORIGINS). Includes polling of ngrok API, process cleanup, and helpful error messages.
- Update docs/LOCAL_DEVELOPMENT.md with an "Exposing Your Local Control Plane (ngrok)" guide and WSL setup notes.
- Revamp frontend pages: ServerDetail.jsx and Servers.jsx refactored with new header layouts, status/avatars, status pills, telemetry rows, selectable table rows, bulk actions, confirm dialogs, improved add-server modal, new icons (FolderTinyIcon, MoreIcon), utilities (formatLastSeen, clamp), and accessibility/interaction improvements (menus, selection, copy install command).
- Update servers SCSS to support the new layouts and loading states.
These changes add a developer-friendly tunnel mode for remote testing and significantly improve the server management UX in the frontend.
Add end-to-end support for removing remote Docker networks and volumes and normalize remote API responses. Server: new Flask endpoints to DELETE remote networks/volumes and helpers to compute external base/ws URLs and v1 API paths. Agent: register new handler docker:network:remove and Client.RemoveNetwork implementation. Backend service: RemoteDockerService.remove_network uses agent registry for remote servers and falls back to local DockerService. Models: expand Server permission logic to map agent actions to profile/legacy scopes (DOCKER_READ_ACTIONS, scope expansion and wildcard matching) and add unit tests covering permission mappings. Frontend: handle agent-backed response shapes (unwrapRemoteData, normalizeListResponse), call new removeRemoteNetwork/removeRemoteVolume API methods, update download/install URLs to /api/v1, and tweak several UI texts. Install scripts: change config/log directories, pass --config when registering/starting agent, and update systemd/service/unit and Windows service install paths. README: small wording updates about fleet/remote deployment and monitoring.
Add cross-platform development launchers (dev.sh, dev.ps1) and a frontend ESLint flat config; remove legacy scripts and update docs to use ./dev.sh. Harden backup and storage logic: require admin for backup endpoints, use realpath checks to ensure backup paths stay inside the backup dir, prevent path traversal during archive extraction, and generate schedule IDs with UUIDs. Improve StorageProviderService with masked-secret handling, set restrictive file perms on config, validate S3/B2 endpoint URLs to mitigate SSRF (urlparse + ipaddress), avoid following symlinks during uploads, and adjust MD5 usage. Update Bandit config to skip expected noisy checks and tweak .gitignore entries and contributing docs to target the dev branch and the new dev scripts.
Move the backend into docker-compose and switch the frontend to a multi-stage Docker build. Key changes:
- Added backend/.dockerignore to ignore virtualenv, pycache, env files, instance, etc.
- Updated backend Flask app to safely serve index.html only if it exists and return JSON status/errors when frontend assets are absent.
- Removed docker-compose.dev.yml and expanded docker-compose.yml to run both backend and frontend in Docker (backend service, healthcheck, restart/logging, new ports 5849:5000 & 3847:80, and named volumes).
- Frontend changes: remove file ignores that prevented in-container builds, replace nginx-only image with a Node builder stage + nginx runtime (builds assets in Docker), and update nginx proxy to target the backend service (proxy_pass http://backend:5000).
- Updated dev script (scripts/dev/dev.bat) to use plain `docker compose` commands and the new service/container names and ports.
These changes enable fully containerized local/dev runs where the frontend is built inside Docker and the backend runs as a service in the compose network.
Replace hardcoded repository URL from serverkit/serverkit to jhd3197/ServerKit across agent Dockerfile, README, packaging scripts (deb/rpm) and install scripts so downloads and metadata point to the correct repo. Backend install endpoints now replace the repo placeholder with the GITHUB_REPO constant, and the frontend Windows install command was adjusted to use the -Server parameter instead of -ServerUrl. These changes ensure install scripts, packages, and generated docs reference the intended repository and that generated installer commands match the agent's expected flags.
Introduces backend/app/paths.py to centralize all key directory paths, allowing overrides via environment variables for local development. Updates all backend services and APIs to use these centralized paths instead of hardcoded values. Adds dev path override examples to backend/.env.example and updates .gitignore to exclude dev-data. Also changes frontend dev server port to 5274 and updates related scripts and CORS settings for consistency.
Introduces backend logic to determine server resource tier and block WordPress site creation on insufficient resources (lite tier). Adds ResourceTierContext and ResourceGate component to the frontend, displaying a UI warning and disabling creation actions when requirements are not met. Updates WordPress page and styles to integrate the new resource gating mechanism.
Moved and improved development helper scripts for better cross-platform support, including WSL and Docker. Updated backend login to accept username or email, and updated frontend login form accordingly. Enhanced backend and frontend dev server compatibility for WSL. Updated documentation for streamlined setup and usage.
Introduced Linux (install.sh) and Windows (install.ps1) installation scripts for ServerKit agent, and exposed new backend endpoints to serve these scripts and installation instructions. Added a GitHub Actions workflow (agent-release.yml) for automated agent builds and releases. Updated ROADMAP.md and SERVERS_INTEGRATION_PLAN.md to reflect progress on installation and distribution features.
Shell script to test the full Docker app routing chain:
- Port accessibility check
- Nginx config existence and enablement
- Nginx syntax validation
- Service status check
- Direct backend HTTP request
- Domain routing via Host header
- Docker container status and port bindings
Provides colored output and actionable hints for failures.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Host nginx routes domains to different apps
- ServerKit frontend moved to port 8080 (internal)
- New CLI commands: setup-proxy, add-site, list-sites, remove-site
- Site config templates for easy domain setup
- Install script now sets up nginx proxy automatically
Usage:
sudo serverkit add-site test.example.com 3000
sudo serverkit list-sites
sudo serverkit remove-site test.example.com
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>