From 3d0d10ea41f6b85c0399908525780b79429331d4 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 29 Dec 2025 00:16:01 +0200 Subject: [PATCH] php version manager attempt #1 --- app/Http/Controllers/PHPManagerController.php | 149 ++++++++++++ laranode-scripts/bin/laranode-php-install.sh | 47 ++++ laranode-scripts/bin/laranode-php-list.sh | 38 ++++ laranode-scripts/bin/laranode-php-service.sh | 43 ++++ .../bin/laranode-php-uninstall.sh | 37 +++ resources/js/Layouts/Partials/SidebarNavi.jsx | 4 +- resources/js/Pages/PHP/Index.jsx | 212 ++++++++++++++++++ .../js/Pages/PHP/Partials/InstallPHPForm.jsx | 92 ++++++++ routes/web.php | 8 + 9 files changed, 627 insertions(+), 3 deletions(-) create mode 100755 laranode-scripts/bin/laranode-php-install.sh create mode 100755 laranode-scripts/bin/laranode-php-list.sh create mode 100755 laranode-scripts/bin/laranode-php-service.sh create mode 100755 laranode-scripts/bin/laranode-php-uninstall.sh create mode 100644 resources/js/Pages/PHP/Index.jsx create mode 100644 resources/js/Pages/PHP/Partials/InstallPHPForm.jsx diff --git a/app/Http/Controllers/PHPManagerController.php b/app/Http/Controllers/PHPManagerController.php index a5a0101..e2881fa 100644 --- a/app/Http/Controllers/PHPManagerController.php +++ b/app/Http/Controllers/PHPManagerController.php @@ -15,4 +15,153 @@ class PHPManagerController extends Controller return response()->json($versions); } + + /** + * Render the PHP management page + */ + public function index() + { + return inertia('PHP/Index'); + } + + /** + * List all installed PHP versions with their systemctl status + */ + public function list(): JsonResponse + { + $scriptPath = base_path('laranode-scripts/bin/laranode-php-list.sh'); + $output = shell_exec("sudo bash {$scriptPath}"); + + $phpVersions = json_decode($output, true) ?? []; + + return response()->json($phpVersions); + } + + /** + * Install a new PHP version + */ + public function install(Request $request): JsonResponse + { + $request->validate([ + 'version' => 'required|string|regex:/^\d+\.\d+$/', + ]); + + $version = $request->input('version'); + $scriptPath = base_path('laranode-scripts/bin/laranode-php-install.sh'); + + // Execute installation script + $output = shell_exec("sudo bash {$scriptPath} {$version} 2>&1"); + + // Check if installation was successful + if (strpos($output, 'installed successfully') !== false) { + return response()->json([ + 'success' => true, + 'message' => "PHP {$version} installed successfully", + 'output' => $output + ]); + } + + return response()->json([ + 'success' => false, + 'message' => "Failed to install PHP {$version}", + 'output' => $output + ], 500); + } + + /** + * Uninstall a PHP version + */ + public function uninstall(Request $request): JsonResponse + { + $request->validate([ + 'version' => 'required|string|regex:/^\d+\.\d+$/', + ]); + + $version = $request->input('version'); + $scriptPath = base_path('laranode-scripts/bin/laranode-php-uninstall.sh'); + + // Execute uninstallation script + $output = shell_exec("sudo bash {$scriptPath} {$version} 2>&1"); + + // Check if uninstallation was successful + if (strpos($output, 'uninstalled successfully') !== false) { + return response()->json([ + 'success' => true, + 'message' => "PHP {$version} uninstalled successfully", + 'output' => $output + ]); + } + + return response()->json([ + 'success' => false, + 'message' => "Failed to uninstall PHP {$version}", + 'output' => $output + ], 500); + } + + /** + * Toggle PHP-FPM service (enable/disable) + */ + public function toggleService(Request $request): JsonResponse + { + $request->validate([ + 'version' => 'required|string|regex:/^\d+\.\d+$/', + 'enabled' => 'required|boolean', + ]); + + $version = $request->input('version'); + $enabled = $request->input('enabled'); + $action = $enabled ? 'enable' : 'disable'; + + $scriptPath = base_path('laranode-scripts/bin/laranode-php-service.sh'); + + // Execute service management script + $output = shell_exec("sudo bash {$scriptPath} {$action} {$version} 2>&1"); + + // Check if action was successful + if (strpos($output, 'completed successfully') !== false) { + return response()->json([ + 'success' => true, + 'message' => "PHP {$version}-FPM service {$action}d successfully", + 'output' => $output + ]); + } + + return response()->json([ + 'success' => false, + 'message' => "Failed to {$action} PHP {$version}-FPM service", + 'output' => $output + ], 500); + } + + /** + * Restart PHP-FPM service + */ + public function restartService(Request $request): JsonResponse + { + $request->validate([ + 'version' => 'required|string|regex:/^\d+\.\d+$/', + ]); + + $version = $request->input('version'); + $scriptPath = base_path('laranode-scripts/bin/laranode-php-service.sh'); + + // Execute restart script + $output = shell_exec("sudo bash {$scriptPath} restart {$version} 2>&1"); + + // Check if restart was successful + if (strpos($output, 'completed successfully') !== false) { + return response()->json([ + 'success' => true, + 'message' => "PHP {$version}-FPM service restarted successfully", + 'output' => $output + ]); + } + + return response()->json([ + 'success' => false, + 'message' => "Failed to restart PHP {$version}-FPM service", + 'output' => $output + ], 500); + } } diff --git a/laranode-scripts/bin/laranode-php-install.sh b/laranode-scripts/bin/laranode-php-install.sh new file mode 100755 index 0000000..4c1726c --- /dev/null +++ b/laranode-scripts/bin/laranode-php-install.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Install a PHP-FPM version with common extensions +# Usage: ./laranode-php-install.sh {version} +# Example: ./laranode-php-install.sh 8.4 + +if [ $# -lt 1 ]; then + echo "Usage: $0 {php version: example 8.4}" + exit 1 +fi + +PHP_VERSION=$1 + +echo "Installing PHP $PHP_VERSION-FPM..." + +# Update apt cache +apt-get update -qq + +# Install PHP-FPM and common extensions +apt-get install -y \ + php${PHP_VERSION}-fpm \ + php${PHP_VERSION}-cli \ + php${PHP_VERSION}-common \ + php${PHP_VERSION}-mysql \ + php${PHP_VERSION}-xml \ + php${PHP_VERSION}-curl \ + php${PHP_VERSION}-mbstring \ + php${PHP_VERSION}-zip \ + php${PHP_VERSION}-gd \ + php${PHP_VERSION}-bcmath \ + php${PHP_VERSION}-intl + +if [ $? -eq 0 ]; then + echo "PHP $PHP_VERSION installed successfully" + + # Enable the service + systemctl enable php${PHP_VERSION}-fpm + + # Start the service + systemctl start php${PHP_VERSION}-fpm + + echo "PHP $PHP_VERSION-FPM service enabled and started" + exit 0 +else + echo "Failed to install PHP $PHP_VERSION" + exit 1 +fi diff --git a/laranode-scripts/bin/laranode-php-list.sh b/laranode-scripts/bin/laranode-php-list.sh new file mode 100755 index 0000000..2b02ecf --- /dev/null +++ b/laranode-scripts/bin/laranode-php-list.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# List all installed PHP-FPM versions with their systemctl status +# Output: JSON array of PHP versions with status + +# Get all installed php*-fpm packages +php_versions=$(dpkg -l | grep -E 'php[0-9]+\.[0-9]+-fpm' | awk '{print $2}' | sed 's/php\(.*\)-fpm/\1/') + +echo "[" +first=true + +for version in $php_versions; do + # Get systemctl status + if systemctl is-active --quiet "php${version}-fpm"; then + status="active" + else + status="inactive" + fi + + # Get systemctl enabled status + if systemctl is-enabled --quiet "php${version}-fpm" 2>/dev/null; then + enabled="true" + else + enabled="false" + fi + + # Output JSON object + if [ "$first" = true ]; then + first=false + else + echo "," + fi + + echo -n " {\"version\": \"$version\", \"status\": \"$status\", \"enabled\": $enabled}" +done + +echo "" +echo "]" diff --git a/laranode-scripts/bin/laranode-php-service.sh b/laranode-scripts/bin/laranode-php-service.sh new file mode 100755 index 0000000..3a7f212 --- /dev/null +++ b/laranode-scripts/bin/laranode-php-service.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Manage PHP-FPM service (enable/disable/restart) +# Usage: ./laranode-php-service.sh {action} {version} +# Example: ./laranode-php-service.sh enable 8.4 + +if [ $# -lt 2 ]; then + echo "Usage: $0 {action: enable|disable|restart} {php version: example 8.4}" + exit 1 +fi + +ACTION=$1 +PHP_VERSION=$2 + +case $ACTION in + enable) + echo "Enabling PHP $PHP_VERSION-FPM service..." + systemctl enable php${PHP_VERSION}-fpm + systemctl start php${PHP_VERSION}-fpm + ;; + disable) + echo "Disabling PHP $PHP_VERSION-FPM service..." + systemctl stop php${PHP_VERSION}-fpm + systemctl disable php${PHP_VERSION}-fpm + ;; + restart) + echo "Restarting PHP $PHP_VERSION-FPM service..." + systemctl restart php${PHP_VERSION}-fpm + ;; + *) + echo "Invalid action: $ACTION" + echo "Valid actions: enable, disable, restart" + exit 1 + ;; +esac + +if [ $? -eq 0 ]; then + echo "Action '$ACTION' completed successfully for PHP $PHP_VERSION-FPM" + exit 0 +else + echo "Failed to $ACTION PHP $PHP_VERSION-FPM" + exit 1 +fi diff --git a/laranode-scripts/bin/laranode-php-uninstall.sh b/laranode-scripts/bin/laranode-php-uninstall.sh new file mode 100755 index 0000000..146150d --- /dev/null +++ b/laranode-scripts/bin/laranode-php-uninstall.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Uninstall a PHP-FPM version +# Usage: ./laranode-php-uninstall.sh {version} +# Example: ./laranode-php-uninstall.sh 8.4 + +if [ $# -lt 1 ]; then + echo "Usage: $0 {php version: example 8.4}" + exit 1 +fi + +PHP_VERSION=$1 + +echo "Uninstalling PHP $PHP_VERSION-FPM..." + +# Stop the service +systemctl stop php${PHP_VERSION}-fpm + +# Disable the service +systemctl disable php${PHP_VERSION}-fpm + +# Remove PHP packages +apt-get remove -y php${PHP_VERSION}-* + +# Purge configuration files +apt-get purge -y php${PHP_VERSION}-* + +# Clean up +apt-get autoremove -y + +if [ $? -eq 0 ]; then + echo "PHP $PHP_VERSION uninstalled successfully" + exit 0 +else + echo "Failed to uninstall PHP $PHP_VERSION" + exit 1 +fi diff --git a/resources/js/Layouts/Partials/SidebarNavi.jsx b/resources/js/Layouts/Partials/SidebarNavi.jsx index 3ea3d6f..e210927 100644 --- a/resources/js/Layouts/Partials/SidebarNavi.jsx +++ b/resources/js/Layouts/Partials/SidebarNavi.jsx @@ -107,11 +107,10 @@ const SidebarNavi = () => { - {/* Postponed for next release {auth.user.role == 'admin' && (
  • @@ -121,7 +120,6 @@ const SidebarNavi = () => {
  • )} - */}
  • { + fetch(route('php.list'), { + headers: { + 'Accept': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + setPhpVersions(data); + setLoading(false); + }) + .catch(() => { + toast.error('Failed to fetch PHP versions'); + setLoading(false); + }); + }; + + useEffect(() => { + fetchPhpVersions(); + + // Subscribe to live stats + const dashboardChannel = echo.private("systemstats"); + + dashboardChannel.listen("SystemStatsEvent", (data) => { + if (data.phpFpm) { + setLiveStats(data.phpFpm); + } + }); + + const whisperInterval = setInterval(() => { + dashboardChannel.whisper("typing", { requesting: "dashboard-realtime-stats" }); + }, 2000); + + return () => { + clearInterval(whisperInterval); + echo.leave("systemstats"); + }; + }, []); + + const uninstallPhp = (version) => { + router.delete(route('php.uninstall'), { + data: { version }, + onBefore: () => toast('Uninstalling PHP ' + version + '...'), + onSuccess: () => { + toast.success('PHP ' + version + ' uninstalled successfully'); + fetchPhpVersions(); + }, + onError: () => toast.error('Failed to uninstall PHP ' + version), + }); + }; + + const toggleService = (version, currentEnabled) => { + const enabled = !currentEnabled; + const action = enabled ? 'enable' : 'disable'; + + router.post(route('php.service.toggle'), + { version, enabled }, + { + onBefore: () => toast(`${action === 'enable' ? 'Enabling' : 'Disabling'} PHP ${version}-FPM...`), + onSuccess: () => { + toast.success(`PHP ${version}-FPM ${action}d successfully`); + fetchPhpVersions(); + }, + onError: () => toast.error(`Failed to ${action} PHP ${version}-FPM`), + } + ); + }; + + const restartService = (version) => { + router.post(route('php.service.restart'), + { version }, + { + onBefore: () => toast('Restarting PHP ' + version + '-FPM...'), + onSuccess: () => { + toast.success('PHP ' + version + '-FPM restarted successfully'); + fetchPhpVersions(); + }, + onError: () => toast.error('Failed to restart PHP ' + version + '-FPM'), + } + ); + }; + + return ( + +

    + + PHP Versions +

    + + + } + > + + +
    + {loading ? ( +
    + Loading PHP versions... +
    + ) : ( +
    + + + + + + + + + + + + + {phpVersions.length === 0 ? ( + + + + ) : ( + phpVersions.map((php, index) => { + const stats = liveStats[php.version] || {}; + return ( + + + + + + + + + ); + }) + )} + +
    VersionStatusMemoryCPU TimeUptimeActions
    + No PHP versions installed +
    +
    + + PHP {php.version} +
    +
    +
    + {php.status === 'active' ? 'Active' : 'Inactive'} +
    +
    + {stats.memory || '--'} + + {stats.cpuTime || '--'} + + {stats.uptime || '--'} + +
    + toggleService(php.version, php.enabled)}> + + + + restartService(php.version)}> + + + + uninstallPhp(php.version)}> + + +
    +
    +
    + )} +
    +
    + ); +} diff --git a/resources/js/Pages/PHP/Partials/InstallPHPForm.jsx b/resources/js/Pages/PHP/Partials/InstallPHPForm.jsx new file mode 100644 index 0000000..24eeb10 --- /dev/null +++ b/resources/js/Pages/PHP/Partials/InstallPHPForm.jsx @@ -0,0 +1,92 @@ +import { useState } from 'react'; +import Modal from '@/Components/Modal'; +import PrimaryButton from '@/Components/PrimaryButton'; +import SecondaryButton from '@/Components/SecondaryButton'; +import { router } from '@inertiajs/react'; +import { toast } from 'react-toastify'; +import { TbBrandPhp } from 'react-icons/tb'; + +export default function InstallPHPForm() { + const [showModal, setShowModal] = useState(false); + const [version, setVersion] = useState(''); + const [isInstalling, setIsInstalling] = useState(false); + + const availableVersions = ['8.4', '8.3', '8.2', '8.1', '8.0', '7.4']; + + const handleInstall = () => { + if (!version) { + toast.error('Please select a PHP version'); + return; + } + + setIsInstalling(true); + + router.post(route('php.install'), + { version }, + { + onBefore: () => toast('Installing PHP ' + version + '...'), + onSuccess: () => { + toast.success('PHP ' + version + ' installed successfully'); + setShowModal(false); + setVersion(''); + router.reload(); + }, + onError: (errors) => { + toast.error('Failed to install PHP ' + version); + console.error(errors); + }, + onFinish: () => setIsInstalling(false), + } + ); + }; + + return ( + <> + setShowModal(true)}> + + Install New Version + + + !isInstalling && setShowModal(false)}> +
    +

    + Install PHP Version +

    + +

    + Select a PHP version to install. This will install PHP-FPM and common extensions. +

    + +
    + + +
    + +
    + setShowModal(false)} disabled={isInstalling}> + Cancel + + + {isInstalling ? 'Installing...' : 'Install'} + +
    +
    +
    + + ); +} diff --git a/routes/web.php b/routes/web.php index 7c640d8..285abb5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -35,7 +35,15 @@ Route::post('/websites/{website}/ssl/toggle', [WebsiteController::class, 'toggle Route::get('/websites/{website}/ssl/status', [WebsiteController::class, 'checkSslStatus'])->middleware(['auth'])->name('websites.ssl.status'); // PHP FPM Pools [Admin | User] +Route::get('/php', [PHPManagerController::class, 'index'])->middleware(['auth', AdminMiddleware::class])->name('php.index'); Route::get('/php/get-versions', [PHPManagerController::class, 'getVersions'])->middleware(['auth'])->name('php.get-versions'); +Route::get('/php/list', [PHPManagerController::class, 'list'])->middleware(['auth', AdminMiddleware::class])->name('php.list'); +Route::post('/php/install', [PHPManagerController::class, 'install'])->middleware(['auth', AdminMiddleware::class])->name('php.install'); +Route::delete('/php/uninstall', [PHPManagerController::class, 'uninstall'])->middleware(['auth', AdminMiddleware::class])->name('php.uninstall'); +Route::post('/php/service/toggle', [PHPManagerController::class, 'toggleService'])->middleware(['auth', AdminMiddleware::class])->name('php.service.toggle'); +Route::post('/php/service/restart', [PHPManagerController::class, 'restartService'])->middleware(['auth', AdminMiddleware::class])->name('php.service.restart'); + + // MySQL management [Admin | User] Route::get('/mysql', [MysqlController::class, 'index'])->middleware(['auth'])->name('mysql.index');