Commit Graph

66 Commits

Author SHA1 Message Date
myron 76726dc47c feat: #41-#47 admin root controls — enhanced pages + new APIs
#41 phpMyAdmin: quick-access links in database manager
#43 PostgreSQL: Adminer at /adminer.php (MySQL + PostgreSQL)
#44 Mail server: virtual domains list, mail log tail, better service controls
#45 FTP server: full account list from DB, better service controls
#47 Web server: stats cards, PHP defaults, log viewer

New APIs: system/read-log, email/domains
Fix: PHP-FPM pm.max_children increased to 20 (was 5, causing exhaustion)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-22 12:20:53 +00:00
myron 697763f333 fix: wrap server_stats INSERT in try/catch — SQLite lock was killing stats API
Concurrent cron writes (collect-stats.php every 5min) caused DB lock errors
that aborted the entire stats response, leaving web/mail/FTP pages empty.
History insert is now non-fatal.
2026-06-22 04:52:08 +00:00
myron 2ecf93a344 fix: hardcode panel ports in CORS check — PORT_USER etc undefined before Core.php loads
Using PORT_USER ?? 8880 threw Error in PHP 8 since the constant isn't defined
until Core.php is require_once'd later in the file. Every API request was
hitting the exception handler and returning 'An internal error occurred.',
breaking all logins and API calls.
2026-06-22 04:29:15 +00:00
myron 956defc34b fix: all code review security findings
- CORS: replace open regex with explicit hostname allowlist + port whitelist
- Exception handler: only expose RuntimeException/InvalidArgumentException
  messages; PDOException and others return generic 'internal error'
- Auth::portalUrl(): allowlist-validate HTTP_HOST before using it in
  redirect URL — prevents open redirect via Host header injection
- _branding.php custom_css: strip HTML tags, js: URLs, @import, expression()
  instead of just </style> which was trivially bypassable
- accounts create: check accounts table as well as users for username
  uniqueness (TOCTOU fix); wrap user INSERT + provisioning in single
  transaction so rollback is atomic on failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
2026-06-21 16:03:26 +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 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 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 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 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 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 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 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 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 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 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
myron a4bf01d78f Remove API rate limiting
Was blocking logins. Can be reintroduced later if needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:55:19 +00:00
myron e8d13678fb Fix rate limiting triggering on login page loads
Only apply the tight 10/min bucket to POST /auth (actual login attempts).
GET /auth (session checks on page load) now falls into the general 120/min
bucket, preventing the login page from rate-limiting itself during normal use.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:53:18 +00:00
myron c07639667b Nginx proxy: local mode — Apache port migration, one-click enable/disable
- VhostManager: getApachePort() reads proxy_apache_port setting (default 80);
  writeApache() uses configured port; migrateApachePort() rewrites all vhosts
  and ports.conf; restoreApachePort() reverses the migration
- ProxyManager::switchToLocalMode() — generator: installs nginx if needed,
  migrates Apache to 8090, configs nginx catch-all, starts nginx, syncs proxy
  hosts; rolls back Apache on nginx config failure
- ProxyManager::disableLocalMode() — stops nginx, restores Apache to 80/443
- proxy.php: POST /api/proxy/switch-local and /api/proxy/disable-local (SSE stream)
- admin.js: two-card "not configured" layout (Local Mode / Remote VM);
  proxySwitchLocal() modal with port picker + live progress stream;
  proxyDisableLocal() reverts with progress; 'Disable Local Mode' in service
  controls when mode=local

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:30:33 +00:00
myron ed552cd5a6 Proxy: setup progress stream, self-healing, uninstall, health check cron
- ProxyManager::runSetupOnRemote() — generator yields step-by-step
  progress; drives SSE stream from /api/proxy/setup-remote POST
- ProxyManager::uninstall(bool) — removes configs from remote or local;
  optionally apt-get removes nginx and sets mode=disabled
- ProxyManager::healthCheck() — called every 5 min from collect-stats.php;
  restarts nginx on remote if found stopped
- proxy.php: POST /api/proxy/setup-remote (SSE stream), DELETE /api/proxy/uninstall
- admin.js: proxyRunSetup() streams output to a live log modal;
  proxyUninstall() with configs-only vs full removal choice;
  'Run Setup on Remote VM' / 'Uninstall' buttons in page header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:23:02 +00:00
