Commit Graph
824 Commits
Author SHA1 Message Date
Juan DenisandClaude Opus 4.8 a09bf0fbbe feat(security): gate panel HSTS on installer SSL mode (keep HTTPS optional)
The Flask middleware emitted HSTS with includeSubDomains+preload whenever not in
debug mode -- even on HTTP-only or Cloudflare-Flexible installs, where preload is
a hard-to-reverse commitment. HTTPS is intentionally optional, so HSTS is now
gated on the operator's recorded SSL choice.

- config._resolve_ssl_mode(): SERVERKIT_SSL_MODE env -> /etc/serverkit/ssl-mode
  file -> safe 'insecure' default. Exposed as SSL_MODE / HSTS_ENABLED.
- security middleware sends HSTS only when HSTS_ENABLED and not debug. Behind a
  proxy Flask can't distinguish real TLS from a Flexible edge via
  X-Forwarded-Proto, so we trust the installer's choice, not the request scheme.
- install.sh records SERVERKIT_SSL_MODE in .env; existing installs are covered by
  the /etc/serverkit/ssl-mode file. The nginx edge still emits HSTS in its own
  secure server block, so secure installs are unaffected.

Proving tests in tests/test_security_headers.py. Full backend suite: 342 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:52:40 -04:00
Juan DenisandClaude Opus 4.8 49611a38f3 feat(security): Cloudflare-aware auto-CAA on certificate issuance
When a cert is issued for a domain managed by a connected DNS provider,
ServerKit now auto-creates a `CAA 0 issue "letsencrypt.org"` record at the
zone apex -- pinning issuance to our CA and clearing the "CAA not enabled"
scanner finding. Degrades to manual instructions when no provider manages the
domain; never fails issuance on a CAA hiccup.

- DNSProviderService.ensure_caa_record(): high-level helper mirroring
  ensure_a_record (find managing zone -> upsert apex CAA -> degrade to manual).
- parse_caa_value(): BIND 'flags tag "value"' -> Cloudflare/DO data object.
- Cloudflare set_record now sends the structured `data` object CAA requires
  (a flat `content` string is rejected), and matches on tag+value so it does
  not clobber a different CA's existing CAA record.
- DigitalOcean set_record sends flags/tag for CAA too.
- DNSZoneService._cloudflare_sync uses structured data for CAA, so the preset
  CAA record syncs correctly to Cloudflare-backed zones.
- ssl_service.obtain_certificate calls the hook and surfaces the result.

