Forráskód Böngészése

Fixed alsa device get logic

TC 1 napja
szülő
commit
c37e540736

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 3 - 3
designs/font_logo.ai


+ 9 - 9
designs/font_logo.svg

@@ -25,13 +25,13 @@
 		l-19.742,56.675l-31.842,86.607h-31.203l-32.158-86.607l-19.105-56.675h-1.273c2.865,23.242,7.322,58.587,7.322,82.15v81.191
 		h-50.945V154.73z"/>
 </g>
-<path fill="#0A6EF5" d="M512,417c0,54.676-44.791,99-100.042,99H100.042C44.79,516,0,471.676,0,417V103C0,48.324,44.79,4,100.042,4
-	h311.916C467.209,4,512,48.324,512,103V417z"/>
-<circle fill="none" stroke="#FFFFFF" stroke-width="70" stroke-miterlimit="10" cx="256" cy="260" r="173"/>
-<polygon fill="#0A6EF5" points="195.561,174.517 120.721,68.667 233.953,45.676 245.063,153.666 "/>
-<polygon fill="#0A6EF5" points="242.833,389.995 233.137,482.274 131.266,436.419 176.432,372.54 "/>
-<circle fill="#FFFFFF" cx="177.615" cy="195.633" r="21.115"/>
-<circle fill="#FFFFFF" cx="257.385" cy="229.261" r="21.115"/>
-<polygon fill="#FFFFFF" points="438.65,212.109 371.779,118.573 486.219,107.43 "/>
-<polygon fill="#FFFFFF" points="363.44,119.377 256,97.143 328.977,15.213 "/>
+<path fill="#0A6EF5" d="M512,413c0,54.676-44.791,99-100.042,99H100.042C44.79,512,0,467.676,0,413V99C0,44.324,44.79,0,100.042,0
+	h311.916C467.209,0,512,44.324,512,99V413z"/>
+<circle fill="none" stroke="#FFFFFF" stroke-width="70" stroke-miterlimit="10" cx="256" cy="256" r="173"/>
+<polygon fill="#0A6EF5" points="195.561,170.517 120.721,64.667 233.953,41.676 245.063,149.666 "/>
+<polygon fill="#0A6EF5" points="242.833,385.995 233.137,478.274 131.266,432.419 176.432,368.54 "/>
+<circle fill="#FFFFFF" cx="190.949" cy="204.966" r="34.449"/>
+<polygon fill="#FFFFFF" points="438.65,208.109 371.779,114.573 486.219,103.43 "/>
+<polygon fill="#FFFFFF" points="363.44,115.377 256,93.143 328.977,11.213 "/>
+<circle fill="#FFFFFF" cx="284.178" cy="253.3" r="34.449"/>
 </svg>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 3 - 3
designs/logo.ai


+ 2 - 2
designs/logo.svg

@@ -9,10 +9,10 @@
 	<circle fill="none" stroke="#FFFFFF" stroke-width="70" stroke-miterlimit="10" cx="256" cy="256" r="173"/>
 	<polygon fill="#0A6EF5" points="195.561,170.517 120.721,64.667 233.954,41.676 245.062,149.666 	"/>
 	<polygon fill="#0A6EF5" points="242.833,385.995 233.136,478.274 131.266,432.419 176.431,368.54 	"/>
-	<circle fill="#FFFFFF" cx="177.615" cy="191.632" r="21.115"/>
-	<circle fill="#FFFFFF" cx="257.385" cy="225.261" r="21.115"/>
+	<circle fill="#FFFFFF" cx="190.949" cy="204.966" r="34.449"/>
 	<polygon fill="#FFFFFF" points="438.65,208.109 371.779,114.573 486.219,103.43 	"/>
 	<polygon fill="#FFFFFF" points="363.44,115.377 256,93.143 328.977,11.213 	"/>
+	<circle fill="#FFFFFF" cx="284.178" cy="253.3" r="34.449"/>
 </g>
 <g id="圖層_2" display="none">
 	<line display="inline" fill="none" stroke="#E4007F" stroke-miterlimit="10" x1="75" y1="0" x2="75" y2="512"/>

+ 0 - 8
dezukvmd/api.go

@@ -2,14 +2,6 @@ package main
 
 import "net/http"
 
