package main import ( "context" "flag" "fmt" "log" "net" "net/http" "os" "os/signal" "path" "strings" "syscall" "time" "srs.epita.fr/fic-server/libfic" "srs.epita.fr/fic-server/settings" ) var TeamsDir string var ChStarted = false var lastRegeneration time.Time var skipInitialGeneration = false var allowRegistration bool func reloadSettings(config *settings.Settings) { fic.HintCoefficient = config.HintCurCoefficient fic.WChoiceCoefficient = config.WChoiceCurCoefficient fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient ChStarted = config.Start.Unix() > 0 && time.Since(config.Start) >= 0 if allowRegistration != config.AllowRegistration || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallengeDepth != config.UnlockedChallengeDepth || fic.UnlockedStandaloneExercices != config.UnlockedStandaloneExercices || fic.UnlockedStandaloneExercicesByThemeStepValidation != config.UnlockedStandaloneExercicesByThemeStepValidation || fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation != config.UnlockedStandaloneExercicesByStandaloneExerciceValidation || fic.UnlockedChallengeUpTo != config.UnlockedChallengeUpTo || fic.DisplayAllFlags != config.DisplayAllFlags || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase || fic.SubmissionUniqueness != config.SubmissionUniqueness || fic.DiscountedFactor != config.DiscountedFactor || fic.HideCaseSensitivity != config.HideCaseSensitivity { allowRegistration = config.AllowRegistration fic.PartialValidation = config.PartialValidation fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo fic.UnlockedStandaloneExercices = config.UnlockedStandaloneExercices fic.UnlockedStandaloneExercicesByThemeStepValidation = config.UnlockedStandaloneExercicesByThemeStepValidation fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation = config.UnlockedStandaloneExercicesByStandaloneExerciceValidation fic.DisplayAllFlags = config.DisplayAllFlags fic.FirstBlood = config.FirstBlood fic.SubmissionCostBase = config.SubmissionCostBase fic.SubmissionUniqueness = config.SubmissionUniqueness fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries fic.HideCaseSensitivity = config.HideCaseSensitivity fic.DiscountedFactor = config.DiscountedFactor if !skipInitialGeneration { log.Println("Generating files...") go func() { waitList := genAll() for _, e := range waitList { <-e } log.Println("Full generation done") }() lastRegeneration = time.Now() } else { skipInitialGeneration = false log.Println("Regeneration skipped by option.") } } else { log.Println("No change found. Skipping regeneration.") } } func main() { if v, exists := os.LookupEnv("FIC_BASEURL"); exists { fic.FilesDir = v + "files" } else { fic.FilesDir = "/files" } var bind = flag.String("bind", "./GENERATOR/generator.socket", "Bind path or port/socket") var dsn = flag.String("dsn", fic.DSNGenerator(), "DSN to connect to the MySQL server") flag.StringVar(&settings.SettingsDir, "settings", "./SETTINGSDIST", "Base directory where load and save settings") flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files") flag.StringVar(&fic.FilesDir, "files", fic.FilesDir, "Request path prefix to reach files") flag.BoolVar(&skipInitialGeneration, "skipfullgeneration", skipInitialGeneration, "Skip the initial regeneration") flag.IntVar(¶llelJobs, "jobs", parallelJobs, "Number of generation workers") flag.Parse() log.SetPrefix("[generator] ") settings.SettingsDir = path.Clean(settings.SettingsDir) TeamsDir = path.Clean(TeamsDir) launchWorkers() log.Println("Opening DB...") if err := fic.DBInit(*dsn); err != nil { log.Fatal("Cannot open the database: ", err) } defer fic.DBClose() // Load configuration settings.LoadAndWatchSettings(path.Join(settings.SettingsDir, settings.SettingsFile), reloadSettings) // Register SIGUSR1, SIGUSR2 interrupt1 := make(chan os.Signal, 1) signal.Notify(interrupt1, syscall.SIGUSR1) interrupt2 := make(chan os.Signal, 1) signal.Notify(interrupt2, syscall.SIGUSR2) // Prepare graceful shutdown interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) srv := &http.Server{ Addr: *bind, ReadHeaderTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 30 * time.Second, } http.HandleFunc("/enqueue", enqueueHandler) http.HandleFunc("/perform", performHandler) http.HandleFunc("/full", performFullResyncHandler) // Serve pages go func() { if !strings.Contains(*bind, ":") { if _, err := os.Stat(*bind); !os.IsNotExist(err) { if err := os.Remove(*bind); err != nil { log.Fatal(err) } } os.MkdirAll(path.Dir(*bind), 0751) unixListener, err := net.Listen("unix", *bind) if err != nil { log.Fatal(err) } log.Fatal(srv.Serve(unixListener)) } else if err := srv.ListenAndServe(); err != nil { log.Fatal(err) } }() log.Println(fmt.Sprintf("Ready, listening on %s", *bind)) // Wait shutdown signal loop: for { select { case <-interrupt: break loop case <-interrupt1: log.Println("SIGUSR1 received, regenerating all files...") genAll() log.Println("SIGUSR1 treated.") case <-interrupt2: inQueueMutex.Lock() log.Printf("SIGUSR2 received, dumping statistics:\n parallelJobs: %d\n genQueue: %d\n Teams in queue: %v\n Challenge started: %v\n Last regeneration: %v\n", parallelJobs, len(genQueue), inGenQueue, ChStarted, lastRegeneration) inQueueMutex.Unlock() } } log.Print("The service is shutting down...") srv.Shutdown(context.Background()) log.Println("done") }