// Package admin provides the "admin" command group: direct, database-level // management of users and namespaces, bypassing the API. // // It is registered on the server's root command by the app package, and reached // in practice through the bin/cli wrapper, which injects the "admin" token so // `./bin/cli user create ...` keeps working. package admin import ( "context" "github.com/shellhub-io/shellhub/pkg/cache" "github.com/shellhub-io/shellhub/pkg/clock" "github.com/shellhub-io/shellhub/pkg/envs" "github.com/shellhub-io/shellhub/server/admin/services" "github.com/shellhub-io/shellhub/server/api/store/pg" pgoptions "github.com/shellhub-io/shellhub/server/api/store/pg/options" "github.com/spf13/cobra" ) type env struct { PostgresHost string `env:"POSTGRES_HOST,default=postgres"` PostgresPort string `env:"POSTGRES_PORT,default=5432"` PostgresUsername string `env:"POSTGRES_USERNAME,default=admin"` PostgresPassword string `env:"POSTGRES_PASSWORD,default=admin"` PostgresDatabase string `env:"POSTGRES_DATABASE,default=main"` PostgresLogLevel string `env:"POSTGRES_LOG_LEVEL,default=INFO"` PostgresLogVerbose bool `env:"POSTGRES_LOG_VERBOSE,default=false"` RedisURI string `env:"REDIS_URI,default=redis://redis:6379"` Edition envs.Edition `env:"SHELLHUB_EDITION"` NamespaceRequireVerifiedDeviceKey bool `env:"SHELLHUB_NAMESPACE_REQUIRE_VERIFIED_DEVICE_KEY,default=true"` } type serviceFunc func() services.Services // Command returns the "admin" command group. func Command() *cobra.Command { var service services.Services cmd := &cobra.Command{ Use: "admin", Short: "Manage users and namespaces directly in the database", Long: `Manage users and namespaces by writing to the database directly, without going through the API. Intended for operators administering a ShellHub instance.`, PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { s, err := connect(cmd.Context()) if err != nil { return err } service = s return nil }, } get := serviceFunc(func() services.Services { return service }) cmd.AddCommand( userCommands(get), namespaceCommands(get), ) return cmd } func connect(ctx context.Context) (services.Services, error) { cfg, err := envs.ParseWithPrefix[env](envs.OS(), "ADMIN_") if err != nil { return nil, err } uri := pg.URI( cfg.PostgresHost, cfg.PostgresPort, cfg.PostgresUsername, cfg.PostgresPassword, cfg.PostgresDatabase, ) store, err := pg.New(ctx, uri, pgoptions.Log(cfg.PostgresLogLevel, cfg.PostgresLogVerbose)) if err != nil { return nil, err } cache, err := cache.NewRedisCache(cfg.RedisURI, 0, cache.Config{}, clock.New()) if err != nil { return nil, err } return services.NewService(store, services.WithEdition(cfg.Edition), services.WithCache(cache), services.WithNamespaceRequireVerifiedDeviceKey(cfg.NamespaceRequireVerifiedDeviceKey), ), nil }