-func registerAPIRoutes() {
-	// Start the web server
-	http.Handle("/", http.FileServer(webfs))
-	http.HandleFunc("/hid", usbKVM.HIDWebSocketHandler)
-	http.HandleFunc("/audio", usbCaptureDevice.AudioStreamingHandler)
-	http.HandleFunc("/stream", usbCaptureDevice.ServeVideoStream)
-}
-
 // Aux APIs for USB KVM mode
 func registerLocalAuxRoutes() {
 	http.HandleFunc("/aux/switchusbkvm", auxMCU.HandleSwitchUSBToKVM)

BIN
dezukvmd/config/sys.db


+ 66 - 0
dezukvmd/configure.go

@@ -1,12 +1,78 @@
 package main
 
 import (
+	"encoding/json"
 	"log"
+	"os"
 	"time"
 
 	"imuslab.com/dezukvm/dezukvmd/mod/kvmhid"
 )
 
+type UsbKvmConfig struct {
+	ListeningAddress        string
+	USBKVMDevicePath        string
+	AuxMCUDevicePath        string
+	VideoCaptureDevicePath  string
+	AudioCaptureDevicePath  string
+	CaptureResolutionWidth  int
+	CaptureResolutionHeight int
+	CaptureResolutionFPS    int
+	USBKVMBaudrate          int
+	AuxMCUBaudrate          int
+}
+
+var (
+	/* Internal variables for USB-KVM mode only */
+	usbKVM              *kvmhid.Controller
+	defaultUsbKvmConfig = &UsbKvmConfig{
+		ListeningAddress:        ":9000",
+		USBKVMDevicePath:        "/dev/ttyUSB0",
+		AuxMCUDevicePath:        "/dev/ttyACM0",
+		VideoCaptureDevicePath:  "/dev/video0",
+		AudioCaptureDevicePath:  "/dev/snd/pcmC1D0c",
+		CaptureResolutionWidth:  1920,
+		CaptureResolutionHeight: 1080,
+		CaptureResolutionFPS:    25,
+		USBKVMBaudrate:          115200,
+		AuxMCUBaudrate:          115200,
+	}
+)
+
+func loadUsbKvmConfig() (*UsbKvmConfig, error) {
+	if _, err := os.Stat(USB_KVM_CFG_PATH); os.IsNotExist(err) {
+		file, err := os.OpenFile(USB_KVM_CFG_PATH, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775)
+		if err != nil {
+			return nil, err
+		}
+
+		// Save default config as JSON
+		enc := json.NewEncoder(file)
+		enc.SetIndent("", "  ")
+		if err := enc.Encode(defaultUsbKvmConfig); err != nil {
+			file.Close()
+			return nil, err
+		}
+		file.Close()
+		return defaultUsbKvmConfig, nil
+	}
+
+	// Load config from file
+	file, err := os.Open(USB_KVM_CFG_PATH)
+	if err != nil {
+		return nil, err
+	}
+
+	cfg := &UsbKvmConfig{}
+	dec := json.NewDecoder(file)
+	if err := dec.Decode(cfg); err != nil {
+		file.Close()
+		return nil, err
+	}
+	file.Close()
+	return cfg, nil
+}
+
 func SetupHIDCommunication(config *UsbKvmConfig) error {
 	// Initiate the HID controller
 	usbKVM = kvmhid.NewHIDController(&kvmhid.Config{

BIN
dezukvmd/dezukvmd


+ 1 - 0
dezukvmd/go.mod

@@ -3,6 +3,7 @@ module imuslab.com/dezukvm/dezukvmd
 go 1.23.4
 
 require (
+	github.com/boltdb/bolt v1.3.1
 	github.com/google/uuid v1.6.0
 	github.com/gorilla/csrf v1.7.2
 	github.com/gorilla/websocket v1.5.3

+ 2 - 0
dezukvmd/go.sum

@@ -1,3 +1,5 @@
+github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
+github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=

+ 114 - 28
dezukvmd/ipkvm.go

@@ -11,28 +11,45 @@ import (
 	"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
-	csrfMiddleware     func(http.Handler) http.Handler
 	listeningServerMux *http.ServeMux
+	authManager        *auth.AuthManager
+	systemLogger       *logger.Logger
 )
 
-func init() {
-	csrfMiddleware = csrf.Protect(
-		[]byte(nodeUUID),
-		csrf.CookieName("dezukvm_csrf_token"),
-		csrf.Secure(false),
-		csrf.Path("/"),
-		csrf.SameSite(csrf.SameSiteLaxMode),
-	)
+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,
@@ -57,6 +74,9 @@ func init_ipkvm_mode() error {
 	}
 	// ~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)
@@ -67,12 +87,51 @@ func init_ipkvm_mode() error {
 		if dezukvmManager != nil {
 			dezukvmManager.Close()
 		}
+		if authManager != nil {
+			authManager.Close()
+		}
 		log.Println("Shutdown complete.")
 		os.Exit(0)
 	}()
 
-	// Middleware to inject CSRF token into HTML files served from www
-	listeningServerMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+	// 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 == "/" {
@@ -93,35 +152,62 @@ func init_ipkvm_mode() error {
 			w.Write([]byte(htmlContent))
 			return
 		}
-		// Fallback to static file server for non-HTML files
-		http.FileServer(http.Dir("www")).ServeHTTP(w, r)
-	})
 
-	// Register DezukVM related APIs
-	register_ipkvm_apis(listeningServerMux)
+		// Serve static files (img, js, css, etc.)
+		targetFilePath := filepath.Join("www", filepath.Clean(path))
+		http.ServeFile(w, r, targetFilePath)
+	})
+}
 
-	err = http.ListenAndServe(":9000", listeningServerMux)
-	return err
+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) {
-	mux.HandleFunc("/api/v1/stream/{uuid}/video", func(w http.ResponseWriter, r *http.Request) {
+	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)
 
-	mux.HandleFunc("/api/v1/stream/{uuid}/audio", func(w http.ResponseWriter, r *http.Request) {
+	authManager.HandleFunc("/api/v1/stream/{uuid}/audio", func(w http.ResponseWriter, r *http.Request) {
 		instanceUUID := r.PathValue("uuid")
 		dezukvmManager.HandleAudioStreams(w, r, instanceUUID)
-	})
+	}, mux)
 
-	mux.HandleFunc("/api/v1/hid/{uuid}/events", func(w http.ResponseWriter, r *http.Request) {
+	authManager.HandleFunc("/api/v1/hid/{uuid}/events", func(w http.ResponseWriter, r *http.Request) {
 		instanceUUID := r.PathValue("uuid")
 		dezukvmManager.HandleHIDEvents(w, r, instanceUUID)
-	})
+	}, mux)
 
-	mux.HandleFunc("/api/v1/mass_storage/switch", func(w http.ResponseWriter, r *http.Request) {
+	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
@@ -144,13 +230,13 @@ func register_ipkvm_apis(mux *http.ServeMux) {
 		default:
 			http.Error(w, "Invalid side parameter", http.StatusBadRequest)
 		}
-	})
+	}, mux)
 
