package main import ( "flag" "fmt" "log" "os" "os/exec" "os/signal" "strconv" "strings" "syscall" "time" ) var DebugMode bool const ( VERSION = "1.0.0" PIDFILE = "techircd.pid" CONFIGFILE = "config.json" ) func main() { // Parse command line arguments if len(os.Args) < 2 { showUsage() os.Exit(1) } command := strings.ToLower(os.Args[1]) // Parse flags for the remaining arguments flagSet := flag.NewFlagSet("techircd", flag.ExitOnError) configFile := flagSet.String("config", CONFIGFILE, "Path to configuration file") daemon := flagSet.Bool("daemon", false, "Run as daemon (background)") verbose := flagSet.Bool("verbose", false, "Enable verbose logging") debug := flagSet.Bool("debug", false, "Enable extremely detailed debug logging (shows all IRC messages)") port := flagSet.Int("port", 0, "Override port from config") // Parse remaining arguments flagSet.Parse(os.Args[2:]) switch command { case "start": startServer(*configFile, *daemon, *verbose, *debug, *port) case "stop": stopServer() case "restart": stopServer() time.Sleep(2 * time.Second) startServer(*configFile, *daemon, *verbose, *debug, *port) case "status": showStatus() case "reload": reloadConfig() case "version", "-v", "--version": fmt.Printf("TechIRCd version %s\n", VERSION) case "help", "-h", "--help": showUsage() default: fmt.Printf("Unknown command: %s\n", command) showUsage() os.Exit(1) } } func showUsage() { fmt.Printf(`TechIRCd %s - Modern IRC Server Usage: %s [options] Commands: start Start the IRC server stop Stop the IRC server restart Restart the IRC server status Show server status reload Reload configuration version Show version information help Show this help message Options: -config Path to configuration file (default: config.json) -daemon Run as daemon (background process) -verbose Enable verbose logging -debug Enable extremely detailed debug logging (shows all IRC messages) -port Override port from configuration Examples: %s start # Start with default config %s start -config custom.json # Start with custom config %s start -daemon # Start as background daemon %s stop # Stop the server %s status # Check if server is running `, VERSION, os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0], os.Args[0]) } func startServer(configFile string, daemon, verbose, debug bool, port int) { // Check if server is already running if isRunning() { fmt.Println("TechIRCd is already running") os.Exit(1) } // Load configuration config, err := LoadConfig(configFile) if err != nil { log.Printf("Failed to load config from %s, using defaults: %v", configFile, err) config = DefaultConfig() if err := SaveConfig(config, configFile); err != nil { log.Printf("Failed to save default config: %v", err) } } // Override port if specified if port > 0 { config.Server.Listen.Port = port fmt.Printf("Port overridden to %d\n", port) } // Validate configuration if err := config.Validate(); err != nil { log.Fatalf("Configuration validation failed: %v", err) } config.SanitizeConfig() if verbose { log.Println("Configuration validated successfully") } // Set global debug mode DebugMode = debug if debug { log.Println("Debug mode enabled - will log all IRC messages") } // Start as daemon if requested if daemon { startDaemon(configFile, verbose, debug, port) return } // Write PID file if err := writePidFile(); err != nil { log.Fatalf("Failed to write PID file: %v", err) } defer removePidFile() // Create server server := NewServer(config) // Setup signal handling for graceful shutdown with forced shutdown capability c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) shutdownInProgress := false go func() { for sig := range c { switch sig { case syscall.SIGHUP: log.Println("Received SIGHUP, reloading configuration...") // Reload config here if needed case os.Interrupt, syscall.SIGTERM: if shutdownInProgress { log.Println("Received second interrupt signal, forcing immediate shutdown...") removePidFile() os.Exit(1) // Force exit } shutdownInProgress = true log.Println("Shutting down server...") // Start shutdown with timeout shutdownComplete := make(chan bool, 1) go func() { defer func() { if r := recover(); r != nil { log.Printf("Panic during shutdown: %v", r) } shutdownComplete <- true }() server.Shutdown() }() // Wait for graceful shutdown or force after timeout select { case <-shutdownComplete: log.Println("Graceful shutdown completed") case <-time.After(10 * time.Second): log.Println("Shutdown timeout reached, forcing exit...") } removePidFile() os.Exit(0) } } }() // Start the server fmt.Printf("Starting TechIRCd %s on %s:%d\n", VERSION, config.Server.Listen.Host, config.Server.Listen.Port) if config.Server.Listen.EnableSSL { fmt.Printf("SSL enabled on port %d\n", config.Server.Listen.SSLPort) } if err := server.Start(); err != nil { log.Fatalf("Failed to start server: %v", err) } } func startDaemon(configFile string, verbose, debug bool, port int) { // Build command arguments args := []string{os.Args[0], "start", "-config", configFile} if verbose { args = append(args, "-verbose") } if debug { args = append(args, "-debug") } if port > 0 { args = append(args, "-port", strconv.Itoa(port)) } // Start as background process cmd := exec.Command(args[0], args[1:]...) cmd.Stdout = nil cmd.Stderr = nil cmd.Stdin = nil if err := cmd.Start(); err != nil { log.Fatalf("Failed to start daemon: %v", err) } fmt.Printf("TechIRCd started as daemon (PID: %d)\n", cmd.Process.Pid) } func stopServer() { pid, err := readPidFile() if err != nil { fmt.Println("TechIRCd is not running") return } process, err := os.FindProcess(pid) if err != nil { fmt.Println("TechIRCd is not running") removePidFile() return } // Send SIGTERM for graceful shutdown if err := process.Signal(syscall.SIGTERM); err != nil { fmt.Println("TechIRCd is not running") removePidFile() return } // Wait for process to stop for i := 0; i < 10; i++ { if !isRunning() { fmt.Println("TechIRCd stopped") return } time.Sleep(500 * time.Millisecond) } // Force kill if still running process.Signal(syscall.SIGKILL) fmt.Println("TechIRCd force stopped") removePidFile() } func showStatus() { if isRunning() { pid, _ := readPidFile() fmt.Printf("TechIRCd is running (PID: %d)\n", pid) } else { fmt.Println("TechIRCd is not running") } } func reloadConfig() { pid, err := readPidFile() if err != nil { fmt.Println("TechIRCd is not running") return } process, err := os.FindProcess(pid) if err != nil { fmt.Println("TechIRCd is not running") return } if err := process.Signal(syscall.SIGHUP); err != nil { fmt.Println("Failed to reload configuration") return } fmt.Println("Configuration reload signal sent") } func isRunning() bool { pid, err := readPidFile() if err != nil { return false } process, err := os.FindProcess(pid) if err != nil { return false } // Send signal 0 to check if process exists err = process.Signal(syscall.Signal(0)) return err == nil } func writePidFile() error { pid := os.Getpid() return os.WriteFile(PIDFILE, []byte(strconv.Itoa(pid)), 0644) } func readPidFile() (int, error) { data, err := os.ReadFile(PIDFILE) if err != nil { return 0, err } return strconv.Atoi(strings.TrimSpace(string(data))) } func removePidFile() { os.Remove(PIDFILE) }