Initial project snapshot

This commit is contained in:
2026-04-28 22:29:50 +03:00
commit 8ba0561f4f
365 changed files with 91832 additions and 0 deletions
+220
View File
@@ -0,0 +1,220 @@
package runtime
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"time"
"github.com/go-chi/chi/v5"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"github.com/example/remote-access-platform/backend/internal/modules/auth"
"github.com/example/remote-access-platform/backend/internal/modules/cluster"
"github.com/example/remote-access-platform/backend/internal/modules/identitysource"
"github.com/example/remote-access-platform/backend/internal/modules/node"
"github.com/example/remote-access-platform/backend/internal/modules/nodeagent"
"github.com/example/remote-access-platform/backend/internal/modules/organization"
"github.com/example/remote-access-platform/backend/internal/modules/resource"
"github.com/example/remote-access-platform/backend/internal/modules/sessionbroker"
"github.com/example/remote-access-platform/backend/internal/modules/sessiongateway"
"github.com/example/remote-access-platform/backend/internal/modules/worker"
"github.com/example/remote-access-platform/backend/internal/platform/authority"
"github.com/example/remote-access-platform/backend/internal/platform/config"
"github.com/example/remote-access-platform/backend/internal/platform/httpserver"
"github.com/example/remote-access-platform/backend/internal/platform/logging"
"github.com/example/remote-access-platform/backend/internal/platform/module"
postgresplatform "github.com/example/remote-access-platform/backend/internal/platform/postgres"
redisplatform "github.com/example/remote-access-platform/backend/internal/platform/redis"
"github.com/example/remote-access-platform/backend/internal/platform/secrets"
)
type App struct {
cfg config.Config
logger *slog.Logger
httpServer *http.Server
workers []backgroundRunner
db closeFunc
redis closeFunc
}
type closeFunc func() error
type backgroundRunner func(context.Context) error
func NewApp(ctx context.Context) (*App, error) {
cfg, err := config.Load()
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
logger := logging.New(cfg.App.Env)
db, err := postgresplatform.Open(ctx, cfg.Postgres)
if err != nil {
return nil, err
}
redisClient, err := redisplatform.Open(ctx, cfg.Redis)
if err != nil {
db.Close()
return nil, err
}
authorityVerifier, err := authority.NewVerifier(cfg.Installation)
if err != nil {
redisClient.Close()
db.Close()
return nil, fmt.Errorf("create installation authority verifier: %w", err)
}
deps := module.Dependencies{
Config: module.Config{
App: cfg.App,
Auth: cfg.Auth,
Installation: cfg.Installation,
DataPlane: cfg.DataPlane,
Secret: cfg.Secret,
Session: cfg.Session,
Worker: cfg.Worker,
WebSocket: cfg.WebSocket,
},
Infra: module.Infra{
Logger: logger,
DB: db,
Redis: redisClient,
},
}
workerStore := worker.NewRedisStore(redisClient)
workerService := worker.NewService(deps, workerStore)
authStore := auth.NewPostgresStore(db)
authTx := auth.NewPostgresTransactor(db)
authService := auth.NewService(deps, authStore, authTx, authorityVerifier)
var resourceSecretStore *secrets.ResourceSecretStore
if cfg.Secret.EncryptionKeyBase64 != "" {
secretEncryptor, err := secrets.NewEncryptor(cfg.Secret.EncryptionKeyBase64, cfg.Secret.EncryptionKeyID)
if err != nil {
redisClient.Close()
db.Close()
return nil, fmt.Errorf("create resource secret encryptor: %w", err)
}
resourceSecretStore = secrets.NewResourceSecretStore(db, secretEncryptor)
}
brokerStore := sessionbroker.NewPostgresStore(db, authorityVerifier)
brokerTx := sessionbroker.NewPostgresTransactor(db, authorityVerifier)
liveStateStore := sessionbroker.NewRedisLiveStateStore(redisClient)
brokerService := sessionbroker.NewService(deps, brokerStore, brokerTx, liveStateStore, workerService, resourceSecretStore)
workerEvents := worker.NewEventProcessor(redisClient, brokerService)
leaseMonitor := worker.NewLeaseMonitor(workerService, brokerService, cfg.Worker.StaleLeaseGracePeriod)
brokerModule := sessionbroker.NewModule(brokerService)
authModule := auth.NewModule(deps, authService)
clusterModule := cluster.NewModule(deps, authorityVerifier)
organizationModule := organization.NewModule(deps)
identitySourceModule := identitysource.NewModule(deps)
resourceModule := resource.NewModule(deps, resourceSecretStore)
nodeModule := node.NewModule(deps)
nodeAgentModule := nodeagent.NewModule(deps)
sessionGatewayModule := sessiongateway.NewModule(deps, brokerModule.Service(), workerService)
router := buildRouter(
logger,
authModule,
clusterModule,
organizationModule,
identitySourceModule,
resourceModule,
brokerModule,
nodeModule,
nodeAgentModule,
sessionGatewayModule,
)
return &App{
cfg: cfg,
logger: logger,
httpServer: httpserver.New(cfg.HTTP, router),
workers: []backgroundRunner{workerEvents.Run, leaseMonitor.Run},
db: func() error {
db.Close()
return nil
},
redis: redisClient.Close,
}, nil
}
func (a *App) Run(ctx context.Context) error {
errCh := make(chan error, 1)
go func() {
a.logger.Info("http server starting", "addr", a.httpServer.Addr, "service", a.cfg.App.Name)
if err := a.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errCh <- err
return
}
errCh <- nil
}()
for _, runner := range a.workers {
runner := runner
go func() {
if err := runner(ctx); err != nil {
errCh <- err
}
}()
}
select {
case <-ctx.Done():
a.logger.Info("shutdown signal received")
case err := <-errCh:
if err != nil {
return err
}
return nil
}
shutdownCtx, cancel := context.WithTimeout(context.Background(), a.cfg.HTTP.ShutdownTimeout)
defer cancel()
if err := a.httpServer.Shutdown(shutdownCtx); err != nil {
return fmt.Errorf("shutdown http server: %w", err)
}
if err := a.redis(); err != nil {
return fmt.Errorf("close redis: %w", err)
}
if err := a.db(); err != nil {
return fmt.Errorf("close postgres: %w", err)
}
a.logger.Info("app stopped", "at", time.Now().UTC())
return nil
}
func buildRouter(logger *slog.Logger, modules ...module.Module) http.Handler {
router := chi.NewRouter()
router.Use(chimiddleware.RequestID)
router.Use(chimiddleware.RealIP)
router.Use(chimiddleware.Recoverer)
router.Use(chimiddleware.Timeout(60 * time.Second))
router.Use(chimiddleware.Heartbeat("/healthz"))
router.Get("/readyz", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ready"))
})
router.Route("/api/v1", func(r chi.Router) {
for _, mod := range modules {
logger.Info("register module routes", "module", mod.Name())
mod.RegisterRoutes(r)
}
})
return router
}