myron 6b95571548 Nginx proxy: remote VM support via SSH
- ProxyManager: all ops (start/stop/reload, config push) work over SSH
  when proxy_mode=remote; sysctl/reload/writeHostConfig/deleteHost all
  route to remoteExec/remotePush helpers
- proxy.php: add GET/POST /api/proxy/settings and POST /api/proxy/test-remote
- admin.js: Settings modal with mode selector + remote fields + Test Connection;
  page header always shows Settings button; status card shows mode + remote host;
  'not installed' state directs to Configure Remote Proxy VM

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:14:18 +00:00
myron 89c9bfdc49 Security hardening: token-at-rest, rate limiting, XSS, transactions
- auth: impersonate stores empty data instead of raw cookie; unimpersonate
  issues a fresh session rather than replaying a stored token
- api/index.php: restore rate limiting (10 req/min auth, 120 general)
- nova.js: 401 redirects to login instead of silently returning error;
  escHtml now escapes single quotes to prevent onclick XSS
- accounts: wrap ownership-change 4-write path in beginTransaction/commit;
  restore audit body on account.update
- reseller/user login cards: use $_pname instead of hardcoded 'NovaCPX'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 07:51:21 +00:00
myron 537d52dafa Role isolation, impersonation, account ownership, loading spinners, Docker fixes
- Enforce portal role isolation: admin/reseller/user can only auth on their own port
- Admin/reseller impersonation: Login As with cookie handoff + Return banner in user panel
- Account ownership: admin can reassign accounts to resellers, DNS NS follows
- accounts/update: ownership change cascades package + NS to new owner
- users.php endpoint: admin list/filter by role (reseller dropdown)
- Docker launch fix: uDockerUpdateParams defined before call
- Nova.loading() spinners: login, SSL, PHP switch/save, backup create, docker launch/actions
- Logo consistency: gradient CPX text on all login pages, novacpx_logo_html() in all sidebars
- BackupManager: fix DB class name, table name, column name
- DNSManager: fix settings keys (ns1_hostname/ns2_hostname)
- docker.php: resolve account_id from user uid for all actions
- Auth: impersonate sets cookie + stores return_token for seamless round-trip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 02:56:45 +00:00
myron 77f88ca5bf Fix suspend/unsuspend/terminate — wrong body field name
JS sends account_id but PHP was reading id; both now accept either.
Same fix applied to terminate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 22:39:22 +00:00
myron fe2d3d457c Add account edit modal — package, PHP version, email
- New accounts/update endpoint: updates package_id, php_version, email,
  and notes; switches PHP-FPM pool when version changes
- Edit button on each account row opens pre-populated modal
- Modal shows email, package dropdown, PHP version selector; domain
  is read-only with tooltip explaining it can't change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:39:54 +00:00
myron af9f1b8f43 Fix accounts list display, OS update terminal modal
- Fix accounts list always showing empty: Response::paginate() returns data
  as res.data (array), not res.data.accounts — fix all 9 call sites in admin.js
- Replace blocking apply-os-update with background job + terminal modal:
  start-os-update runs apt-get as nohup subprocess with sudo, writes to
  /tmp log file; os-update-status polls log and done-file; admin.js shows
  scrolling terminal modal that auto-closes when complete
- Fix OS update: was running apt-get without sudo (www-data lacks root)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:24:36 +00:00
myron d587ad4ebd Fix web server switch — panel always stays on Apache
- novacpx-webserver-switch: new helper script that manages ports 80/443
  only; panel ports 8880-8883 are never touched
- system.php: save-option web_server now calls the helper script instead
  of stopping all web servers (which killed the panel)
- admin.js: server options shows live Apache/Nginx status badges and notes
  that the panel always runs on Apache
