diff options
Diffstat (limited to 'weed/command/admin.go')
| -rw-r--r-- | weed/command/admin.go | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/weed/command/admin.go b/weed/command/admin.go new file mode 100644 index 000000000..ef1d54bb3 --- /dev/null +++ b/weed/command/admin.go @@ -0,0 +1,236 @@ +package command + +import ( + "context" + "crypto/rand" + "crypto/tls" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" + "github.com/gin-gonic/gin" + + "github.com/seaweedfs/seaweedfs/weed/admin/dash" + "github.com/seaweedfs/seaweedfs/weed/admin/handlers" +) + +var ( + a AdminOptions +) + +type AdminOptions struct { + port *int + masters *string + tlsCertPath *string + tlsKeyPath *string + adminUser *string + adminPassword *string +} + +func init() { + cmdAdmin.Run = runAdmin // break init cycle + a.port = cmdAdmin.Flag.Int("port", 23646, "admin server port") + a.masters = cmdAdmin.Flag.String("masters", "localhost:9333", "comma-separated master servers") + a.tlsCertPath = cmdAdmin.Flag.String("tlsCert", "", "path to TLS certificate file") + a.tlsKeyPath = cmdAdmin.Flag.String("tlsKey", "", "path to TLS private key file") + + a.adminUser = cmdAdmin.Flag.String("adminUser", "admin", "admin interface username") + a.adminPassword = cmdAdmin.Flag.String("adminPassword", "", "admin interface password (if empty, auth is disabled)") +} + +var cmdAdmin = &Command{ + UsageLine: "admin -port=23646 -masters=localhost:9333", + Short: "start SeaweedFS web admin interface", + Long: `Start a web admin interface for SeaweedFS cluster management. + + The admin interface provides a modern web interface for: + - Cluster topology visualization and monitoring + - Volume management and operations + - File browser and management + - System metrics and performance monitoring + - Configuration management + - Maintenance operations + + The admin interface automatically discovers filers from the master servers. + + Example Usage: + weed admin -port=23646 -masters="master1:9333,master2:9333" + weed admin -port=443 -tlsCert=/etc/ssl/admin.crt -tlsKey=/etc/ssl/admin.key + + Authentication: + - If adminPassword is not set, the admin interface runs without authentication + - If adminPassword is set, users must login with adminUser/adminPassword + - Sessions are secured with auto-generated session keys + + Security: + - Use HTTPS in production by providing TLS certificates + - Set strong adminPassword for production deployments + - Configure firewall rules to restrict admin interface access + +`, +} + +func runAdmin(cmd *Command, args []string) bool { + // Validate required parameters + if *a.masters == "" { + fmt.Println("Error: masters parameter is required") + fmt.Println("Usage: weed admin -masters=master1:9333,master2:9333") + return false + } + + // Validate TLS configuration + if (*a.tlsCertPath != "" && *a.tlsKeyPath == "") || + (*a.tlsCertPath == "" && *a.tlsKeyPath != "") { + fmt.Println("Error: Both tlsCert and tlsKey must be provided for TLS") + return false + } + + // Security warnings + if *a.adminPassword == "" { + fmt.Println("WARNING: Admin interface is running without authentication!") + fmt.Println(" Set -adminPassword for production use") + } + + if *a.tlsCertPath == "" { + fmt.Println("WARNING: Admin interface is running without TLS encryption!") + fmt.Println(" Use -tlsCert and -tlsKey for production use") + } + + fmt.Printf("Starting SeaweedFS Admin Interface on port %d\n", *a.port) + fmt.Printf("Masters: %s\n", *a.masters) + fmt.Printf("Filers will be discovered automatically from masters\n") + if *a.adminPassword != "" { + fmt.Printf("Authentication: Enabled (user: %s)\n", *a.adminUser) + } else { + fmt.Printf("Authentication: Disabled\n") + } + if *a.tlsCertPath != "" { + fmt.Printf("TLS: Enabled\n") + } else { + fmt.Printf("TLS: Disabled\n") + } + + // Set up graceful shutdown + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Handle interrupt signals + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + go func() { + sig := <-sigChan + fmt.Printf("\nReceived signal %v, shutting down gracefully...\n", sig) + cancel() + }() + + // Start the admin server + err := startAdminServer(ctx, a) + if err != nil { + fmt.Printf("Admin server error: %v\n", err) + return false + } + + fmt.Println("Admin server stopped") + return true +} + +// startAdminServer starts the actual admin server +func startAdminServer(ctx context.Context, options AdminOptions) error { + // Set Gin mode + gin.SetMode(gin.ReleaseMode) + + // Create router + r := gin.New() + r.Use(gin.Logger(), gin.Recovery()) + + // Session store - always auto-generate session key + sessionKeyBytes := make([]byte, 32) + _, err := rand.Read(sessionKeyBytes) + if err != nil { + return fmt.Errorf("failed to generate session key: %v", err) + } + store := cookie.NewStore(sessionKeyBytes) + r.Use(sessions.Sessions("admin-session", store)) + + // Static files - serve from filesystem + staticPath := filepath.Join("weed", "admin", "static") + if _, err := os.Stat(staticPath); err == nil { + r.Static("/static", staticPath) + } else { + log.Printf("Warning: Static files not found at %s", staticPath) + } + + // Create admin server + adminServer := dash.NewAdminServer(*options.masters, nil) + + // Show discovered filers + filers := adminServer.GetAllFilers() + if len(filers) > 0 { + fmt.Printf("Discovered filers: %s\n", strings.Join(filers, ", ")) + } else { + fmt.Printf("No filers discovered from masters\n") + } + + // Create handlers and setup routes + adminHandlers := handlers.NewAdminHandlers(adminServer) + adminHandlers.SetupRoutes(r, *options.adminPassword != "", *options.adminUser, *options.adminPassword) + + // Server configuration + addr := fmt.Sprintf(":%d", *options.port) + server := &http.Server{ + Addr: addr, + Handler: r, + } + + // TLS configuration + if *options.tlsCertPath != "" && *options.tlsKeyPath != "" { + server.TLSConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + } + } + + // Start server + go func() { + log.Printf("Starting SeaweedFS Admin Server on port %d", *options.port) + + var err error + if *options.tlsCertPath != "" && *options.tlsKeyPath != "" { + log.Printf("Using TLS with cert: %s, key: %s", *options.tlsCertPath, *options.tlsKeyPath) + err = server.ListenAndServeTLS(*options.tlsCertPath, *options.tlsKeyPath) + } else { + err = server.ListenAndServe() + } + + if err != nil && err != http.ErrServerClosed { + log.Printf("Failed to start server: %v", err) + } + }() + + // Wait for context cancellation + <-ctx.Done() + + // Graceful shutdown + log.Println("Shutting down admin server...") + shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := server.Shutdown(shutdownCtx); err != nil { + return fmt.Errorf("admin server forced to shutdown: %v", err) + } + + return nil +} + +// GetAdminOptions returns the admin command options for testing +func GetAdminOptions() *AdminOptions { + return &AdminOptions{} +} |
