From 2af9e34fb0ae5f93642fe40503337b6bd776d396 Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Tue, 9 Jun 2026 16:23:51 +0000 Subject: [PATCH] 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 --- .github/workflows/version-bump.yml | 50 +++++++++++++++++++++ panel/api/endpoints/system.php | 71 ++++++++++++++++++++++++++++++ panel/public/admin/index.php | 4 +- panel/public/assets/js/admin.js | 43 +++++++++++++++++- 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/version-bump.yml diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml new file mode 100644 index 0000000..ad608e7 --- /dev/null +++ b/.github/workflows/version-bump.yml @@ -0,0 +1,50 @@ +name: Auto Version Bump + +on: + push: + branches: + - main + - beta + paths-ignore: + - 'VERSION' + - '.github/**' + +permissions: + contents: write + +jobs: + bump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Bump version + id: bump + run: | + BRANCH="${{ github.ref_name }}" + CURRENT=$(cat VERSION) + IFS='.' read -r MAJOR MINOR PATCH <<< "${CURRENT%%-*}" + PATCH=${PATCH:-0} + + if [ "$BRANCH" = "main" ]; then + NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" + else + # beta branch: append -beta.N + BETA_NUM=$(echo "$CURRENT" | grep -oP '(?<=beta\.)\d+' || echo "0") + NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}-beta.$((BETA_NUM + 1))" + fi + + echo "$NEW_VERSION" > VERSION + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "Bumped $CURRENT → $NEW_VERSION" + + - name: Commit version + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add VERSION + git commit -m "chore: bump version to ${{ steps.bump.outputs.version }} [skip ci]" + git push diff --git a/panel/api/endpoints/system.php b/panel/api/endpoints/system.php index bfa4dd1..92ad06e 100644 --- a/panel/api/endpoints/system.php +++ b/panel/api/endpoints/system.php @@ -370,6 +370,77 @@ BASH; Response::success(['service' => $svc, 'status' => $status]); })(), + // ── Installed service versions with latest available ────────────────────── + 'service-versions' => (function() { + Auth::getInstance()->require('admin'); + + // pkg => [label, description, systemd service name] + $catalog = [ + 'apache2' => ['Apache 2', 'Web server — serves all hosted websites on ports 80/443', 'apache2'], + 'nginx' => ['Nginx', 'High-performance web server / reverse proxy', 'nginx'], + 'php8.3' => ['PHP 8.3', 'Server-side scripting runtime (CLI + FPM)', 'php8.3-fpm'], + 'php8.2' => ['PHP 8.2', 'PHP 8.2 runtime (legacy support)', 'php8.2-fpm'], + 'php8.1' => ['PHP 8.1', 'PHP 8.1 runtime (legacy support)', 'php8.1-fpm'], + 'php7.4' => ['PHP 7.4', 'PHP 7.4 runtime (end-of-life compatibility)', 'php7.4-fpm'], + 'mysql-server' => ['MySQL', 'Relational database server (MariaDB/MySQL) for hosted sites', 'mysql'], + 'mariadb-server' => ['MariaDB', 'MySQL-compatible database server fork', 'mariadb'], + 'postfix' => ['Postfix', 'MTA — routes and delivers email for hosted domains', 'postfix'], + 'dovecot-core' => ['Dovecot', 'IMAP/POP3 server — lets users retrieve email via mail clients', 'dovecot'], + 'rspamd' => ['Rspamd', 'Spam filter that scores and rejects unwanted email', 'rspamd'], + 'proftpd' => ['ProFTPD', 'FTP server for account file transfers', 'proftpd'], + 'vsftpd' => ['vsftpd', 'Lightweight FTP server', 'vsftpd'], + 'bind9' => ['BIND9', 'Authoritative DNS server — serves zone files for hosted domains', 'named'], + 'fail2ban' => ['Fail2Ban', 'Intrusion prevention — bans IPs after repeated failed logins', 'fail2ban'], + 'ufw' => ['UFW', 'Uncomplicated Firewall — iptables front-end for port management', null], + 'certbot' => ['Certbot', "Let's Encrypt client — automates SSL certificate issuance", null], + 'sqlite3' => ['SQLite3', 'Embedded SQL database — stores NovaCPX panel data', null], + 'redis-server' => ['Redis', 'In-memory data store — used for caching and queuing', 'redis'], + 'memcached' => ['Memcached', 'Memory object cache', 'memcached'], + 'nodejs' => ['Node.js', 'JavaScript runtime — used by some web applications', null], + 'git' => ['Git', 'Version control system — used for site deployments', null], + 'curl' => ['curl', 'HTTP client — used by health checks and API calls', null], + ]; + + $dpkgOut = shell_exec("dpkg-query -W -f='\${Package} \${Version}\\n' 2>/dev/null") ?: ''; + $installed = []; + foreach (explode("\n", trim($dpkgOut)) as $line) { + [$pkg, $ver] = array_pad(explode(' ', $line, 2), 2, ''); + if ($pkg) $installed[$pkg] = trim($ver); + } + + $aptOut = shell_exec("apt-cache policy " . implode(' ', array_keys($catalog)) . " 2>/dev/null") ?: ''; + $latest = []; + $curPkg = null; + foreach (explode("\n", $aptOut) as $line) { + if (preg_match('/^(\S+):$/', trim($line), $m)) { $curPkg = $m[1]; continue; } + if ($curPkg && preg_match('/Candidate:\s+(.+)/', $line, $m)) { + $latest[$curPkg] = trim($m[1]); + $curPkg = null; + } + } + + $services = []; + foreach ($catalog as $pkg => [$label, $desc, $svc]) { + $ver = $installed[$pkg] ?? null; + if (!$ver) continue; // skip not-installed + $latestVer = $latest[$pkg] ?? 'unknown'; + $status = $svc ? trim(shell_exec("systemctl is-active $svc 2>/dev/null") ?: 'inactive') : null; + $upToDate = ($latestVer === 'unknown' || $latestVer === '(none)') ? null + : (version_compare($ver, $latestVer, '>=')); + $services[] = [ + 'pkg' => $pkg, + 'label' => $label, + 'desc' => $desc, + 'installed' => $ver, + 'latest' => $latestVer, + 'up_to_date' => $upToDate, + 'status' => $status, + ]; + } + + Response::success(['services' => $services]); + })(), + // ── Service control (start/stop/restart) ────────────────────────────────── 'service' => (function() use ($body, $db) { Auth::getInstance()->require('admin'); diff --git a/panel/public/admin/index.php b/panel/public/admin/index.php index 9c9a225..5a6691f 100644 --- a/panel/public/admin/index.php +++ b/panel/public/admin/index.php @@ -21,7 +21,9 @@ require_once dirname(__DIR__) . '/_branding.php';