diff --git a/menu b/menu index 23205b5..df7b0ea 100644 --- a/menu +++ b/menu @@ -189,18 +189,103 @@ fi #menuv2 #-------------------- elif [ "$cake" == "4" ]; then -clear -echo "Portas Ativas" -echo "" -php /opt/DragonCore/menu.php infoport -echo "" -echo -ne "Pressione enter para continuar"; read enter -menucon + # Xray Core management + clear + menuxray +elif [ "$cake" == "5" ]; then + clear + echo "Portas Ativas" + echo "" + php /opt/DragonCore/menu.php infoport + echo "" + echo -ne "Pressione enter para continuar"; read enter + menucon else -menucon + menucon fi } +menuxray(){ + clear + php /opt/DragonCore/menu.php menuxray + echo -ne "> "; read option + if [ -z "$option" ]; then + menuxray + elif [ "$option" == "0" ]; then + menucon + elif [ "$option" == "1" ]; then + clear + echo -ne "Nome do usuario > "; read usr + if [ -z "$usr" ]; then + menuxray + else + echo -ne "Protocolo (ws/xhttp/grpc/tcp) [ws] > "; read proto + [ -z "$proto" ] && proto="ws" + php /opt/DragonCore/menu.php xrayAddUser "$usr" "$proto" + echo "" + echo -ne "Pressione enter para continuar"; read enter + menuxray + fi + elif [ "$option" == "2" ]; then + clear + php /opt/DragonCore/menu.php xrayListUsers + echo "" + echo -ne "ID ou UUID para remover > "; read ident + if [ -n "$ident" ]; then + php /opt/DragonCore/menu.php xrayRemoveUser "$ident" + echo "" + echo -ne "Pressione enter para continuar"; read enter + fi + menuxray + elif [ "$option" == "3" ]; then + clear + php /opt/DragonCore/menu.php xrayListUsers + echo "" + echo -ne "Pressione enter para continuar"; read enter + menuxray + elif [ "$option" == "4" ]; then + clear + php /opt/DragonCore/menu.php xrayInfo + echo "" + echo -ne "Pressione enter para continuar"; read enter + menuxray + elif [ "$option" == "5" ]; then + clear + echo -ne "Dominio (ex: vpn.seudominio.com) > "; read dominio + if [ -n "$dominio" ]; then + php /opt/DragonCore/menu.php xrayCert "$dominio" + echo "" + chmod 777 -R /opt/DragonCore/ssl + echo -ne "Pressione enter para continuar"; read enter + fi + menuxray + elif [ "$option" == "6" ]; then + clear + echo -ne "Porta do inbound [443] > "; read p + [ -z "$p" ] && p=443 + echo -ne "Protocolo (ws/xhttp/grpc/tcp) [ws] > "; read pr + [ -z "$pr" ] && pr="ws" + bash <(php /opt/DragonCore/menu.php xrayInstall) + php /opt/DragonCore/menu.php xrayGenerateConfig "$p" "$pr" + echo "" + chmod 777 /usr/local/etc/xray/config.json + echo -ne "Pressione enter para continuar"; read enter + menuxray + elif [ "$option" == "7" ]; then + clear + echo -ne "Deseja remover o Xray Core? S/n > "; read sn + lower=$(echo "$sn" | tr '[:upper:]' '[:lower:]') + if [[ "$lower" == "s" || "$lower" == "y" ]]; then + bash <(php /opt/DragonCore/menu.php xrayRemove) + echo "" + echo -ne "Pressione enter para continuar"; read enter + fi + menuxray + else + menuxray + fi +} + menuferrament(){ clear php /opt/DragonCore/menu.php menuferramenta diff --git a/menu.php b/menu.php index ab497d4..4706d30 100644 --- a/menu.php +++ b/menu.php @@ -31,6 +31,7 @@ require "/opt/DragonCore/speedtest.php"; require "/opt/DragonCore/limiterstart.php"; require "/opt/DragonCore/statusvps.php"; require "/opt/DragonCore/bottg.php"; +require "/opt/DragonCore/xray.php"; function clearScreen() @@ -91,6 +92,7 @@ function menuconnect() "Dragon X Go" => "", "Stunnel4" => "", "OpenVPN" => "", + "Xray Core" => "", "Portas Ativas" => "", ]; @@ -222,6 +224,37 @@ function menuv2() echo "------------\n"; } +function menuxray() +{ + // Build a static set of management options for Xray. Users can install, configure or + // remove Xray regardless of its current state. This avoids confusing toggles and allows + // explicit selection of actions. + $menuItems = [ + 'Criar Usuario Xray' => '', + 'Remover Usuario Xray' => '', + 'Listar Usuarios Xray' => '', + 'Informacao Xray' => '', + 'Gerar Certificado TLS' => '', + 'Instalar/Configurar Xray Core' => '', + 'Remover Xray Core' => '', + ]; + $maxDigits = strlen(count($menuItems)); + echo "Gerenciamento Xray\n"; + echo "------------\n"; + $i = 1; + foreach ($menuItems as $item => $description) { + $number = str_pad($i, $maxDigits, " ", STR_PAD_LEFT); + echo "$number. $item"; + if (!empty($description)) { + echo " $description"; + } + echo "\n"; + $i++; + } + echo "0. Voltar para o menu\n"; + echo "------------\n"; +} + if ($argc < 2) { die("Use o MENU!\n"); diff --git a/version.txt b/version.txt index 4ee2c43..71ab79c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -33 Year Of the Tubular Dragon \ No newline at end of file +34 Year Of the Tubular Dragon \ No newline at end of file diff --git a/xray.php b/xray.php new file mode 100644 index 0000000..2a06f73 --- /dev/null +++ b/xray.php @@ -0,0 +1,535 @@ + 65535) { + $port = 443; + } + + $network = strtolower($network); + if (!in_array($network, ['xhttp', 'ws', 'grpc', 'tcp'], true)) { + $network = 'xhttp'; + } + + + $apiInbound = [ + 'tag' => 'api', + 'port' => 1080, + 'protocol' => 'dokodemo-door', + 'settings' => [ + 'address' => '127.0.0.1', + ], + 'listen' => '127.0.0.1', + ]; + + + $streamSettings = [ + 'network' => $network, + ]; + + if ($network === 'xhttp') { + $streamSettings['security'] = 'tls'; + $streamSettings['tlsSettings'] = [ + 'certificates' => [[ + 'certificateFile' => '/opt/DragonCore/ssl/fullchain.pem', + 'keyFile' => '/opt/DragonCore/ssl/privkey.pem', + ]], + 'alpn' => ['http/1.1'], + ]; + $streamSettings['xhttpSettings'] = [ + 'headers' => null, + 'host' => '', + 'mode' => '', + 'noSSEHeader' => false, + 'path' => '/', + 'scMaxBufferedPosts' => 30, + 'scMaxEachPostBytes' => '1000000', + 'scStreamUpServerSecs' => '20-80', + 'xPaddingBytes' => '100-1000', + ]; + } elseif ($network === 'ws') { + $streamSettings['security'] = 'none'; + $streamSettings['wsSettings'] = [ + 'acceptProxyProtocol' => false, + 'headers' => (object)[], + 'heartbeatPeriod' => 0, + 'host' => '', + 'path' => '/', + ]; + } elseif ($network === 'grpc') { + $streamSettings['security'] = 'none'; + $streamSettings['grpcSettings'] = [ + 'serviceName' => 'vlessgrpc', + 'multiMode' => false, + 'idle_timeout' => 60, + 'permit_without_stream' => false, + ]; + } else { + $streamSettings['security'] = 'none'; + } + + + $dragonInbound = [ + 'tag' => 'inbound-dragoncore', + 'port' => $port, + 'protocol' => 'vless', + 'settings' => [ + 'clients' => [], + 'decryption' => 'none', + 'fallbacks' => [], + ], + 'streamSettings' => $streamSettings, + ]; + + $config = [ + 'api' => [ + 'services' => [ + 'HandlerService', + 'LoggerService', + 'StatsService', + ], + 'tag' => 'api', + ], + 'burstObservatory' => null, + 'dns' => null, + 'fakedns' => null, + 'inbounds' => [ + $apiInbound, + $dragonInbound, + ], + 'log' => [ + 'access' => '/var/log/v2ray/access.log', + 'dnsLog' => false, + 'error' => '', + 'loglevel' => 'info', + 'maskAddress' => '', + ], + 'observatory' => null, + 'outbounds' => [ + [ + 'protocol' => 'freedom', + 'settings' => (object)[], + 'tag' => 'direct', + ], + [ + 'protocol' => 'blackhole', + 'settings' => (object)[], + 'tag' => 'blocked', + ], + ], + 'policy' => [ + 'levels' => [ + '0' => [ + 'statsUserDownlink' => true, + 'statsUserUplink' => true, + ], + ], + 'system' => [ + 'statsInboundDownlink' => true, + 'statsInboundUplink' => true, + 'statsOutboundDownlink' => false, + 'statsOutboundUplink' => false, + ], + ], + 'reverse' => null, + 'routing' => [ + 'domainStrategy' => 'AsIs', + 'rules' => [ + [ + 'inboundTag' => ['api'], + 'outboundTag' => 'api', + 'type' => 'field', + ], + [ + 'ip' => ['geoip:private'], + 'outboundTag' => 'blocked', + 'type' => 'field', + ], + [ + 'outboundTag' => 'blocked', + 'protocol' => ['bittorrent'], + 'type' => 'field', + ], + ], + ], + 'stats' => (object)[], + 'transport' => null, + ]; + + file_put_contents( + $configPath, + json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + + @shell_exec('systemctl restart xray'); + echo "Config Xray gerado em {$configPath} (porta {$port}, protocolo {$network})\n"; +} + +function xrayFixEmptyObjects(array &$config): void +{ + + if (isset($config['stats']) && is_array($config['stats']) && empty($config['stats'])) { + $config['stats'] = (object)[]; + } + + + if (isset($config['outbounds']) && is_array($config['outbounds'])) { + foreach ($config['outbounds'] as &$out) { + if (isset($out['settings']) && is_array($out['settings']) && empty($out['settings'])) { + $out['settings'] = (object)[]; + } + } + unset($out); + } +} + +function xrayAddUser(string $nick, string $protocol = 'xhttp') +{ + global $db_user, $db_pass; + + $uuid = xrayUuid(); + $expiry = date('Y-m-d', strtotime('+30 days')); + + $configPath = xrayConfigPath(); + if (!file_exists($configPath)) { + echo "Arquivo de configuracao {$configPath} nao encontrado. Gere o config Xray primeiro.\n"; + return; + } + + $config = json_decode(file_get_contents($configPath), true); + if (!is_array($config)) { + echo "Falha ao ler/decodificar {$configPath}.\n"; + return; + } + + + $inboundIndex = null; + if (isset($config['inbounds']) && is_array($config['inbounds'])) { + foreach ($config['inbounds'] as $idx => $in) { + if (isset($in['tag']) && $in['tag'] === 'inbound-dragoncore') { + $inboundIndex = $idx; + break; + } + } + } + + if ($inboundIndex === null) { + echo "Inbound 'inbound-dragoncore' nao encontrado no config Xray.\n"; + return; + } + + if ( + !isset($config['inbounds'][$inboundIndex]['settings']['clients']) || + !is_array($config['inbounds'][$inboundIndex]['settings']['clients']) + ) { + $config['inbounds'][$inboundIndex]['settings']['clients'] = []; + } + + $client = [ + 'id' => $uuid, + 'email' => $nick, + 'level' => 0, + ]; + $config['inbounds'][$inboundIndex]['settings']['clients'][] = $client; + + xrayFixEmptyObjects($config); + file_put_contents( + $configPath, + json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + + + $conn = pg_connect("host=localhost dbname=dragoncore user={$db_user} password={$db_pass}"); + if ($conn) { + $query = "INSERT INTO xray (uuid, nick, expiry, protocol) VALUES ($1,$2,$3,$4)"; + $stmt = pg_prepare($conn, '', $query); + pg_execute($conn, '', [$uuid, $nick, $expiry, $protocol]); + pg_close($conn); + } + + @shell_exec('systemctl restart xray'); + + $domain = trim((string)shell_exec('hostname -I | awk "{print $1}"')); + if ($domain === '') { + $domain = '127.0.0.1'; + } + $port = $config['inbounds'][$inboundIndex]['port']; + $uri = "vless://{$uuid}@{$domain}:{$port}#{$nick}"; + + echo "UUID Criado: {$uuid}\n{$uri}\n"; +} + +function xrayRemoveUser($identifier) +{ + global $db_user, $db_pass; + $uuid = null; + + if (is_numeric($identifier)) { + $conn = pg_connect("host=localhost dbname=dragoncore user={$db_user} password={$db_pass}"); + if ($conn) { + $res = pg_query_params($conn, 'SELECT uuid FROM xray WHERE id = $1', [$identifier]); + if ($res && ($row = pg_fetch_assoc($res))) { + $uuid = $row['uuid']; + } + pg_close($conn); + } + } else { + $uuid = $identifier; + } + + if (!$uuid) { + echo "UUID nao encontrado\n"; + return; + } + + $configPath = xrayConfigPath(); + if (file_exists($configPath)) { + $config = json_decode(file_get_contents($configPath), true); + if (isset($config['inbounds']) && is_array($config['inbounds'])) { + $inboundIndex = null; + foreach ($config['inbounds'] as $idx => $in) { + if (isset($in['tag']) && $in['tag'] === 'inbound-dragoncore') { + $inboundIndex = $idx; + break; + } + } + + if ( + $inboundIndex !== null && + isset($config['inbounds'][$inboundIndex]['settings']['clients']) && + is_array($config['inbounds'][$inboundIndex]['settings']['clients']) + ) { + + $clients = &$config['inbounds'][$inboundIndex]['settings']['clients']; + foreach ($clients as $index => $cli) { + if (isset($cli['id']) && $cli['id'] === $uuid) { + array_splice($clients, $index, 1); + break; + } + } + + xrayFixEmptyObjects($config); + file_put_contents( + $configPath, + json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + } + } + } + + $conn = pg_connect("host=localhost dbname=dragoncore user={$db_user} password={$db_pass}"); + if ($conn) { + pg_query_params($conn, 'DELETE FROM xray WHERE uuid = $1', [$uuid]); + pg_close($conn); + } + + @shell_exec('systemctl restart xray'); + echo "Usuario removido: {$uuid}\n"; +} + +function xrayListUsers() +{ + global $db_user, $db_pass; + $conn = pg_connect("host=localhost dbname=dragoncore user={$db_user} password={$db_pass}"); + if (!$conn) { + echo "Falha ao conectar ao banco de dados\n"; + return; + } + $res = pg_query($conn, 'SELECT id, uuid, nick, expiry, protocol FROM xray ORDER BY id'); + while ($row = pg_fetch_assoc($res)) { + echo "ID: {$row['id']} | NICK: {$row['nick']} | UUID: {$row['uuid']} | EXPIRA: {$row['expiry']} | PROTO: {$row['protocol']}\n"; + } + pg_close($conn); +} + +function xrayCert(string $domain) +{ + $sslDir = '/opt/DragonCore/ssl'; + $keyFile = $sslDir . '/privkey.pem'; + $crtFile = $sslDir . '/fullchain.pem'; + + if (!is_dir($sslDir)) { + mkdir($sslDir, 0700, true); + } + + $subject = '/C=BR/ST=SP/L=SaoPaulo/O=DragonCore/OU=VPN/CN=' . $domain; + + $cmd = 'openssl req -x509 -nodes -newkey rsa:2048 ' + . '-days 9999 ' + . '-subj ' . escapeshellarg($subject) . ' ' + . '-keyout ' . escapeshellarg($keyFile) . ' ' + . '-out ' . escapeshellarg($crtFile) . ' 2>/dev/null'; + + @shell_exec($cmd); + + if (file_exists($keyFile) && file_exists($crtFile)) { + echo "Certificado TLS autoassinado (9999 dias) gerado para {$domain}\n"; + } else { + echo "Falha ao gerar certificado autoassinado para {$domain}\n"; + } +} + +function xrayPurgeExpired() +{ + global $db_user, $db_pass; + $today = date('Y-m-d'); + $conn = pg_connect("host=localhost dbname=dragoncore user={$db_user} password={$db_pass}"); + if ($conn) { + $res = pg_query_params($conn, 'SELECT uuid FROM xray WHERE expiry < $1', [$today]); + while ($row = pg_fetch_assoc($res)) { + xrayRemoveUser($row['uuid']); + } + pg_close($conn); + } +} + +function xrayInfo() +{ + $binary = isXrayInstalled() ? '/usr/local/bin/xray' : 'xray'; + $version = trim((string)shell_exec($binary . ' -version 2>&1')); + + global $db_user, $db_pass; + $conn = pg_connect("host=localhost dbname=dragoncore user={$db_user} password={$db_pass}"); + $count = 0; + $protocols = []; + if ($conn) { + $res = pg_query($conn, 'SELECT COUNT(*) AS c FROM xray'); + if ($row = pg_fetch_assoc($res)) { + $count = $row['c']; + } + $res2 = pg_query($conn, 'SELECT DISTINCT protocol FROM xray'); + while ($r = pg_fetch_assoc($res2)) { + $protocols[] = $r['protocol']; + } + pg_close($conn); + } + + echo "Xray Versao: {$version}\n"; + echo "Usuarios cadastrados: {$count}\n"; + echo "Protocolos em uso: " . implode(', ', $protocols) . "\n"; +}