Proving tests in tests/test_dns_caa.py (7 cases, all green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:34:14 -04:00
Juan DenisandClaude Opus 4.8 3703b47456 feat(security): server-wide TLS floor at install/update + fix HTTPS update regression
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>
2026-06-19 16:26:54 -04:00
Juan Denis 02afdec9f0 feat(security): optional SSL/TLS hardening with Cloudflare-aware configs and HTTP fallback
- 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
2026-06-19 16:13:06 -04:00
Juan DenisandClaude Opus 4.8 1f700cea06 docs: endpoint & page reference for this session's features
Reviewer-facing summary of the new endpoints, pages, models, config flags,
and operational notes for the DDNS / image-update / auto-sleep / auto-scale /
GPU / backup-encryption / WAF features and the layout switcher.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:34:55 -04:00
Juan DenisandClaude Opus 4.8 70883d50a9 feat(ui): WAF panel + tab on the app detail page
- API client: WAF methods (getWafPolicy/updateWafPolicy/applyWaf/
  getWafEvents/getWafStatus/installWaf) in containerOps.js.
- WafPanel.jsx: install banner (+ install action), mode (off/detect/block)
  with descriptions, paranoia level + anomaly threshold, a disabled-CRS-rule
  chip editor, Save (saves + applies) + Re-apply (surfaces manual_include
  when no vhost is found), and a recent-events table.
- ApplicationDetail: a "WAF" tab (nginx-served apps), wired like Container Ops.

Build passes; new file lint-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:41:26 -04:00
Juan DenisandClaude Opus 4.8 ada030553a feat(security): per-app WAF (ModSecurity v3 + OWASP CRS)
- WafPolicy model (per app: mode off/detect/block, paranoia level, anomaly
  threshold, disabled CRS rule ids).
- WafService: render_rules (SecRuleEngine + CRS include + paranoia/anomaly +
  SecRuleRemoveById), nginx_snippet, install/status (distro-aware,
  best-effort), apply() that writes per-app rules under
  /etc/nginx/serverkit-conf.d/waf and idempotently injects an include into
  the app vhost (graceful manual_include fallback), and audit-log event
  parsing.
- API /api/v1/waf: per-app policy GET/PUT, apply, events, install status.
- 29 tests: rule rendering per mode, rule-disable, nginx snippet, policy
  validation, audit-log parsing, API round-trip + auth.

Backend only; WAF UI + verified install on a real nginx+ModSecurity host are
follow-ups. Integration is additive — existing nginx/site management untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:02:24 -04:00
Juan DenisandClaude Opus 4.8 007c4b984a feat(backups): client-side encryption (opt-in, reuses the Fernet key)
- backup_crypto.py: Fernet file encrypt/decrypt using the existing
  SERVERKIT_ENCRYPTION_KEY (no new deps) — encrypt_file (-> .enc),
  decrypt_file, is_encrypted_backup, encryption_available.
- BackupService: an opt-in `encrypt_backups` config flag; _finalize_backup
  encrypts each artifact before _auto_upload (so the stored/remote copy is
  encrypted) and records encrypted/path/size; restore_application and
  restore_database transparently decrypt a .enc backup to a temp file first.
- 9 tests: byte-exact roundtrip, wrong-key failure, availability reflects
  env, is_encrypted_backup, and an encrypted backup_files create+restore.

Notes: in-memory (streaming large archives is a follow-up); key loss =
unrecoverable backups (back the key up out of band). Additional remote
targets (WebDAV/Azure/Dropbox/SFTP) and restore drills remain follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:22:43 -04:00
Juan DenisandClaude Opus 4.8 24d5362493 feat(ui): surfaces for DDNS, image-update, auto-sleep, auto-scale, GPU
- API client: DDNS methods (dns.js) + per-app container-ops/GPU methods
  (new containerOps.js), auto-bound via index.js.
- ApplicationDetail: a "Container Ops" tab (Docker apps) via the new
  ContainerOpsPanel — image-update check/apply, auto-sleep toggle +
  sleep/wake, and auto-scale policy + manual scale.
- GpuMonitor page (/gpu): per-GPU cards (utilization, VRAM, temp, power,
  fan, driver) + compute-process table; clean empty state when no GPU.
- DynamicDns page (/dynamic-dns): DDNS host CRUD with a one-time token +
  ready-to-use update URL, regenerate-token, and delete.
- Routes + sidebar nav (GPU Monitor, Dynamic DNS) + page SCSS.

Build passes; new files lint-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:34:45 -04:00
Juan DenisandClaude Opus 4.8 633c626d43 feat(docker): horizontal auto-scaling (CPU thresholds, min/max, cooldown)
- ContainerScalePolicy model (per-app: enabled, service_name, min/max
  replicas, cpu high/low %, cooldown, current_replicas).
- ContainerScaleService: evaluate() scales a compose service up/down by one
  replica when average CPU crosses the high/low thresholds, within bounds and
  outside the cooldown; scale_to() for manual; sweep() for cron. Uses
  `docker compose --scale`; local + scale-capable services only.
- apps.py: GET/PUT /<id>/scale-policy, POST /<id>/scale,
  POST /<id>/scale/evaluate, POST /scale-sweep (admin).
- 10 tests: bounds clamping, scale up/down, hold-at-max, cooldown, disabled,
  unknown-cpu, manual endpoint, auth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:13:50 -04:00
Juan DenisandClaude Opus 4.8 d58fdd7da7 feat(system): NVIDIA GPU monitoring
- GpuService shells out to nvidia-smi for per-GPU stats (utilization, VRAM +
  memory %, temperature, power, fan, driver) and GPU compute processes with
  best-effort PID->container resolution via cgroup. Coerces [N/A] to null and
  degrades to "no GPUs" when nvidia-smi is absent.
- GET /api/v1/gpu/ returns {available, gpus, processes}.
- 6 tests: row parse, N/A coercion, availability, no-GPU info, endpoint, auth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:08:58 -04:00
Juan DenisandClaude Opus 4.8 6554e79821 feat(docker): container auto-sleep (policy, manual sleep/wake, idle sweep)
- ContainerSleepPolicy model (per-app: enabled, idle_timeout_minutes,
  last_activity_at, asleep) + a sleep badge in Application.to_dict.
- ContainerSleepService: sleep/wake an app (compose_down/up or
  stop/start_container, local), record_activity, and sweep_idle() which
  stops enabled+awake apps idle past their timeout — and never sleeps an app
  with no activity baseline.
- apps.py endpoints: GET/PUT /<id>/sleep-policy, POST /<id>/sleep,
  POST /<id>/wake, POST /sweep-idle (admin, cron-drivable).
- 9 tests: policy, sleep/wake, docker-failure, remote guard, sweep
  selection, no-baseline skip, endpoints, auth.

Follow-ups: traffic-based idle detection (feed record_activity from the nginx
log) and request-triggered wake-on-demand; remote-server sleep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:54:40 -04:00
Juan DenisandClaude Opus 4.8 2b471a39a2 feat(docker): one-click apply for image updates
- POST /api/v1/apps/<id>/image-update/apply pulls the newest image for a
  compose-managed app, then recreates it (compose_pull + compose_up),
  reusing the existing local/remote deploy path (DockerService /
  RemoteDockerService) exactly like start_app. Refreshes the image_update
  badge afterward.
- Guarded to docker-compose apps; a standalone container would need its full
  run spec to recreate, so those get a clear "update manually" message.
- 4 tests: pull-then-recreate ordering, pull-failure propagation, non-compose
  guard, auth gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:37:30 -04:00
Juan DenisandClaude Opus 4.8 7dc0bffb96 feat(docker): image-digest update detection
- ImageUpdateCheck model + ImageUpdateService: compare an app's local
  RepoDigest against the registry's current index digest (via
  `docker buildx imagetools inspect`) for the same tag, and flag
  update_available when they differ. Shells out to docker like the rest of
  the Docker layer; degrades gracefully when the image is local-only or the
  registry is unreachable.