-	mux.HandleFunc("/api/v1/instances", func(w http.ResponseWriter, r *http.Request) {
+	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)
 }

+ 22 - 20
dezukvmd/main.go

@@ -3,6 +3,7 @@ package main
 import (
 	"embed"
 	"flag"
+	"fmt"
 	"io/fs"
 	"log"
 	"net/http"
@@ -12,15 +13,16 @@ import (
 )
 
 const (
-	defaultDevMode   = true
-	configPath       = "./config"
-	usbKvmConfigPath = configPath + "/usbkvm.json"
-	uuidFile         = configPath + "/uuid.cfg"
+	DEFAULT_DEV_MODE = true
+	CONFIG_PATH      = "./config"
+	USB_KVM_CFG_PATH = CONFIG_PATH + "/usbkvm.json"
+	UUID_FILE        = CONFIG_PATH + "/uuid.cfg"
+	DB_FILE_PATH     = CONFIG_PATH + "/sys.db"
 )
 
 var (
 	nodeUUID   = "00000000-0000-0000-0000-000000000000"
-	developent = flag.Bool("dev", defaultDevMode, "Enable development mode with local static files")
+	developent = flag.Bool("dev", DEFAULT_DEV_MODE, "Enable development mode with local static files")
 	mode       = flag.String("mode", "ipkvm", "Mode of operation: usbkvm, ipkvm or debug")
 	tool       = flag.String("tool", "", "Run debug tool, must be used with -mode=debug")
 )
@@ -56,15 +58,15 @@ func main() {
 
 	//Generate the node uuid if not set
 
-	if _, err := os.Stat(uuidFile); os.IsNotExist(err) {
+	if _, err := os.Stat(UUID_FILE); os.IsNotExist(err) {
 		newUUID := uuid.NewString()
-		err = os.WriteFile(uuidFile, []byte(newUUID), 0644)
+		err = os.WriteFile(UUID_FILE, []byte(newUUID), 0644)
 		if err != nil {
 			log.Fatal("Failed to write UUID to file:", err)
 		}
 	}
 
-	uuidBytes, err := os.ReadFile(uuidFile)
+	uuidBytes, err := os.ReadFile(UUID_FILE)
 	if err != nil {
 		log.Fatal("Failed to read UUID from file:", err)
 	}
@@ -102,24 +104,24 @@ func main() {
 		if err != nil {
 			log.Fatal(err)
 		}
-	case "usbkvm":
-		//Check runtime dependencies
-		err := run_dependency_precheck()
+	case "setpw":
+		// Set system access password interactively
+		err := init_auth_manager()
 		if err != nil {
-			log.Fatal(err)
+			log.Fatal("Failed to initialize Auth Manager:", err)
 		}
-
-		//Load config file or create default one
-		kvmCfg, err := loadUsbKvmConfig()
+		var pw string
+		fmt.Print("Enter new password: ")
+		_, err = fmt.Scanln(&pw)
 		if err != nil {
-			log.Fatal("Failed to load or create USB KVM config:", err)
+			log.Fatal("Failed to read password:", err)
 		}
-
-		//Start USB KVM mode
-		err = startUsbKvmMode(kvmCfg)
+		err = authManager.SetPassword(pw)
 		if err != nil {
-			log.Fatal(err)
+			log.Fatal("Failed to set password:", err)
 		}
+		fmt.Println("Password set successfully.")
+		authManager.Close()
 	default:
 		log.Fatalf("Unknown mode: %s. Supported modes are: usbkvm, capture", *mode)
 	}

+ 176 - 0
dezukvmd/mod/auth/auth.go

@@ -0,0 +1,176 @@
+package auth
+
+import (
+	"encoding/json"
+	"errors"
+	"net/http"
+	"os"
+	"path/filepath"
+	"sync"
+
+	"github.com/boltdb/bolt"
+)
+
+// LogFunc is a function type for logging.
+type LogFunc func(format string, v ...interface{})
+
+// Options holds configuration for AuthManager.
+type Options struct {
+	DBPath string
+	Log    LogFunc
+}
+
+// AuthManager handles authentication.
+type AuthManager struct {
+	db  *bolt.DB
+	log LogFunc
+	mu  sync.RWMutex
+}
+
+const (
+	authBucket = "auth"
+	passKey    = "password"
+)
+
+// NewAuthManager creates a new AuthManager.
+func NewAuthManager(opt Options) (*AuthManager, error) {
+
+	dir := filepath.Dir(opt.DBPath)
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+		if err := os.MkdirAll(dir, 0755); err != nil {
+			return nil, err
+		}
+	}
+	db, err := bolt.Open(opt.DBPath, 0755, nil)
+	if err != nil {
+		return nil, err
+	}
+	// Ensure bucket exists
+	err = db.Update(func(tx *bolt.Tx) error {
+		_, err := tx.CreateBucketIfNotExists([]byte(authBucket))
+		return err
+	})
+	if err != nil {
+		db.Close()
+		return nil, err
+	}
+	return &AuthManager{db: db, log: opt.Log}, nil
+}
+
+// SetPassword sets the password (overwrites any existing).
+func (a *AuthManager) SetPassword(password string) error {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	return a.db.Update(func(tx *bolt.Tx) error {
+		b := tx.Bucket([]byte(authBucket))
+		return b.Put([]byte(passKey), []byte(password))
+	})
+}
+
+// ChangePassword changes password if oldpassword matches.
+func (a *AuthManager) ChangePassword(oldPassword, newPassword string) error {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	return a.db.Update(func(tx *bolt.Tx) error {
+		b := tx.Bucket([]byte(authBucket))
+		stored := b.Get([]byte(passKey))
+		if stored == nil || string(stored) != oldPassword {
+			return errors.New("old password incorrect")
+		}
+		return b.Put([]byte(passKey), []byte(newPassword))
+	})
+}
+
+// ResetPassword removes the password.
+func (a *AuthManager) ResetPassword() error {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	return a.db.Update(func(tx *bolt.Tx) error {
+		b := tx.Bucket([]byte(authBucket))
+		return b.Delete([]byte(passKey))
+	})
+}
+
+// ValidatePassword checks password from request
+func (a *AuthManager) ValidatePassword(password string) (bool, error) {
+	a.mu.RLock()
+	defer a.mu.RUnlock()
+	var ok bool
+	err := a.db.View(func(tx *bolt.Tx) error {
+		b := tx.Bucket([]byte(authBucket))
+		stored := b.Get([]byte(passKey))
+		ok = stored != nil && string(stored) == password
+		return nil
+	})
+	return ok, err
+}
+
+// UserIsLoggedIn checks if the user is logged in via cookie.
+func (a *AuthManager) UserIsLoggedIn(r *http.Request) bool {
+	cookie, err := r.Cookie("dezukvm_auth")
+	return err == nil && cookie.Value == "1"
+}
+
+// HandleFunc wraps an http.HandlerFunc with auth check.
+func (a *AuthManager) HandleFunc(pattern string, handler http.HandlerFunc, mux *http.ServeMux) {
+	mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
+		if a.UserIsLoggedIn(r) {
+			handler(w, r)
+			return
+		}
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Header().Set("Content-Type", "application/json")
+		json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"})
+	})
+}
+
+// LoginUser sets a session/cookie if password is correct
+func (a *AuthManager) LoginUser(w http.ResponseWriter, r *http.Request) error {
+	var req struct {
+		Password string `json:"password"`
+	}
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		return err
+	}
+	a.mu.RLock()
+	defer a.mu.RUnlock()
+	var ok bool
+	err := a.db.View(func(tx *bolt.Tx) error {
+		b := tx.Bucket([]byte(authBucket))
+		stored := b.Get([]byte(passKey))
+		ok = stored != nil && string(stored) == req.Password
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+	if !ok {
+		return errors.New("unauthorized")
+	}
+	// Set a simple session cookie
+	http.SetCookie(w, &http.Cookie{
+		Name:     "dezukvm_auth",
+		Value:    "1",
+		Path:     "/",
+		HttpOnly: true,
+		MaxAge:   86400,
+	})
+	return nil
+}
+
+// LogoutUser removes the session/cookie for the user.
+func (a *AuthManager) LogoutUser(w http.ResponseWriter, r *http.Request) error {
+	http.SetCookie(w, &http.Cookie{
+		Name:     "dezukvm_auth",
+		Value:    "",
+		Path:     "/",
+		HttpOnly: true,
+		MaxAge:   -1,
+	})
+	return nil
+}
+
+// Close closes the underlying DB.
+func (a *AuthManager) Close() error {
+	return a.db.Close()
+}

+ 2 - 1
dezukvmd/mod/dezukvm/handlers.go

@@ -20,7 +20,8 @@ func (d *DezukVM) HandleAudioStreams(w http.ResponseWriter, r *http.Request, ins
 		http.Error(w, "Instance with specified UUID not found", http.StatusNotFound)
 		return
 	}
-	targetInstance.usbCaptureDevice.AudioStreamingHandler(w, r)
+	pcmDevicePath := targetInstance.captureConfig.AudioDeviceName
+	targetInstance.usbCaptureDevice.AudioStreamingHandler(w, r, pcmDevicePath)
 }
 
 func (d *DezukVM) HandleHIDEvents(w http.ResponseWriter, r *http.Request, instanceUuid string) {

+ 6 - 1
dezukvmd/mod/kvmhid/handler.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"log"
 	"net/http"
+	"strings"
 
 	"github.com/gorilla/websocket"
 )
