diff --git a/tools/usercheck/README.md b/tools/usercheck/README.md index a86e5c145..13cf79141 100644 --- a/tools/usercheck/README.md +++ b/tools/usercheck/README.md @@ -14,6 +14,8 @@ go build -o usercheck ./usercheck [options] ``` +By default, the tool reads database credentials from `config.json` in the project root. + ### Commands | Command | Description | @@ -27,28 +29,42 @@ go build -o usercheck ### Examples ```bash -# List connected users -./usercheck list -password "dbpass" +# List connected users (uses config.json) +./usercheck list # Verbose output with last login times -./usercheck list -v -password "dbpass" +./usercheck list -v # Search for a player -./usercheck search -name "Hunter" -password "dbpass" +./usercheck search -name "Hunter" # Show server status -./usercheck servers -password "dbpass" +./usercheck servers # Player login history -./usercheck history -name "Hunter" -password "dbpass" +./usercheck history -name "Hunter" + +# Use a specific config file +./usercheck list -config /path/to/config.json + +# Override database password +./usercheck list -password "different_password" ``` -### Database Options +### Configuration Priority -| Flag | Env Variable | Default | -|------|--------------|---------| -| `-host` | `ERUPE_DB_HOST` | localhost | -| `-port` | - | 5432 | -| `-user` | `ERUPE_DB_USER` | postgres | -| `-password` | `ERUPE_DB_PASSWORD` | (required) | -| `-dbname` | `ERUPE_DB_NAME` | erupe | +1. CLI flags (highest priority) +2. Environment variables (`ERUPE_DB_*`) +3. `config.json` file +4. Default values (lowest priority) + +### Database Flags + +| Flag | Env Variable | Description | +|------|--------------|-------------| +| `-config` | - | Path to config.json | +| `-host` | `ERUPE_DB_HOST` | Database host | +| `-port` | - | Database port | +| `-user` | `ERUPE_DB_USER` | Database user | +| `-password` | `ERUPE_DB_PASSWORD` | Database password | +| `-dbname` | `ERUPE_DB_NAME` | Database name | diff --git a/tools/usercheck/db.go b/tools/usercheck/db.go index 0c826957c..7b8e374a3 100644 --- a/tools/usercheck/db.go +++ b/tools/usercheck/db.go @@ -2,43 +2,169 @@ package main import ( "database/sql" + "encoding/json" "flag" "fmt" "os" + "path/filepath" _ "github.com/lib/pq" ) // DBConfig holds database connection configuration. type DBConfig struct { - Host string - Port int - User string - Password string - DBName string + Host string + Port int + User string + Password string + DBName string + ConfigPath string +} + +// ErupeConfig represents the relevant parts of config.json. +type ErupeConfig struct { + Database struct { + Host string `json:"Host"` + Port int `json:"Port"` + User string `json:"User"` + Password string `json:"Password"` + Database string `json:"Database"` + } `json:"Database"` +} + +// loadConfigFile loads database settings from config.json. +func loadConfigFile(path string) (*ErupeConfig, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var cfg ErupeConfig + if err := json.Unmarshal(data, &cfg); err != nil { + return nil, err + } + + return &cfg, nil +} + +// findConfigFile searches for config.json in common locations. +func findConfigFile() string { + // Check paths relative to current directory and up to project root + paths := []string{ + "config.json", + "../../config.json", // From tools/usercheck/ + "../../../config.json", + } + + // Also check if we can find it via executable path + if exe, err := os.Executable(); err == nil { + dir := filepath.Dir(exe) + paths = append(paths, + filepath.Join(dir, "config.json"), + filepath.Join(dir, "../../config.json"), + ) + } + + for _, p := range paths { + if _, err := os.Stat(p); err == nil { + return p + } + } + + return "" } // addDBFlags adds common database flags to a FlagSet. func addDBFlags(fs *flag.FlagSet, cfg *DBConfig) { - fs.StringVar(&cfg.Host, "host", getEnvOrDefault("ERUPE_DB_HOST", "localhost"), "Database host") - fs.IntVar(&cfg.Port, "port", 5432, "Database port") - fs.StringVar(&cfg.User, "user", getEnvOrDefault("ERUPE_DB_USER", "postgres"), "Database user") - fs.StringVar(&cfg.Password, "password", os.Getenv("ERUPE_DB_PASSWORD"), "Database password") - fs.StringVar(&cfg.DBName, "dbname", getEnvOrDefault("ERUPE_DB_NAME", "erupe"), "Database name") + fs.StringVar(&cfg.ConfigPath, "config", "", "Path to config.json (auto-detected if not specified)") + fs.StringVar(&cfg.Host, "host", "", "Database host (overrides config.json)") + fs.IntVar(&cfg.Port, "port", 0, "Database port (overrides config.json)") + fs.StringVar(&cfg.User, "user", "", "Database user (overrides config.json)") + fs.StringVar(&cfg.Password, "password", "", "Database password (overrides config.json)") + fs.StringVar(&cfg.DBName, "dbname", "", "Database name (overrides config.json)") } -// getEnvOrDefault returns the environment variable value or a default. -func getEnvOrDefault(key, defaultValue string) string { - if value := os.Getenv(key); value != "" { - return value +// resolveDBConfig resolves the final database configuration. +// Priority: CLI flags > environment variables > config.json > defaults +func resolveDBConfig(cfg *DBConfig) error { + // Try to load from config.json + configPath := cfg.ConfigPath + if configPath == "" { + configPath = findConfigFile() } - return defaultValue + + var fileCfg *ErupeConfig + if configPath != "" { + var err error + fileCfg, err = loadConfigFile(configPath) + if err != nil { + // Only error if user explicitly specified a config path + if cfg.ConfigPath != "" { + return fmt.Errorf("failed to load config file: %w", err) + } + // Otherwise just ignore and use defaults/flags + } + } + + // Apply config.json values as base + if fileCfg != nil { + if cfg.Host == "" { + cfg.Host = fileCfg.Database.Host + } + if cfg.Port == 0 { + cfg.Port = fileCfg.Database.Port + } + if cfg.User == "" { + cfg.User = fileCfg.Database.User + } + if cfg.Password == "" { + cfg.Password = fileCfg.Database.Password + } + if cfg.DBName == "" { + cfg.DBName = fileCfg.Database.Database + } + } + + // Apply environment variables + if cfg.Host == "" { + cfg.Host = os.Getenv("ERUPE_DB_HOST") + } + if cfg.User == "" { + cfg.User = os.Getenv("ERUPE_DB_USER") + } + if cfg.Password == "" { + cfg.Password = os.Getenv("ERUPE_DB_PASSWORD") + } + if cfg.DBName == "" { + cfg.DBName = os.Getenv("ERUPE_DB_NAME") + } + + // Apply defaults + if cfg.Host == "" { + cfg.Host = "localhost" + } + if cfg.Port == 0 { + cfg.Port = 5432 + } + if cfg.User == "" { + cfg.User = "postgres" + } + if cfg.DBName == "" { + cfg.DBName = "erupe" + } + + // Password is required + if cfg.Password == "" { + return fmt.Errorf("database password is required (set in config.json, use -password flag, or ERUPE_DB_PASSWORD env var)") + } + + return nil } // connectDB establishes a connection to the PostgreSQL database. func connectDB(cfg *DBConfig) (*sql.DB, error) { - if cfg.Password == "" { - return nil, fmt.Errorf("database password is required (use -password flag or ERUPE_DB_PASSWORD env var)") + if err := resolveDBConfig(cfg); err != nil { + return nil, err } connStr := fmt.Sprintf( diff --git a/tools/usercheck/main.go b/tools/usercheck/main.go index 8a0285347..d61f24f7d 100644 --- a/tools/usercheck/main.go +++ b/tools/usercheck/main.go @@ -31,12 +31,15 @@ func main() { fmt.Fprintf(os.Stderr, " search Search for a specific connected user by name\n") fmt.Fprintf(os.Stderr, " servers Show server/channel status and player counts\n") fmt.Fprintf(os.Stderr, " history Show recent login history for a player\n") - fmt.Fprintf(os.Stderr, "\nDatabase connection options (apply to all commands):\n") - fmt.Fprintf(os.Stderr, " -host Database host (default: localhost)\n") - fmt.Fprintf(os.Stderr, " -port Database port (default: 5432)\n") - fmt.Fprintf(os.Stderr, " -user Database user (default: postgres)\n") - fmt.Fprintf(os.Stderr, " -password Database password (required)\n") - fmt.Fprintf(os.Stderr, " -dbname Database name (default: erupe)\n") + fmt.Fprintf(os.Stderr, "\nDatabase configuration:\n") + fmt.Fprintf(os.Stderr, " By default, reads from config.json in the project root.\n") + fmt.Fprintf(os.Stderr, " Use flags to override specific settings.\n\n") + fmt.Fprintf(os.Stderr, " -config Path to config.json (auto-detected if not specified)\n") + fmt.Fprintf(os.Stderr, " -host Database host (overrides config.json)\n") + fmt.Fprintf(os.Stderr, " -port Database port (overrides config.json)\n") + fmt.Fprintf(os.Stderr, " -user Database user (overrides config.json)\n") + fmt.Fprintf(os.Stderr, " -password Database password (overrides config.json)\n") + fmt.Fprintf(os.Stderr, " -dbname Database name (overrides config.json)\n") fmt.Fprintf(os.Stderr, "\nUse '%s -h' for more information about a command.\n", os.Args[0]) }