package main import ( "database/sql" "flag" "log/slog" "os" "os/signal" "sync" "syscall" "time" _ "github.com/joho/godotenv/autoload" _ "github.com/mattn/go-sqlite3" ) var ( runStart = time.Now() logPath = runStart.Format("2006-01-02.15:04:05") + ".gomeshalerter.json" logFile *os.File logData []LogEntry db *sql.DB logMutex sync.Mutex // Mutex for thread-safe logging ) func main() { setupLogging() defer flushLog() logInfo("main", "Starting gomeshalerter", map[string]interface{}{ "timestamp": runStart.Format("15:04:05"), }) var err error db, err = sql.Open("sqlite3", dbPath+"?_journal=WAL") // Use WAL mode for better concurrency if err != nil { slog.Error("Failed to open database", "error", err) os.Exit(1) } // Define a cleanup function to properly close resources cleanup := func() { logInfo("shutdown", "Closing database", nil) if err := db.Close(); err != nil { logInfo("shutdown", "Error closing database", map[string]interface{}{ "error": err.Error(), }) } flushLog() logInfo("shutdown", "Cleanup complete", nil) } // Ensure cleanup runs on normal exit defer cleanup() if err := setupDatabase(); err != nil { slog.Error("Failed to setup database", "error", err) os.Exit(1) } ollamaModel := "qwen3:32b" ollamaURL := os.Getenv("OLLAMA_URL") if ollamaURL == "" { ollamaURL = "http://localhost:11434" // Default Ollama server URL } logInfo("ollama", "Using model", map[string]interface{}{ "model": ollamaModel, "url": ollamaURL, }) // Replace --broadcast flag with --dry-run flag (default is to broadcast) dryRun := flag.Bool("dry-run", false, "don't actually send to meshtastic, just print what would be sent") flag.Parse() // Create a WaitGroup to manage goroutines var wg sync.WaitGroup // Create a channel to signal shutdown shutdown := make(chan struct{}) // Set up signal handling for immediate exit with cleanup sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) go func() { <-sigChan logInfo("shutdown", "Received signal, performing cleanup before exit", nil) cleanup() logInfo("shutdown", "Exiting", nil) os.Exit(0) // Exit after cleanup }() // Start RSS feed checker goroutine wg.Add(1) go func() { defer wg.Done() rssFeedChecker(shutdown, ollamaURL, ollamaModel) }() // Start article summarizer goroutine wg.Add(1) go func() { defer wg.Done() articleSummarizer(shutdown, ollamaURL, ollamaModel) }() // Start broadcaster goroutine wg.Add(1) go func() { defer wg.Done() broadcaster(shutdown, *dryRun) }() // Start web server goroutine wg.Add(1) go func() { defer wg.Done() webServer(shutdown) }() // Start log cleanup worker goroutine wg.Add(1) go func() { defer wg.Done() logCleanupWorker(shutdown) }() // Start device manager goroutine for periodic device maintenance wg.Add(1) go func() { defer wg.Done() deviceManager(shutdown, *dryRun) }() // Wait for all goroutines to finish wg.Wait() logInfo("shutdown", "All goroutines stopped, exiting", nil) }