7 Commits
Author SHA1 Message Date
Juan DenisandClaude Opus 5 96b06a7e11 fix(security): installed firewall no longer reports as absent
UFW installed cleanly, dpkg said ii, the install notification said so — and
/security/firewall still rendered "No Firewall Installed". fail2ban worked on
the same box, which is the clue: fail2ban-client lives in /usr/bin, ufw in
/usr/sbin, and the panel unit ships

    PATH=<venv>/bin:/usr/local/bin:/usr/bin:/bin

with no sbin. subprocess resolves argv[0] through $PATH alone, so `ufw status`
raised FileNotFoundError. _check_ufw wrapped both questions in one try/except,
so that exception discarded an `installed` already computed as True and
returned False. Measured on the affected host:

    is_command_available('ufw')        = True
    PackageManager.is_installed('ufw') = True
    get_status() -> ufw {'installed': False}, any_installed False

FirewallTab.jsx gates its empty state on any_installed. The Enable button was
broken identically — same bare-name exec.

Three fixes, each independent:
- resolve_command() gives argv[0] an absolute path, but ONLY when $PATH cannot
  resolve it, so working commands pass through byte-identical.
- probe failure can no longer unsay "installed" (same split for firewalld).
- the unit template gains /usr/local/sbin, /usr/sbin and /sbin. update.sh
  re-renders the unit, so existing installs pick this up on upgrade.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 09:31:29 -04:00
Juan DenisandClaude Opus 4.8 69123a8b80 harden: broaden insecure-secret detection, bind API to loopback, fix break-glass sub
- config: is_insecure_secret() rejects any dev/placeholder key (dev-*,
  jwt-secret-key*, *change-in-production*, *change-this*, empty), not just the
  two exact default strings — a variant like `dev-jwt-...` can no longer boot
  in production. Used in both ProductionConfig.__init__ and init_app.
- systemd unit: bind gunicorn to 127.0.0.1 by default so the raw API port is
  not world-reachable (host nginx already fronts :80/:443). Override with
  SERVERKIT_BIND_HOST=0.0.0.0 for setups that front it differently.
- cli break-glass: mint the token with a string identity; flask-jwt-extended
  rejects a non-string `sub` at decode time with a 422, which broke every CLI
  call that authenticated against the panel API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:07:14 -04:00
Juan DenisandClaude Fable 5 751dde7a7d fix(deploy): threaded gunicorn worker — gevent-websocket class broke WebSocket
The app runs flask-socketio with async_mode='threading' (the backend
is real-thread based: jobs scheduler, queue bus, agent registry), where
engineio serves WebSocket via simple-websocket on the raw socket. The
systemd unit template and Dockerfile still launched gunicorn with the
GeventWebSocketWorker, which ALSO answers the upgrade — the client got
HTTP/1.1 101 twice and read the second one as a frame ('Invalid frame
header'), so every panel silently degraded to polling. Proven by
hexdump (double 101, first 'frame' byte 0x48 'H') and fixed live:
single 101 + 0x81 text frame, socket.io connects through Cloudflare.

- unit template + Dockerfile: gunicorn -w 1 --threads 100 (single
  PROCESS stays mandatory — in-memory agent gateway)
- requirements: pin simple-websocket explicitly (was transitive-only)
- CLAUDE.md / ARCHITECTURE.md / SECURITY.md single-worker notes updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 00:14:49 -04:00
Juan DenisandClaude Fable 5 e797ec3a3f fix(system): version resolution honors custom install dirs and Docker
The About page's /system/version (and check-update's current-version
read) hardcoded /opt/serverkit first, duplicated in three places:

- app/utils/version.py is now the single resolver: explicit
  SERVERKIT_INSTALL_DIR override -> the running code's own tree ->
  /opt/serverkit last (a box moved to a custom dir may still have a
  stale tree there); system.py's two inline copies now call it
- the systemd unit template renders SERVERKIT_INSTALL_DIR from the
  installer's SERVERKIT_DIR (deliberately not the bare SERVERKIT_DIR
  env name, which app/paths.py already uses for the /var/serverkit
  data root); flows to existing installs via the updater's re-render
- crypto's .env discovery gains the same install-dir hint
- Docker image now ships VERSION (/app/VERSION) — containers showed
  the 1.0.0 fallback because no candidate path existed in the image
