diff --git a/monolith/app.go b/monolith/app.go index 1e1c3dd95..3e67d4960 100644 --- a/monolith/app.go +++ b/monolith/app.go @@ -2,12 +2,14 @@ package monolith import ( "context" - + "github.com/cortezaproject/corteza-server/pkg/webapp" "github.com/go-chi/chi" _ "github.com/joho/godotenv/autoload" + "github.com/pkg/errors" "github.com/spf13/cobra" "go.uber.org/zap" "google.golang.org/grpc" + "strings" "github.com/cortezaproject/corteza-server/compose" "github.com/cortezaproject/corteza-server/corteza" @@ -20,6 +22,7 @@ import ( type ( App struct { + Opts *app.Options Core *corteza.App System *system.App Compose *compose.App @@ -28,12 +31,16 @@ type ( ) func (monolith *App) Setup(log *zap.Logger, opts *app.Options) (err error) { - + monolith.Opts = opts // Make sure system behaves properly // // This will alter the auth settings provision procedure system.IsMonolith = true + if err = monolith.CheckOptions(); err != nil { + return + } + err = app.RunSetup( log, opts, @@ -49,6 +56,15 @@ func (monolith *App) Setup(log *zap.Logger, opts *app.Options) (err error) { return } +func (monolith *App) CheckOptions() error { + o := monolith.Opts.HTTPServer + if o.ApiEnabled && o.WebappEnabled && o.ApiBaseUrl == o.WebappBaseUrl { + return errors.Errorf("cannot serve api and web apps form the same base url (%v)", o.ApiBaseUrl) + } + + return nil +} + func (monolith *App) Upgrade(ctx context.Context) (err error) { return app.RunUpgrade( ctx, @@ -101,17 +117,33 @@ func (monolith *App) Provision(ctx context.Context) (err error) { } func (monolith *App) MountApiRoutes(r chi.Router) { - r.Route("/system", func(r chi.Router) { - monolith.System.MountApiRoutes(r) - }) + var ( + apiEnabled = monolith.Opts.HTTPServer.ApiEnabled + apiBaseUrl = strings.Trim(monolith.Opts.HTTPServer.ApiBaseUrl, "/") - r.Route("/compose", func(r chi.Router) { - monolith.Compose.MountApiRoutes(r) - }) + webappEnabled = monolith.Opts.HTTPServer.WebappEnabled + webappBaseUrl = strings.Trim(monolith.Opts.HTTPServer.WebappBaseUrl, "/") + ) - r.Route("/messaging", func(r chi.Router) { - monolith.Messaging.MountApiRoutes(r) - }) + if apiEnabled { + r.Route("/"+apiBaseUrl, func(r chi.Router) { + r.Route("/system", func(r chi.Router) { + monolith.System.MountApiRoutes(r) + }) + + r.Route("/compose", func(r chi.Router) { + monolith.Compose.MountApiRoutes(r) + }) + + r.Route("/messaging", func(r chi.Router) { + monolith.Messaging.MountApiRoutes(r) + }) + }) + } + + if webappEnabled { + r.Route("/"+webappBaseUrl, webapp.MakeWebappServer(monolith.Opts.HTTPServer)) + } } func (monolith *App) RegisterGrpcServices(srv *grpc.Server) { diff --git a/pkg/app/options/http.go b/pkg/app/options/http.go index 7d5c65694..21441c283 100644 --- a/pkg/app/options/http.go +++ b/pkg/app/options/http.go @@ -20,6 +20,14 @@ type ( MetricsPassword string `env:"HTTP_METRICS_PASSWORD"` EnablePanicReporting bool `env:"HTTP_REPORT_PANIC"` + + ApiEnabled bool `env:"HTTP_API_ENABLED"` + ApiBaseUrl string `env:"HTTP_API_BASE_URL"` + + WebappEnabled bool `env:"HTTP_WEBAPP_ENABLED"` + WebappBaseUrl string `env:"HTTP_WEBAPP_BASE_URL"` + WebappBaseDir string `env:"HTTP_WEBAPP_BASE_DIR"` + WebappList string `env:"HTTP_WEBAPP_LIST"` } ) @@ -35,11 +43,19 @@ func HTTP(pfix string) (o *HTTPServerOpt) { MetricsServiceLabel: "corteza", MetricsUsername: "metrics", - // Reports panics to Sentry throught HTTP middleware + // Reports panics to Sentry through HTTP middleware EnablePanicReporting: true, // Setting metrics password to random string to prevent security accidents... MetricsPassword: string(rand.Bytes(5)), + + ApiEnabled: true, + ApiBaseUrl: "", + + WebappEnabled: false, + WebappBaseUrl: "/", + WebappBaseDir: "/webapp", + WebappList: "admin,auth,messaging,compose", } fill(o, pfix) diff --git a/pkg/app/runner.go b/pkg/app/runner.go index 181fc8a80..7bb8837ee 100644 --- a/pkg/app/runner.go +++ b/pkg/app/runner.go @@ -235,6 +235,7 @@ func (r *runner) serve(ctx context.Context) (err error) { if err = r.Provision(ctx); err != nil { return } + r.setupHttpApi() r.setupGRPCServices() diff --git a/pkg/webapp/serve.go b/pkg/webapp/serve.go new file mode 100644 index 000000000..490c79622 --- /dev/null +++ b/pkg/webapp/serve.go @@ -0,0 +1,51 @@ +package webapp + +import ( + "fmt" + "github.com/cortezaproject/corteza-server/pkg/app/options" + "github.com/go-chi/chi" + "net/http" + "os" + "path" + "strings" +) + +func MakeWebappServer(opt options.HTTPServerOpt) func(r chi.Router) { + // Serves static files directly from FS + return func(r chi.Router) { + fileserver := http.FileServer(http.Dir(opt.WebappBaseDir)) + + for _, app := range strings.Split(opt.WebappList, ",") { + basedir := path.Join(opt.WebappBaseUrl, app) + serveConfig(r, basedir, opt.ApiBaseUrl) + r.Get(basedir+"*", serveIndex(opt.WebappBaseDir, basedir+"/index.html", fileserver)) + } + + serveConfig(r, opt.WebappBaseUrl, opt.ApiBaseUrl) + r.Get(opt.WebappBaseUrl+"*", serveIndex(opt.WebappBaseDir, opt.WebappBaseUrl+"/index.html", fileserver)) + } +} + +// Serves index.html in case the requested file isn't found (or some other os.Stat error) +func serveIndex(assetPath string, indexPath string, serve http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + indexPage := path.Join(assetPath, indexPath) + requestedPage := path.Join(assetPath, r.URL.Path) + _, err := os.Stat(requestedPage) + + if err != nil { + http.ServeFile(w, r, indexPage) + return + } + serve.ServeHTTP(w, r) + } +} + +func serveConfig(r chi.Router, appUrl, apiBaseUrl string) { + r.Get(strings.TrimRight(appUrl, "/")+"/config.js", func(w http.ResponseWriter, r *http.Request) { + const line = "window.%sAPI = '%s/%s';\n" + _, _ = fmt.Fprintf(w, line, "System", apiBaseUrl, "system") + _, _ = fmt.Fprintf(w, line, "Messaging", apiBaseUrl, "messaging") + _, _ = fmt.Fprintf(w, line, "Compose", apiBaseUrl, "compose") + }) +}