package main import ( "fmt" "log" "net/http" "os" "os/signal" "path/filepath" "strings" "syscall" "github.com/gorilla/csrf" "imuslab.com/dezukvm/dezukvmd/mod/auth" "imuslab.com/dezukvm/dezukvmd/mod/dezukvm" "imuslab.com/dezukvm/dezukvmd/mod/logger" "imuslab.com/dezukvm/dezukvmd/mod/utils" ) var ( dezukvmManager *dezukvm.DezukVM listeningServerMux *http.ServeMux authManager *auth.AuthManager systemLogger *logger.Logger ) func init_auth_manager() error { // Initialize logger systemLogger = logger.NewLogger(logger.WithLogLevel(logger.InfoLevel)) // Initialize AuthManager with logger and DB path var err error authManager, err = auth.NewAuthManager(auth.Options{ DBPath: DB_FILE_PATH, Log: systemLogger.Info, }) if err != nil { return err } return nil } func init_ipkvm_mode() error { listeningServerMux = http.NewServeMux() // Initialize the Auth Manager err := init_auth_manager() if err != nil { log.Fatal("Failed to initialize Auth Manager:", err) return err } //Create a new DezukVM manager dezukvmManager = dezukvm.NewKvmHostInstance(&dezukvm.RuntimeOptions{ EnableLog: true, }) // Experimental connectedUsbKvms, err := dezukvm.ScanConnectedUsbKvmDevices() if err != nil { return err } for _, dev := range connectedUsbKvms { err := dezukvmManager.AddUsbKvmDevice(dev) if err != nil { return err } } err = dezukvmManager.StartAllUsbKvmDevices() if err != nil { return err } // ~Experimental // Handle root routing with CSRF protection handle_root_routing(listeningServerMux) // Handle program exit to close the HID controller c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c log.Println("Shutting down DezuKVM...") if dezukvmManager != nil { dezukvmManager.Close() } if authManager != nil { authManager.Close() } log.Println("Shutdown complete.") os.Exit(0) }() // Register Auth related APIs register_auth_apis(listeningServerMux) // Register DezukVM related APIs register_ipkvm_apis(listeningServerMux) err = http.ListenAndServe(":9000", listeningServerMux) return err } func request_url_allow_unauthenticated(r *http.Request) bool { // Define a list of URL paths that can be accessed without authentication allowedPaths := []string{ "/login.html", "/api/v1/login", "/img/", "/js/", "/css/", "/favicon.png", } requestPath := r.URL.Path for _, path := range allowedPaths { if strings.HasPrefix(requestPath, path) { return true } } return false } func handle_root_routing(mux *http.ServeMux) { // Root router: check login status, redirect to login.html if not authenticated mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if !authManager.UserIsLoggedIn(r) && !request_url_allow_unauthenticated(r) { w.Header().Set("Location", "/login.html") w.WriteHeader(http.StatusFound) return } // Only inject for .html files path := r.URL.Path if path == "/" { path = "/index.html" } if strings.HasSuffix(path, ".html") { // Read the HTML file from disk targetFilePath := filepath.Join("www", filepath.Clean(path)) content, err := os.ReadFile(targetFilePath) if err != nil { http.NotFound(w, r) return } htmlContent := string(content) // Replace CSRF token placeholder htmlContent = strings.ReplaceAll(htmlContent, "{{.csrfToken}}", csrf.Token(r)) w.Header().Set("Content-Type", "text/html") w.Write([]byte(htmlContent)) return } // Serve static files (img, js, css, etc.) targetFilePath := filepath.Join("www", filepath.Clean(path)) http.ServeFile(w, r, targetFilePath) }) } func register_auth_apis(mux *http.ServeMux) { // Check API for session validation mux.HandleFunc("/api/v1/check", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } ok := authManager.UserIsLoggedIn(r) if !ok { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } w.WriteHeader(http.StatusOK) w.Write([]byte("{\"status\":\"ok\"}")) }) // Login API mux.HandleFunc("/api/v1/login", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } err := authManager.LoginUser(w, r) if err != nil { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } w.WriteHeader(http.StatusOK) w.Write([]byte("{\"status\":\"success\"}")) }) } func register_ipkvm_apis(mux *http.ServeMux) { authManager.HandleFunc("/api/v1/stream/{uuid}/video", func(w http.ResponseWriter, r *http.Request) { instanceUUID := r.PathValue("uuid") fmt.Println("Requested video stream for instance UUID:", instanceUUID) dezukvmManager.HandleVideoStreams(w, r, instanceUUID) }, mux) authManager.HandleFunc("/api/v1/stream/{uuid}/audio", func(w http.ResponseWriter, r *http.Request) { instanceUUID := r.PathValue("uuid") dezukvmManager.HandleAudioStreams(w, r, instanceUUID) }, mux) authManager.HandleFunc("/api/v1/hid/{uuid}/events", func(w http.ResponseWriter, r *http.Request) { instanceUUID := r.PathValue("uuid") dezukvmManager.HandleHIDEvents(w, r, instanceUUID) }, mux) authManager.HandleFunc("/api/v1/mass_storage/switch", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } instanceUUID, err := utils.PostPara(r, "uuid") if err != nil { http.Error(w, "Missing or invalid uuid parameter", http.StatusBadRequest) return } side, err := utils.PostPara(r, "side") if err != nil { http.Error(w, "Missing or invalid side parameter", http.StatusBadRequest) return } switch side { case "kvm": dezukvmManager.HandleMassStorageSideSwitch(w, r, instanceUUID, true) case "remote": dezukvmManager.HandleMassStorageSideSwitch(w, r, instanceUUID, false) default: http.Error(w, "Invalid side parameter", http.StatusBadRequest) } }, mux) authManager.HandleFunc("/api/v1/instances", func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { dezukvmManager.HandleListInstances(w, r) } else { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } }, mux) }