- 4 proving tests in backend/tests/test_panel_version.py

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:14:37 -04:00
Juan DenisandClaude Opus 4.8 ff35375b50 feat(install): Phase 3 — multi-distro abstraction, init/env detection, templated unit
Hardening plan §6. Reduces the distro-specific if/else surface and handles
edge environments:

- Dependency manifest (scripts/deps/manifest.yaml): declarative per-family
  package/python/node/docker/init spec. Consumed directly by the Phase 5
  Python bootstrap; install.sh mirrors it inline (it can't parse YAML before
  Python exists in the curl|bash path).

- New sourceable libs (built in parallel, each shellcheck-clean + unit-tested):
  - scripts/lib/pkg.sh  — pkg_detect/refresh/install/is_installed across
    apt/dnf/yum/zypper/pacman/apk (PKG_MGR_OVERRIDE + PKG_DRY_RUN for tests).
  - scripts/lib/init.sh — init_detect + start/stop/enable/disable/reload/
    is_active across systemd/openrc/runit/sysvinit/none (INIT_OVERRIDE/DRY_RUN).
  - scripts/lib/env.sh  — is_container/is_wsl/has_systemd (override-friendly).

- ID_LIKE handling (G5/6.5): install.sh's os_family_from() (now a pure,
  testable function reading ${SERVERKIT_OS_RELEASE}) maps unknown IDs via
  ID_LIKE to debian/rhel/fedora/suse/arch/alpine — rhel before fedora so RHEL
  clones pick the RHEL Docker repo.

- zypper + pacman + apk (G6/6.6): choose_pkg_manager/refresh_pkg_index/pkg_add,
  provision_docker and provision_python gained suse/arch/alpine branches;
  Docker enable/start is now init-aware (systemd OR OpenRC) so it no longer
  aborts on Alpine.

- Templated systemd unit (6.7): serverkit-backend.service → templates/
  serverkit-backend.service.in, rendered (render_service_unit) at install and
  in refresh_config so a custom SERVERKIT_DIR/venv/log path is honored.
  install_service warns-and-skips enable when has_systemd is false (G5).

- nginx TLS floor as a reversible conf.d snippet (6.8): install.sh + update.sh
  prefer /etc/nginx/conf.d/serverkit-tls.conf when nginx.conf doesn't already
  declare ssl_protocols (RHEL/Fedora/SUSE), falling back to the in-place edit
  on Debian/Ubuntu to avoid a 'duplicate ssl_protocols' error. uninstall
  removes only that snippet.

Tests: new scripts/test/test_lib.sh (13) + 4 new test_install.sh cases
(os_family_from, render_service_unit, harden_global_tls A/B). 13+15+10 pass;
shellcheck error+warning clean across all new/changed shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 00:46:38 -04:00
Juan DenisandClaude Opus 4.5 cab18a32b7 Consolidate templates into backend/templates marketplace
- Removed duplicate root templates/ folder
- Added PHP, Python, Node.js templates to marketplace (YAML format)
- Templates now installable via web UI Templates page
- CLI create-app now points users to web UI
- All 63 templates in one place: backend/templates/

New templates:
- php-app: Custom PHP 8.0-8.3 with Apache
- python-app: Custom Python 3.9-3.12 (Flask/FastAPI/Django)
- node-app: Custom Node.js 18-21 (Express/Next.js)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 22:23:50 -05:00
Juan DenisandClaude Opus 4.5 e65c680ba2 Add Docker-based app templates for PHP, Python, and Node.js
No need to install PHP/Python/Node on the host - each app runs
in its own isolated Docker container with the exact version needed.

Templates:
- PHP: nginx + php-fpm (8.0, 8.1, 8.2, 8.3)
- Python: Flask/FastAPI/Django (3.9-3.12)
- Node.js: Express/Next.js (18, 20, 21)

New CLI commands:
- create-app <type> <name> [port] - Create new app from template
- list-apps - List all deployed apps
- start-app <name> - Start an app
- stop-app <name> - Stop an app
- app-logs <name> - View app logs

Usage:
  sudo serverkit create-app php myblog
  sudo serverkit create-app python myapi 8200
  sudo serverkit add-site myblog.domain.com 8100

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 22:17:46 -05:00