> "$SINGBOX_LOG" 2>&1 log() { echo "[$(date)] $1" >> "$FIREWALL_LOG" } log "Starting Sing-box with config: $CONFIG_FILE" log "Restarting firewall..." /etc/init.d/firewall restart sleep 2 if command -v fw4 > /dev/null; then log "FW4 Detected. Starting nftables." nft flush ruleset nft -f - <<'NFTABLES' flush ruleset table inet singbox { set local_ipv4 { type ipv4_addr flags interval elements = { 10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, 240.0.0.0/4 } } set local_ipv6 { type ipv6_addr flags interval elements = { ::ffff:0.0.0.0/96, 64:ff9b::/96, 100::/64, 2001::/32, 2001:10::/28, 2001:20::/28, 2001:db8::/32, 2002::/16, fc00::/7, fe80::/10 } } chain singbox-tproxy { fib daddr type { unspec, local, anycast, multicast } return ip daddr @local_ipv4 return ip6 daddr @local_ipv6 return udp dport { 123 } return meta l4proto { tcp, udp } meta mark set 1 tproxy to :9888 accept } chain singbox-mark { fib daddr type { unspec, local, anycast, multicast } return ip daddr @local_ipv4 return ip6 daddr @local_ipv6 return udp dport { 123 } return meta mark set 1 } chain mangle-output { type route hook output priority mangle; policy accept; meta l4proto { tcp, udp } skgid != 1 ct direction original goto singbox-mark } chain mangle-prerouting { type filter hook prerouting priority mangle; policy accept; iifname { lo, eth0 } meta l4proto { tcp, udp } ct direction original goto singbox-tproxy } } NFTABLES elif command -v fw3 > /dev/null; then log "FW3 Detected. Starting iptables." iptables -t mangle -F iptables -t mangle -X iptables -t mangle -N singbox-mark iptables -t mangle -A singbox-mark -m addrtype --dst-type UNSPEC,LOCAL,ANYCAST,MULTICAST -j RETURN iptables -t mangle -A singbox-mark -d 10.0.0.0/8 -j RETURN iptables -t mangle -A singbox-mark -d 127.0.0.0/8 -j RETURN iptables -t mangle -A singbox-mark -d 169.254.0.0/16 -j RETURN iptables -t mangle -A singbox-mark -d 172.16.0.0/12 -j RETURN iptables -t mangle -A singbox-mark -d 192.168.0.0/16 -j RETURN iptables -t mangle -A singbox-mark -d 240.0.0.0/4 -j RETURN iptables -t mangle -A singbox-mark -p udp --dport 123 -j RETURN iptables -t mangle -A singbox-mark -j MARK --set-mark 1 iptables -t mangle -N singbox-tproxy iptables -t mangle -A singbox-tproxy -m addrtype --dst-type UNSPEC,LOCAL,ANYCAST,MULTICAST -j RETURN iptables -t mangle -A singbox-tproxy -d 10.0.0.0/8 -j RETURN iptables -t mangle -A singbox-tproxy -d 127.0.0.0/8 -j RETURN iptables -t mangle -A singbox-tproxy -d 169.254.0.0/16 -j RETURN iptables -t mangle -A singbox-tproxy -d 172.16.0.0/12 -j RETURN iptables -t mangle -A singbox-tproxy -d 192.168.0.0/16 -j RETURN iptables -t mangle -A singbox-tproxy -d 240.0.0.0/4 -j RETURN iptables -t mangle -A singbox-tproxy -p udp --dport 123 -j RETURN iptables -t mangle -A singbox-tproxy -p tcp -j TPROXY --tproxy-mark 0x1/0x1 --on-port 9888 iptables -t mangle -A singbox-tproxy -p udp -j TPROXY --tproxy-mark 0x1/0x1 --on-port 9888 iptables -t mangle -A OUTPUT -p tcp -m cgroup ! --cgroup 1 -j singbox-mark iptables -t mangle -A OUTPUT -p udp -m cgroup ! --cgroup 1 -j singbox-mark iptables -t mangle -A PREROUTING -i lo -p tcp -j singbox-tproxy iptables -t mangle -A PREROUTING -i lo -p udp -j singbox-tproxy iptables -t mangle -A PREROUTING -i eth0 -p tcp -j singbox-tproxy iptables -t mangle -A PREROUTING -i eth0 -p udp -j singbox-tproxy ip6tables -t mangle -N singbox-mark ip6tables -t mangle -A singbox-mark -m addrtype --dst-type UNSPEC,LOCAL,ANYCAST,MULTICAST -j RETURN ip6tables -t mangle -A singbox-mark -d ::ffff:0.0.0.0/96 -j RETURN ip6tables -t mangle -A singbox-mark -d 64:ff9b::/96 -j RETURN ip6tables -t mangle -A singbox-mark -d 100::/64 -j RETURN ip6tables -t mangle -A singbox-mark -d 2001::/32 -j RETURN ip6tables -t mangle -A singbox-mark -d 2001:10::/28 -j RETURN ip6tables -t mangle -A singbox-mark -d 2001:20::/28 -j RETURN ip6tables -t mangle -A singbox-mark -d 2001:db8::/32 -j RETURN ip6tables -t mangle -A singbox-mark -d 2002::/16 -j RETURN ip6tables -t mangle -A singbox-mark -d fc00::/7 -j RETURN ip6tables -t mangle -A singbox-mark -d fe80::/10 -j RETURN ip6tables -t mangle -A singbox-mark -p udp --dport 123 -j RETURN ip6tables -t mangle -A singbox-mark -j MARK --set-mark 1 ip6tables -t mangle -N singbox-tproxy ip6tables -t mangle -A singbox-tproxy -m addrtype --dst-type UNSPEC,LOCAL,ANYCAST,MULTICAST -j RETURN ip6tables -t mangle -A singbox-tproxy -d ::ffff:0.0.0.0/96 -j RETURN ip6tables -t mangle -A singbox-tproxy -d 64:ff9b::/96 -j RETURN ip6tables -t mangle -A singbox-tproxy -d 100::/64 -j RETURN ip6tables -t mangle -A singbox-tproxy -d 2001::/32 -j RETURN ip6tables -t mangle -A singbox-tproxy -d 2001:10::/28 -j RETURN ip6tables -t mangle -A singbox-tproxy -d 2001:20::/28 -j RETURN ip6tables -t mangle -A singbox-tproxy -d 2001:db8::/32 -j RETURN ip6tables -t mangle -A singbox-tproxy -d 2002::/16 -j RETURN ip6tables -t mangle -A singbox-tproxy -d fc00::/7 -j RETURN ip6tables -t mangle -A singbox-tproxy -d fe80::/10 -j RETURN ip6tables -t mangle -A singbox-tproxy -p udp --dport 123 -j RETURN ip6tables -t mangle -A singbox-tproxy -p tcp -j TPROXY --tproxy-mark 0x1/0x1 --on-port 9888 ip6tables -t mangle -A singbox-tproxy -p udp -j TPROXY --tproxy-mark 0x1/0x1 --on-port 9888 ip6tables -t mangle -A OUTPUT -p tcp -m cgroup ! --cgroup 1 -j singbox-mark ip6tables -t mangle -A OUTPUT -p udp -m cgroup ! --cgroup 1 -j singbox-mark ip6tables -t mangle -A PREROUTING -i lo -p tcp -j singbox-tproxy ip6tables -t mangle -A PREROUTING -i lo -p udp -j singbox-tproxy ip6tables -t mangle -A PREROUTING -i eth0 -p tcp -j singbox-tproxy ip6tables -t mangle -A PREROUTING -i eth0 -p udp -j singbox-tproxy else log "Neither fw3 nor fw4 detected, unable to configure firewall rules." exit 1 fi log "Firewall rules applied successfully" log "Starting sing-box with config: $CONFIG_FILE" exec "$SINGBOX_BIN" run -c "$CONFIG_FILE" EOF; function createStartScript($configFile) { global $start_script_template, $singbox_bin, $singbox_log, $log; $script = sprintf($start_script_template, $singbox_log, $configFile, $singbox_bin, $log); $dir = dirname('/etc/neko/core/start.sh'); if (!file_exists($dir)) { mkdir($dir, 0755, true); } file_put_contents('/etc/neko/core/start.sh', $script); chmod('/etc/neko/core/start.sh', 0755); //writeToLog("Created start script with config: $configFile"); //writeToLog("Singbox binary: $singbox_bin"); //writeToLog("Log file: $singbox_log"); //writeToLog("Firewall log file: $log"); } function writeToLog($message) { global $log; $dateTime = new DateTime(); $time = $dateTime->format('H:i:s'); $logMessage = "[ $time ] $message\n"; if (file_put_contents($log, $logMessage, FILE_APPEND) === false) { error_log("Failed to write to log file: $log"); } } function createCronScript() { $log_file = '/var/log/singbox_log.txt'; $tmp_log_file = '/etc/neko/tmp/neko_log.txt'; $additional_log_file = '/etc/neko/tmp/log.txt'; $max_size = 1048576; $cron_schedule = "0 */4 * * * /bin/bash /etc/neko/core/set_cron.sh"; $cronScriptContent = <</dev/null; echo "$cron_schedule") | crontab - timestamp() { date "+[ %H:%M:%S ]" } if [ -f "\$LOG_FILE" ] && [ \$(stat -c %s "\$LOG_FILE") -gt \$MAX_SIZE ]; then echo "\$(timestamp) Sing-box 日志文件 (\$LOG_FILE) 超过 \$MAX_SIZE 字节. 清理日志..." >> \$LOG_PATH 2>&1 > "\$LOG_FILE" echo "\$(timestamp) Sing-box 日志文件 (\$LOG_FILE) 已清空." >> \$LOG_PATH 2>&1 else echo "\$(timestamp) Sing-box 日志文件 (\$LOG_FILE) 在大小限制内, 无需操作." >> \$LOG_PATH 2>&1 fi if [ -f "\$TMP_LOG_FILE" ] && [ \$(stat -c %s "\$TMP_LOG_FILE") -gt \$MAX_SIZE ]; then echo "\$(timestamp) Mihomo 日志文件 (\$TMP_LOG_FILE) 超过 \$MAX_SIZE 字节. 清理日志..." >> \$LOG_PATH 2>&1 > "\$TMP_LOG_FILE" echo "\$(timestamp) Mihomo 日志文件 (\$TMP_LOG_FILE) 已清空." >> \$LOG_PATH 2>&1 else echo "\$(timestamp) Mihomo 日志文件 (\$TMP_LOG_FILE) 在大小限制内, 无需操作." >> \$LOG_PATH 2>&1 fi if [ -f "\$ADDITIONAL_LOG_FILE" ] && [ \$(stat -c %s "\$ADDITIONAL_LOG_FILE") -gt \$MAX_SIZE ]; then echo "\$(timestamp) NeKoBox 日志文件 (\$ADDITIONAL_LOG_FILE) 超过 \$MAX_SIZE 字节. 清理日志..." >> \$LOG_PATH 2>&1 > "\$ADDITIONAL_LOG_FILE" echo "\$(timestamp) NeKoBox 日志文件 (\$ADDITIONAL_LOG_FILE) 已清空." >> \$LOG_PATH 2>&1 else echo "\$(timestamp) NeKoBox 日志文件 (\$ADDITIONAL_LOG_FILE) 在大小限制内, 无需操作." >> \$LOG_PATH 2>&1 fi echo "\$(timestamp) 日志轮换完成." >> \$LOG_PATH 2>&1 EOL; $cronScriptPath = '/etc/neko/core/set_cron.sh'; file_put_contents($cronScriptPath, $cronScriptContent); chmod($cronScriptPath, 0755); shell_exec("sh $cronScriptPath"); echo ''; } function rotateLogs($logFile, $maxSize = 1048576) { if (file_exists($logFile) && filesize($logFile) > $maxSize) { file_put_contents($logFile, ''); chmod($logFile, 0644); // echo "Log file cleared successfully.\n"; } } function isSingboxRunning() { global $singbox_bin; $command = "pgrep -f " . escapeshellarg($singbox_bin); exec($command, $output); return !empty($output); } function isNekoBoxRunning() { global $neko_dir; $pid = trim(shell_exec("cat $neko_dir/tmp/neko.pid 2>/dev/null")); return !empty($pid) && file_exists("/proc/$pid"); } function getSingboxPID() { global $singbox_bin; $command = "pgrep -f " . escapeshellarg($singbox_bin); exec($command, $output); return isset($output[0]) ? $output[0] : null; } function getRunningConfigFile() { global $singbox_bin; $command = "ps w | grep '$singbox_bin' | grep -v grep"; exec($command, $output); foreach ($output as $line) { if (strpos($line, '-c') !== false) { $parts = explode('-c', $line); if (isset($parts[1])) { $configPath = trim(explode(' ', trim($parts[1]))[0]); return $configPath; } } } return null; } function getAvailableConfigFiles() { global $singbox_config_dir; return glob("$singbox_config_dir/*.json"); } $availableConfigs = getAvailableConfigFiles(); //writeToLog("Script started"); if(isset($_POST['neko'])){ $dt = $_POST['neko']; writeToLog("Received neko action: $dt"); if ($dt == 'start') { if (isSingboxRunning()) { writeToLog("Cannot start NekoBox: Sing-box is running"); } else { shell_exec("$neko_dir/core/neko -s"); writeToLog("Mihomo started successfully"); } } if ($dt == 'disable') { shell_exec("$neko_dir/core/neko -k"); writeToLog("Mihomo stopped"); } if ($dt == 'restart') { if (isSingboxRunning()) { writeToLog("Cannot restart NekoBox: Sing-box is running"); } else { shell_exec("$neko_dir/core/neko -r"); writeToLog("Mihomo restarted successfully"); } } if ($dt == 'clear') { shell_exec("echo \"Logs has been cleared...\" > $neko_dir/tmp/neko_log.txt"); writeToLog("Mihomo logs cleared"); } writeToLog("Neko action completed: $dt"); } if (isset($_POST['singbox'])) { $action = $_POST['singbox']; $config_file = isset($_POST['config_file']) ? $_POST['config_file'] : ''; writeToLog("Received singbox action: $action"); //writeToLog("Config file: $config_file"); switch ($action) { case 'start': if (isNekoBoxRunning()) { writeToLog("Cannot start Sing-box: NekoBox is running"); } else { writeToLog("Starting Sing-box"); $singbox_version = trim(shell_exec("$singbox_bin version")); writeToLog("Sing-box version: $singbox_version"); shell_exec("mkdir -p " . dirname($singbox_log)); shell_exec("touch $singbox_log && chmod 644 $singbox_log"); rotateLogs($singbox_log); createStartScript($config_file); createCronScript(); $output = shell_exec("sh $start_script_path >> $singbox_log 2>&1 &"); //writeToLog("Shell output: " . ($output ?: "No output")); sleep(3); $pid = getSingboxPID(); if ($pid) { writeToLog("Sing-box Started successfully. PID: $pid"); } else { writeToLog("Failed to start Sing-box"); } } break; case 'disable': writeToLog("Stopping Sing-box"); $pid = getSingboxPID(); if ($pid) { writeToLog("Killing Sing-box PID: $pid"); shell_exec("kill $pid"); if (file_exists('/usr/sbin/fw4')) { shell_exec("nft flush ruleset"); } else { shell_exec("iptables -t mangle -F"); shell_exec("iptables -t mangle -X"); } shell_exec("/etc/init.d/firewall restart"); writeToLog("Cleared firewall rules and restarted firewall"); sleep(1); if (!isSingboxRunning()) { writeToLog("Sing-box has been stopped successfully"); } else { writeToLog("Force killing Sing-box"); shell_exec("kill -9 $pid"); writeToLog("Sing-box has been force stopped"); } } else { writeToLog("Sing-box is not running"); } break; case 'restart': if (isNekoBoxRunning()) { writeToLog("Cannot restart Sing-box: NekoBox is running"); } else { writeToLog("Restarting Sing-box"); $pid = getSingboxPID(); if ($pid) { writeToLog("Killing Sing-box PID: $pid"); shell_exec("kill $pid"); sleep(1); } shell_exec("mkdir -p " . dirname($singbox_log)); shell_exec("touch $singbox_log && chmod 644 $singbox_log"); rotateLogs($singbox_log); createStartScript($config_file); shell_exec("sh $start_script_path >> $singbox_log 2>&1 &"); sleep(3); $new_pid = getSingboxPID(); if ($new_pid) { writeToLog("Sing-box Restarted successfully. New PID: $new_pid"); } else { writeToLog("Failed to restart Sing-box"); } } break; } sleep(2); $singbox_status = isSingboxRunning() ? '1' : '0'; exec("uci set neko.cfg.singbox_enabled='$singbox_status'"); exec("uci commit neko"); //writeToLog("Singbox status set to: $singbox_status"); } if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['cronTime'])) { $cronTime = $_POST['cronTime']; if (empty($cronTime)) { $logMessage = "请提供有效的 Cron 时间格式!"; file_put_contents('/etc/neko/tmp/log.txt', date('Y-m-d H:i:s') . " - ERROR: $logMessage\n", FILE_APPEND); echo $logMessage; exit; } $startScriptPath = '/etc/neko/core/start.sh'; if (!file_exists('/etc/neko/tmp')) { mkdir('/etc/neko/tmp', 0755, true); } $restartScriptContent = << /dev/null return $? } if pgrep -x "singbox" > /dev/null then echo "$(timestamp) Sing-box 正在运行,正在重启..." >> \$LOG_PATH kill $(pgrep -x "singbox") sleep 2 start_singbox RETRY_COUNT=0 while ! check_singbox && [ \$RETRY_COUNT -lt \$MAX_RETRIES ]; do echo "$(timestamp) Sing-box 重启失败,正在尝试重新启动... (\$((RETRY_COUNT + 1))/\$MAX_RETRIES)" >> \$LOG_PATH sleep \$RETRY_INTERVAL start_singbox ((RETRY_COUNT++)) done if check_singbox; then echo "$(timestamp) Sing-box 重启成功!" >> \$LOG_PATH else echo "$(timestamp) Sing-box 重启失败,已达到最大重试次数!" >> \$LOG_PATH fi else echo "$(timestamp) Sing-box 没有运行, 启动 Sing-box..." >> \$LOG_PATH start_singbox RETRY_COUNT=0 while ! check_singbox && [ \$RETRY_COUNT -lt \$MAX_RETRIES ]; do echo "$(timestamp) Sing-box 启动失败,正在尝试重新启动... (\$((RETRY_COUNT + 1))/\$MAX_RETRIES)" >> \$LOG_PATH sleep \$RETRY_INTERVAL start_singbox ((RETRY_COUNT++)) done if check_singbox; then echo "$(timestamp) Sing-box 启动成功!" >> \$LOG_PATH else echo "$(timestamp) Sing-box 启动失败,已达到最大重试次数!" >> \$LOG_PATH fi fi EOL; $scriptPath = '/etc/neko/core/restart_singbox.sh'; file_put_contents($scriptPath, $restartScriptContent); chmod($scriptPath, 0755); $cronSchedule = $cronTime . " /bin/bash $scriptPath"; exec("crontab -l | grep -v '$scriptPath' | crontab -"); exec("(crontab -l 2>/dev/null; echo \"$cronSchedule\") | crontab -"); $logMessage = "定时任务已设置成功,Sing-box 将在 $cronTime 自动重启。"; file_put_contents('/etc/neko/tmp/log.txt', date('[ H:i:s ] ') . "$logMessage\n", FILE_APPEND); echo json_encode(['success' => true, 'message' => '定时任务已设置成功']); exit; } if (isset($_POST['clear_singbox_log'])) { file_put_contents($singbox_log, ''); writeToLog("Singbox log cleared"); } if (isset($_POST['clear_plugin_log'])) { $plugin_log_file = "$neko_dir/tmp/log.txt"; file_put_contents($plugin_log_file, ''); writeToLog("Nekobox log cleared"); } $neko_status = exec("uci -q get neko.cfg.enabled"); $singbox_status = isSingboxRunning() ? '1' : '0'; exec("uci set neko.cfg.singbox_enabled='$singbox_status'"); exec("uci commit neko"); //writeToLog("Final neko status: $neko_status"); //writeToLog("Final singbox status: $singbox_status"); if ($singbox_status == '1') { $runningConfigFile = getRunningConfigFile(); if ($runningConfigFile) { $str_cfg = htmlspecialchars(basename($runningConfigFile)); //writeToLog("Running config file: $str_cfg"); } else { $str_cfg = 'Sing-box 配置文件:未找到运行中的配置文件'; writeToLog("No running config file found"); } } function readRecentLogLines($filePath, $lines = 1000) { if (!file_exists($filePath)) { return "日志文件不存在: $filePath"; } if (!is_readable($filePath)) { return "无法读取日志文件: $filePath"; } $command = "tail -n $lines " . escapeshellarg($filePath); $output = shell_exec($command); return $output ?: "日志为空"; } function readLogFile($filePath) { if (file_exists($filePath)) { return nl2br(htmlspecialchars(readRecentLogLines($filePath, 1000), ENT_NOQUOTES)); } else { return '日志文件不存在。'; } } $neko_log_content = readLogFile("$neko_dir/tmp/neko_log.txt"); $singbox_log_content = readLogFile($singbox_log); ?> "$devices - $fullOSInfo", 'ramUsage' => "$ramUsage/$ramTotal MB", 'cpuLoad' => "$cpuLoadAvg1Min $cpuLoadAvg5Min $cpuLoadAvg15Min", 'uptime' => "{$days}天 {$hours}小时 {$minutes}分钟 {$seconds}秒", 'cpuLoadAvg1Min' => $cpuLoadAvg1Min, 'ramTotal' => $ramTotal, 'ramUsageOnly' => $ramUsage, ]); exit; } ?> "0.0.0.0:9090", "secret" => "Akun", "external-ui" => "ui" ]; foreach ($default_config_content as $key => $value) { if (strpos($config_content, "$key:") === false) { $config_content .= "$key: $value\n"; $missing_config = true; } } if ($missing_config) { file_put_contents($current_config, $config_content); $logMessage = "配置文件缺少某些选项,已自动添加缺失的配置项。"; } } if (isset($logMessage)) { echo ""; } if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['selected_config'])) { $selected_file = $_POST['selected_config']; $config_dir = '/etc/neko/config'; $selected_file_path = $config_dir . '/' . $selected_file; if (file_exists($selected_file_path) && pathinfo($selected_file, PATHINFO_EXTENSION) == 'yaml') { file_put_contents('/www/nekobox/lib/selected_config.txt', $selected_file_path); } else { echo ""; } } ?> Home - Nekobox
首页 面板 订阅 设定

NekoBox

运行状态
Mihomo 运行中\n"; } else { echo "\n"; } echo "\n"; if ($singbox_status == 1) { echo "\n"; } else { echo "\n"; } ?>
Mihomo
Singbox
运行模式

日志