// Command stepd provides an HTTP server that automatically executes STEP files. package main import "os" import "log" import "time" import "errors" import "context" import "net/http" import "path/filepath" import "git.tebibyte.media/sashakoshka/step" import "git.tebibyte.media/sashakoshka/go-cli" import "git.tebibyte.media/sashakoshka/step/providers" import "git.tebibyte.media/sashakoshka/goutil/container" import "git.tebibyte.media/sashakoshka/go-service/daemon" import stephttp"git.tebibyte.media/sashakoshka/step/http" import "git.tebibyte.media/sashakoshka/go-service/routines" func main () { // parse command line arguments flagPidFile := cli.NewInputFlag ( 'p', "pid-file", "Write the PID to the specified file.", "", cli.ValString) flagAddress := cli.NewInputFlag ( 'a', "address", "The address to host the server on.", ":8080", cli.ValString) // TODO have in conf, override here flagErrorDocument := cli.NewInputFlag ( 0, "error-document", "The document to use for displaying errors.", "", cli.ValString) // TODO have in conf, override here flagDirectories := cli.NewFlag ( 'd', "directories", "Serve the contents of directories.") cmd := cli.New ( "Run an HTTP server that automaticaly executes STEP files.", flagPidFile, flagAddress, flagErrorDocument, flagDirectories, cli.NewHelp()) cmd.Syntax = "[OPTIONS]... [DIRECTORY]" cmd.ParseOrExit(os.Args) log.Println(`==========| STEP |===========`) log.Println(`Scriptable Template Processor`) log.Println(`... initializing`) // manage start and end of program ctx, done := context.WithCancel(context.Background()) daemon.OnSigint(done) pidFileAbs, err := filepath.Abs(flagPidFile.Value) if err != nil { log.Fatalln("XXX", err) } pidFile := daemon.PidFile(pidFileAbs) if !pidFile.Empty() { err := pidFile.Start() if err != nil { log.Println("!!! could not write pid:", err) } defer func () { err := pidFile.Close() if err != nil { log.Println("!!! could not delete pidfile:", err) } } () } // the single argument is for the directory to serve. we actually cd // there. if len(cmd.Args) == 1 { err := os.Chdir(cmd.Args[0]) if err != nil { log.Fatalln("XXX", err) } } else if len(cmd.Args) > 1 { cmd.Usage() os.Exit(1) } // set up the environment environment := step.Environment { } environment.Providers = providers.All() err = environment.Init(context.Background()) if err != nil { log.Fatal(err) } // set up the HTTP handler handler := stephttp.Handler { Environment: &environment, Directories: flagDirectories.Value == "true", StepExt: ucontainer.NewSet(".step"), Index: []string { "index.step", "index.html", "index" }, ErrorDocument: flagErrorDocument.Value, } // set up the HTTP server httpServer := http.Server { Addr: flagAddress.Value, Handler: &handler, } // set up the routine manager httpServerRoutine := httpServerRoutine(httpServer) manager := routines.Manager { Routines: []routines.Routine { &httpServerRoutine, }, } log.Println(`.// initialized.`) log.Printf("(i) listening on %s\n", httpServer.Addr) if err := manager.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { log.Fatalln("XXX", err) } } type httpServerRoutine http.Server func (this *httpServerRoutine) Run (ctx context.Context) error { ctx, done := context.WithCancel(ctx) defer done() server := http.Server(*this) go func () { <- ctx.Done() shutdownCtx, _ := context.WithTimeout ( context.Background(), 16 * time.Second) server.Shutdown(shutdownCtx) } () err := server.ListenAndServe() if ctx.Err() != nil { return ctx.Err() } return err }