mirror of
https://github.com/chrislusf/seaweedfs
synced 2025-07-23 20:12:46 +02:00
* init version * relocate * add s3 bucket link * refactor handlers into weed/admin folder * fix login logout * adding favicon * remove fall back to http get topology * grpc dial option, disk total capacity * show filer count * fix each volume disk usage * add filers to dashboard * adding hosts, volumes, collections * refactor code and menu * remove "refresh" button * fix data for collections * rename cluster hosts into volume servers * add masters, filers * reorder * adding file browser * create folder and upload files * add filer version, created at time * remove mock data * remove fields * fix submenu item highlighting * fix bucket creation * purge files * delete multiple * fix bucket creation * remove region from buckets * add object store with buckets and users * rendering permission * refactor * get bucket objects and size * link to file browser * add file size and count for collections page * paginate the volumes * fix possible SSRF https://github.com/seaweedfs/seaweedfs/pull/6928/checks?check_run_id=45108469801 * Update weed/command/admin.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update weed/command/admin.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix build * import * remove filer CLI option * remove filer option * remove CLI options --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
236 lines
6.6 KiB
Go
236 lines
6.6 KiB
Go
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{}
|
|
}
|