package main import ( "context" "flag" "fmt" "log" "net/http" "os" "os/signal" "path" "strings" "syscall" "time" "srs.epita.fr/fic-server/settings" ) func main() { bind := "127.0.0.1:8080" prefix := "/" teamsDir := "./TEAMS/" settings.SettingsDir = "./SETTINGSDIST" // Read paremeters from environment if v, exists := os.LookupEnv("FIC_BASEURL"); exists { prefix = v } if v, exists := os.LookupEnv("FIC_RECEIVER_BIND"); exists { bind = v } if v, exists := os.LookupEnv("FIC_SETTINGSDIST"); exists { settings.SettingsDir = v } if v, exists := os.LookupEnv("FIC_STARTED_FILE"); exists { startedFile = v } if v, exists := os.LookupEnv("FIC_SUBMISSIONS_DIRECTORY"); exists { SubmissionDir = v } if v, exists := os.LookupEnv("FIC_TEAMS_DIRECTORY"); exists { teamsDir = v } flag.StringVar(&bind, "bind", bind, "Bind port/socket") flag.StringVar(&prefix, "prefix", prefix, "Request path prefix to strip (from proxy)") flag.StringVar(&teamsDir, "teams", teamsDir, "Base directory where find existing teams") flag.StringVar(&settings.SettingsDir, "settings", settings.SettingsDir, "Base directory where read settings") flag.StringVar(&startedFile, "startedFile", startedFile, "Path to the file to create/remove whether or not the challenge is running") flag.StringVar(&SubmissionDir, "submission", SubmissionDir, "Base directory where save submissions") var simulator = flag.String("simulator", "", "Team to simulate (for development only)") flag.StringVar(&staticDir, "static", staticDir, "Set to serve pages as well (for development only, use with -simulator)") flag.Parse() log.SetPrefix("[receiver] ") startedFile = path.Clean(startedFile) SubmissionDir = path.Clean(SubmissionDir) TmpSubmissionDir = path.Join(SubmissionDir, ".tmp") log.Println("Creating submission directory...") if _, err := os.Stat(TmpSubmissionDir); os.IsNotExist(err) { if err = os.MkdirAll(TmpSubmissionDir, 0700); err != nil { log.Fatal("Unable to create submission directory:", err) } } prefix = strings.TrimRight(prefix, "/") // Load configuration settings.LoadAndWatchSettings(path.Join(settings.SettingsDir, settings.SettingsFile), reloadSettings) // Register handlers http.Handle(fmt.Sprintf("%s/chname", prefix), http.StripPrefix(fmt.Sprintf("%s/chname", prefix), submissionTeamChecker{"name change", ChNameHandler, teamsDir, *simulator})) http.Handle(fmt.Sprintf("%s/issue", prefix), http.StripPrefix(fmt.Sprintf("%s/issue", prefix), submissionTeamChecker{"issue", IssueHandler, teamsDir, *simulator})) http.Handle(fmt.Sprintf("%s/openhint/", prefix), http.StripPrefix(fmt.Sprintf("%s/openhint/", prefix), submissionTeamChecker{"opening hint", HintHandler, teamsDir, *simulator})) http.Handle(fmt.Sprintf("%s/wantchoices/", prefix), http.StripPrefix(fmt.Sprintf("%s/wantchoices/", prefix), submissionTeamChecker{"wantint choices", WantChoicesHandler, teamsDir, *simulator})) http.Handle(fmt.Sprintf("%s/registration", prefix), http.StripPrefix(fmt.Sprintf("%s/registration", prefix), submissionChecker{"registration", RegistrationHandler})) http.Handle(fmt.Sprintf("%s/resolution/", prefix), http.StripPrefix(fmt.Sprintf("%s/resolution/", prefix), ResolutionHandler{})) http.Handle(fmt.Sprintf("%s/reset_progress", prefix), http.StripPrefix(fmt.Sprintf("%s/reset_progress", prefix), submissionTeamChecker{"reset_progress", ResetProgressHandler, teamsDir, *simulator})) http.Handle(fmt.Sprintf("%s/submission/", prefix), http.StripPrefix(fmt.Sprintf("%s/submission/", prefix), submissionTeamChecker{"submission", SubmissionHandler, teamsDir, *simulator})) if *simulator != "" { if _, err := os.Stat(path.Join(teamsDir, *simulator)); os.IsNotExist(err) { log.Printf("WARNING: Team '%s' doesn't exist yet in %s.", *simulator, teamsDir) } // Serve team files http.Handle(fmt.Sprintf("%s/wait.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(path.Join(teamsDir, *simulator))))) http.Handle(fmt.Sprintf("%s/my.json", prefix), http.StripPrefix(prefix, TeamMyServer{path.Join(teamsDir, *simulator)})) // Serve generated content http.Handle(fmt.Sprintf("%s/teams.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(teamsDir)))) http.Handle(fmt.Sprintf("%s/themes.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(teamsDir)))) http.Handle(fmt.Sprintf("%s/stats.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(teamsDir)))) http.Handle(fmt.Sprintf("%s/settings.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(settings.SettingsDir)))) // Serve static assets http.Handle(fmt.Sprintf("%s/css/", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(staticDir)))) http.Handle(fmt.Sprintf("%s/js/", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(staticDir)))) http.Handle(fmt.Sprintf("%s/files/", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir("FILES")))) // Serve index http.HandleFunc(fmt.Sprintf("%s/edit", prefix), serveIndex) http.HandleFunc(fmt.Sprintf("%s/rank", prefix), serveIndex) http.HandleFunc(fmt.Sprintf("%s/register", prefix), serveIndex) http.HandleFunc(fmt.Sprintf("%s/rules", prefix), serveIndex) http.HandleFunc(fmt.Sprintf("%s/videos", prefix), serveIndex) } // 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, } // Serve pages go func() { log.Fatal(srv.ListenAndServe()) }() log.Println(fmt.Sprintf("Ready, listening on %s", bind)) // Wait shutdown signal and touch timestamp ticker := time.NewTicker(time.Second) defer ticker.Stop() loop: for { select { case <-interrupt: break loop case <-ticker.C: now := time.Now() os.Chtimes(SubmissionDir, now, now) } } log.Print("The service is shutting down...") srv.Shutdown(context.Background()) log.Println("done") }