Commit Graph

105 Commits

Author SHA1 Message Date
myron 1a907d18b0 feat: collapsible sidebar nav with localStorage state (#48)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 21:17:08 +00:00
myron 65a8690750 fix: use /bin/rm explicitly in removePool so sudoers path matches
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 21:04:17 +00:00
myron 91d0e625c4 fix: decouple php-fpm reload from HTTP request using flag file + cron
Reload during account creation was causing 502 by killing the fpm worker
before nginx finished reading the response. Flag file picked up by cron
within 60s instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 17:06:52 +00:00
myron 9aa67f7efd fix: email uniqueness check only applies to hosting accounts, not admin/reseller users
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 16:42:16 +00:00
myron eb84504689 fix: remove php-fpm pool on account creation rollback to prevent fpm crash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 16:39:48 +00:00
myron 8e623427e3 fix: reload php-fpm async to prevent killing the account-creation request
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 16:34:59 +00:00
myron 3ad7ee44c2 fix: nova.js 401 handler in correct panel/public path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 16:09:33 +00:00
myron 3dab4ffe0f fix: show real error message on login 401, not misleading 'Session expired'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 15:59:34 +00:00
myron b534e7e306 fix: nested transaction crash and favicon 404
- accounts.php: remove outer beginTransaction() — AccountManager already wraps in its own transaction; nested transactions fail in SQLite with 'already an active transaction'
- accounts.php: on AccountManager failure, manually delete the inserted user row instead
- admin/reseller/user index.php: fix favicon href from /assets/img/favicon.svg to nova-favicon.svg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 05:46:32 +00:00
myron 6dd2e3a08d fix: add all server-only assets and panel files missing from repo
Previously missing from git (rsync --delete was wiping them on every deploy):
- assets/css/nova.css
- assets/js/nova.js, features.js, reseller.js, user.js
- assets/img/*.svg (favicon, icons, logo, mark)
- index.php, _branding.php, errors/404.php, errors/500.php
- reseller/index.php, user/index.php

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 05:40:00 +00:00
myron b077226581 fix: recover full admin.js from server, fix port redirects and account create validation
- admin.js: 1292 lines of features were on server but not in repo — recovered and committed
- admin.js: impersonation redirect now uses location.origin instead of hardcoded :8880 port
- accounts.php: pre-validate email uniqueness and username before INSERT to prevent SQLSTATE 23000
- accounts.php: wrap user INSERT + AccountManager::create() in single transaction for full rollback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 05:33:35 +00:00
myron 91bf8d965f fix: portalUrl stays on proxy host when accessed via reverse proxy on port 443
When HTTP_HOST has no port (NPM on 443), return URL without appending panel port.
Direct access (HTTP_HOST includes :8882 etc.) still redirects to correct port.
Prevents browser being sent to :8882 directly after login via novacpx.orbishosting.com.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 05:27:34 +00:00
myron 39942929a7 fix: global exception handler (prevents 502), transaction rollback on account create, CORS for reverse proxy
- set_exception_handler in api/index.php prevents uncaught exceptions from crashing PHP-FPM
- AccountManager::create() wrapped in DB transaction with rollback + Linux user cleanup on failure
- CORS origin regex updated to allow requests from port 443 (NPM reverse proxy)
- index.html written via sudo tee instead of file_put_contents (www-data permission fix)
- chpasswd now called with sudo prefix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-20 05:23:42 +00:00
myron 5ce5bd1520 fix: reseller creation and management in admin panel
- admin.js was calling auth/register (action does not exist) — changed
  to users/create
- Reseller list was fetching from accounts/list which is for hosting
  accounts; fixed to users/list?role=reseller
- Replaced shared adminSuspend/adminChangePass (account-scoped) with
  dedicated adminResellerSuspend/Unsuspend/Passwd/Delete functions that
  operate on the users table
- Added users endpoint actions: create, suspend, unsuspend,
  change-password, delete — all admin-only, operating on user rows
  rather than hosting account rows
- Reseller delete disowns their accounts (sets reseller_id=NULL) rather
  than cascading delete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 14:44:51 +00:00
myron 008658e0ec feat: expand Docker app catalog from 60 to 140 apps
Added 80 new apps across 13 categories: AI/LLM (Ollama, Open-WebUI,
Flowise, Langfuse, AnythingLLM, LocalAI, ComfyUI), Dev Tools
(code-server, Jenkins, SonarQube, Vault, MailHog, Dozzle, Yacht,
Semaphore), Databases (Redis, MongoDB, PostgreSQL, MariaDB,
Elasticsearch, InfluxDB, Neo4j, Qdrant), Monitoring (Loki+Grafana,
Jaeger, VictoriaMetrics, changedetection.io, Metabase), Networking
(Pi-hole, AdGuard Home, NPM, Traefik, wg-easy, Cloudflared, CrowdSec,
Authelia), CMS/Commerce (Drupal, Joomla, Grav, PrestaShop, OpenCart,
Medusa, Answer), Project Mgmt (Plane, OpenProject, Leantime, WeKan,
Focalboard), Communication (Matrix Synapse, Gotify, ntfy, Grist,
Mailpit), File/Storage (Syncthing, SFTPGo, ownCloud, Duplicati,
Calibre-Web), ERP/Business (Odoo, Akaunting, Monica, Twenty CRM,
Dolibarr), Media (Audiobookshelf, Komga, Grocy, TubeArchivist, AFFiNE),
Smart Home (Home Assistant, Node-RED, Mosquitto, Zigbee2MQTT),
Dashboards (Dashy, Homarr, Homer, IT-Tools, CloudBeaver, pgAdmin,
phpMyAdmin, Infisical).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 13:34:31 +00:00
myron 9bc427f8a2 feat: per-stack Reinstall + fix stack ownership enforcement
- API: stack-action/stack-remove now verify ownership for non-admin users
- API: add stack-reinstall action (pull latest images → down → up)
- User panel: add Reinstall button per stack; fix bug where remove-stack was called instead of stack-remove
- Admin panel: add Reinstall button per stack + dockerStackReinstall() handler
- User panel: Remove All My Apps now only removes the calling user's own containers/stacks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 13:01:01 +00:00
myron 7a42be8d01 feat: Docker catalog in admin panel + per-account app removal
- Admin Docker page: add App Catalog tab (60 apps, account-picker modal)
- Admin Docker page: add dockerAdminLaunchApp() for launching apps on behalf of any account
- User panel: add 'Remove All My Apps' button — stops/removes only that user's own containers and stacks
- API: add uninstall-account action (user-scoped; admin can specify account_id, users limited to own account)
- Admin panel: no global Docker uninstall (would affect all users on the server)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 12:58:43 +00:00
myron 61329ff343 feat: expand Docker app catalog from 21 to 60 apps
Adds 39 new apps across 8 categories:
- Media & Files: Jellyfin, Navidrome, Kavita, Paperless-ngx, File Browser, Seafile, Immich
- Monitoring & DevOps: Adminer, Grafana, Prometheus, Netdata, Glances, Healthchecks, Docker Registry, Verdaccio, Watchtower
- Git & CI/CD: Forgejo, Woodpecker CI
- Knowledge: BookStack, HedgeDoc, FreshRSS, Wallabag, Homepage
- Auth & Security: Keycloak, Authentik, Passbolt CE
- Analytics: Plausible (Postgres + ClickHouse)
- Low-code / No-code: Baserow, Appsmith CE, NocoDB
- Communication: Rocket.Chat, Chatwoot, Zammad
- Business: Invoice Ninja, Linkding, Mealie
- Design: Penpot, Excalidraw, Stirling PDF

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 12:55:35 +00:00
myron 2cc219f9fa feat: add 12 Docker app catalog entries
Adds Uptime Kuma, Portainer CE, MinIO, n8n, Directus, Listmonk, Umami,
PhotoPrism, Meilisearch, Wiki.js, Vikunja, and Mattermost — each with
catalog metadata and a complete docker-compose template.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 12:45:30 +00:00
myron 2fa1f10901 Security: fix 8 code-review findings
- install.sh: replace /usr/sbin/ufw * with scoped subcommands
- install.sh: remove /usr/bin/curl * and /usr/bin/env * NOPASSWD (trivial root escalation)
- PHPManager: switchVersion() uses sudo rm -f instead of unlink() for old pool
- PHPManager: updateConfig() SQLite syntax (ON CONFLICT / datetime('now'))
- WordPressManager: cloneStaging() escapeshellarg() on all shell-interpolated paths
- WordPressManager: delete() removes DB record before filesystem to avoid phantom records
- WordPressManager: ensureWpCli() validates download size and enforces 30s timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 12:32:06 +00:00
myron 658e2f9057 Fix PHP-FPM pool not removed on account termination
Two bugs that together left stale pool files behind after termination,
crashing php-fpm on next startup (exit-code 78, user not found):

1. removePool() used file_exists() to guard the rm — fails silently when
   www-data can't read /etc/php/*/fpm/pool.d/; now always attempts sudo rm -f
2. reloadFPM() called systemctl without sudo — silently failed as www-data,
   leaving the old pool loaded even when the file was successfully removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 05:53:25 +00:00
myron b9c37030b6 Fix terminate 500: require DatabaseManager before calling drop
AccountManager::terminate() called DatabaseManager::drop() without
requiring the class first — fatal class not found error on every
account termination.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 05:45:33 +00:00
myron 57949214de Fix WordPress manager 500: define DB_HOST, lazy-load MySQL provDb
- Core.php: add DB_HOST constant (was undefined, causing fatal error on any
  WordPress manager page load in PHP 8)
- WordPressManager: make provDb lazy (only connects to MySQL when actually
  needed for install/clone/delete — not on list/info which only use SQLite)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 03:17:39 +00:00
myron 4d7c35076b Fix 10 code review findings: security, correctness, and SQLite compat
- system.php: fix null dereference on fetchOne (TypeError on null['value'])
- system.php: validate update_channel to ['stable','beta'] to prevent shell injection
- system.php: escapeshellarg remoteBranch in git log/show calls (was RCE vector)
- system.php: fix backup path — rsync contents, not directory, so restore is symmetric
- system.php: syntax check only changed files (git diff) not all 300+ panel files
- system.php: copy VERSION to $webRoot/VERSION not $webRoot/../VERSION (wrong path)
- system.php: fix 3× ON DUPLICATE KEY UPDATE → SQLite ON CONFLICT syntax
- deploy-runner.sh: hoist DB_PATH/CHANNEL above while loop
- deploy-runner.sh: sanitize NEW_VERSION and commit hashes before SQL interpolation
- deploy-runner.sh: parse queued branch (4th field) from webhook queue entry
- webhook.php: remove dead $branch config variable
- webhook.php: include pushed branch in queue entry to eliminate TOCTOU race

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 03:06:14 +00:00
myron 14aa6e8b4d Fix column name: commit_hash → git_commit in novacpx_version INSERT
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:55:43 +00:00
myron 9cabe8af5e Wire update channel (stable/beta) into settings, check, deploy, and version tracking
- Settings page now loads current values from DB and saves via save-option API
- check-novacpx-update reads update_channel setting, checks origin/main or origin/beta
- apply-novacpx-update pulls from channel branch, fixes backup dir (/tmp), fixes SQLite migration syntax, records new version in novacpx_version table + settings.panel_version
- deploy-runner.sh reads update_channel from DB, pulls correct branch, records version after deploy
- webhook.php accepts pushes to both main and beta branches
- Updates page shows channel badge and latest remote version

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:44:46 +00:00
myron 846683c7a2 Add SEO meta description, keywords, and noindex to all three panel pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:30:53 +00:00
myron f9d423b15a Fix OS upgrade script: date format and backup dir permission
- date -u +%H:%M:%S UTC → ts() helper with date -u +"%H:%M:%S UTC"
  (UTC as a separate word was being treated as an extra date argument)
- Backup dir changed from /var/novacpx/backups/ (root-owned, doesn't exist)
  to /tmp/novacpx-backup-TIMESTAMP/ (always writable by www-data)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:24:50 +00:00
myron 09bd0820a5 Updates page: serve cached results instantly, nightly cron refreshes cache
- check-novacpx-update and check-os-update return cached data (12h TTL)
  immediately instead of running slow git fetch / apt-get update on page load
- Cache stored in settings table (update_cache_novacpx, update_cache_os)
- Updates page shows "Cached · last checked X ago" when serving cache
- "Refresh now" button forces a live re-check and updates cache
- bin/cache-update-check.php: standalone cron script that warms cache nightly
- Cron registered at 2am daily on panel server

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:23:45 +00:00
myron 877f157665 Docker page: show compose stacks in My Apps tab instead of raw containers
launchFromCatalog creates compose stacks, not docker_containers entries.
Replace My Containers tab with My Apps tab backed by docker/stacks endpoint.
Add Refresh, Start/Stop, Logs, Remove actions per stack row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:18:39 +00:00
myron 1ac9728fd7 Fix Docker async launch, email SUBSTRING_INDEX (SQLite), postfix sudo writes
- Docker app launch now runs docker compose up -d in background (nohup &)
  so the API returns immediately instead of timing out during image pulls
- EmailManager syncPostfix: replace MySQL SUBSTRING_INDEX with SQLite SUBSTR/INSTR
- EmailManager syncPostfix: write postfix files via sudo tee (www-data permission fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:15:41 +00:00
myron 24a2434ccd Email create modal: split input into local-part + domain dropdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:13:29 +00:00
myron dc8829e2c9 Fix DockerManager stack directory creation using sudo mkdir for www-data
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:09:55 +00:00
myron 89996cc0ea Fix _branding.php session lookup using correct column name (id not token)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:04:18 +00:00
myron b90f753890 Add version badge to reseller and user panel sidebars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:57:48 +00:00
myron ddd81d73e7 Fix MySQL create: sanitize db names, fix empty db_user default, catch RuntimeException
Dots/dashes in names were failing validateName; now stripped to underscores.
Empty db_user field sent as "" (not null) so ?? fallback never fired; fixed
to check for empty string explicitly.  Wrap createMySQL/Postgres in try/catch
so validation errors return 400 JSON instead of 500.  Also pass db_type from
JS (was being sent as db_type not type).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:42:24 +00:00
myron c22e1fd067 Fix multiple user panel 500 errors
- domains: VhostManager::create() called with array instead of 4 params
- PHPManager: VhostManager not required; pool writes use sudo tee (permission);
  updateConfig creates pool if missing instead of throwing
- DatabaseManager: MySQL ops used SQLite panel PDO; add dedicated mysqlPdo()
  using MariaDB socket auth
- BackupManager: column name is size_mb not size; diskUsage returns float
- DB.php: add LAST_INSERT_ID() → last_insert_rowid() translation
- user.js: SSL issue/submit used Nova.api (JSON) but endpoint streams SSE;
  add _sslStream() helper matching admin panel behavior
- schema/migration: add enc_password column to email_accounts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:32:10 +00:00
myron 8179326526 Fix NOVACPX_ROOT undefined on panel pages (black screen)
Constants were only defined in api/index.php, so direct requests to
admin/reseller/user index.php got a fatal error before rendering any HTML.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:22:05 +00:00
myron 1c2c11251c Streaming terminals for PHP extensions, SSL certificates; UFW logging state fix
- php.php: install-extension and remove-extension now stream via SSE (real-time progress, proper error detection, sudo)
- ssl.php: issue action now streams certbot output via SSE
- admin.js: phpExtInstall/Remove use streaming terminal modal
- admin.js: adminIssueBulkSSL uses streaming modal with per-domain progress
- admin.js: adminRenewCert now confirms before renewing
- admin.js: adminIssueSingleSSL helper for per-domain streaming SSL
- admin.js: firewall page pre-selects current UFW logging level from API response
- admin.js: fwSetLogging reloads firewall page on success
- firewall.php: ufw_status() now parses and returns logging level

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:04:43 +00:00
myron 4d016b4156 Add notification email templates: DB migration, API CRUD, admin UI
- db/migrations/009_email_templates.sql: email_templates table with 8 default templates
- db/schema.sql: email_templates table added
- system.php: email-templates/get/save/delete/test actions with placeholder rendering
- admin.js: notifications page enhanced with template list, edit modal, CRUD, send test
- Templates support placeholders: {{name}}, {{domain}}, {{username}}, {{password}}, etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 17:59:54 +00:00
myron 2af9e34fb0 Add service versions panel, version auto-tracking, Fail2Ban sidebar, streaming service switch
- .github/workflows/version-bump.yml: auto-increment patch version on push to main/beta
- admin/index.php: show version under logo from VERSION file
- system.php: service-versions endpoint (catalog of 22 services with version/description/status)
- admin.js: updates page shows Installed Services table with current/latest/status/description
- admin.js: loadServiceVersions() lazy-loaded after page render via setTimeout
- admin/index.php: separate Fail2Ban sidebar entry (was merged into Firewall label)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 16:23:51 +00:00
myron 7aa33defa2 Fix SQLite backtick translation, add service-switch SSE streaming, Fail2Ban management page
- DB.php: fix backtick-quoted column names in ON DUPLICATE KEY UPDATE VALUES() regex
- DB.php: add global backtick→double-quote identifier strip
- system.php: add service-switch SSE streaming endpoint for web/mail/ftp/dns server changes
- system.php: simplify save-option to DB save only (no inline shell)
- firewall.php: add f2b-config-get, f2b-config-save, f2b-log, f2b-jail, f2b-ban, f2b-unban, f2b-ignoreip-* actions
- admin.js: Fail2Ban dedicated management page with jail table, global settings, whitelist, log viewer
- admin.js: soSave() now uses streaming terminal overlay instead of blocking spinner
- admin/index.php: split Firewall (UFW) and Fail2Ban into separate sidebar entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 16:18:28 +00:00
myron bcd3b65520 Run panel on dedicated novacpx-web Nginx service; fix auth/transaction methods
- deploy/nginx-panel.conf: standalone Nginx config for ports 8880-8883
- deploy/novacpx-web.service: systemd unit, survives apache2/nginx stop
- server_setup.php: fix Auth::requireRole() -> require('admin')
- DB.php: add beginTransaction()/commit()/rollback() methods
2026-06-09 16:00:32 +00:00
myron fbc445dad2 Migrate panel DB from MySQL to SQLite
Panel no longer depends on the user-managed MariaDB service.
SQLite at /var/lib/novacpx/panel.db runs independently so the
control panel stays up even when MariaDB is stopped.

- DB.php: switch to sqlite: DSN, add SQL translator (ON DUPLICATE KEY,
  DATE_ADD/DATE_SUB INTERVAL, NOW(), UNIX_TIMESTAMP(), IFNULL)
- Core.php: replace DB_HOST/NAME/USER/PASS with DB_PATH constant
- schema.sql: full SQLite syntax, add TOTP columns to users table
- _branding.php: use sqlite: PDO, datetime('now') for session check
- install.sh: apt install sqlite3, create SQLite DB instead of MySQL DB
- tools/migrate-to-sqlite.sh: one-shot migration script for existing installs
2026-06-09 14:52:02 +00:00
myron 9bd78a81ea Fix uninstall not resetting to setup screen
- proxy.php: always set proxy_mode=disabled and clear remote_host/backend_ip
  on any uninstall, not just when nginx binary is removed
- admin.js: show setup cards when mode==='disabled' regardless of whether
  nginx binary still exists on the remote VM
- Status card shows 'Disabled' instead of 'Stopped' when mode is disabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 13:16:50 +00:00
myron 667f3b3a3c Fix auto-updater running git against web root instead of source repo
NOVACPX_ROOT (/srv/novacpx/public) is a deployed file copy, not a git
repo — hence 'fatal: not a git repository'. The actual git clone lives
at /opt/novacpx-src (installed by the installer).

check-update and apply-update now use /opt/novacpx-src for all git
operations. apply-update also deploys the pulled files back to the web
root with cp -a (public/, api/, lib/, bin/) and re-sets ownership.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 13:12:20 +00:00
myron 7e89ab6709 Fix proxy modals never saving — all were passing callbacks as footerHtml
Nova.modal(title, body, footerHtml) expects an HTML string for the third
parameter. proxyAddHost, proxyEditHost, proxySwitchLocal, and proxyUninstall
were all passing async functions instead, which got stringified as garbage
text with no actual buttons rendered.

Each modal now gets proper Cancel + action button footer HTML, with the
save/action logic wired via addEventListener after modal creation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 13:09:26 +00:00
myron a900c5d490 Fix setup-remote connection lost — EventSource can't do POST requests
proxyRunSetup() used EventSource which only sends GET. The setup-remote
endpoint requires POST, so the request hit the 404 default and the SSE
connection immediately errored with 'Connection lost'.

Replace EventSource with fetch+ReadableStream (same pattern already used
by proxySwitchLocal). Also remove the dead EventSource+close() pair that
was left in proxySwitchLocal from an earlier draft.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:58:34 +00:00
myron 120449a40a Fix remote nginx always showing Stopped — SSH warning poisoned isRunning()
remoteExec uses 2>&1 so SSH's own stderr merged with command stdout.
The 'Warning: Permanently added...' line prepended to 'active' made the
=== 'active' check in isRunning() always return false.

Add -o LogLevel=ERROR to suppress SSH informational/warning messages
while keeping actual remote command output and real SSH errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:55:33 +00:00
myron 5e75d4cae4 Fix nginx proxy start/stop: missing sudo, silent failures, no progress UI
- ProxyManager::sysctl() and reload() now use sudo for local commands —
  www-data cannot run systemctl directly, so start/stop/restart/reload
  were silently failing with permission denied
- Control endpoint now returns success:false when nginx stays stopped
  after a start/restart, or stays running after a stop
- proxyControl() JS shows a loading overlay while the action runs and
  uses error toast when the action reports failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 11:16:41 +00:00