2026-06-08 16:36:49 +00:00
myron 906720e215 Fix updates page and server options page
- system.php: sudo git for check/apply-novacpx-update (fixes www-data ownership)
- system.php: find instead of glob(**) for PHP syntax check
- system.php: php8.3 -l instead of php -l
- system.php: sudo rsync/chown for deploy
- system.php: steps[] tracking in apply-novacpx-update response
- system.php: config.ini sync on save-option web/ftp/dns_server change
- system.php: safety guard blocking removal of active DB engine
- admin.js: Nova.loading() in soSave() for server options page
- admin.js: fix soSave page reload (window._novaPages -> adminPage())
- admin.js: applyNovaCPXUpdate shows step-by-step modal on completion
2026-06-08 16:23:27 +00:00
myron 237c19c13d Guard db-engine remove from dropping panel DB; restore VERSION 2026-06-08 16:05:03 +00:00
myron ffb623dd16 Fix service controls, loading overlay, DB engine awareness
- system.php: add sudo to all systemctl/apt-get calls (www-data runs as non-root)
- system.php: flush command for postfix uses postqueue -f
- system.php: save-option writes web_server to config.ini so VhostManager picks it up
- databases.php: list endpoint supports admin (no account_id), defaults db type to active_db_engine setting
- nova.js: add Nova.loading() / Nova.loadingDone() spinner overlay
- admin.js: adminServiceAction shows loading overlay + optimistic badge update
- admin.js: phpInstallVersion, dbEngineAction, docker install, OS/NovaCPX update all show loading overlay
- WordPressManager.php: fix Database::getInstance() -> DB::getInstance()->pdo()
- DockerManager.php: fix install to write script file and sudo bash (no interactive terminal)
2026-06-08 12:20:55 +00:00
myron 99eb8ede67 Fix service status refresh, DNS zones, Docker page, SSL manager, input styling, updates
- Service status: data-svc-status/data-svc-dot attrs + refreshSvcStatus() updates in-place after restart/stop/start
- svc-check endpoint: lightweight is-active poll for single service
- Docker page: fix function signature (was docker(el), now returns HTML)
- DNS zones: fix records response (array not object), fix add-record content field, fix delete-zone accept zone_id
- DNS create-zone: allow admin to create zones without account_id
- SSL manager: add Generate CSR modal (openssl req), Upload Custom SSL modal, explain both options
- nova.css: add input:not([type]), date/search/tel/time types, .form-control to styling selector; fix date picker icon
- Updates: fix panel-up check (was fsockopen on HTTPS port, now curl -sk); add set_time_limit(180/300)
- apply-os-update: set_time_limit(300)
- DB engine manager: fix duplicate INSERT line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:56:14 +00:00
myron 5ef458dfb0 Fix service switching, dynamic dashboard services, DB engine manager
- save-option: inline service switching (web/ftp/dns) instead of missing shell scripts
- stats: dynamic service list based on web_server/ftp_server/dns_server settings
- service action: allow all variants (nginx, pure-ftpd, pdns, nsd, etc.)
- mysqlManager: full rewrite with MySQL/MariaDB/PostgreSQL engine cards (install/remove/start/stop), active engine selector, all-databases table
- ftpServer page: dynamic — shows whichever FTP server is configured, not hardcoded proftpd
- db-engine-action: fixed duplicate INSERT line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:41:49 +00:00
myron 5251494f7a feat: full PHP Manager — version install/remove, per-version extension management
php.php: install-version, remove-version, version-extensions, install-extension,
remove-extension, fpm-action endpoints. versions now returns fpm_active status
and panel_php (current runtime version).

admin.js phpManager(): grid of installed/not-installed versions with Install/
Remove/Restart FPM buttons; Extensions panel slides in below with filterable
list, dropdown of common extensions + custom input, per-extension Remove buttons.
Panel PHP info card shows which version NovaCPX runs on.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:35:12 +00:00
myron c80513d17b fix: server_stats column names + remove hardcoded spamassassin unknown
system.php: INSERT used wrong column names (cpu_pct/ram_pct/disk_pct/
load_1m) — table has cpu_usage/ram_usage/disk_usage/load_avg matching
migration 007. Fatal PDOException was crashing stats API → all services
showing as unknown.

admin.js mailServer(): remove spamassassin row (not installed, was
permanently hardcoded to 'unknown').

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:23:20 +00:00
myron bfa2cfc3f8 fix: CyberMail from field format + deploy runner syncs api/ and lib/
- Notifier.php + test-notify: use plain email address in 'from' field
  (CyberMail rejects "Name <email>" format)
- deploy-runner.sh: rsync panel/api/ and panel/lib/ to web root after
  panel/public/ sync; also syncs panel/bin/ to /opt/novacpx/bin/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 04:40:16 +00:00
