diff --git a/.gitignore b/.gitignore index f72249a83..feb1500a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Ignore dependencies and cache +/.idea /node_modules /vendor /storage/*.key diff --git a/app/Console/Commands/GetSettingCommand.php b/app/Console/Commands/GetSettingCommand.php new file mode 100644 index 000000000..9be2191ad --- /dev/null +++ b/app/Console/Commands/GetSettingCommand.php @@ -0,0 +1,49 @@ +argument('class'); + $key = $this->argument('key'); + $sameline = $this->option('sameline'); + + try { + $settings_class = "App\\Settings\\$class"; + $settings = new $settings_class(); + + $this->output->write($settings->$key, !$sameline); + + return Command::SUCCESS; + } catch (\Throwable $th) { + $this->error('Error: ' . $th->getMessage()); + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/app/Console/Commands/SetSettingCommand.php b/app/Console/Commands/SetSettingCommand.php new file mode 100644 index 000000000..03481f93e --- /dev/null +++ b/app/Console/Commands/SetSettingCommand.php @@ -0,0 +1,52 @@ +argument('class'); + $key = $this->argument('key'); + $value = $this->argument('value'); + + try { + $settings_class = "App\\Settings\\$class"; + $settings = new $settings_class(); + + $settings->$key = $value; + + $settings->save(); + + $this->info("Successfully updated '$key'."); + } catch (\Throwable $th) { + $this->error('Error: ' . $th->getMessage()); + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d0b1c7f7e..cd7cef5fb 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -4,8 +4,7 @@ use App\Http\Middleware\ApiAuthToken; use App\Http\Middleware\CheckSuspended; -use App\Http\Middleware\isAdmin; -use App\Http\Middleware\isMod; +use App\Http\Middleware\InstallerLock; use App\Http\Middleware\LastSeen; use Illuminate\Foundation\Http\Kernel as HttpKernel; @@ -36,6 +35,7 @@ class Kernel extends HttpKernel */ protected $middlewareGroups = [ 'web' => [ + InstallerLock::class, \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, @@ -70,8 +70,6 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, - 'admin' => isAdmin::class, - 'moderator' => isMod::class, 'api.token' => ApiAuthToken::class, 'checkSuspended' => CheckSuspended::class, 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, diff --git a/app/Http/Middleware/InstallerLock.php b/app/Http/Middleware/InstallerLock.php new file mode 100644 index 000000000..9390a598c --- /dev/null +++ b/app/Http/Middleware/InstallerLock.php @@ -0,0 +1,24 @@ +getMessage(), 'error'); - header('LOCATION: index.php?step=2&message=' . $e->getMessage()); + header('LOCATION: index.php?step=3&message=' . $e->getMessage()); exit(); } @@ -44,24 +55,25 @@ setenv($key, $param); } - wh_log('Database connection successful', 'debug'); - header('LOCATION: index.php?step=2.5'); -} + wh_log('Start APP_KEY generation', 'debug'); -if (isset($_POST['checkGeneral'])) { - wh_log('setting app settings', 'debug'); - $appname = '"' . $_POST['name'] . '"'; - $appurl = $_POST['url']; + try { + if (!str_contains(getenv('APP_KEY'), 'base64')) { + $logs = run_console('php artisan key:generate --force'); + wh_log($logs, 'debug'); - if (substr($appurl, -1) === '/') { - $appurl = substr_replace($appurl, '', -1); + wh_log('Created APP_KEY successful', 'debug'); + } else { + wh_log('Key already exists. Skipping', 'debug'); + } + } catch (Throwable $th) { + wh_log('Creating APP_KEY failed', 'error'); + header("LOCATION: index.php?step=3&message=" . $th->getMessage() . "
Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + exit(); } - setenv('APP_NAME', $appname); - setenv('APP_URL', $appurl); - - wh_log('App settings set', 'debug'); - header('LOCATION: index.php?step=4'); + wh_log('Database connection successful', 'debug'); + header('LOCATION: index.php?step=3.5'); } if (isset($_POST['feedDB'])) { @@ -71,11 +83,6 @@ try { //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); //$logs .= run_console('composer install --no-dev --optimize-autoloader'); - if (!str_contains(getenv('APP_KEY'), 'base64')) { - $logs .= run_console('php artisan key:generate --force'); - } else { - $logs .= "Key already exists. Skipping\n"; - } $logs .= run_console('php artisan storage:link'); $logs .= run_console('php artisan migrate --seed --force'); $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); @@ -84,40 +91,98 @@ wh_log($logs, 'debug'); wh_log('Feeding the Database successful', 'debug'); - header('LOCATION: index.php?step=3'); - } catch (\Throwable $th) { + header('LOCATION: index.php?step=4'); + } catch (Throwable $th) { wh_log('Feeding the Database failed', 'error'); - header("LOCATION: index.php?step=2.5&message=" . $th->getMessage() . "
Please check the installer.log file in /var/www/ctrlpanel/storage/logs !"); + header("LOCATION: index.php?step=3.5&message=" . $th->getMessage() . "
Please check the installer.log file in /var/www/controlpanel/storage/logs !"); } } +if (isset($_POST['redisSetup'])) { + wh_log('Setting up Redis', 'debug'); + $redisHost = $_POST['redishost']; + $redisPort = $_POST['redisport']; + $redisPassword = $_POST['redispassword']; + + $redisClient = new Client([ + 'host' => $redisHost, + 'port' => $redisPort, + 'password' => $redisPassword, + 'timeout' => 1.0, + ]); + + try { + $redisClient->ping(); + + setenv('MEMCACHED_HOST', $redisHost); + setenv('REDIS_HOST', $redisHost); + setenv('REDIS_PORT', $redisPort); + setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); + + wh_log('Redis connection successful. Settings updated.', 'debug'); + header('LOCATION: index.php?step=5'); + } catch (Throwable $th) { + wh_log('Redis connection failed. Settings updated.', 'debug'); + header("LOCATION: index.php?step=4&message=Please check your credentials!
" . $th->getMessage()); + } +} + +if (isset($_POST['checkGeneral'])) { + wh_log('setting app settings', 'debug'); + $appname = '"' . $_POST['name'] . '"'; + $appurl = $_POST['url']; + + $parsedUrl = parse_url($appurl); + + if (!isset($parsedUrl['scheme'])) { + header('LOCATION: index.php?step=5&message=Please set an URL Scheme like "https://"!'); + exit(); + } + + if (!isset($parsedUrl['host'])) { + header('LOCATION: index.php?step=5&message=Please set an valid URL host like "https://ctrlpanel.example.com"!'); + exit(); + } + + $appurl = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; + + setenv('APP_NAME', $appname); + setenv('APP_URL', $appurl); + + wh_log('App settings set', 'debug'); + header('LOCATION: index.php?step=6'); +} + if (isset($_POST['checkSMTP'])) { wh_log('Checking SMTP Settings', 'debug'); try { $mail = new PHPMailer(true); //Server settings - $mail->isSMTP(); // Send using SMTP - $mail->Host = $_POST['host']; // Set the SMTP server to send through - $mail->SMTPAuth = true; // Enable SMTP authentication - $mail->Username = $_POST['user']; // SMTP username - $mail->Password = $_POST['pass']; // SMTP password - $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged - $mail->Port = $_POST['port']; // TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` - - //Recipients + // Send using SMTP + $mail->isSMTP(); + $mail->Host = $_POST['host']; + // Enable SMTP authentication + $mail->SMTPAuth = true; + $mail->Username = $_POST['user']; + $mail->Password = $_POST['pass']; + $mail->SMTPSecure = $_POST['encryption']; + $mail->Port = (int) $_POST['port']; + + // Test E-mail metadata $mail->setFrom($_POST['user'], $_POST['user']); - $mail->addAddress($_POST['user'], $_POST['user']); // Add a recipient + $mail->addAddress($_POST['user'], $_POST['user']); // Content - $mail->isHTML(true); // Set email format to HTML - $mail->Subject = 'It Worked!'; + // Set email format to HTML + $mail->isHTML(true); + $mail->Subject = 'It Worked! - Test E-Mail from Ctrlpanel.gg'; $mail->Body = 'Your E-Mail Settings are correct!'; $mail->send(); } catch (Exception $e) { wh_log($mail->ErrorInfo, 'error'); - header('LOCATION: index.php?step=4&message=Something wasnt right when sending the E-Mail!'); + header('LOCATION: index.php?step=6&message=Something went wrong while sending test E-Mail!
' . $mail->ErrorInfo); exit(); } @@ -126,7 +191,7 @@ $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); if ($db->connect_error) { wh_log($db->connect_error, 'error'); - header('LOCATION: index.php?step=4&message=Could not connect to the Database: '); + header('LOCATION: index.php?step=6&message=Could not connect to the Database: '); exit(); } $values = [ @@ -140,12 +205,11 @@ ]; foreach ($values as $key => $value) { - $query = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '$value' WHERE `name` = '$key' AND `group` = 'mail'"; - $db->query($query); + run_console("php artisan settings:set 'MailSettings' '$key' '$value'"); } wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=5'); + header('LOCATION: index.php?step=7'); } if (isset($_POST['checkPtero'])) { @@ -155,10 +219,20 @@ $key = $_POST['key']; $clientkey = $_POST['clientkey']; - if (substr($url, -1) === '/') { - $url = substr_replace($url, '', -1); + $parsedUrl = parse_url($url); + + if (!isset($parsedUrl['scheme'])) { + header('LOCATION: index.php?step=7&message=Please set an URL Scheme like "https://"!'); + exit(); + } + + if (!isset($parsedUrl['host'])) { + header('LOCATION: index.php?step=7&message=Please set an valid URL host like "https://panel.example.com"!'); + exit(); } + $url = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; + $callpteroURL = $url . '/api/client/account'; $call = curl_init(); @@ -171,7 +245,7 @@ ]); $callresponse = curl_exec($call); $callresult = json_decode($callresponse, true); - curl_close($call); // Close the connection + curl_close($call); $pteroURL = $url . '/api/application/users'; $ch = curl_init(); @@ -185,50 +259,46 @@ ]); $response = curl_exec($ch); $result = json_decode($response, true); - curl_close($ch); // Close the connection + curl_close($ch); + + if (!is_array($result)) { + wh_log('No array in response found', 'error'); + header('LOCATION: index.php?step=7&message=An unknown Error occured, please try again!'); + } - if (!is_array($result) and $result['errors'][0] !== null) { - header('LOCATION: index.php?step=5&message=Couldn\'t connect to Pterodactyl. Make sure your API key has all read and write permissions!'); + if (array_key_exists('errors', $result) && $result['errors'][0]['detail'] === 'This action is unauthorized.') { wh_log('API CALL ERROR: ' . $result['errors'][0]['code'], 'error'); + header('LOCATION: index.php?step=7&message=Couldn\'t connect to Pterodactyl. Make sure your Application API key has all read and write permissions!'); exit(); - } elseif (!is_array($callresult) and $callresult['errors'][0] !== null or $callresult['attributes']['admin'] == false) { - header('LOCATION: index.php?step=5&message=Your ClientAPI Key is wrong or the account is not an admin!'); + } + + if (array_key_exists('errors', $callresult) && $callresult['errors'][0]['detail'] === 'Unauthenticated.') { wh_log('API CALL ERROR: ' . $callresult['errors'][0]['code'], 'error'); + header('LOCATION: index.php?step=7&message=Your ClientAPI Key is wrong or the account is not an admin!'); exit(); - } else { - wh_log('Pterodactyl Settings are correct', 'debug'); - wh_log('Updating Database', 'debug'); - - $key = $key; - $clientkey = $clientkey; - - $query1 = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '" . json_encode($url) . "' WHERE (`name` = 'panel_url' AND `group` = 'pterodactyl')"; - $query2 = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '" . json_encode($key) . "' WHERE (`name` = 'admin_token' AND `group` = 'pterodactyl')"; - $query3 = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '" . json_encode($clientkey) . "' WHERE (`name` = 'user_token' AND `group` = 'pterodactyl')"; - - $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); - if ($db->connect_error) { - wh_log($db->connect_error, 'error'); - header('LOCATION: index.php?step=5&message=Could not connect to the Database'); - exit(); - } + } - if ($db->query($query1) && $db->query($query2) && $db->query($query3)) { - wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=6'); - } else { - wh_log($db->error, 'error'); - header('LOCATION: index.php?step=5&message=Something went wrong when communicating with the Database!'); - } + try { + run_console("php artisan settings:set 'PterodactylSettings' 'panel_url' '$url'"); + run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'"); + run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); + wh_log('Database updated', 'debug'); + header('LOCATION: index.php?step=8'); + } catch (Throwable $th) { + wh_log("Setting Pterodactyl information failed.", 'error'); + header("LOCATION: index.php?step=7&message=" . $th->getMessage() . "
Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + exit(); } } if (isset($_POST['createUser'])) { - wh_log('Creating User', 'debug'); - $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); - if ($db->connect_error) { - wh_log($db->connect_error, 'error'); - header('LOCATION: index.php?step=6&message=Could not connect to the Database'); + wh_log('Getting Pterodactyl User', 'debug'); + + try { + $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); + } catch (Throwable $th) { + wh_log($th->getMessage(), 'error'); + header('LOCATION: index.php?step=8&message=Could not connect to the Database'); exit(); } @@ -236,30 +306,37 @@ $pass = $_POST['pass']; $repass = $_POST['repass']; - $key = $db->query('SELECT `payload` FROM `' . getenv('DB_DATABASE') . "`.`settings` WHERE `name` = 'admin_token' AND `group` = 'pterodactyl'")->fetch_assoc(); - $key = removeQuotes($key['payload']); - $pterobaseurl = $db->query('SELECT `payload` FROM `' . getenv('DB_DATABASE') . "`.`settings` WHERE `name` = 'panel_url' AND `group` = 'pterodactyl'")->fetch_assoc(); + try { + $panelUrl = run_console("php artisan settings:get 'PterodactylSettings' 'panel_url' --sameline"); + $adminToken = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); + } catch (Throwable $th) { + wh_log("Getting Pterodactyl information failed.", 'error'); + header("LOCATION: index.php?step=7&message=" . $th->getMessage() . "
Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + exit(); + } + + $panelApiUrl = $panelUrl . '/api/application/users/' . $pteroID; - $pteroURL = removeQuotes($pterobaseurl['payload']) . '/api/application/users/' . $pteroID; $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $pteroURL); + curl_setopt($ch, CURLOPT_URL, $panelApiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json', 'Content-Type: application/json', - 'Authorization: Bearer ' . $key, + 'Authorization: Bearer ' . $adminToken, ]); $response = curl_exec($ch); $result = json_decode($response, true); - curl_close($ch); // Close the connection + curl_close($ch); - if (!$result['attributes']['email']) { - header('LOCATION: index.php?step=6&message=Could not find the user with pterodactyl ID ' . $pteroID); + if ($pass !== $repass) { + header('LOCATION: index.php?step=8&message=The Passwords did not match!'); exit(); } - if ($pass !== $repass) { - header('LOCATION: index.php?step=6&message=The Passwords did not match!'); + + if (array_key_exists('errors', $result)) { + header('LOCATION: index.php?step=8&message=Could not find the user with pterodactyl ID ' . $pteroID); exit(); } @@ -267,15 +344,14 @@ $name = $result['attributes']['username']; $pass = password_hash($pass, PASSWORD_DEFAULT); - $pteroURL = removeQuotes($pterobaseurl['payload']) . '/api/application/users/' . $pteroID; $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $pteroURL); + curl_setopt($ch, CURLOPT_URL, $panelApiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json', 'Content-Type: application/json', - 'Authorization: Bearer ' . $key, + 'Authorization: Bearer ' . $adminToken, ]); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'email' => $mail, @@ -286,22 +362,25 @@ ]); $response = curl_exec($ch); $result = json_decode($response, true); - curl_close($ch); // Close the connection - - if (!is_array($result) or in_array($result['errors'][0]['code'], $result)) { - header('LOCATION: index.php?step=5&message=Couldn\'t connect to Pterodactyl. Make sure your API key has all read and write permissions!'); - exit(); - } + curl_close($ch); $random = generateRandomString(); $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `role`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; $query2 = "INSERT INTO `" . getenv('DB_DATABASE') . "`.`model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES ('1', 'App\\\Models\\\User', '1')"; - if ($db->query($query1) && $db->query($query2)) { - wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID, 'info'); - header('LOCATION: index.php?step=7'); - } else { - wh_log($db->error, 'error'); - header('LOCATION: index.php?step=6&message=Something went wrong when communicating with the Database'); + try { + $db->query($query1); + $db->query($query2); + + wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID); + header('LOCATION: index.php?step=9'); + } catch (Throwable $th) { + wh_log($th->getMessage(), 'error'); + if (str_contains($th->getMessage(), 'Duplicate entry')) { + header('LOCATION: index.php?step=8&message=User already exists in CtrlPanel\'s Database.'); + } else { + header('LOCATION: index.php?step=8&message=Something went wrong when communicating with the Database.'); + } + exit(); } } diff --git a/public/install/functions.php b/public/install/functions.php index 5e8b847aa..e450e8cc1 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -15,7 +15,7 @@ (new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); -$required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl']; +$required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl', 'redis']; $requirements = [ 'minPhp' => '8.1', @@ -48,36 +48,6 @@ function checkWriteable(): bool return is_writable('../../.env'); } -/** - * Check if the server runs using HTTPS - * @return bool Returns true on HTTPS or false on HTTP. - */ -function checkHTTPS(): bool -{ - $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443; - wh_log('https:', 'debug', (array)$isHttps); - return $isHttps; -} - -/** - * Check if MySQL is installed and runs the correct version using a shell command - * @return mixed|string 'OK' if required version is met, returns MySQL version if not met. - */ -function getMySQLVersion(): mixed -{ - global $requirements; - - wh_log('attempting to get mysql version', 'debug'); - - $output = shell_exec('mysql -V') ?? ''; - preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); - - $versionoutput = $version[0] ?? '0'; - wh_log('mysql version: ' . $versionoutput, 'debug'); - - return intval($versionoutput) > intval($requirements['mysql']) ? 'OK' : $versionoutput; -} - /** * Check if zip is installed using a shell command * @return string 'OK' on success and 'not OK' on failure. @@ -303,3 +273,8 @@ function generateRandomString(int $length = 8): string return $randomString; } + +function determineIfRunningInDocker(): bool +{ + return file_exists('/.dockerenv'); +} diff --git a/public/install/index.php b/public/install/index.php index ee36cc4dd..a4d78b0fa 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -5,7 +5,7 @@ exit("The installation has been completed already. Please delete the File 'install.lock' to re-run"); } -function cardStart($title, $subtitle = null) +function cardStart($title, $subtitle = null): string { return "
@@ -14,13 +14,12 @@ function cardStart($title, $subtitle = null)

$title

" . (isset($subtitle) ? "

$subtitle

" : ""); } -?> - +?> + CtrlPanel.gg installer Script -