user = $user; $this->servers = $servers; } public function getFlags(): array { return $this->flags; } public function handle() { $appName = admin_setting('app_name', 'XBoard'); $this->config = $this->loadConfig(); $this->buildOutbounds(); $this->buildRule(); $user = $this->user; return response() ->json($this->config) ->header('profile-title', 'base64:' . base64_encode($appName)) ->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}") ->header('profile-update-interval', '24'); } protected function loadConfig() { $defaultConfig = base_path('resources/rules/default.sing-box.json'); $customConfig = base_path('resources/rules/custom.sing-box.json'); $jsonData = file_exists($customConfig) ? file_get_contents($customConfig) : file_get_contents($defaultConfig); return json_decode($jsonData, true); } protected function buildOutbounds() { $outbounds = $this->config['outbounds']; $proxies = []; foreach ($this->servers as $item) { if ($item['type'] === 'shadowsocks') { $ssConfig = $this->buildShadowsocks($item['password'], $item); $proxies[] = $ssConfig; } if ($item['type'] === 'trojan') { $trojanConfig = $this->buildTrojan($this->user['uuid'], $item); $proxies[] = $trojanConfig; } if ($item['type'] === 'vmess') { $vmessConfig = $this->buildVmess($this->user['uuid'], $item); $proxies[] = $vmessConfig; } if ($item['type'] === 'vless') { $vlessConfig = $this->buildVless($this->user['uuid'], $item); $proxies[] = $vlessConfig; } if ($item['type'] === 'hysteria') { $hysteriaConfig = $this->buildHysteria($this->user['uuid'], $item, $this->user); $proxies[] = $hysteriaConfig; } } foreach ($outbounds as &$outbound) { if (in_array($outbound['type'], ['urltest', 'selector'])) { array_push($outbound['outbounds'], ...array_column($proxies, 'tag')); } } $outbounds = array_merge($outbounds, $proxies); $this->config['outbounds'] = $outbounds; return $outbounds; } /** * Build rule */ protected function buildRule() { $rules = $this->config['route']['rules']; // Force the nodes ip to be a direct rule array_unshift($rules, [ 'ip_cidr' => collect($this->servers)->pluck('host')->map(function ($host) { return filter_var($host, FILTER_VALIDATE_IP) ? [$host] : Helper::getIpByDomainName($host); })->flatten()->unique()->values(), 'outbound' => 'direct', ]); $this->config['route']['rules'] = $rules; } protected function buildShadowsocks($password, $server) { $array = []; $array['tag'] = $server['name']; $array['type'] = 'shadowsocks'; $array['server'] = $server['host']; $array['server_port'] = $server['port']; $array['method'] = data_get($server, 'protocol_settings.cipher'); $array['password'] = $password; return $array; } protected function buildVmess($uuid, $server) { $protocol_settings = $server['protocol_settings']; $array = [ 'tag' => $server['name'], 'type' => 'vmess', 'server' => $server['host'], 'server_port' => $server['port'], 'uuid' => $uuid, 'security' => 'auto', 'alter_id' => 0, 'transport' => [], 'tls' => $protocol_settings['tls'] ? [ 'enabled' => true, 'insecure' => (bool) data_get($protocol_settings, 'tls_settings.allow_insecure'), 'server_name' => data_get($protocol_settings, 'tls_settings.server_name') ] : null ]; $transport = match ($protocol_settings['network']) { 'tcp' => [ 'type' => 'http', 'path' => \Arr::random(data_get($protocol_settings, 'network_settings.header.request.path', [])) ], 'ws' => [ 'type' => 'ws', 'path' => data_get($protocol_settings, 'network_settings.path'), 'headers' => data_get($protocol_settings, 'network_settings.headers.Host') ? ['Host' => data_get($protocol_settings, 'network_settings.headers.Host')] : null, 'max_early_data' => 2048, 'early_data_header_name' => 'Sec-WebSocket-Protocol' ], 'grpc' => [ 'type' => 'grpc', 'service_name' => data_get($protocol_settings, 'network_settings.serviceName') ], default => null }; if ($transport) { $array['transport'] = array_filter($transport, fn($value) => !is_null($value)); } return $array; } protected function buildVless($password, $server) { $protocol_settings = data_get($server, 'protocol_settings', []); $array = [ "type" => "vless", "tag" => $server['name'], "server" => $server['host'], "server_port" => $server['port'], "uuid" => $password, "packet_encoding" => "xudp", 'flow' => data_get($protocol_settings, 'flow', ''), ]; if ($protocol_settings['tls']) { $tlsConfig = [ 'enabled' => true, 'insecure' => (bool) data_get($protocol_settings, 'tls_settings.allow_insecure'), 'server_name' => data_get($protocol_settings, 'tls_settings.server_name'), 'utls' => [ 'enabled' => true, 'fingerprint' => Helper::getRandFingerprint() ] ]; if ($protocol_settings['tls'] == 2) { $tlsConfig['reality'] = [ 'enabled' => true, 'public_key' => data_get($protocol_settings, 'reality_settings.public_key'), 'short_id' => data_get($protocol_settings, 'reality_settings.short_id') ]; } $array['tls'] = $tlsConfig; } $transport = match ($protocol_settings['network']) { 'tcp' => data_get($protocol_settings, 'network_settings.header.type') == 'http' ? [ 'type' => 'http', 'path' => data_get($protocol_settings, 'network_settings.header.request.path') ] : null, 'ws' => [ 'type' => 'ws', 'path' => data_get($protocol_settings, 'network_settings.path'), 'headers' => data_get($protocol_settings, 'network_settings.headers.Host') ? ['Host' => [data_get($protocol_settings, 'network_settings.headers.Host')]] : null, 'max_early_data' => 2048, 'early_data_header_name' => 'Sec-WebSocket-Protocol' ], 'grpc' => [ 'type' => 'grpc', 'service_name' => data_get($protocol_settings, 'network_settings.serviceName') ], 'h2' => [ 'type' => 'http', 'host' => data_get($protocol_settings, 'network_settings.host') ? [data_get($protocol_settings, 'network_settings.host')] : null, 'path' => data_get($protocol_settings, 'network_settings.path') ], default => null }; if ($transport) { $array['transport'] = array_filter($transport, fn($value) => !is_null($value)); } return $array; } protected function buildTrojan($password, $server) { $protocol_settings = $server['protocol_settings']; $array = [ 'tag' => $server['name'], 'type' => 'trojan', 'server' => $server['host'], 'server_port' => $server['port'], 'password' => $password, 'tls' => [ 'enabled' => true, 'insecure' => (bool) data_get($protocol_settings, 'allow_insecure', false), 'server_name' => data_get($protocol_settings, 'server_name') ] ]; $transport = match (data_get($protocol_settings, 'network')) { 'grpc' => [ 'type' => 'grpc', 'service_name' => data_get($protocol_settings, 'network_settings.serviceName') ], 'ws' => [ 'type' => 'ws', 'path' => data_get($protocol_settings, 'network_settings.path'), 'headers' => data_get($protocol_settings, 'network_settings.headers.Host') ? ['Host' => [data_get($protocol_settings, 'network_settings.headers.Host')]] : null, 'max_early_data' => 2048, 'early_data_header_name' => 'Sec-WebSocket-Protocol' ], default => null }; $array['transport'] = $transport; return $array; } protected function buildHysteria($password, $server): array { $protocol_settings = $server['protocol_settings']; $baseConfig = [ 'server' => $server['host'], 'server_port' => $server['port'], 'tag' => $server['name'], 'tls' => [ 'enabled' => true, 'insecure' => (bool) $protocol_settings['tls']['allow_insecure'], 'server_name' => $protocol_settings['tls']['server_name'] ] ]; $speedConfig = [ 'up_mbps' => $protocol_settings['bandwidth']['up'], 'down_mbps' => $protocol_settings['bandwidth']['down'], ]; $versionConfig = match (data_get($protocol_settings, 'version', 1)) { 2 => [ 'type' => 'hysteria2', 'password' => $password, 'obfs' => $protocol_settings['obfs']['open'] ? [ 'type' => $protocol_settings['obfs']['type'], 'password' => $protocol_settings['obfs']['password'] ] : null, ], default => [ 'type' => 'hysteria', 'auth_str' => $password, 'obfs' => $protocol_settings['obfs']['password'], 'disable_mtu_discovery' => true, ] }; return array_merge( $baseConfig, $speedConfig, $versionConfig ); } }