Commit Graph

201 Commits

Author SHA1 Message Date
github-actions[bot] d53eb309eb chore: bump version to 1.0.13 [skip ci] 2026-06-09 22:31:02 +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
github-actions[bot] 9dd75bfff8 chore: bump version to 1.0.12 [skip ci] 2026-06-09 22:25:00 +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
github-actions[bot] 2f141973c1 chore: bump version to 1.0.11 [skip ci] 2026-06-09 22:23:55 +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
github-actions[bot] 0c32c8a018 chore: bump version to 1.0.10 [skip ci] 2026-06-09 22:18:49 +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
github-actions[bot] cdb92c5ef2 chore: bump version to 1.0.9 [skip ci] 2026-06-09 22:15:52 +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
github-actions[bot] b90ef41677 chore: bump version to 1.0.8 [skip ci] 2026-06-09 22:13:39 +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
github-actions[bot] 071f32ce76 chore: bump version to 1.0.7 [skip ci] 2026-06-09 22:10:06 +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
github-actions[bot] 24e0ce3633 chore: bump version to 1.0.6 [skip ci] 2026-06-09 22:04:29 +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
github-actions[bot] 978ea6082c chore: bump version to 1.0.5 [skip ci] 2026-06-09 18:58:00 +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
github-actions[bot] 8a11458220 chore: bump version to 1.0.4 [skip ci] 2026-06-09 18:42:41 +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
github-actions[bot] 8716299201 chore: bump version to 1.0.3 [skip ci] 2026-06-09 18:32:26 +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
github-actions[bot] 563386b8b8 chore: bump version to 1.0.2 [skip ci] 2026-06-09 18:22:22 +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 601155aa6b Add GitHub Actions workflow for automatic version bumping
Increments PATCH on push to main (1.0.1 → 1.0.2), appends -beta.N on
beta branch pushes.  Uses [skip ci] to prevent infinite loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:15:30 +00:00
myron 5aaeaa9a35 Fix deploy-runner: SQLite migration tracking, re-create webhook symlink after rsync
Switches migration tracking from MySQL to SQLite (panel.db), reads DB path
from config.ini with fallback to /var/lib/novacpx/panel.db.  Re-creates the
webhook symlink after each rsync deploy so it survives --delete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 18:09:02 +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 99cdc6f3dc chore: exclude .github/workflows from PAT push [skip ci] 2026-06-09 18:00:03 +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 b295f8ca8e chore: remove workflow from push (PAT lacks workflow scope) [skip ci] 2026-06-09 16:24:07 +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 c01a03645d Fix server_stats column names to match PHP code (cpu_usage/ram_usage/disk_usage/load_avg) 2026-06-09 15:00:10 +00:00
myron 1ebd146eb5 Fix sessions table: add impersonator_id column 2026-06-09 14:56:33 +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
myron db1f6b8bb8 Fix proxy settings modal never saving — wrong third arg to Nova.modal
Nova.modal(title, bodyHtml, footerHtml) expects footerHtml as an HTML
string, but an async callback was passed instead. The function got
stringified as garbage text in the footer with no save button, so
nothing ever saved regardless of mode chosen.

Replaced with proper footer HTML (Cancel + Save buttons) and wired the
save logic as an event listener on the save button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 11:12:29 +00:00
myron 98f6a0700c Fix NovaCPX update action name mismatch and progress bar null error
- admin.js was calling 'apply-novacpx-update' but endpoint is 'apply-update'
- Add null guard in nova.js progress bar setInterval — _barEl can be set to
  null by the fade-out timer between interval ticks, causing a crash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 11:03:04 +00:00
myron 18c5989c17 Fix constant reload loop caused by 401 redirect in nova.js
The redirect on 401 sent the browser back to /?redirect=... on the same
panel port, which re-served the panel page itself — causing an infinite
reload. The panel pages (admin/reseller/user) already have inline login
forms that activate when auth/me fails, so no redirect is needed.
Return a failure object on 401 so the existing login form shows instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:59:27 +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 4409a94d78 Fix install.sh gaps and add missing schema tables
- Add sshpass to base packages (required by ProxyManager remote SSH)
- Add PORT_WEBMAIL to Apache ports.conf listen loop (was missing port 8883)
- Add systemctl nginx/apache2 to www-data sudoers (local proxy mode needs these)
- Fix cron to use real script paths: bin/collect-stats.php + bin/notify-checks.php
- Seed proxy_mode=disabled and proxy_apache_port=80 defaults after schema import
- Add api_rate_limits table (rate limiting middleware requires it)
- Add proxy_hosts table (ProxyManager requires it for host CRUD)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:40:37 +00:00
myron a2daaa1ea3 Fix SSH known_hosts permission error when running as www-data
Add -o UserKnownHostsFile=/dev/null to remoteExec so SSH doesn't
attempt to write to /var/www/.ssh/known_hosts (permission denied).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 10:35: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