package surf import ( "context" "fmt" "net" "net/http" "os" "os/signal" "strings" "syscall" "time" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/bonfire" "git.golem15.com/golem15/summercms/modules/lagoon" "git.golem15.com/golem15/summercms/modules/lagoon/attach" "git.golem15.com/golem15/summercms/modules/party" "gocloud.dev/blob" ) // ServeCommand starts a signal-aware HTTP server on the assembled router. func ServeCommand(app *backpack.App, plugins []party.Plugin) bonfire.Command { return bonfire.Command{ Name: "serve", Description: "Serve the application HTTP API", Flags: []bonfire.Flag{{ Name: "addr", Description: "Listen address", Default: ":8080", }}, Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error { addr, _ := in.Flag("addr") if strings.TrimSpace(addr) == "" { addr = ":8080" } sqlDB, gdb, err := lagoon.OpenFromApp(ctx, app) if err != nil { return err } defer sqlDB.Close() if err := lagoon.Publish(app, sqlDB, gdb); err != nil { return err } bucket, err := publishUploads(ctx, app) if err != nil { return err } defer bucket.Close() h, err := Assemble(app, plugins) if err != nil { return err } ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) defer stop() srv := &http.Server{Addr: addr, Handler: h, BaseContext: func(net.Listener) context.Context { return ctx }} errCh := make(chan error, 1) go func() { out.Info(fmt.Sprintf("listening on %s", addr)) errCh <- srv.ListenAndServe() }() select { case <-ctx.Done(): shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(shutdownCtx); err != nil { return err } err := <-errCh if err == nil || err == http.ErrServerClosed { return nil } return err case err := <-errCh: if err == nil || err == http.ErrServerClosed { return nil } return err } }, } } // publishUploads opens storage.uploads.bucket_url and publishes *blob.Bucket. // An empty URL fails boot the same way an empty JWT secret does. func publishUploads(ctx context.Context, app *backpack.App) (*blob.Bucket, error) { if app == nil { return nil, fmt.Errorf("surf: app is nil") } bucket, err := attach.OpenBucket(ctx, app.Config) if err != nil { return nil, err } if err := attach.Publish(app, bucket); err != nil { _ = bucket.Close() return nil, err } return bucket, nil }