diff options
| author | Chris Lu <chrislusf@users.noreply.github.com> | 2025-07-06 13:57:02 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-06 13:57:02 -0700 |
| commit | aa668523047c273dc4065dc0f40852efcdf9e9f0 (patch) | |
| tree | 87f7f145d699cf1824c8251ae71435462bfd3318 /weed/command/admin.go | |
| parent | 302e62d4805c60f3fdb6620b01e85859d68078ed (diff) | |
| download | seaweedfs-aa668523047c273dc4065dc0f40852efcdf9e9f0.tar.xz seaweedfs-aa668523047c273dc4065dc0f40852efcdf9e9f0.zip | |
Admin UI add maintenance menu (#6944)
* add ui for maintenance
* valid config loading. fix workers page.
* refactor
* grpc between admin and workers
* add a long-running bidirectional grpc call between admin and worker
* use the grpc call to heartbeat
* use the grpc call to communicate
* worker can remove the http client
* admin uses http port + 10000 as its default grpc port
* one task one package
* handles connection failures gracefully with exponential backoff
* grpc with insecure tls
* grpc with optional tls
* fix detecting tls
* change time config from nano seconds to seconds
* add tasks with 3 interfaces
* compiles reducing hard coded
* remove a couple of tasks
* remove hard coded references
* reduce hard coded values
* remove hard coded values
* remove hard coded from templ
* refactor maintenance package
* fix import cycle
* simplify
* simplify
* auto register
* auto register factory
* auto register task types
* self register types
* refactor
* simplify
* remove one task
* register ui
* lazy init executor factories
* use registered task types
* DefaultWorkerConfig remove hard coded task types
* remove more hard coded
* implement get maintenance task
* dynamic task configuration
* "System Settings" should only have system level settings
* adjust menu for tasks
* ensure menu not collapsed
* render job configuration well
* use templ for ui of task configuration
* fix ordering
* fix bugs
* saving duration in seconds
* use value and unit for duration
* Delete WORKER_REFACTORING_PLAN.md
* Delete maintenance.json
* Delete custom_worker_example.go
* remove address from workers
* remove old code from ec task
* remove creating collection button
* reconnect with exponential backoff
* worker use security.toml
* start admin server with tls info from security.toml
* fix "weed admin" cli description
Diffstat (limited to 'weed/command/admin.go')
| -rw-r--r-- | weed/command/admin.go | 190 |
1 files changed, 152 insertions, 38 deletions
diff --git a/weed/command/admin.go b/weed/command/admin.go index ef1d54bb3..027fbec68 100644 --- a/weed/command/admin.go +++ b/weed/command/admin.go @@ -3,12 +3,12 @@ package command import ( "context" "crypto/rand" - "crypto/tls" "fmt" "log" "net/http" "os" "os/signal" + "os/user" "path/filepath" "strings" "syscall" @@ -17,9 +17,12 @@ import ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" + "github.com/spf13/viper" "github.com/seaweedfs/seaweedfs/weed/admin/dash" "github.com/seaweedfs/seaweedfs/weed/admin/handlers" + "github.com/seaweedfs/seaweedfs/weed/security" + "github.com/seaweedfs/seaweedfs/weed/util" ) var ( @@ -29,25 +32,23 @@ var ( type AdminOptions struct { port *int masters *string - tlsCertPath *string - tlsKeyPath *string adminUser *string adminPassword *string + dataDir *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.dataDir = cmdAdmin.Flag.String("dataDir", "", "directory to store admin configuration and data files") 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", + UsageLine: "admin -port=23646 -masters=localhost:9333 [-dataDir=/path/to/data]", Short: "start SeaweedFS web admin interface", Long: `Start a web admin interface for SeaweedFS cluster management. @@ -60,25 +61,56 @@ var cmdAdmin = &Command{ - Maintenance operations The admin interface automatically discovers filers from the master servers. + A gRPC server for worker connections runs on HTTP port + 10000. 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 + weed admin -port=23646 -masters="localhost:9333" -dataDir="/var/lib/seaweedfs-admin" + weed admin -port=23646 -masters="localhost:9333" -dataDir="~/seaweedfs-admin" + + Data Directory: + - If dataDir is specified, admin configuration and maintenance data is persisted + - The directory will be created if it doesn't exist + - Configuration files are stored in JSON format for easy editing + - Without dataDir, all configuration is kept in memory only 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 + Security Configuration: + - The admin server reads TLS configuration from security.toml + - Configure [https.admin] section in security.toml for HTTPS support + - If https.admin.key is set, the server will start in TLS mode + - If https.admin.ca is set, mutual TLS authentication is enabled - Set strong adminPassword for production deployments - Configure firewall rules to restrict admin interface access + security.toml Example: + [https.admin] + cert = "/etc/ssl/admin.crt" + key = "/etc/ssl/admin.key" + ca = "/etc/ssl/ca.crt" # optional, for mutual TLS + + Worker Communication: + - Workers connect via gRPC on HTTP port + 10000 + - Workers use [grpc.admin] configuration from security.toml + - TLS is automatically used if certificates are configured + - Workers fall back to insecure connections if TLS is unavailable + + Configuration File: + - The security.toml file is read from ".", "$HOME/.seaweedfs/", + "/usr/local/etc/seaweedfs/", or "/etc/seaweedfs/", in that order + - Generate example security.toml: weed scaffold -config=security + `, } func runAdmin(cmd *Command, args []string) bool { + // Load security configuration + util.LoadSecurityConfiguration() + // Validate required parameters if *a.masters == "" { fmt.Println("Error: masters parameter is required") @@ -86,37 +118,25 @@ func runAdmin(cmd *Command, args []string) bool { 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.dataDir != "" { + fmt.Printf("Data Directory: %s\n", *a.dataDir) + } else { + fmt.Printf("Data Directory: Not specified (configuration will be in-memory only)\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()) @@ -169,8 +189,29 @@ func startAdminServer(ctx context.Context, options AdminOptions) error { log.Printf("Warning: Static files not found at %s", staticPath) } + // Create data directory if specified + var dataDir string + if *options.dataDir != "" { + // Expand tilde (~) to home directory + expandedDir, err := expandHomeDir(*options.dataDir) + if err != nil { + return fmt.Errorf("failed to expand dataDir path %s: %v", *options.dataDir, err) + } + dataDir = expandedDir + + // Show path expansion if it occurred + if dataDir != *options.dataDir { + fmt.Printf("Expanded dataDir: %s -> %s\n", *options.dataDir, dataDir) + } + + if err := os.MkdirAll(dataDir, 0755); err != nil { + return fmt.Errorf("failed to create data directory %s: %v", dataDir, err) + } + fmt.Printf("Data directory created/verified: %s\n", dataDir) + } + // Create admin server - adminServer := dash.NewAdminServer(*options.masters, nil) + adminServer := dash.NewAdminServer(*options.masters, nil, dataDir) // Show discovered filers filers := adminServer.GetAllFilers() @@ -180,6 +221,19 @@ func startAdminServer(ctx context.Context, options AdminOptions) error { fmt.Printf("No filers discovered from masters\n") } + // Start worker gRPC server for worker connections + err = adminServer.StartWorkerGrpcServer(*options.port) + if err != nil { + return fmt.Errorf("failed to start worker gRPC server: %v", err) + } + + // Set up cleanup for gRPC server + defer func() { + if stopErr := adminServer.StopWorkerGrpcServer(); stopErr != nil { + log.Printf("Error stopping worker gRPC server: %v", stopErr) + } + }() + // Create handlers and setup routes adminHandlers := handlers.NewAdminHandlers(adminServer) adminHandlers.SetupRoutes(r, *options.adminPassword != "", *options.adminUser, *options.adminPassword) @@ -191,21 +245,37 @@ func startAdminServer(ctx context.Context, options AdminOptions) error { 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) + // start http or https server with security.toml + var ( + clientCertFile, + certFile, + keyFile string + ) + useTLS := false + useMTLS := false + + if viper.GetString("https.admin.key") != "" { + useTLS = true + certFile = viper.GetString("https.admin.cert") + keyFile = viper.GetString("https.admin.key") + } + + if viper.GetString("https.admin.ca") != "" { + useMTLS = true + clientCertFile = viper.GetString("https.admin.ca") + } + + if useMTLS { + server.TLSConfig = security.LoadClientTLSHTTP(clientCertFile) + } + + if useTLS { + log.Printf("Starting SeaweedFS Admin Server with TLS on port %d", *options.port) + err = server.ListenAndServeTLS(certFile, keyFile) } else { err = server.ListenAndServe() } @@ -234,3 +304,47 @@ func startAdminServer(ctx context.Context, options AdminOptions) error { func GetAdminOptions() *AdminOptions { return &AdminOptions{} } + +// expandHomeDir expands the tilde (~) in a path to the user's home directory +func expandHomeDir(path string) (string, error) { + if path == "" { + return path, nil + } + + if !strings.HasPrefix(path, "~") { + return path, nil + } + + // Get current user + currentUser, err := user.Current() + if err != nil { + return "", fmt.Errorf("failed to get current user: %v", err) + } + + // Handle different tilde patterns + if path == "~" { + return currentUser.HomeDir, nil + } + + if strings.HasPrefix(path, "~/") { + return filepath.Join(currentUser.HomeDir, path[2:]), nil + } + + // Handle ~username/ patterns + if strings.HasPrefix(path, "~") { + parts := strings.SplitN(path[1:], "/", 2) + username := parts[0] + + targetUser, err := user.Lookup(username) + if err != nil { + return "", fmt.Errorf("user %s not found: %v", username, err) + } + + if len(parts) == 1 { + return targetUser.HomeDir, nil + } + return filepath.Join(targetUser.HomeDir, parts[1]), nil + } + + return path, nil +} |