@@ -45,7 +46,7 @@ func (c *Controller) HIDWebSocketHandler(w http.ResponseWriter, r *http.Request)
 			errmsg := map[string]string{"error": err.Error()}
 			if err := conn.WriteJSON(errmsg); err != nil {
 				// Check for broken pipe error to handle closed websocket
-				if err != nil && (err.Error() == "write: broken pipe" || err.Error() == "broken pipe") {
+				if err != nil && strings.Contains(err.Error(), "broken pipe") {
 					log.Println("WebSocket connection closed (broken pipe), cleaning up")
 					break
 				}
@@ -61,6 +62,10 @@ func (c *Controller) HIDWebSocketHandler(w http.ResponseWriter, r *http.Request)
 			prettyBytes += fmt.Sprintf("0x%02X ", b)
 		}
 		if err := conn.WriteMessage(websocket.TextMessage, []byte(prettyBytes)); err != nil {
+			if err != nil && strings.Contains(err.Error(), "broken pipe") {
+				log.Println("WebSocket connection closed (broken pipe), cleaning up")
+				break
+			}
 			log.Println("Error writing message:", err)
 			continue
 		}

+ 77 - 0
dezukvmd/mod/logger/logger.go

@@ -0,0 +1,77 @@
+package logger
+
+import (
+	"log"
+	"os"
+)
+
+type LogLevel int
+
+const (
+	DebugLevel LogLevel = iota
+	InfoLevel
+	ErrorLevel
+)
+
+type Logger struct {
+	level  LogLevel
+	logger *log.Logger
+}
+
+type LogFunc func(format string, v ...interface{})
+
+type LoggerOption func(*Logger)
+
+func WithLogLevel(level LogLevel) LoggerOption {
+	return func(l *Logger) {
+		l.level = level
+	}
+}
+
+func WithOutput(output *os.File) LoggerOption {
+	return func(l *Logger) {
+		l.logger.SetOutput(output)
+	}
+}
+
+func NewLogger(opts ...LoggerOption) *Logger {
+	l := &Logger{
+		level:  InfoLevel,
+		logger: log.New(os.Stdout, "", log.LstdFlags),
+	}
+	for _, opt := range opts {
+		opt(l)
+	}
+	return l
+}
+
+func (l *Logger) Debug(format string, v ...interface{}) {
+	if l.level <= DebugLevel {
+		l.logger.Printf("[DEBUG] "+format, v...)
+	}
+}
+
+func (l *Logger) Info(format string, v ...interface{}) {
+	if l.level <= InfoLevel {
+		l.logger.Printf("[INFO] "+format, v...)
+	}
+}
+
+func (l *Logger) Error(format string, v ...interface{}) {
+	if l.level <= ErrorLevel {
+		l.logger.Printf("[ERROR] "+format, v...)
+	}
+}
+
+func (l *Logger) LogFunc(level LogLevel) LogFunc {
+	return func(format string, v ...interface{}) {
+		switch level {
+		case DebugLevel:
+			l.Debug(format, v...)
+		case InfoLevel:
+			l.Info(format, v...)
+		case ErrorLevel:
+			l.Error(format, v...)
+		}
+	}
+}

+ 10 - 7
dezukvmd/mod/usbcapture/audio_device.go

@@ -115,7 +115,7 @@ func GetDefaultAudioDevice() string {
 }
 
 // AudioStreamingHandler handles incoming WebSocket connections for audio streaming.
-func (i *Instance) AudioStreamingHandler(w http.ResponseWriter, r *http.Request) {
+func (i *Instance) AudioStreamingHandler(w http.ResponseWriter, r *http.Request, devicePath string) {
 	// Check if the request contains ?quality=low
 	quality := r.URL.Query().Get("quality")
 	qualityKey := []string{"low", "standard", "high"}
@@ -149,12 +149,15 @@ func (i *Instance) AudioStreamingHandler(w http.ResponseWriter, r *http.Request)
 		}
 	}
 
-	//Get the capture card audio input
-	pcmdev, err := FindHDMICapturePCMPath()
-	if err != nil {
-		log.Println("Failed to find HDMI capture PCM path:", err)
-		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
-		return
+	pcmdev := devicePath
+	if pcmdev == "" {
+		//Try finding the HDMI capture card automatically
+		pcmdev, err = FindHDMICapturePCMPath()
+		if err != nil {
+			log.Println("Failed to find HDMI capture PCM path:", err)
+			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+			return
+		}
 	}
 
 	log.Println("Found HDMI capture PCM path:", pcmdev)

+ 0 - 175
dezukvmd/usbkvm.go

@@ -1,175 +0,0 @@
-package main
-
-/*
-	usbkvm.go
-
-	Handles the USB KVM device connections and auxiliary devices
-	running in USB KVM mode. This mode only support 1 USB KVM device
-	at a time.
-
-	For running multiple USB KVM devices, use the ipkvm mode.
-*/
-import (
-	"encoding/json"
-	"log"
-	"net/http"
-	"os"
-	"os/signal"
-	"syscall"
-
-	"imuslab.com/dezukvm/dezukvmd/mod/kvmaux"
-	"imuslab.com/dezukvm/dezukvmd/mod/kvmhid"
-	"imuslab.com/dezukvm/dezukvmd/mod/usbcapture"
-)
-
-type UsbKvmConfig struct {
-	ListeningAddress        string
-	USBKVMDevicePath        string
-	AuxMCUDevicePath        string
-	VideoCaptureDevicePath  string
-	AudioCaptureDevicePath  string
-	CaptureResolutionWidth  int
-	CaptureResolutionHeight int
-	CaptureResolutionFPS    int
-	USBKVMBaudrate          int
-	AuxMCUBaudrate          int
-}
-
-var (
-	/* Internal variables for USB-KVM mode only */
-	usbKVM              *kvmhid.Controller
-	auxMCU              *kvmaux.AuxMcu
-	usbCaptureDevice    *usbcapture.Instance
-	defaultUsbKvmConfig = &UsbKvmConfig{
-		ListeningAddress:        ":9000",
-		USBKVMDevicePath:        "/dev/ttyUSB0",
-		AuxMCUDevicePath:        "/dev/ttyACM0",
-		VideoCaptureDevicePath:  "/dev/video0",
-		AudioCaptureDevicePath:  "/dev/snd/pcmC1D0c",
-		CaptureResolutionWidth:  1920,
-		CaptureResolutionHeight: 1080,
-		CaptureResolutionFPS:    25,
-		USBKVMBaudrate:          115200,
-		AuxMCUBaudrate:          115200,
-	}
-)
-
-func loadUsbKvmConfig() (*UsbKvmConfig, error) {
-	if _, err := os.Stat(usbKvmConfigPath); os.IsNotExist(err) {
-		file, err := os.OpenFile(usbKvmConfigPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
-		if err != nil {
-			return nil, err
-		}
-
-		// Save default config as JSON
-		enc := json.NewEncoder(file)
-		enc.SetIndent("", "  ")
-		if err := enc.Encode(defaultUsbKvmConfig); err != nil {
-			file.Close()
-			return nil, err
-		}
-		file.Close()
-		return defaultUsbKvmConfig, nil
-	}
-
-	// Load config from file
-	file, err := os.Open(usbKvmConfigPath)
-	if err != nil {
-		return nil, err
-	}
-
-	cfg := &UsbKvmConfig{}
-	dec := json.NewDecoder(file)
-	if err := dec.Decode(cfg); err != nil {
-		file.Close()
-		return nil, err
-	}
-	file.Close()
-	return cfg, nil
-}
-
-func startUsbKvmMode(config *UsbKvmConfig) error {
-	log.Println("Starting in USB KVM mode...")
-	// Initiate the HID controller
-	usbKVM = kvmhid.NewHIDController(&kvmhid.Config{
-		PortName:          config.USBKVMDevicePath,
-		BaudRate:          config.USBKVMBaudrate,
-		ScrollSensitivity: 0x01, // Set mouse scroll sensitivity
-	})
-
-	//Start the HID controller
-	err := usbKVM.Connect()
-	if err != nil {
-		return err
-	}
-
-	//Start auxiliary MCU connections
-	auxMCU, err = kvmaux.NewAuxOutbandController(config.AuxMCUDevicePath, config.AuxMCUBaudrate)
-	if err != nil {
-		return err
-	}
-
-	//Try get the UUID from the auxiliary MCU
-	uuid, err := auxMCU.GetUUID()
-	if err != nil {
-		log.Println("Get UUID failed:", err, " - Auxiliary MCU may not be connected.")
-
-		//Register dummy AUX routes if failed to get UUID
-		registerDummyLocalAuxRoutes()
-	} else {
-		log.Println("Auxiliary MCU found with UUID:", uuid)
-
-		//Register the AUX routes if success
-		registerLocalAuxRoutes()
-	}
-
-	// Initiate the video capture device
-	usbCaptureDevice, err = usbcapture.NewInstance(&usbcapture.Config{
-		VideoDeviceName: config.VideoCaptureDevicePath,
-		AudioDeviceName: config.AudioCaptureDevicePath,
-		AudioConfig:     usbcapture.GetDefaultAudioConfig(),
-	})
-
-	if err != nil {
-		log.Println("Video capture device init failed:", err, " - Video capture device may not be connected.")
-		return err
-	}
-
-	//Get device information for debug
-	usbcapture.PrintV4L2FormatInfo(config.VideoCaptureDevicePath)
-
-	//Start the video capture device
-	err = usbCaptureDevice.StartVideoCapture(&usbcapture.CaptureResolution{
-		Width:  config.CaptureResolutionWidth,
-		Height: config.CaptureResolutionHeight,
-		FPS:    config.CaptureResolutionFPS,
-	})
-	if err != nil {
-		return err
-	}
-
-	// 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 usbKVM...")
-
-		if auxMCU != nil {
-			auxMCU.Close()
-		}
-		log.Println("Shutting down capture device...")
-		if usbCaptureDevice != nil {
-			usbCaptureDevice.Close()
-		}
-		os.Exit(0)
-	}()
-
-	// Register the rest of the API routes
-	registerAPIRoutes()
-
-	addr := config.ListeningAddress
-	log.Printf("Serving on%s\n", addr)
-	err = http.ListenAndServe(addr, nil)
-	return err
-}

BIN
dezukvmd/www/favicon.png


+ 9 - 9
dezukvmd/www/img/font_logo.svg

@@ -25,13 +25,13 @@
 		l-19.742,56.675l-31.842,86.607h-31.203l-32.158-86.607l-19.105-56.675h-1.273c2.865,23.242,7.322,58.587,7.322,82.15v81.191
 		h-50.945V154.73z"/>
 </g>
-<path fill="#0A6EF5" d="M512,417c0,54.676-44.791,99-100.042,99H100.042C44.79,516,0,471.676,0,417V103C0,48.324,44.79,4,100.042,4
-	h311.916C467.209,4,512,48.324,512,103V417z"/>
-<circle fill="none" stroke="#FFFFFF" stroke-width="70" stroke-miterlimit="10" cx="256" cy="260" r="173"/>
-<polygon fill="#0A6EF5" points="195.561,174.517 120.721,68.667 233.953,45.676 245.063,153.666 "/>
-<polygon fill="#0A6EF5" points="242.833,389.995 233.137,482.274 131.266,436.419 176.432,372.54 "/>
-<circle fill="#FFFFFF" cx="177.615" cy="195.633" r="21.115"/>
-<circle fill="#FFFFFF" cx="257.385" cy="229.261" r="21.115"/>
-<polygon fill="#FFFFFF" points="438.65,212.109 371.779,118.573 486.219,107.43 "/>
-<polygon fill="#FFFFFF" points="363.44,119.377 256,97.143 328.977,15.213 "/>
+<path fill="#0A6EF5" d="M512,413c0,54.676-44.791,99-100.042,99H100.042C44.79,512,0,467.676,0,413V99C0,44.324,44.79,0,100.042,0
+	h311.916C467.209,0,512,44.324,512,99V413z"/>
+<circle fill="none" stroke="#FFFFFF" stroke-width="70" stroke-miterlimit="10" cx="256" cy="256" r="173"/>
+<polygon fill="#0A6EF5" points="195.561,170.517 120.721,64.667 233.953,41.676 245.063,149.666 "/>
+<polygon fill="#0A6EF5" points="242.833,385.995 233.137,478.274 131.266,432.419 176.432,368.54 "/>
+<circle fill="#FFFFFF" cx="190.949" cy="204.966" r="34.449"/>
+<polygon fill="#FFFFFF" points="438.65,208.109 371.779,114.573 486.219,103.43 "/>
+<polygon fill="#FFFFFF" points="363.44,115.377 256,93.143 328.977,11.213 "/>
+<circle fill="#FFFFFF" cx="284.178" cy="253.3" r="34.449"/>
 </svg>

BIN
dezukvmd/www/img/logo.png


+ 2 - 2
dezukvmd/www/img/logo.svg

@@ -9,10 +9,10 @@
 	<circle fill="none" stroke="#FFFFFF" stroke-width="70" stroke-miterlimit="10" cx="256" cy="256" r="173"/>
 	<polygon fill="#0A6EF5" points="195.561,170.517 120.721,64.667 233.954,41.676 245.062,149.666 	"/>
 	<polygon fill="#0A6EF5" points="242.833,385.995 233.136,478.274 131.266,432.419 176.431,368.54 	"/>
-	<circle fill="#FFFFFF" cx="177.615" cy="191.632" r="21.115"/>
-	<circle fill="#FFFFFF" cx="257.385" cy="225.261" r="21.115"/>
+	<circle fill="#FFFFFF" cx="190.949" cy="204.966" r="34.449"/>
 	<polygon fill="#FFFFFF" points="438.65,208.109 371.779,114.573 486.219,103.43 	"/>
 	<polygon fill="#FFFFFF" points="363.44,115.377 256,93.143 328.977,11.213 	"/>
+	<circle fill="#FFFFFF" cx="284.178" cy="253.3" r="34.449"/>
 </g>
 <g id="圖層_2" display="none">
 	<line display="inline" fill="none" stroke="#E4007F" stroke-miterlimit="10" x1="75" y1="0" x2="75" y2="512"/>

+ 3 - 2
dezukvmd/www/index.html

@@ -134,7 +134,7 @@
                 <div>
                     <div class="menu-top">
                         <div class="logo">
-                            <img src="img/logo.png" alt="Logo">
+                            <img src="img/logo.svg" alt="Logo">
                         </div>
                         <div class="menu-options">
                             <div class="active item" menu="instances"><i class="ui server icon"></i></div>
@@ -172,6 +172,7 @@
         </div>
         <script>
             let currentTab = 'instances';
+
             $(document).ready(function() {
                 listInstances();
             });
@@ -228,7 +229,7 @@
             }
 
             function connectToSession(sessionId, callback=undefined) {
-                $('#sessionContext').attr('src', `/viewport.html#${sessionId}`);
+                $('#sessionContext').attr('src', `/viewport.html?ts=${Date.now()}#${sessionId}`);
                 if (callback) callback();
             }
 

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott