Merge pull request #67 from GameServerPanel/copilot/add-dependency-check-page
This commit is contained in:
commit
50ebc92a6a
4 changed files with 861 additions and 0 deletions
187
INSTALL_README.md
Normal file
187
INSTALL_README.md
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
# GSP / WDS Panel — Installer Guide
|
||||
|
||||
> **GSP is a heavily customized fork of OGP maintained by WDS.**
|
||||
|
||||
---
|
||||
|
||||
## 1. Quick Install
|
||||
|
||||
### 1.1 Install Ubuntu 24.04 dependencies
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install apache2 mysql-client unzip tar screen sudo subversion git rsync \
|
||||
php8.3 php8.3-mysql php8.3-gd php8.3-curl php8.3-mbstring php8.3-zip \
|
||||
php8.3-xml php8.3-xmlrpc php-pear libapache2-mod-php8.3 -y
|
||||
sudo a2enmod rewrite
|
||||
sudo systemctl restart apache2
|
||||
```
|
||||
|
||||
### 1.2 Set file permissions
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /var/www/html/gsp
|
||||
sudo chmod -R 755 /var/www/html/gsp
|
||||
sudo chmod 664 /var/www/html/gsp/includes/config.inc.php
|
||||
```
|
||||
|
||||
### 1.3 Create database
|
||||
|
||||
```sql
|
||||
CREATE DATABASE panel CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER 'localuser'@'localhost' IDENTIFIED BY 'your_password';
|
||||
GRANT ALL PRIVILEGES ON panel.* TO 'localuser'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
### 1.4 Run the installer
|
||||
|
||||
1. Open `http://your-server/check.php` — review dependency status (no hard blockers).
|
||||
2. Open `http://your-server/install.php` — follow the wizard.
|
||||
3. Fill in database credentials (host, port, name, user, password, table prefix).
|
||||
4. Default table prefix: `gsp_`
|
||||
5. Click **Next** to install.
|
||||
|
||||
---
|
||||
|
||||
## 2. Dependency Check (`check.php`)
|
||||
|
||||
`check.php` is a standalone page that checks your server environment **before or after install**. It never blocks installation and can be loaded at any time.
|
||||
|
||||
Checks include:
|
||||
|
||||
| Category | What's checked |
|
||||
|---|---|
|
||||
| PHP Runtime | PHP version (≥ 8.3 recommended) |
|
||||
| PHP Extensions | mysqli, curl, gd, mbstring, zip, xml, json, openssl, fileinfo, session, xmlrpc |
|
||||
| PHP Libraries | PEAR |
|
||||
| Filesystem | Writable paths (includes/, modules/, upload/, cache/, log/, temp/) |
|
||||
| Linux Commands | unzip, tar, screen, sudo, subversion, git, rsync, mysql |
|
||||
| Apache | mod_rewrite |
|
||||
| Database | Optional live connectivity test if config.inc.php exists |
|
||||
|
||||
**Statuses:**
|
||||
- ✅ **OK** — Requirement satisfied
|
||||
- ⚠ **Warning** — Missing but non-fatal; installation can proceed
|
||||
- ❌ **Missing** — Extension or binary not found
|
||||
- ❓ **Unknown** — Cannot be determined (e.g. shell_exec disabled)
|
||||
|
||||
---
|
||||
|
||||
## 3. Installer Form Fields
|
||||
|
||||
| Field | Default | Description |
|
||||
|---|---|---|
|
||||
| Database Host | `localhost` | MySQL hostname or IP |
|
||||
| Database Port | `3306` | MySQL TCP port |
|
||||
| Database Name | _(empty)_ | Target database name |
|
||||
| Database User | _(empty)_ | MySQL username |
|
||||
| Database Password | _(empty)_ | MySQL password |
|
||||
| Table Prefix | `gsp_` | Prefix for all panel tables |
|
||||
|
||||
**Generated `includes/config.inc.php`:**
|
||||
|
||||
```php
|
||||
$db_host="HOST";
|
||||
$db_port="3306";
|
||||
$db_user="USER";
|
||||
$db_pass="PASSWORD";
|
||||
$db_name="DATABASE";
|
||||
$table_prefix="gsp_";
|
||||
$db_type="mysql";
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Reinstall Flow
|
||||
|
||||
If you need to reinstall the panel (e.g. after a migration or reset):
|
||||
|
||||
1. Restore the full installer:
|
||||
```bash
|
||||
cp install.php.bak install.php
|
||||
```
|
||||
_Or_ open `install.php` in your browser — it will show a **Restore & Re-run Installer** button.
|
||||
|
||||
2. Navigate to `http://your-server/install.php` and follow the wizard again.
|
||||
|
||||
3. The installer will detect an existing database, back it up, then reinstall cleanly.
|
||||
|
||||
---
|
||||
|
||||
## 5. Backup Behavior
|
||||
|
||||
When the installer detects **existing tables** in the target database, it:
|
||||
|
||||
1. Displays a warning: _"Existing database detected. A backup will be created before reinstall."_
|
||||
2. Creates a backup database named `{database_name}_BAK` (e.g. `panel_BAK` if your DB is `panel`).
|
||||
- If `{database_name}_BAK` already exists, a timestamped name is used: `{database_name}_BAK_YYYYMMDD_HHMMSS`.
|
||||
3. Copies schema + data for every table into the backup database.
|
||||
4. Drops all tables from the target database.
|
||||
5. Proceeds with a fresh install.
|
||||
|
||||
**To restore from backup:**
|
||||
|
||||
```sql
|
||||
-- Example restore of a single table
|
||||
INSERT INTO panel.gsp_users SELECT * FROM panel_BAK.gsp_users;
|
||||
|
||||
-- Or restore the full backup DB (replace panel_BAK with the actual backup name)
|
||||
mysqldump panel_BAK | mysql panel
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Post-Install Security
|
||||
|
||||
After installation completes:
|
||||
|
||||
- `install.php` is automatically replaced with a **stub page** (the full installer is saved as `install.php.bak`).
|
||||
- The stub prevents accidental re-runs and offers an admin action to restore the installer.
|
||||
- **Change the default admin password immediately** — default credentials are `admin` / `admin`.
|
||||
- Secure `includes/config.inc.php`:
|
||||
```bash
|
||||
sudo chmod 640 includes/config.inc.php
|
||||
sudo chown www-data:www-data includes/config.inc.php
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Table Prefix Migration (ogp_ → gsp_)
|
||||
|
||||
If you are migrating from an older OGP installation:
|
||||
|
||||
- The installer automatically renames `ogp_*` tables to `gsp_*` (or your chosen prefix) when they don't already exist.
|
||||
- Tables that already exist under the new prefix are skipped safely.
|
||||
- You can choose a custom prefix (e.g. `mypanel_`) in the installer form.
|
||||
|
||||
**Module SQL files** containing `ogp_` references are dynamically converted to the chosen prefix at import time.
|
||||
|
||||
---
|
||||
|
||||
## 8. Modules
|
||||
|
||||
All modules found in the `modules/` directory are automatically detected and installed. Module SQL files are imported with dynamic prefix substitution.
|
||||
|
||||
- `modulemanager` is installed first (prerequisite for all other modules).
|
||||
- Prerequisite failures are treated as warnings, not hard errors.
|
||||
|
||||
---
|
||||
|
||||
## 9. Rollback
|
||||
|
||||
| Scenario | Action |
|
||||
|---|---|
|
||||
| Bad install, want fresh start | Restore DB from `panel_BAK`, restore installer via `cp install.php.bak install.php`, re-run |
|
||||
| Config broken | Edit `includes/config.inc.php` manually or re-run installer |
|
||||
| Installer stub needs removal | `cp install.php.bak install.php` |
|
||||
| Modules failed to install | Re-run installer (it detects existing DB and backs up first) |
|
||||
|
||||
---
|
||||
|
||||
## 10. Security Notes
|
||||
|
||||
- Passwords are **never printed** in check.php or any installer output.
|
||||
- All user input is escaped with `htmlspecialchars()` before rendering.
|
||||
- The `install.php.bak` stub restore action is unprotected — remove `install.php` and `install.php.bak` once you no longer need them.
|
||||
- The default admin password is stored as MD5 to match the legacy panel login system. Change it immediately.
|
||||
484
check.php
Normal file
484
check.php
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
<?php
|
||||
/*
|
||||
* GSP - Game Server Panel (WDS)
|
||||
* Copyright (C) 2008 - 2018 The OGP Development Team
|
||||
* GSP customizations (C) WDS / GameServerPanel
|
||||
*
|
||||
* GSP is a heavily customized fork of OGP maintained by WDS.
|
||||
* https://github.com/GameServerPanel/GSP
|
||||
*
|
||||
* Dependency Check Page — check.php
|
||||
* Safe to run before install or at any time. Never blocks installation.
|
||||
*/
|
||||
|
||||
// Never leak PHP errors in output (log them server-side instead).
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Safely escape a value for HTML output.
|
||||
*
|
||||
* @param mixed $v
|
||||
* @return string
|
||||
*/
|
||||
function h($v): string {
|
||||
return htmlspecialchars((string)$v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to run a shell command via shell_exec.
|
||||
* Returns the trimmed output, or NULL when shell_exec is not available / the
|
||||
* command produced no output.
|
||||
*
|
||||
* @param string $cmd The command to run (must already be safe / validated).
|
||||
* @return string|null
|
||||
*/
|
||||
function safe_shell(?string $cmd): ?string {
|
||||
if (!function_exists('shell_exec')) return null;
|
||||
$out = @shell_exec($cmd);
|
||||
return ($out !== null && $out !== '') ? trim($out) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a Linux command exists via "command -v".
|
||||
* Returns "ok", "missing", or "unknown".
|
||||
*
|
||||
* @param string $cmd Binary name, alphanumeric + dashes only.
|
||||
* @return string
|
||||
*/
|
||||
function check_command(string $cmd): string {
|
||||
$safe = preg_replace('/[^a-zA-Z0-9_\-]/', '', $cmd);
|
||||
$result = safe_shell('command -v ' . escapeshellarg($safe) . ' 2>/dev/null');
|
||||
if ($result === null) return 'unknown';
|
||||
return ($result !== '') ? 'ok' : 'missing';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
define('CHECK_BASE', __DIR__);
|
||||
define('PANEL_VERSION_MIN', '8.3.0');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run all checks and collect rows
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
$rows = [];
|
||||
|
||||
// ── PHP version ─────────────────────────────────────────────────────────────
|
||||
$php_ver = PHP_VERSION;
|
||||
$php_ok = version_compare($php_ver, PANEL_VERSION_MIN, '>=');
|
||||
$rows[] = [
|
||||
'section' => 'PHP Runtime',
|
||||
'name' => 'PHP Version',
|
||||
'status' => $php_ok ? 'ok' : 'warning',
|
||||
'current' => $php_ver,
|
||||
'fix' => $php_ok ? '' : 'sudo apt install php8.3 libapache2-mod-php8.3 -y',
|
||||
'notes' => 'Minimum recommended: PHP ' . PANEL_VERSION_MIN,
|
||||
];
|
||||
|
||||
// ── Required PHP extensions ──────────────────────────────────────────────────
|
||||
$required_exts = [
|
||||
'mysqli' => 'Database connectivity',
|
||||
'curl' => 'Remote HTTP requests',
|
||||
'gd' => 'Image processing',
|
||||
'mbstring' => 'Multi-byte string handling',
|
||||
'zip' => 'Archive extraction',
|
||||
'xml' => 'XML parsing (game configs)',
|
||||
'json' => 'JSON encoding/decoding',
|
||||
'openssl' => 'Encrypted connections',
|
||||
'fileinfo' => 'MIME type detection',
|
||||
'session' => 'Session management',
|
||||
];
|
||||
|
||||
foreach ($required_exts as $ext => $desc) {
|
||||
$loaded = extension_loaded($ext);
|
||||
$rows[] = [
|
||||
'section' => 'PHP Extensions',
|
||||
'name' => 'ext/' . $ext,
|
||||
'status' => $loaded ? 'ok' : 'missing',
|
||||
'current' => $loaded ? 'Loaded' : 'Not loaded',
|
||||
'fix' => $loaded ? '' : 'sudo apt install php8.3-' . $ext . ' -y',
|
||||
'notes' => $desc,
|
||||
];
|
||||
}
|
||||
|
||||
// xmlrpc is packaged separately on modern Debian/Ubuntu so check it alone.
|
||||
$xmlrpc_loaded = extension_loaded('xmlrpc');
|
||||
$rows[] = [
|
||||
'section' => 'PHP Extensions',
|
||||
'name' => 'ext/xmlrpc',
|
||||
'status' => $xmlrpc_loaded ? 'ok' : 'warning',
|
||||
'current' => $xmlrpc_loaded ? 'Loaded' : 'Not loaded',
|
||||
'fix' => $xmlrpc_loaded ? '' : 'sudo apt install php8.3-xmlrpc -y',
|
||||
'notes' => 'Required for agent communication. May need separate package on PHP 8+.',
|
||||
];
|
||||
|
||||
// ── PEAR ─────────────────────────────────────────────────────────────────────
|
||||
$pear_path = stream_resolve_include_path('PEAR.php');
|
||||
$rows[] = [
|
||||
'section' => 'PHP Libraries',
|
||||
'name' => 'PEAR',
|
||||
'status' => $pear_path !== false ? 'ok' : 'warning',
|
||||
'current' => $pear_path !== false ? $pear_path : 'Not found',
|
||||
'fix' => $pear_path !== false ? '' : 'sudo apt install php-pear -y',
|
||||
'notes' => 'Used by some legacy OGP/GSP modules.',
|
||||
];
|
||||
|
||||
// ── Writable / readable paths ────────────────────────────────────────────────
|
||||
$paths_to_check = [
|
||||
'includes/' => 'Config directory (must be writable at install time)',
|
||||
'modules/' => 'Modules directory',
|
||||
'upload/' => 'Upload directory (optional)',
|
||||
'cache/' => 'Cache directory (optional)',
|
||||
'log/' => 'Log directory (optional)',
|
||||
'temp/' => 'Temp directory (optional)',
|
||||
'includes/config.inc.php' => 'Panel config file (writable at install time)',
|
||||
];
|
||||
|
||||
foreach ($paths_to_check as $rel => $note) {
|
||||
$abs = CHECK_BASE . '/' . $rel;
|
||||
$optional = in_array($rel, ['upload/', 'cache/', 'log/', 'temp/'], true);
|
||||
|
||||
if (!file_exists($abs)) {
|
||||
$rows[] = [
|
||||
'section' => 'Filesystem',
|
||||
'name' => $rel,
|
||||
'status' => $optional ? 'warning' : 'warning',
|
||||
'current' => 'Does not exist',
|
||||
'fix' => 'mkdir -p ' . escapeshellarg($rel),
|
||||
'notes' => $note . ($optional ? ' (optional)' : ''),
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_dir = is_dir($abs);
|
||||
$readable = is_readable($abs);
|
||||
$writable = is_writable($abs);
|
||||
|
||||
if ($is_dir) {
|
||||
$status = ($readable && $writable) ? 'ok' : 'warning';
|
||||
$cur = 'Exists — readable: ' . ($readable ? 'yes' : 'no') . ', writable: ' . ($writable ? 'yes' : 'no');
|
||||
$fix = (!$writable) ? 'sudo chmod -R 775 ' . escapeshellarg($rel) . ' && sudo chown -R www-data:www-data ' . escapeshellarg($rel) : '';
|
||||
} else {
|
||||
// It's a file
|
||||
$status = ($readable && $writable) ? 'ok' : 'warning';
|
||||
$cur = 'Exists — readable: ' . ($readable ? 'yes' : 'no') . ', writable: ' . ($writable ? 'yes' : 'no');
|
||||
$fix = (!$writable) ? 'sudo chmod 664 ' . escapeshellarg($rel) : '';
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'section' => 'Filesystem',
|
||||
'name' => $rel,
|
||||
'status' => $status,
|
||||
'current' => $cur,
|
||||
'fix' => $fix,
|
||||
'notes' => $note,
|
||||
];
|
||||
}
|
||||
|
||||
// ── Linux commands ───────────────────────────────────────────────────────────
|
||||
$commands = ['unzip', 'tar', 'screen', 'sudo', 'subversion', 'git', 'rsync', 'mysql'];
|
||||
$shell_available = function_exists('shell_exec');
|
||||
|
||||
foreach ($commands as $cmd) {
|
||||
$status_str = check_command($cmd);
|
||||
if (!$shell_available) {
|
||||
$status_str = 'unknown';
|
||||
$cur = 'shell_exec disabled';
|
||||
$fix = 'Enable shell_exec in php.ini';
|
||||
} else {
|
||||
$cur = $status_str === 'ok' ? safe_shell('command -v ' . escapeshellarg($cmd) . ' 2>/dev/null') ?? $cmd : 'Not found in PATH';
|
||||
$fix = ($status_str === 'missing') ? 'sudo apt install ' . escapeshellarg($cmd) . ' -y' : '';
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'section' => 'Linux Commands',
|
||||
'name' => $cmd,
|
||||
'status' => $status_str,
|
||||
'current' => $cur,
|
||||
'fix' => $fix,
|
||||
'notes' => 'Required for game server management',
|
||||
];
|
||||
}
|
||||
|
||||
// ── Apache modules ───────────────────────────────────────────────────────────
|
||||
if (function_exists('apache_get_modules')) {
|
||||
$apache_mods = apache_get_modules();
|
||||
$rewrite_loaded = in_array('mod_rewrite', $apache_mods, true);
|
||||
$rows[] = [
|
||||
'section' => 'Apache',
|
||||
'name' => 'mod_rewrite',
|
||||
'status' => $rewrite_loaded ? 'ok' : 'warning',
|
||||
'current' => $rewrite_loaded ? 'Enabled' : 'Not enabled',
|
||||
'fix' => $rewrite_loaded ? '' : "sudo a2enmod rewrite\nsudo systemctl restart apache2",
|
||||
'notes' => 'Required for clean panel URLs',
|
||||
];
|
||||
} else {
|
||||
$rows[] = [
|
||||
'section' => 'Apache',
|
||||
'name' => 'mod_rewrite',
|
||||
'status' => 'unknown',
|
||||
'current' => 'apache_get_modules() unavailable (CGI/FPM mode or non-Apache?)',
|
||||
'fix' => "sudo a2enmod rewrite\nsudo systemctl restart apache2",
|
||||
'notes' => 'Required for clean panel URLs. Verify manually if not using mod_php.',
|
||||
];
|
||||
}
|
||||
|
||||
// ── Optional DB connectivity test ────────────────────────────────────────────
|
||||
$config_path = CHECK_BASE . '/includes/config.inc.php';
|
||||
if (is_readable($config_path)) {
|
||||
// Extract credentials using regex instead of executing the config file,
|
||||
// to avoid running arbitrary PHP code from the config.
|
||||
$raw = file_get_contents($config_path);
|
||||
$cfg = [];
|
||||
foreach (['db_host', 'db_port', 'db_user', 'db_pass', 'db_name'] as $var) {
|
||||
if ($raw !== false && preg_match('/\$' . $var . '\s*=\s*"([^"]*)"/', $raw, $m)) {
|
||||
$cfg[$var] = $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
$db_host_cfg = $cfg['db_host'] ?? null;
|
||||
$db_user_cfg = $cfg['db_user'] ?? null;
|
||||
$db_pass_cfg = $cfg['db_pass'] ?? null;
|
||||
$db_name_cfg = $cfg['db_name'] ?? null;
|
||||
$db_port_cfg = isset($cfg['db_port']) ? (int)$cfg['db_port'] : 3306;
|
||||
|
||||
if ($db_host_cfg !== null && $db_user_cfg !== null && $db_name_cfg !== null) {
|
||||
$conn = @mysqli_connect($db_host_cfg, $db_user_cfg, $db_pass_cfg ?? '', $db_name_cfg, $db_port_cfg);
|
||||
if ($conn) {
|
||||
$db_status = 'ok';
|
||||
$db_current = 'Connected to ' . $db_host_cfg . ':' . $db_port_cfg . ' / ' . $db_name_cfg;
|
||||
$db_fix = '';
|
||||
mysqli_close($conn);
|
||||
} else {
|
||||
$db_status = 'warning';
|
||||
$db_current = 'Connection failed — ' . (mysqli_connect_error() ?? 'unknown error');
|
||||
$db_fix = 'Check credentials in includes/config.inc.php';
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'section' => 'Database',
|
||||
'name' => 'MySQL connection',
|
||||
'status' => $db_status,
|
||||
'current' => $db_current,
|
||||
'fix' => $db_fix,
|
||||
'notes' => 'Host: ' . $db_host_cfg . ' | Port: ' . $db_port_cfg . ' | DB: ' . $db_name_cfg . ' | User: ' . $db_user_cfg,
|
||||
];
|
||||
} else {
|
||||
$rows[] = [
|
||||
'section' => 'Database',
|
||||
'name' => 'MySQL connection',
|
||||
'status' => 'warning',
|
||||
'current' => 'config.inc.php present but incomplete (missing host/user/db)',
|
||||
'fix' => 'Run the installer at install.php',
|
||||
'notes' => 'Cannot test connection without full credentials',
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$rows[] = [
|
||||
'section' => 'Database',
|
||||
'name' => 'MySQL connection',
|
||||
'status' => 'warning',
|
||||
'current' => 'config.inc.php not found or not readable — not yet installed',
|
||||
'fix' => 'Run the installer at install.php',
|
||||
'notes' => 'This is normal before first install',
|
||||
];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Summary counts
|
||||
// ---------------------------------------------------------------------------
|
||||
$count_ok = 0;
|
||||
$count_warning = 0;
|
||||
$count_missing = 0;
|
||||
$count_unknown = 0;
|
||||
|
||||
foreach ($rows as $r) {
|
||||
switch ($r['status']) {
|
||||
case 'ok': $count_ok++; break;
|
||||
case 'warning': $count_warning++; break;
|
||||
case 'missing': $count_missing++; break;
|
||||
default: $count_unknown++; break;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Render HTML
|
||||
// ---------------------------------------------------------------------------
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GSP / WDS — Dependency Check</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
background: #1a1a2e;
|
||||
color: #e0e0e0;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 { font-size: 22px; margin-bottom: 4px; color: #ffffff; }
|
||||
h2 { font-size: 15px; font-weight: 600; margin: 18px 0 6px; color: #ccc; border-bottom: 1px solid #333; padding-bottom: 4px; }
|
||||
.container { max-width: 1100px; margin: 0 auto; }
|
||||
.header { background: #16213e; border-radius: 8px; padding: 18px 22px; margin-bottom: 20px; border: 1px solid #0f3460; }
|
||||
.header p { color: #aaa; margin-top: 6px; font-size: 13px; }
|
||||
.summary { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 20px; }
|
||||
.summary-box { flex: 1 1 130px; border-radius: 8px; padding: 14px 18px; text-align: center; }
|
||||
.summary-box .num { font-size: 32px; font-weight: bold; }
|
||||
.summary-box .lbl { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; margin-top: 4px; }
|
||||
.summary-ok { background: #1a3a1a; border: 1px solid #2e6b2e; color: #7ddb7d; }
|
||||
.summary-warning { background: #3a2e00; border: 1px solid #7a6000; color: #ffd84d; }
|
||||
.summary-missing { background: #3a1a1a; border: 1px solid #7a2e2e; color: #ff7b7b; }
|
||||
.summary-unknown { background: #252535; border: 1px solid #4a4a6a; color: #aaaacc; }
|
||||
.actions { margin-bottom: 22px; }
|
||||
.btn {
|
||||
display: inline-block; padding: 9px 20px; border-radius: 6px; text-decoration: none;
|
||||
font-size: 13px; font-weight: 600; border: none; cursor: pointer;
|
||||
}
|
||||
.btn-primary { background: #0f3460; color: #fff; border: 1px solid #1a5fa8; }
|
||||
.btn-primary:hover { background: #1a5fa8; }
|
||||
.btn-secondary { background: #2a2a4a; color: #aaa; border: 1px solid #4a4a7a; margin-left: 8px; }
|
||||
.btn-secondary:hover { background: #3a3a6a; color: #fff; }
|
||||
table { width: 100%; border-collapse: collapse; background: #12122a; border-radius: 8px; overflow: hidden; border: 1px solid #2a2a4a; margin-bottom: 20px; }
|
||||
thead th { background: #0d0d28; color: #aab; text-transform: uppercase; font-size: 11px; letter-spacing: .06em; padding: 10px 12px; text-align: left; border-bottom: 1px solid #2a2a5a; }
|
||||
tbody tr { border-bottom: 1px solid #1e1e3a; }
|
||||
tbody tr:last-child { border-bottom: none; }
|
||||
tbody tr:hover { background: #1a1a38; }
|
||||
tbody td { padding: 9px 12px; vertical-align: top; }
|
||||
.td-name { font-weight: 600; font-family: "Courier New", monospace; font-size: 13px; white-space: nowrap; }
|
||||
.td-status { white-space: nowrap; text-align: center; }
|
||||
.badge { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; }
|
||||
.badge-ok { background: #1a3a1a; color: #7ddb7d; border: 1px solid #2e6b2e; }
|
||||
.badge-warning { background: #3a2e00; color: #ffd84d; border: 1px solid #7a6000; }
|
||||
.badge-missing { background: #3a1a1a; color: #ff7b7b; border: 1px solid #7a2e2e; }
|
||||
.badge-unknown { background: #252535; color: #aaaacc; border: 1px solid #4a4a6a; }
|
||||
.td-fix code, .td-current code { font-family: "Courier New", monospace; font-size: 12px; background: #0d0d22; padding: 2px 6px; border-radius: 4px; color: #aaf; word-break: break-all; display: inline-block; }
|
||||
.td-fix pre { font-family: "Courier New", monospace; font-size: 12px; background: #0d0d22; padding: 6px 8px; border-radius: 4px; color: #aaf; white-space: pre-wrap; word-break: break-all; }
|
||||
.td-notes { color: #888; font-size: 12px; }
|
||||
.section-header td { background: #0d1430; color: #7a9fd4; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; padding: 6px 12px; }
|
||||
.note-box { background: #1a2a1a; border: 1px solid #2e5a2e; border-radius: 8px; padding: 14px 18px; margin-bottom: 20px; color: #9ddd9d; font-size: 13px; }
|
||||
.note-box strong { color: #7ddb7d; }
|
||||
footer { text-align: center; color: #555; font-size: 11px; margin-top: 30px; }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<div class="header">
|
||||
<h1>🔍 GSP / WDS — Dependency Check</h1>
|
||||
<p>This page checks the server environment for GSP panel compatibility.
|
||||
<strong>No dependency is a hard blocker</strong> — missing items appear as warnings only.
|
||||
The installer at <a href="install.php" style="color:#7aaaf5">install.php</a> can proceed regardless.</p>
|
||||
</div>
|
||||
|
||||
<!-- Summary boxes -->
|
||||
<div class="summary">
|
||||
<div class="summary-box summary-ok">
|
||||
<div class="num"><?= $count_ok ?></div>
|
||||
<div class="lbl">OK</div>
|
||||
</div>
|
||||
<div class="summary-box summary-warning">
|
||||
<div class="num"><?= $count_warning ?></div>
|
||||
<div class="lbl">Warning</div>
|
||||
</div>
|
||||
<div class="summary-box summary-missing">
|
||||
<div class="num"><?= $count_missing ?></div>
|
||||
<div class="lbl">Missing</div>
|
||||
</div>
|
||||
<div class="summary-box summary-unknown">
|
||||
<div class="num"><?= $count_unknown ?></div>
|
||||
<div class="lbl">Unknown</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="actions">
|
||||
<a href="install.php" class="btn btn-primary">⚙ Run Installer</a>
|
||||
<a href="check.php" class="btn btn-secondary">↺ Refresh Check</a>
|
||||
</div>
|
||||
|
||||
<?php if ($count_missing === 0 && $count_warning === 0): ?>
|
||||
<div class="note-box">
|
||||
<strong>✔ All checks passed.</strong>
|
||||
Your server environment looks good for GSP installation.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Results table -->
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:160px">Name</th>
|
||||
<th style="width:90px;text-align:center">Status</th>
|
||||
<th style="width:220px">Current Value</th>
|
||||
<th>Recommended Fix</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$last_section = null;
|
||||
foreach ($rows as $row):
|
||||
if ($row['section'] !== $last_section):
|
||||
$last_section = $row['section'];
|
||||
?>
|
||||
<tr class="section-header">
|
||||
<td colspan="5"><?= h($row['section']) ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<tr>
|
||||
<td class="td-name"><code><?= h($row['name']) ?></code></td>
|
||||
<td class="td-status">
|
||||
<span class="badge badge-<?= h($row['status']) ?>">
|
||||
<?= h($row['status']) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td class="td-current"><?= h($row['current']) ?></td>
|
||||
<td class="td-fix">
|
||||
<?php if (!empty($row['fix'])): ?>
|
||||
<?php if (strpos($row['fix'], "\n") !== false): ?>
|
||||
<pre><?= h($row['fix']) ?></pre>
|
||||
<?php else: ?>
|
||||
<code><?= h($row['fix']) ?></code>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<span style="color:#555">—</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="td-notes"><?= h($row['notes']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Suggested apt install block -->
|
||||
<h2>📦 Ubuntu 24.04 — Full Dependency Install</h2>
|
||||
<table>
|
||||
<thead><tr><th>One-liner to install all recommended packages</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><pre><?= h(
|
||||
'sudo apt update
|
||||
sudo apt install apache2 mysql-client unzip tar screen sudo subversion git rsync \
|
||||
php8.3 php8.3-mysql php8.3-gd php8.3-curl php8.3-mbstring php8.3-zip \
|
||||
php8.3-xml php8.3-xmlrpc php-pear libapache2-mod-php8.3 -y
|
||||
sudo a2enmod rewrite
|
||||
sudo systemctl restart apache2'
|
||||
) ?></pre></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<footer>GSP / WDS Dependency Checker — safe to run at any time — generated at <?= h(gmdate('Y-m-d H:i:s')) ?> UTC</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
# Site configuration
|
||||
###############################################
|
||||
$db_host="mysql.iaregamer.com";
|
||||
$db_port="3306";
|
||||
$db_user="remoteuser";
|
||||
$db_pass="Pkloyn7yvpht!";
|
||||
$db_name="panel";
|
||||
|
|
|
|||
189
install.php
189
install.php
|
|
@ -122,9 +122,11 @@ function install() {
|
|||
|
||||
echo "<div id=\"install-title\">GSP / WDS Panel Installer</div>";
|
||||
echo "<p>Welcome to the <strong>GSP (Game Server Panel)</strong> installer, maintained by WDS.</p>";
|
||||
echo "<p style='margin:8px 0;'><a href='check.php' style='background:#1565c0;color:#fff;padding:6px 14px;border-radius:5px;text-decoration:none;font-weight:bold;'>🔍 Run Dependency Check</a></p>";
|
||||
echo "<p>GSP is a heavily customized fork of OGP. This installer will:</p>";
|
||||
echo "<ul>
|
||||
<li>Write <code>includes/config.inc.php</code> with your database credentials.</li>
|
||||
<li>Back up any existing database before reinstalling.</li>
|
||||
<li>Optionally migrate any existing <code>ogp_</code> tables to your chosen prefix.</li>
|
||||
<li>Install all modules found in the <code>modules/</code> directory.</li>
|
||||
<li>Create a default admin account (<em>admin / admin</em>) if none exists.</li>
|
||||
|
|
@ -141,6 +143,7 @@ function install() {
|
|||
require_once "includes/config.inc.php";
|
||||
|
||||
echo "<table class='install'><tr><td>\n";
|
||||
echo "<p style='margin-bottom:10px;'><a href='check.php' style='background:#1565c0;color:#fff;padding:5px 12px;border-radius:5px;text-decoration:none;font-weight:bold;'>🔍 Run Dependency Check</a></p>";
|
||||
echo "<form name='setup' method='post' action='?step=2'>";
|
||||
echo "<table class='install'>\n";
|
||||
echo "<tr><td colspan='2'><div id=\"install-title\" style=\"margin-left:-21px; margin-top:-7px;\">".get_lang('database_settings')."</div></td></tr>";
|
||||
|
|
@ -239,6 +242,9 @@ function install() {
|
|||
return;
|
||||
}
|
||||
|
||||
// --- Detect existing database and back it up before reinstall ---
|
||||
gsp_backup_existing_db($db, $db_host, $db_user, $db_pass, $db_name, $db_port);
|
||||
|
||||
// --- Optional ogp_ → gsp_ migration ---
|
||||
gsp_migrate_tables($db, $table_prefix);
|
||||
|
||||
|
|
@ -312,6 +318,9 @@ function install() {
|
|||
echo "<p class='note' style='color:#c00; font-weight:bold;'>SECURITY: The default admin password is <strong>admin</strong>. Change it immediately after your first login at Admin → User Management.</p>";
|
||||
echo "<p class='note'><a href='index.php'>".get_lang('go_to_panel')."</a></p>";
|
||||
echo "</td></tr></table>\n";
|
||||
|
||||
// --- Disable the installer by renaming install.php to install.php.bak ---
|
||||
gsp_disable_installer();
|
||||
echo "</div>\n";
|
||||
}
|
||||
|
||||
|
|
@ -401,5 +410,185 @@ function mysqli_real_escape_string_compat($identifier) {
|
|||
return preg_replace('/[^a-zA-Z0-9_]/', '', $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether the target database already contains tables.
|
||||
* If so, create a backup database (panel_BAK or panel_BAK_YYYYMMDD_HHMMSS)
|
||||
* by copying schema + data, then drop all tables in the target database so
|
||||
* the fresh install proceeds cleanly.
|
||||
*
|
||||
* The backup is performed entirely through the panel's own DB connection so
|
||||
* no external mysqldump binary is required.
|
||||
*/
|
||||
function gsp_backup_existing_db($db, $db_host, $db_user, $db_pass, $db_name, $db_port) {
|
||||
// Count tables in the target database
|
||||
$tables_result = $db->resultQuery("SHOW TABLES");
|
||||
if (!$tables_result || !is_array($tables_result) || count($tables_result) === 0) {
|
||||
// Fresh database – nothing to back up
|
||||
return;
|
||||
}
|
||||
|
||||
$table_count = count($tables_result);
|
||||
echo "<p class='note' style='color:#b35900;font-weight:bold;'>⚠ Existing database detected (" . htmlspecialchars((string)$table_count, ENT_QUOTES, 'UTF-8') . " table(s)). Creating backup before reinstall…</p>";
|
||||
|
||||
// Determine backup DB name
|
||||
$backup_name = $db_name . '_BAK';
|
||||
$check_bak = $db->resultQuery("SHOW DATABASES LIKE '" . mysqli_real_escape_string_compat($backup_name) . "'");
|
||||
if ($check_bak && is_array($check_bak) && count($check_bak) > 0) {
|
||||
$backup_name = $db_name . '_BAK_' . date('Ymd_His');
|
||||
}
|
||||
|
||||
// Create backup database
|
||||
$safe_backup = '`' . mysqli_real_escape_string_compat($backup_name) . '`';
|
||||
$safe_src = '`' . mysqli_real_escape_string_compat($db_name) . '`';
|
||||
|
||||
$created = $db->query("CREATE DATABASE IF NOT EXISTS {$safe_backup} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
if (!$created) {
|
||||
echo "<p class='note' style='color:#900;'>Could not create backup database <code>" . htmlspecialchars($backup_name) . "</code>. Skipping backup (install will continue).</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy each table: CREATE … SELECT
|
||||
$copied = 0;
|
||||
$failed = 0;
|
||||
foreach ($tables_result as $row) {
|
||||
$tbl = array_values($row)[0];
|
||||
$safe_tbl = '`' . mysqli_real_escape_string_compat($tbl) . '`';
|
||||
|
||||
// Copy structure
|
||||
$ok1 = $db->query("CREATE TABLE {$safe_backup}.{$safe_tbl} LIKE {$safe_src}.{$safe_tbl}");
|
||||
// Copy data
|
||||
$ok2 = $ok1 && $db->query("INSERT INTO {$safe_backup}.{$safe_tbl} SELECT * FROM {$safe_src}.{$safe_tbl}");
|
||||
if ($ok2) {
|
||||
$copied++;
|
||||
} else {
|
||||
$failed++;
|
||||
}
|
||||
}
|
||||
|
||||
print_success("Backup created as <code>" . htmlspecialchars($backup_name) . "</code> — {$copied} table(s) copied" . ($failed > 0 ? ", {$failed} failed (non-fatal)" : "") . ".");
|
||||
|
||||
// Drop all tables in the target database so the fresh install is clean
|
||||
$db->query("SET FOREIGN_KEY_CHECKS = 0");
|
||||
foreach ($tables_result as $row) {
|
||||
$tbl = array_values($row)[0];
|
||||
$safe_tbl = '`' . mysqli_real_escape_string_compat($tbl) . '`';
|
||||
$db->query("DROP TABLE IF EXISTS {$safe_src}.{$safe_tbl}");
|
||||
}
|
||||
$db->query("SET FOREIGN_KEY_CHECKS = 1");
|
||||
|
||||
print_success("Target database cleared. Fresh install will proceed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* After a successful install, rename install.php to install.php.bak and write
|
||||
* a minimal bootstrap stub as the new install.php that prevents accidental
|
||||
* re-runs while giving an admin an easy path to restore the real installer.
|
||||
*/
|
||||
function gsp_disable_installer() {
|
||||
$self = __FILE__;
|
||||
$bak_path = dirname($self) . '/install.php.bak';
|
||||
|
||||
// Read the current file before we rename it
|
||||
$installer_content = @file_get_contents($self);
|
||||
if ($installer_content === false) {
|
||||
echo "<p class='note' style='color:#900;'>Could not read install.php for backup — installer not renamed (non-fatal).</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the backup copy
|
||||
if (@file_put_contents($bak_path, $installer_content) === false) {
|
||||
echo "<p class='note' style='color:#900;'>Could not write install.php.bak — installer not renamed (non-fatal).</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the stub that replaces install.php
|
||||
$stub = <<<'STUB'
|
||||
<?php
|
||||
/*
|
||||
* GSP / WDS — Installer bootstrap stub
|
||||
*
|
||||
* The full installer has been moved to install.php.bak after a successful
|
||||
* installation to prevent accidental re-runs.
|
||||
*
|
||||
* To restore the installer:
|
||||
* cp install.php.bak install.php
|
||||
*
|
||||
* Or via the button below (admin action — requires a one-time restore token).
|
||||
*/
|
||||
session_start();
|
||||
|
||||
// Generate a one-time CSRF token if not already set
|
||||
if (empty($_SESSION['gsp_restore_token'])) {
|
||||
$_SESSION['gsp_restore_token'] = bin2hex(random_bytes(16));
|
||||
}
|
||||
|
||||
if (isset($_POST['restore_installer'])) {
|
||||
// Validate CSRF token
|
||||
$submitted = $_POST['gsp_restore_token'] ?? '';
|
||||
if (!hash_equals($_SESSION['gsp_restore_token'], $submitted)) {
|
||||
http_response_code(403);
|
||||
die('<p style="color:red">Invalid or expired restore token. Please reload the page and try again.</p>');
|
||||
}
|
||||
|
||||
$bak = __DIR__ . '/install.php.bak';
|
||||
if (!is_readable($bak)) {
|
||||
die('<p style="color:red">install.php.bak not found. Restore manually.</p>');
|
||||
}
|
||||
$content = file_get_contents($bak);
|
||||
if ($content === false || file_put_contents(__FILE__, $content) === false) {
|
||||
die('<p style="color:red">Could not overwrite install.php. Check file permissions.</p>');
|
||||
}
|
||||
// Invalidate the token after use
|
||||
unset($_SESSION['gsp_restore_token']);
|
||||
header('Location: install.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$token = htmlspecialchars($_SESSION['gsp_restore_token'], ENT_QUOTES, 'UTF-8');
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>GSP Installer — Disabled</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background:#1a1a2e; color:#e0e0e0; display:flex; align-items:center; justify-content:center; min-height:100vh; margin:0; }
|
||||
.box { background:#12122a; border:1px solid #2a2a5a; border-radius:10px; padding:40px 50px; max-width:480px; text-align:center; }
|
||||
h1 { color:#ffd84d; font-size:20px; margin-bottom:12px; }
|
||||
p { color:#aaa; font-size:14px; margin:8px 0; }
|
||||
code { background:#0d0d22; color:#aaf; padding:2px 8px; border-radius:4px; font-size:13px; }
|
||||
.btn { display:inline-block; margin-top:18px; padding:10px 22px; background:#b35900; color:#fff; border:none; border-radius:6px; cursor:pointer; font-size:14px; font-weight:bold; }
|
||||
.btn:hover { background:#e07000; }
|
||||
.note { color:#888; font-size:12px; margin-top:14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<h1>⚠ Installer Disabled</h1>
|
||||
<p>The GSP installer has been disabled after a successful installation to prevent accidental re-runs.</p>
|
||||
<p>The original installer is preserved at <code>install.php.bak</code>.</p>
|
||||
<form method="post">
|
||||
<input type="hidden" name="gsp_restore_token" value="<?= $token ?>">
|
||||
<button type="submit" name="restore_installer" class="btn" onclick="return confirm('Restore the full installer? Only do this if you intend to reinstall the panel.');">↺ Restore & Re-run Installer</button>
|
||||
</form>
|
||||
<p class="note">
|
||||
To restore manually:<br>
|
||||
<code>cp install.php.bak install.php</code>
|
||||
</p>
|
||||
<p class="note"><a href="index.php" style="color:#7aaaf5;">Go to Panel</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
STUB;
|
||||
|
||||
if (@file_put_contents($self, $stub) === false) {
|
||||
echo "<p class='note' style='color:#900;'>Could not overwrite install.php with stub — installer not disabled (non-fatal). Delete or rename install.php manually.</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
print_success("Installer disabled: <code>install.php</code> renamed to stub. Full installer preserved as <code>install.php.bak</code>.");
|
||||
echo "<p class='note'>To re-enable the installer, click <em>Restore & Re-run Installer</em> on the new <code>install.php</code> page, or run: <code>cp install.php.bak install.php</code></p>";
|
||||
}
|
||||
|
||||
$view->printView();
|
||||
?>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue