package main import ( "fmt" "log" "time" "github.com/getsentry/sentry-go" sentryecho "github.com/getsentry/sentry-go/echo" "github.com/labstack/echo/v4" "github.com/nats-io/nats.go" "github.com/rosti-cz/node-api/apps" "github.com/rosti-cz/node-api/apps/drivers" "github.com/rosti-cz/node-api/common" "github.com/rosti-cz/node-api/glue" "github.com/rosti-cz/node-api/node" ) // JSONIndent Indendation of JSON output format const JSONIndent = " " var config common.Config var nc *nats.Conn var snapshotProcessor apps.SnapshotProcessor var nodeProcessor node.Processor var elapsedMetric int // time elapsed while loading stats about apps func _init() { var err error // Load config from environment variables config = *common.GetConfig() // Sentry sentry.Init(sentry.ClientOptions{ Dsn: config.SentryDSN, AttachStacktrace: true, Environment: config.SentryENV, TracesSampleRate: 0.1, }) // Connect to the NATS service nc, err = nats.Connect(config.NATSURL) if err != nil { sentry.CaptureException(err) log.Fatalln(err) } // Database migrations processor := apps.AppsProcessor{ DB: common.GetDBConnection(), } processor.Init() // Prepare snapshot processor snapshotProcessor = apps.SnapshotProcessor{ AppsPath: config.AppsPath, TmpSnapshotPath: config.SnapshotsPath, IndexLabel: config.SnapshotsIndexLabel, Driver: drivers.S3Driver{ S3AccessKey: config.SnapshotsS3AccessKey, S3SecretKey: config.SnapshotsS3SecretKey, S3Endpoint: config.SnapshotsS3Endpoint, S3SSL: config.SnapshotsS3SSL, Bucket: config.SnapshotsS3Bucket, }, } nodeProcessor = node.Processor{ DB: common.GetDBConnection(), } nodeProcessor.Init() // Reset elapsed stats elapsedMetric = -1 } func main() { _init() defer nc.Drain() defer sentry.Flush(time.Second * 10) // Close database at the end db := common.GetDBConnection() defer db.Close() // Templating t := &Template{} // Stats loop go func() { statsProcessor := glue.StatsProcessor{ DB: common.GetDBConnection(), DockerSock: config.DockerSocket, BindIPHTTP: config.AppsBindIPHTTP, BindIPSSH: config.AppsBindIPSSH, AppsPath: config.AppsPath, } for { log.Println("Stats gathering started") start := time.Now() err := statsProcessor.GatherStats() if err != nil { sentry.CaptureException(err) log.Println("LOOP ERROR:", err.Error()) } elapsed := time.Since(start) elapsedMetric = int(elapsed) log.Printf("Stats gathering elapsed time: %.2fs\n", elapsed.Seconds()) time.Sleep(300 * time.Second) } }() // Node stats go func() { for { err := nodeProcessor.Log() if err != nil { sentry.CaptureException(err) log.Println("NODE PERFORMANCE LOG ERROR:", err.Error()) } time.Sleep(5 * time.Minute) } }() // API e := echo.New() e.Renderer = t e.Use(TokenMiddleware) e.Use(sentryecho.New(sentryecho.Options{})) // NATS handling // admin.apps.ALIAS.events // admin.apps.ALIAS.states subjectEvents := fmt.Sprintf("admin.apps.%s.requests", config.NATSAlias) log.Println("> listening on " + subjectEvents) nc.Subscribe(subjectEvents, messageHandler) // UI e.GET("/", homeHandler) // Prometheus metrics e.GET("/metrics", metricsHandler) // Returns list of apps e.GET("/v1/apps", listAppsHandler) // Returns one app e.GET("/v1/apps/:name", getAppHandler) // Create a new app // If you add register_only=1 into query string, it won't start or create any container, just adds record into the database. e.POST("/v1/apps", createAppHandler) // Update existing app e.PUT("/v1/apps/:name", updateAppHandler) // Stop existing app e.PUT("/v1/apps/:name/stop", stopAppHandler) // Start existing app e.PUT("/v1/apps/:name/start", startAppHandler) // Stop existing app e.PUT("/v1/apps/:name/restart", restartAppHandler) // Application processes e.GET("/v1/apps/:name/processes", getAppProcessesHandler) // Set password for the app user in the container e.PUT("/v1/apps/:name/password", setPasswordHandler) // Copies body of the request into /srv/.ssh/authorized_keys e.PUT("/v1/apps/:name/keys", setKeysHandler) // Enable one of the supported technologies or services (python, node, redis, ...) e.PUT("/v1/apps/:name/set-services", setServicesHandler) // Rebuilds existing app, it keeps the data but creates the container again e.PUT("/v1/apps/:name/rebuild", rebuildAppHandler) // Save metadata about app e.POST("/v1/apps/:name/metadata", saveMetadataHandler) // Adds new label e.POST("/v1/apps/:name/labels", addLabelHandler) // Removes existing label e.DELETE("/v1/apps/:name/labels", deleteLabelHandler) // Delete one app e.DELETE("/v1/apps/:name", deleteAppHandler) // Orphans returns directories in /srv that doesn't match any hosted application e.GET("/v1/orphans", getOrphansHander) // Return info about the node including performance index e.GET("/v1/node", getNodeInfoHandler) err := e.Start(":1323") // fmt.Println(err.Error()) // if strings.Contains(err.Error(), "SIGTERM") { // e.Logger.Info(err) // os.Exit(0) // } e.Logger.Fatal(err) }