- API: admin POST /api/v1/image-updates/applications/<id>/check and
  GET /api/v1/image-updates/applications/<id> for the latest result.
- Application.to_dict gains a lightweight image_update badge (mirrors the
  existing image_scan badge).
- 7 tests: digest diff/match/unknown, no-image, badge, endpoint, auth gate.

One-click apply (pull + recreate) to follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:22:18 -04:00
Juan DenisandClaude Opus 4.8 5f28f3e700 feat(dns): dynamic DNS — token-authenticated A/AAAA updates
- DdnsHost model: a hostname bound to a DNS zone plus a secret token; the
  token is masked in API responses except when first minted.
- DdnsService.update_ip reuses DNSZoneService so a configured provider
  (e.g. Cloudflare) is synced automatically; chooses A vs AAAA by IP family;
  no-op when the IP is unchanged.
- API: admin CRUD for hosts + a public, token-authenticated /ddns/update
  endpoint that a router or cron calls when its IP changes (IP from ?ip= or
  the request source). Wrong token -> 401, bad IP -> 400.
- 8 tests covering the service flow and both endpoints.

UI panel to follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 03:12:07 -04:00
Juan DenisandClaude Opus 4.8 e738f3d509 feat(ui): add topbar layout variant to the layout switcher
- Add 'topbar' to LayoutContext and a third switcher button (Sidebar |
  Compact | Top bar).
