2023-06-23 04:08:00 +03:00
package cmd
import (
"fmt"
2026-04-04 19:32:23 +03:00
"os"
2025-09-30 05:27:54 +03:00
"strconv"
2023-06-30 22:49:58 +03:00
"strings"
2026-04-28 03:46:08 +03:00
"time"
2023-06-23 04:08:00 +03:00
2023-06-23 04:16:45 +03:00
"github.com/karust/openserp/core"
2026-04-23 03:06:49 +03:00
browserprofile "github.com/karust/openserp/core/browser"
2023-06-23 04:08:00 +03:00
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
const (
2026-05-24 02:29:04 +03:00
version = "0.7.13"
2023-06-30 22:49:58 +03:00
defaultConfigFilename = "config"
envPrefix = "OPENSERP"
2023-06-23 04:08:00 +03:00
)
2023-06-30 22:49:58 +03:00
type Config struct {
2026-04-01 00:54:21 +03:00
Server ServerConfig ` mapstructure:"server" `
App AppConfig ` mapstructure:"app" `
Proxies core . ProxiesConfig ` mapstructure:"proxies" `
Cache CacheConfig ` mapstructure:"cache" `
Resilience ResilienceConfig ` mapstructure:"resilience" `
CircuitBreaker CircuitBreakerConfig ` mapstructure:"circuit_breaker" `
CORS CORSConfig ` mapstructure:"cors" `
2026-04-20 20:12:19 +03:00
Captcha CaptchaConfig ` mapstructure:"captcha" `
2026-04-01 00:54:21 +03:00
Config2Capcha Config2Captcha ` mapstructure:"2captcha" `
GoogleConfig EngineConfig ` mapstructure:"google" `
YandexConfig EngineConfig ` mapstructure:"yandex" `
BaiduConfig EngineConfig ` mapstructure:"baidu" `
BingConfig EngineConfig ` mapstructure:"bing" `
DuckDuckGoConfig EngineConfig ` mapstructure:"duckduckgo" `
2026-04-28 10:31:05 +02:00
EcosiaConfig EngineConfig ` mapstructure:"ecosia" `
2024-05-06 01:13:07 +03:00
}
type Config2Captcha struct {
ApiKey string ` mapstructure:"apikey" `
2023-06-30 22:49:58 +03:00
}
2026-04-01 00:54:21 +03:00
type ServerConfig struct {
2023-06-30 22:49:58 +03:00
Host string ` mapstructure:"host" `
Port int ` mapstructure:"port" `
ConfigPath string ` mapstructure:"config_path" `
IsDebug bool ` mapstructure:"debug" `
IsVerbose bool ` mapstructure:"verbose" `
IsRawRequests bool ` mapstructure:"raw_requests" `
2025-04-08 16:06:41 +08:00
Insecure bool ` mapstructure:"insecure" `
2026-04-01 00:54:21 +03:00
}
type AppConfig struct {
2026-04-28 03:46:08 +03:00
Timeout int ` mapstructure:"timeout" `
BrowserPath string ` mapstructure:"browser_path" `
ProfilesJSON string ` mapstructure:"profiles" `
IsBrowserHead bool ` mapstructure:"head" `
IsLeaveHead bool ` mapstructure:"leave_head" `
IsLeakless bool ` mapstructure:"leakless" `
BlockResources string ` mapstructure:"block_resources" `
BlockTrackers bool ` mapstructure:"block_trackers" `
DebugEndpoints bool ` mapstructure:"debug_endpoints" `
LogFormat string ` mapstructure:"log_format" `
MaxProcesses int ` mapstructure:"max_processes" `
IdleTTL time . Duration ` mapstructure:"idle_ttl" `
2026-04-29 02:45:16 +03:00
MegaTimeout time . Duration ` mapstructure:"mega_timeout" `
2023-06-23 04:08:00 +03:00
}
2026-04-01 00:54:21 +03:00
type EngineConfig struct {
core . SearchEngineOptions ` mapstructure:",squash" `
Proxy string ` mapstructure:"proxy" `
2026-03-30 22:46:03 +03:00
}
2026-03-30 00:31:49 +03:00
type CacheConfig struct {
TTLSeconds int ` mapstructure:"ttl_seconds" `
MaxSize int ` mapstructure:"max_size" `
}
2026-03-25 23:31:41 +03:00
type ResilienceConfig struct {
MaxRetries int ` mapstructure:"max_retries" `
AllowEndpointFallback bool ` mapstructure:"allow_endpoint_fallback" `
}
type CircuitBreakerConfig struct {
Failures int ` mapstructure:"failures" `
RecoverySeconds int ` mapstructure:"recovery_seconds" `
Successes int ` mapstructure:"successes" `
}
type CORSConfig struct {
Enabled bool ` mapstructure:"enabled" `
AllowOrigins string ` mapstructure:"allow_origins" `
AllowMethods string ` mapstructure:"allow_methods" `
AllowHeaders string ` mapstructure:"allow_headers" `
MaxAge int ` mapstructure:"max_age" `
}
2026-04-20 20:12:19 +03:00
type CaptchaConfig struct {
SolverEnabled bool ` mapstructure:"solver_enabled" `
}
2023-06-30 22:49:58 +03:00
var config = Config { }
2023-06-23 04:08:00 +03:00
2025-09-30 05:27:54 +03:00
var flagToConfigKey = map [ string ] string {
2026-04-01 00:54:21 +03:00
"host" : "server.host" ,
"port" : "server.port" ,
"timeout" : "app.timeout" ,
"config" : "server.config_path" ,
2026-03-25 23:31:41 +03:00
"browser-path" : "app.browser_path" ,
2026-04-23 03:06:49 +03:00
"profiles-json" : "app.profiles" ,
2026-04-01 00:54:21 +03:00
"verbose" : "server.verbose" ,
"debug" : "server.debug" ,
"head" : "app.head" ,
"leakless" : "app.leakless" ,
"raw" : "server.raw_requests" ,
2026-03-25 23:31:41 +03:00
"leave" : "app.leave_head" ,
"2captcha_key" : "2captcha.apikey" ,
2026-04-01 00:54:21 +03:00
"proxy" : "proxies.global" ,
2026-04-23 00:53:30 +03:00
"debug-endpoints" : "app.debug_endpoints" ,
2026-04-01 00:54:21 +03:00
"insecure" : "server.insecure" ,
2026-03-30 00:31:49 +03:00
"cache_ttl" : "cache.ttl_seconds" ,
"cache_max_size" : "cache.max_size" ,
2026-03-25 23:31:41 +03:00
"max_retries" : "resilience.max_retries" ,
"allow_endpoint_fallback" : "resilience.allow_endpoint_fallback" ,
"cb_failures" : "circuit_breaker.failures" ,
"cb_recovery" : "circuit_breaker.recovery_seconds" ,
"cb_successes" : "circuit_breaker.successes" ,
2026-04-22 03:39:30 +03:00
"log_format" : "app.log_format" ,
2025-09-30 05:27:54 +03:00
}
2023-06-23 04:08:00 +03:00
var RootCmd = & cobra . Command {
2023-06-30 22:49:58 +03:00
Use : "openserp" ,
Short : "Open SERP" ,
Long : ` Get [Google, Yandex, Baidu] search engine results via API or CLI. ` ,
Version : version ,
SilenceUsage : true ,
2023-06-23 04:08:00 +03:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2023-06-24 22:26:36 +03:00
err := initializeConfig ( cmd )
2023-06-30 22:49:58 +03:00
if err != nil {
return err
}
2026-04-23 03:06:49 +03:00
if err := browserprofile . LoadProfilesFromJSON ( config . App . ProfilesJSON ) ; err != nil {
return fmt . Errorf ( "load app.profiles: %w" , err )
}
2023-06-30 22:49:58 +03:00
2026-04-22 03:39:30 +03:00
logFormat , err := core . NormalizeLogFormat ( config . App . LogFormat )
if err != nil {
return err
}
config . App . LogFormat = logFormat
core . InitLogger ( config . Server . IsVerbose , config . Server . IsDebug , config . App . LogFormat )
2026-04-28 03:46:08 +03:00
logrus . WithField ( "config" , sanitizedConfigForLog ( config ) ) . Debug ( "Final config" )
2023-06-30 22:49:58 +03:00
return nil
2023-06-23 04:08:00 +03:00
} ,
}
2026-04-28 03:46:08 +03:00
func sanitizedConfigForLog ( cfg Config ) map [ string ] interface { } {
return map [ string ] interface { } {
"server" : cfg . Server ,
"app" : map [ string ] interface { } {
"timeout" : cfg . App . Timeout ,
"browser_path" : cfg . App . BrowserPath != "" ,
"profiles" : cfg . App . ProfilesJSON != "" ,
"head" : cfg . App . IsBrowserHead ,
"leave_head" : cfg . App . IsLeaveHead ,
"leakless" : cfg . App . IsLeakless ,
"block_resources" : cfg . App . BlockResources ,
"block_trackers" : cfg . App . BlockTrackers ,
"debug_endpoints" : cfg . App . DebugEndpoints ,
"log_format" : cfg . App . LogFormat ,
"max_processes" : cfg . App . MaxProcesses ,
"idle_ttl" : cfg . App . IdleTTL . String ( ) ,
2026-04-29 02:45:16 +03:00
"mega_timeout" : cfg . App . MegaTimeout . String ( ) ,
2026-04-28 03:46:08 +03:00
} ,
"proxies" : map [ string ] interface { } {
"global" : maskedProxyForLog ( cfg . Proxies . Global ) ,
"entries" : len ( cfg . Proxies . Entries ) ,
"allow_request_proxy_url" : cfg . Proxies . AllowRequestProxyURL ,
"health" : cfg . Proxies . Health ,
"lanes" : cfg . Proxies . Lanes ,
} ,
"cache" : cfg . Cache ,
"resilience" : cfg . Resilience ,
"circuit_breaker" : cfg . CircuitBreaker ,
"cors" : cfg . CORS ,
"captcha" : cfg . Captcha ,
"2captcha" : map [ string ] interface { } {
"apikey_configured" : strings . TrimSpace ( cfg . Config2Capcha . ApiKey ) != "" ,
} ,
"google" : cfg . GoogleConfig ,
"yandex" : cfg . YandexConfig ,
"baidu" : cfg . BaiduConfig ,
"bing" : cfg . BingConfig ,
"duckduckgo" : cfg . DuckDuckGoConfig ,
2026-04-28 10:31:05 +02:00
"ecosia" : cfg . EcosiaConfig ,
2026-04-28 03:46:08 +03:00
}
}
func maskedProxyForLog ( proxyURL string ) string {
if strings . TrimSpace ( proxyURL ) == "" {
return ""
}
return core . MaskProxyURL ( proxyURL )
}
2023-06-23 04:08:00 +03:00
// Bind each cobra flag to its associated viper configuration (config file and environment variable)
func bindFlags ( cmd * cobra . Command , vpr * viper . Viper ) {
cmd . Flags ( ) . VisitAll ( func ( flg * pflag . Flag ) {
2025-09-30 05:27:54 +03:00
configName , ok := flagToConfigKey [ flg . Name ]
if ! ok {
configName = "app." + flg . Name
}
if err := vpr . BindPFlag ( configName , flg ) ; err != nil {
2026-04-22 03:39:30 +03:00
logrus . WithError ( err ) . Error ( fmt . Sprintf ( "Unable to bind flag %s: %v" , flg . Name , err ) )
2025-09-30 05:27:54 +03:00
}
2023-06-23 04:08:00 +03:00
2023-06-30 22:49:58 +03:00
if flg . Changed {
2025-09-30 05:27:54 +03:00
val , err := parseFlagValue ( flg )
if err != nil {
2026-04-22 03:39:30 +03:00
logrus . WithError ( err ) . Error ( fmt . Sprintf ( "Unable to parse flag %s: %v" , flg . Name , err ) )
2025-09-30 05:27:54 +03:00
return
}
vpr . Set ( configName , val )
2023-06-23 04:08:00 +03:00
}
} )
}
2025-09-30 05:27:54 +03:00
func parseFlagValue ( flg * pflag . Flag ) ( interface { } , error ) {
switch flg . Value . Type ( ) {
case "string" :
return flg . Value . String ( ) , nil
case "bool" :
return strconv . ParseBool ( flg . Value . String ( ) )
case "int" :
return strconv . Atoi ( flg . Value . String ( ) )
default :
return flg . Value . String ( ) , nil
}
}
2023-06-23 04:08:00 +03:00
// Initialize Viper
func initializeConfig ( cmd * cobra . Command ) error {
v := viper . New ( )
2026-03-25 23:31:41 +03:00
setConfigDefaults ( v )
2023-06-23 04:08:00 +03:00
2026-04-04 19:32:23 +03:00
explicitConfigPath := strings . TrimSpace ( cmd . Flag ( "config" ) . Value . String ( ) )
if explicitConfigPath == "" {
explicitConfigPath = strings . TrimSpace ( os . Getenv ( envPrefix + "_SERVER_CONFIG_PATH" ) )
}
if explicitConfigPath != "" {
v . SetConfigFile ( explicitConfigPath )
} else {
// Base name of the config file, without the file extension
v . SetConfigName ( defaultConfigFilename )
v . AddConfigPath ( "." )
}
2023-06-23 04:08:00 +03:00
2025-09-30 05:27:54 +03:00
// 1. Config file (lowest priority). Return an error if we cannot parse the config file.
2023-06-30 22:49:58 +03:00
err := v . ReadInConfig ( )
if err != nil {
2026-04-04 19:32:23 +03:00
if explicitConfigPath != "" {
return fmt . Errorf ( "cannot read config %q: %w" , explicitConfigPath , err )
}
2024-05-06 01:13:07 +03:00
err = fmt . Errorf ( "cannot read config: %v" , err )
2023-06-30 22:49:58 +03:00
logrus . Warn ( err )
2023-06-23 04:08:00 +03:00
}
2025-09-30 05:27:54 +03:00
// 2. Environment variables (medium priority). Bind environment variables to their equivalent keys with underscores
2023-06-30 22:49:58 +03:00
for _ , key := range v . AllKeys ( ) {
envKey := envPrefix + "_" + strings . ToUpper ( strings . ReplaceAll ( key , "." , "_" ) )
err := v . BindEnv ( key , envKey )
if err != nil {
2026-04-22 03:39:30 +03:00
logrus . WithError ( err ) . Error ( fmt . Sprintf ( "Unable to bind ENV valye: %v" , err ) )
2023-06-30 22:49:58 +03:00
}
}
2023-06-23 04:08:00 +03:00
2025-09-30 05:27:54 +03:00
// 3. Command flags (highest priority). Bind the current command's flags to viper
2023-06-23 04:08:00 +03:00
bindFlags ( cmd , v )
2026-04-24 04:02:23 +03:00
// Keep compatibility with historical typo in local configs. Runs after all
// sources are merged so CLI flags and env vars take precedence over the typo key.
if v . IsSet ( "app.block_resorces" ) && ! v . IsSet ( "app.block_resources" ) {
v . Set ( "app.block_resources" , v . Get ( "app.block_resorces" ) )
logrus . Warn ( ` config key "app.block_resorces" is deprecated, use "app.block_resources" ` )
}
2026-04-01 00:54:21 +03:00
if err := validateRemovedConfigPaths ( v ) ; err != nil {
return err
}
2023-06-30 22:49:58 +03:00
// Dump Viper values to config struct
2026-04-01 00:54:21 +03:00
if err := validateEngineProxyTags ( v ) ; err != nil {
return err
}
2023-06-30 22:49:58 +03:00
err = v . Unmarshal ( & config )
if err != nil {
2024-05-06 01:13:07 +03:00
return fmt . Errorf ( "cannot unmarshall config: %v" , err )
2023-06-30 22:49:58 +03:00
}
2026-04-24 04:02:23 +03:00
if _ , err := core . ParseBlockedResourceTypes ( config . App . BlockResources ) ; err != nil {
return fmt . Errorf ( "invalid app.block_resources: %w" , err )
}
2026-04-01 00:54:21 +03:00
config . Proxies , err = core . NormalizeProxiesConfig ( config . Proxies )
2026-03-30 22:46:03 +03:00
if err != nil {
2026-04-01 00:54:21 +03:00
return fmt . Errorf ( "invalid proxies config: %w" , err )
2026-03-30 22:46:03 +03:00
}
2026-04-01 00:54:21 +03:00
return nil
}
func validateEngineProxyTags ( v * viper . Viper ) error {
2026-05-05 02:47:47 +03:00
for _ , engineName := range [ ] string { "google" , "yandex" , "baidu" , "bing" , "duckduckgo" , "ecosia" } {
2026-04-01 00:54:21 +03:00
key := engineName + ".proxy"
if ! v . IsSet ( key ) {
continue
}
raw := v . Get ( key )
tag , ok := raw . ( string )
if ! ok {
return fmt . Errorf ( "invalid %s.proxy config: proxy must be a string tag" , engineName )
}
if _ , err := core . NormalizeProxyTag ( tag ) ; err != nil {
return fmt . Errorf ( "invalid %s.proxy config: %w" , engineName , err )
}
2026-03-30 22:46:03 +03:00
}
2026-04-01 00:54:21 +03:00
return nil
}
func validateRemovedConfigPaths ( v * viper . Viper ) error {
legacyKeys := map [ string ] string {
"app.proxy" : "use proxies.global or proxies.entries with per-engine proxy tags instead" ,
"proxy_pool" : "use proxies.entries and proxies.health.failure_threshold instead" ,
"proxy_pool.urls" : "use proxies.entries instead" ,
"proxy_pool.failure_threshold" : "use proxies.health.failure_threshold instead" ,
"app.host" : "move to server.host" ,
"app.port" : "move to server.port" ,
"app.debug" : "move to server.debug" ,
"app.verbose" : "move to server.verbose" ,
"app.raw_requests" : "move to server.raw_requests" ,
"app.insecure" : "move to server.insecure" ,
"proxies.defaults" : "use proxies.global or per-engine proxy tags instead" ,
"proxies.defaults.mode" : "use proxies.global or per-engine proxy tags instead" ,
"proxies.defaults.tag" : "use per-engine proxy tags on each engine instead" ,
"google.proxy.mode" : "use google.proxy: <tag> or omit it for direct mode" ,
"google.proxy.tag" : "use google.proxy: <tag>" ,
"yandex.proxy.mode" : "use yandex.proxy: <tag> or omit it for direct mode" ,
"yandex.proxy.tag" : "use yandex.proxy: <tag>" ,
"baidu.proxy.mode" : "use baidu.proxy: <tag> or omit it for direct mode" ,
"baidu.proxy.tag" : "use baidu.proxy: <tag>" ,
"bing.proxy.mode" : "use bing.proxy: <tag> or omit it for direct mode" ,
"bing.proxy.tag" : "use bing.proxy: <tag>" ,
"duckduckgo.proxy.mode" : "use duckduckgo.proxy: <tag> or omit it for direct mode" ,
"duckduckgo.proxy.tag" : "use duckduckgo.proxy: <tag>" ,
2026-03-30 22:46:03 +03:00
}
2026-04-01 00:54:21 +03:00
for key , hint := range legacyKeys {
if v . IsSet ( key ) {
return fmt . Errorf ( "config key %q is removed in proxy v2: %s" , key , hint )
}
2023-06-30 22:49:58 +03:00
}
2023-06-23 04:08:00 +03:00
return nil
}
2026-03-25 23:31:41 +03:00
func setConfigDefaults ( v * viper . Viper ) {
2026-04-01 00:54:21 +03:00
v . SetDefault ( "server.host" , "127.0.0.1" )
v . SetDefault ( "server.port" , 7070 )
v . SetDefault ( "server.debug" , false )
v . SetDefault ( "server.verbose" , false )
v . SetDefault ( "server.raw_requests" , false )
v . SetDefault ( "server.insecure" , false )
2026-04-22 03:39:30 +03:00
v . SetDefault ( "app.log_format" , "" )
2026-04-01 00:54:21 +03:00
v . SetDefault ( "app.timeout" , 30 )
v . SetDefault ( "app.browser_path" , "" )
2026-04-23 03:06:49 +03:00
v . SetDefault ( "app.profiles" , "" )
2026-04-01 00:54:21 +03:00
v . SetDefault ( "app.head" , false )
v . SetDefault ( "app.leave_head" , false )
v . SetDefault ( "app.leakless" , false )
2026-04-24 04:02:23 +03:00
v . SetDefault ( "app.block_resources" , "" )
v . SetDefault ( "app.block_trackers" , false )
2026-04-23 00:53:30 +03:00
v . SetDefault ( "app.debug_endpoints" , false )
2026-04-28 03:46:08 +03:00
v . SetDefault ( "app.max_processes" , 4 )
v . SetDefault ( "app.idle_ttl" , "10m" )
2026-04-29 02:45:16 +03:00
v . SetDefault ( "app.mega_timeout" , "90s" )
2026-04-01 00:54:21 +03:00
v . SetDefault ( "proxies.entries" , [ ] interface { } { } )
v . SetDefault ( "proxies.global" , "" )
2026-04-28 03:46:08 +03:00
v . SetDefault ( "proxies.allow_request_proxy_url" , false )
2026-04-01 00:54:21 +03:00
v . SetDefault ( "proxies.health.failure_threshold" , core . DefaultProxyFailureThreshold )
2026-04-28 03:46:08 +03:00
v . SetDefault ( "proxies.lanes.enabled" , true )
v . SetDefault ( "proxies.lanes.max_lanes" , core . DefaultProxyLaneMaxLanes )
v . SetDefault ( "proxies.lanes.drop_cookies_on_challenge" , true )
2026-04-01 00:54:21 +03:00
2026-03-30 00:31:49 +03:00
v . SetDefault ( "cache.ttl_seconds" , 300 )
v . SetDefault ( "cache.max_size" , 1000 )
2026-03-25 23:31:41 +03:00
// Keep stage2 defaults stable even when config file is absent.
v . SetDefault ( "resilience.max_retries" , 3 )
v . SetDefault ( "resilience.allow_endpoint_fallback" , false )
v . SetDefault ( "circuit_breaker.failures" , 5 )
v . SetDefault ( "circuit_breaker.recovery_seconds" , 60 )
v . SetDefault ( "circuit_breaker.successes" , 2 )
v . SetDefault ( "cors.enabled" , true )
v . SetDefault ( "cors.allow_origins" , "*" )
v . SetDefault ( "cors.allow_methods" , "GET, POST, OPTIONS" )
2026-04-28 03:46:08 +03:00
v . SetDefault ( "cors.allow_headers" , "Origin, Content-Type, Accept, Authorization, X-Use-Proxy, X-Proxy-URL, X-Proxy-Country, X-Proxy-Class, X-Proxy-Provider, X-Proxy-Session-ID, X-Request-ID, X-Tenant" )
2026-03-25 23:31:41 +03:00
v . SetDefault ( "cors.max_age" , 86400 )
2026-04-20 20:12:19 +03:00
v . SetDefault ( "captcha.solver_enabled" , false )
2026-03-25 23:31:41 +03:00
}
2023-06-23 04:08:00 +03:00
func init ( ) {
2026-04-01 00:54:21 +03:00
RootCmd . PersistentFlags ( ) . IntVarP ( & config . Server . Port , "port" , "p" , 7070 , "Port number to run server" )
RootCmd . PersistentFlags ( ) . StringVarP ( & config . Server . Host , "host" , "a" , "127.0.0.1" , "Host address to run server" )
2023-06-30 22:49:58 +03:00
RootCmd . PersistentFlags ( ) . IntVarP ( & config . App . Timeout , "timeout" , "t" , 30 , "Timeout to fail request" )
2026-04-01 00:54:21 +03:00
RootCmd . PersistentFlags ( ) . StringVarP ( & config . Server . ConfigPath , "config" , "c" , "" , "Configuration file path" )
2026-03-17 02:18:51 +03:00
RootCmd . PersistentFlags ( ) . StringVarP ( & config . App . BrowserPath , "browser-path" , "" , "" , "Custom browser binary path (Chrome/Chromium/Edge/Brave..)" )
2026-04-23 03:06:49 +03:00
RootCmd . PersistentFlags ( ) . StringVar ( & config . App . ProfilesJSON , "profiles" , "" , "Path to browser profile catalog JSON" )
2026-04-01 00:54:21 +03:00
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . Server . IsVerbose , "verbose" , "v" , false , "Use verbose output" )
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . Server . IsDebug , "debug" , "d" , false , "Use debug output. Disable headless browser" )
2023-06-30 22:49:58 +03:00
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsBrowserHead , "head" , "" , false , "Enable browser UI" )
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsLeakless , "leakless" , "l" , false , "Use leakless mode to insure browser instances are closed after search" )
2026-04-01 00:54:21 +03:00
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . Server . IsRawRequests , "raw" , "r" , false , "Disable browser usage, use HTTP requests" )
2023-06-30 22:49:58 +03:00
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . App . IsLeaveHead , "leave" , "" , false , "Leave browser and tabs opened after search is made" )
2024-05-06 01:13:07 +03:00
RootCmd . PersistentFlags ( ) . StringVarP ( & config . Config2Capcha . ApiKey , "2captcha_key" , "" , "" , "2 captcha api key" )
2026-04-01 00:54:21 +03:00
RootCmd . PersistentFlags ( ) . StringVarP ( & config . Proxies . Global , "proxy" , "x" , "" , "Force a single proxy for all engines (same as proxies.global)" )
2026-04-23 00:53:30 +03:00
RootCmd . PersistentFlags ( ) . BoolVar ( & config . App . DebugEndpoints , "debug-endpoints" , false , "Enable debug-only HTTP endpoints" )
2026-04-01 00:54:21 +03:00
RootCmd . PersistentFlags ( ) . BoolVarP ( & config . Server . Insecure , "insecure" , "k" , false , "Allow insecure TLS connections" )
2026-03-30 00:31:49 +03:00
RootCmd . PersistentFlags ( ) . IntVar ( & config . Cache . TTLSeconds , "cache_ttl" , 300 , "Cache TTL in seconds (0 to disable)" )
RootCmd . PersistentFlags ( ) . IntVar ( & config . Cache . MaxSize , "cache_max_size" , 1000 , "Maximum number of cached responses" )
2026-03-25 23:31:41 +03:00
RootCmd . PersistentFlags ( ) . IntVar ( & config . Resilience . MaxRetries , "max_retries" , 3 , "Max retry attempts per search engine (0 to disable)" )
RootCmd . PersistentFlags ( ) . BoolVar ( & config . Resilience . AllowEndpointFallback , "allow_endpoint_fallback" , false , "Allow dedicated endpoints to fallback to other engines" )
RootCmd . PersistentFlags ( ) . IntVar ( & config . CircuitBreaker . Failures , "cb_failures" , 5 , "Consecutive failures before circuit breaker opens" )
RootCmd . PersistentFlags ( ) . IntVar ( & config . CircuitBreaker . RecoverySeconds , "cb_recovery" , 60 , "Seconds before retrying an engine with open circuit" )
RootCmd . PersistentFlags ( ) . IntVar ( & config . CircuitBreaker . Successes , "cb_successes" , 2 , "Consecutive successful half-open checks needed to close circuit" )
2026-04-22 03:39:30 +03:00
RootCmd . PersistentFlags ( ) . StringVar ( & config . App . LogFormat , "log_format" , "" , "Log format: json or text (default: json in production, text in debug)" )
2023-06-23 04:08:00 +03:00
}