myron 2ab74b7569 feat(#25): email notifications via CyberMail
- Notifier.php: CyberMail API sender with 4 trigger types (account
  created, suspended, disk quota warning, SSL expiry)
- Reads cybermail_api_key / notify_from_* / notify_admin_email from
  settings table
- accounts.php: fires Notifier on create (welcome + admin alert) and
  suspend (user + admin alert)
- system.php: notify-settings GET, save-notify-settings POST,
  test-notify POST (with API key masking)
- bin/notify-checks.php: daily cron for disk ≥85% and SSL ≤14 days
  (flag-based dedup in settings table)
- admin panel: Notifications page with form + trigger reference table;
  sidebar link added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 04:12:47 +00:00
myron 33c36ffc65 Add #18 reseller white-label branding + #24 audit log UI with filters
#18: reseller_branding table (migration 008). branding.php endpoint: get/save/
     upload-logo/delete-logo/resellers. _branding.php server-side helper injects
     CSS vars (--primary, --accent), custom CSS, favicon, and panel name into
     <head> of reseller + user portals at page-load time (no flash of unbranded
     content). NOVACPX_BRANDING JS global carries panel_name/support_email/
     support_url/hide_powered_by for runtime use. Reseller panel gets a new
     "White Label" sidebar page with logo upload, color pickers with live preview,
     support contact fields, powered-by toggle, and custom CSS textarea.

#24: audit-log backend now accepts user/action/date_from/date_to filter params.
     auditLog() JS rebuilt: filter bar at top, paginated table, expandable detail
     rows (click row to show JSON detail), total entry count, page buttons.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 03:51:45 +00:00
myron dbc5a01de9 Fix #4-#8: mail virtual domains, DNS verified, reseller isolation, missing DB tables
#4: Postfix virtual mailbox config (virtual_mailbox_domains/maps, vmail user, maildir
    at /var/mail/vhosts/%d/%n). Dovecot SQL backend pointed at novacpx.email_accounts
    with SHA512-CRYPT passdb and per-domain Maildir userdb.

#5: BIND9 confirmed working — dig @localhost resolves testdomain1.com correctly.

#6: Certbot 2.9.0 confirmed installed; domains.document_root wired; infrastructure
    ready for live domain issuance (testdomain1.com not publicly resolvable so
    dry-run expected to fail).

#7: Fixed all broken user-panel API queries — missing tables (databases, ftp_accounts,
    ssl_certs, cron_jobs, php_configs, notifications) created; `databases` reserved-word
    backtick-quoted across DatabaseManager+endpoints; domains.php is_primary→type=main,
    doc_root→document_root column fixes; DNSManager::createZone call signature fixed;
    stats/account auto-resolves account_id for user role.

#8: assert_account_access() helper added to api/index.php; reseller ownership check
    wired into email, ftp, databases, domains, dns, ssl endpoints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 03:31:30 +00:00
myron d49095f4e8 fix: stats.php RAM% calculation key typo (MemTotal: not MemTotal_kB)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 03:01:39 +00:00
myron c0c9865653 feat: server monitoring charts, package limits, WHMCS bridge, server options (#19-22)
#19 Server monitoring charts:
- server_stats table (migration 007) + collect-stats.php cron script
- serverStatus() page rebuilt with Chart.js line charts (CPU/RAM/disk)
- Chart.js lazy-loaded from CDN; history shown for last 24h

#20 Cron job manager: already complete in prior session

#21 Package limits enforcement:
- email.php: checks max_email before creating email account
- databases.php: checks max_databases before creating database
- ftp.php: checks max_ftp before creating FTP account
- stats.php: fixed column names (max_email/max_ftp/max_databases vs old aliases)

#22b WHMCS billing bridge:
- whmcs.php endpoint: create/suspend/unsuspend/terminate/changepackage/info
- Auth via X-WHMCS-Key header; enabled/key stored in settings table

#22a/c/d/e Server options admin page:
- Web/mail/FTP/DNS server selection with settings persistence
- Server switch triggers /opt/novacpx/bin/switch-*.sh scripts (if present)
- NS health checker: live dig lookup of all zones vs configured NS1/NS2
- system.php: server-options + save-option actions
- dns.php: ns-health action

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