- Topbar: the sidebar becomes a horizontal bar pinned to the top; top-level
  items lay out in a horizontally-scrollable row with an underline active
  indicator. Category headers, expand chevrons, and sub-items are hidden
  (sub-nav is reached from each page's in-page navigation), which sidesteps
  the horizontal-overflow vs. dropdown clipping trap. Desktop-only; the
  mobile drawer is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 03:05:39 -04:00
Juan DenisandClaude Opus 4.8 cd16d9a479 feat(ui): add layout switcher with compact rail variant
- New LayoutContext (localStorage-persisted, sets data-layout on <html>),
  mirroring ThemeContext.
- "Layout" switcher in the sidebar user menu: Sidebar | Compact.
- Compact rail: a 64px icon-only sidebar that expands to the full sidebar
  on hover/keyboard focus; desktop-only, the mobile drawer is unchanged.
- Reuses existing nav markup — labels hide via font-size:0 (kept in the
  accessibility tree); no nav-render changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 03:02:23 -04:00
Juan Denis 2b7a98c348 feat: canonical domain + encryption key bootstrap
- Auto-generate SERVERKIT_ENCRYPTION_KEY in production when missing.
- Detect canonical domain from Host/X-Forwarded-Host; persist via SystemSettings.
- Expand CORS origins dynamically to include the canonical domain.
- Add SystemTab UI to view/save detected panel domain and HTTPS preference.
- Install script writes key, prompts for PANEL_DOMAIN, and sets nginx IP→domain redirect.
- Add backend tests for key bootstrap and domain detection helpers.
2026-06-19 01:34:02 -04:00
Juan Denis bf9fee131e feat: add secrets manager and inbound webhook gateway
- Encrypted vault + secret models with Fernet encryption
- Webhook endpoint + delivery log models with HMAC verification
- SecretVaultService / WebhookGatewayService business logic
- /api/v1/vaults, /secrets, /webhooks REST endpoints (JWT + public receive)
- Migration 020_secrets_webhooks
- Frontend Secrets & Webhooks page, API methods, sidebar, styles
- Add SERVERKIT_ENCRYPTION_KEY to .env.example
2026-06-19 01:33:46 -04:00
Juan Denis 47c92e0571 feat(auth): WebAuthn / Passkey support
- Add webauthn dependency.
- Add PasskeyCredential model and Alembic migration 019_passkey_credentials.
- Add PasskeyService for registration/authentication challenge flow.
- Add auth API endpoints:
  - POST /auth/passkeys/options/register
  - POST /auth/passkeys/register
  - GET /auth/passkeys
  - DELETE /auth/passkeys/<id>
  - POST /auth/passkeys/options/authenticate
  - POST /auth/passkeys/authenticate
- Expose passkey_enabled flag in User.to_dict().
2026-06-19 01:33:45 -04:00
Juan Denis 57cbdfd5de feat(security): per-image CVE scanning (grype) and SBOM generation (syft)
- Add ImageVulnerabilityScan and SbomArtifact models.
- Add ImageScannerService to download/install grype and syft into
  /var/lib/serverkit/scanners, run CVE scans, generate SPDX SBOMs,
  and enforce a configurable deploy gate.
- Add security API endpoints for scanner install, image scans,
  scan history, deploy-gate checks, and SBOM create/download/list.
- Include lightweight image_scan badge in Application.to_dict() for UI.
- Add Alembic migration 018_image_scan_sbom.
2026-06-19 01:33:45 -04:00
Juan Denis 31b613ade1 feat(security): encrypt system-setting secrets at rest
- Add SERVERKIT_ENCRYPTION_KEY generation in install.sh.
- Ensure SERVERKIT_ENCRYPTION_KEY exists during update.sh runs.
- Encrypt/decrypt SSO client secrets, source GitHub client secret, and
  AI API key in SettingsService transparently.
- Mask secret values in SettingsService.get_all() and
  get_all_with_metadata() so the API never returns plaintext.
- Add SettingsService.migrate_legacy_secrets() and invoke it at startup
  alongside DNS/storage/cloud legacy-secret migrations.
2026-06-19 01:33:45 -04:00
Juan Denis f9c31aec32 feat(installer): guided install/update flow with health-checked rollback
- install.sh: staged installer with pre-flight checks (disk/memory/root),
  apt/dnf lock handling, low-RAM safe mode + swap, source-or-release install
  modes, generated secrets/.env, self-signed cert bootstrap, nginx + systemd
  wiring, a post-install health probe with automatic rollback, and optional
  Let's Encrypt issuance for the panel domain.
- scripts/update.sh: in-place updater with release self-refresh, pre-update
  database + tree backups, release/source paths, health-checked rollback,
  and backup pruning.
- scripts/build-release.sh: builds a portable release tarball (pre-built
  venv + frontend) at the repo root.
- serverkit CLI: 'update' delegates to scripts/update.sh and accepts
  --release [vX.Y.Z].
- Harden the serverkit-agent unit (kernel/namespace protections, MemoryMax,
  LimitNOFILE) and ignore release tarball artifacts.
2026-06-19 01:33:17 -04:00
github-actions[bot] 400c43e087 chore: bump version to 1.6.22 [skip ci] 2026-06-19 02:57:19 +00:00
Juan Denis bdb786ba52 Clarify WiX build instructions in Product.wxs
Simplify and clarify the MSI build comments in agent/packaging/msi/Product.wxs. Replace explicit dotnet tool/install commands with concise guidance to install WiX Toolset v5 and the matching Util extension (pinned to 5.0.2), and add a reference to agent/packaging/msi/build.ps1 for the exact CI commands. This makes the instructions shorter and points maintainers to the CI script for reproducible steps.
2026-06-18 22:57:01 -04:00
github-actions[bot] 6857ff2e8e chore: bump version to 1.6.21 [skip ci] 2026-06-19 02:49:04 +00:00
Juan Denis ba9ec0de8a Fix WiX extension install syntax
Use the correct WiX extension version specifier (wix extension add --global WixToolset.Util.wixext/5.0.2). Updated the GitHub workflow, MSI Product.wxs comments, and PowerShell build script message to use the slash-style version token so the extension installs correctly with wix v5.
2026-06-18 22:48:47 -04:00
github-actions[bot] 4e579e6f94 chore: bump version to 1.6.20 [skip ci] 2026-06-19 02:38:01 +00:00
Juan Denis 428dc58f09 Split agent CI: build UI & resolve version
Refactor agent GitHub Actions and build tooling to separate concerns: add a dedicated build-ui job (upload/download agent-ui-dist) and a version job to resolve the release version; make downstream jobs depend on these outputs. Update workflow steps to consume artifacts/outputs (use needs.version where appropriate), and mark multi-platform jobs to not rebuild the UI. Add Makefile UI targets (build-ui, UI_DIST dependency, cleanup) so go:embed succeeds locally. Improve Windows MSI docs and build script: require specific wix tool/version and verify WixToolset.Util extension before building.
2026-06-18 22:37:46 -04:00
github-actions[bot] a82d3de2ac chore: bump version to 1.6.19 [skip ci] 2026-06-19 02:25:37 +00:00
Juan Denis 210f699de1 Build agent UI in Docker multi-stage image
Add a dedicated Node.js build stage to the agent Dockerfile to compile the desktop UI and embed its output into the Go binary. Copy the built UI into internal/agentui/dist before go build and make the Go build respect TARGETOS/TARGETARCH args for multi-arch builds. Also update agent/.dockerignore to exclude dist/, ui/dist/, and ui/node_modules/ to keep the image build context small and cache-friendly.
2026-06-18 22:25:22 -04:00
Juan Denis 866c837698 Upgrade WiX packaging to Toolset v5
Switch MSI packaging to WiX Toolset v5: update CI to install the WiX Util extension, update Product.wxs comments and namespaces for v5 and show using the Util extension at build time, and enhance build.ps1 to pass the extension to wix build, fail loudly on non‑zero exit code, verify the MSI output exists and print the resolved output path. These changes ensure builds work with WiX v5 and provide clearer errors/output.
2026-06-18 22:23:10 -04:00
github-actions[bot] 7a1303ae44 chore: bump version to 1.6.18 [skip ci] 2026-06-19 02:21:09 +00:00
Juan Denis 70a70642c1 feat(services): support manual/local apps and zip uploads
- Extend Application model with source, compose_file, systemd_unit,
  managed_by, version, and upload_path columns.
- Add migration 017_manual_apps for the new columns.
- Add POST /apps/manual to register existing server paths/systemd units.
- Add POST /apps/upload with versioning, rollback, and .env preservation.
- Update DockerService compose helpers to accept a custom compose_file.
- Update ProcessService to query systemd units and compose projects.
- Update apps API start/stop/restart/status/delete to honor compose_file
  and avoid deleting unmanaged manual paths.
- Add Manual/Local and Upload ZIP source modes to NewService.
- Show source badges in Services list and disable Git controls for
  manual/upload apps in ServiceDetail.
- Add version list, rollback, and new-version upload UI for upload apps.
2026-06-18 22:16:00 -04:00
github-actions[bot] d772896281 chore: bump version to 1.6.17 [skip ci] 2026-06-16 05:27:04 +00:00
Juan Denis 4fdb51004b Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-06-16 01:26:41 -04:00
Juan DenisandClaude Opus 4.8 07bbb27818 test(ci): guard conftest's SQLAlchemy import for minimal-deps jobs
The previous commit added a module-level `from sqlalchemy import event` to
register SQLite speed PRAGMAs. But the test-system-utils workflow runs
tests/test_utils_system.py with only `pip install pytest` (no SQLAlchemy), and
pytest still imports conftest.py — so it broke with
ModuleNotFoundError: No module named 'sqlalchemy'.

Wrap the import + listener in try/except ImportError. Those jobs run no
DB-backed test, so skipping the PRAGMA tuning there is harmless; the full
backend job still gets it.

Verified: tests/test_utils_system.py (41 passed) and the agent e2e roundtrip
both pass; conftest now imports with just pytest + stdlib.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 01:09:50 -04:00
github-actions[bot] 59f3df02da chore: bump version to 1.6.16 [skip ci] 2026-06-16 04:31:01 +00:00
Juan Denis 76dce677ca Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-06-16 00:30:48 -04:00
Juan DenisandClaude Opus 4.8 84c943ce2f test(ci): use a file-backed SQLite so threaded tests don't race
test_command_roundtrip_via_send_command drives agent_registry.send_command
from a background thread while the main thread handles /poll and /result.
Flask-SQLAlchemy serves :memory: from a single shared connection (StaticPool),
so the two threads issue interleaved transactions on one connection and the
background commit intermittently dies with ObjectDeletedError /
PendingRollbackError (it passed some CI runs, failed others).

Point tests at a temp-file SQLite instead: each thread gets its own connection
with SQLite's file locking, which is safe and deterministic. Add
synchronous=OFF / journal_mode=MEMORY PRAGMAs so the file DB stays ~as fast as
in-memory (throwaway data needs no durability). No production code changed.

Verified: the e2e roundtrip passes 22/22 consecutive runs (was flaky); full
suite green (exit 0, 232 passed / 12 skipped locally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 00:17:34 -04:00
github-actions[bot] a053df2b34 chore: bump version to 1.6.15 [skip ci] 2026-06-16 03:57:08 +00:00
Juan DenisandClaude Opus 4.8 2483541356 fix(storage): create the config file's own dir in save_config
save_config() ran os.makedirs(SERVERKIT_CONFIG_DIR) unconditionally rather
than the directory of the file it actually writes (cls.CONFIG_FILE). When a
caller overrides CONFIG_FILE (test_storage_secret_encrypted_at_rest points it
at a pytest tmp dir), creating the real config dir failed on the sandboxed CI
runner and — because the surrounding except swallows it and returns
{success: False} — the file was never written, surfacing as a confusing
FileNotFoundError. Now makedirs(dirname(CONFIG_FILE)); production behavior is
unchanged (there dirname(CONFIG_FILE) == SERVERKIT_CONFIG_DIR). 8/8 encryption
tests pass; full backend suite was 241 passed / 1 failed -> now green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:55:03 -04:00
github-actions[bot] ad7e38c203 chore: bump version to 1.6.14 [skip ci] 2026-06-16 03:49:33 +00:00
Juan Denis bedbd06bff Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-06-15 23:49:15 -04:00
Juan DenisandClaude Opus 4.8 3a8fafcba4 chore(deps): bump PyJWT 2.13.0 + cryptography 48.0.1 (Safety advisories)
The Safety dependency scan flagged PyJWT==2.12.1 (5 vulns) and
cryptography==46.0.7 (1 vuln, bundled-OpenSSL). Bump to the Safety-suggested
fixes, which resolve all 6 currently-active (non-policy-ignored) findings.
Verified: installs with no resolver conflicts, the Fernet/encryption tests
pass, the app boots (JWT manager), and the full suite is 232 passed / 12
skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:47:15 -04:00
Juan DenisandClaude Opus 4.8 66ec2a79b3 ci(backend): install pytest so the test job can run
The Backend CI installed only requirements.txt (which doesn't carry the
test-only pytest dependency) and then ran `python -m pytest`, failing with
"No module named pytest". Add `pip install pytest` to the install step.
pytest is the only test-only import across tests/ (everything else they
import — cryptography, flask_jwt_extended, prompture, yaml — is already a
runtime dependency). Full suite: 232 passed, 12 skipped locally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:47:15 -04:00
github-actions[bot] 3cf19059f2 chore: bump version to 1.6.13 [skip ci] 2026-06-16 03:39:35 +00:00
Juan Denis a9c22c27c1 Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-06-15 23:39:23 -04:00
Juan DenisandClaude Opus 4.8 b596925313 ci(agent): build the embedded UI before go (fix embed all:dist)
The agent embeds its desktop console via //go:embed all:dist
(internal/agentui/embed.go), which needs agent/internal/agentui/dist to
exist at compile time. The CI and release workflows ran go build/vet/test
without first building the React app (agent/ui -> vite build outputs to
that dist), so a fresh checkout failed with
"pattern all:dist: no matching files found".

Add a Node setup + `npm ci && npm run build` (in agent/ui) before the go
steps in both jobs of agent-ci.yml and in the release build job. Verified
the build command produces internal/agentui/dist locally.

Not a Go-version issue: wireguard-go@2026, gvisor@2025 and the agent all
declare go 1.23.1, and gvisor's gohacks build constraints are go1.20+
(no upper bound), so CI's Go 1.23 compiles them fine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:39:21 -04:00
github-actions[bot] 267066dce0 chore: bump version to 1.6.12 [skip ci] 2026-06-16 03:31:05 +00:00