ipkvm.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "os"
  7. "os/signal"
  8. "path/filepath"
  9. "strings"
  10. "syscall"
  11. "github.com/gorilla/csrf"
  12. "imuslab.com/dezukvm/dezukvmd/mod/auth"
  13. "imuslab.com/dezukvm/dezukvmd/mod/dezukvm"
  14. "imuslab.com/dezukvm/dezukvmd/mod/logger"
  15. "imuslab.com/dezukvm/dezukvmd/mod/utils"
  16. )
  17. var (
  18. dezukvmManager *dezukvm.DezukVM
  19. listeningServerMux *http.ServeMux
  20. authManager *auth.AuthManager
  21. systemLogger *logger.Logger
  22. )
  23. func init_auth_manager() error {
  24. // Initialize logger
  25. systemLogger = logger.NewLogger(logger.WithLogLevel(logger.InfoLevel))
  26. // Initialize AuthManager with logger and DB path
  27. var err error
  28. authManager, err = auth.NewAuthManager(auth.Options{
  29. DBPath: DB_FILE_PATH,
  30. Log: systemLogger.Info,
  31. })
  32. if err != nil {
  33. return err
  34. }
  35. return nil
  36. }
  37. func init_ipkvm_mode() error {
  38. listeningServerMux = http.NewServeMux()
  39. // Initialize the Auth Manager
  40. err := init_auth_manager()
  41. if err != nil {
  42. log.Fatal("Failed to initialize Auth Manager:", err)
  43. return err
  44. }
  45. //Create a new DezukVM manager
  46. dezukvmManager = dezukvm.NewKvmHostInstance(&dezukvm.RuntimeOptions{
  47. EnableLog: true,
  48. })
  49. // Experimental
  50. connectedUsbKvms, err := dezukvm.ScanConnectedUsbKvmDevices()
  51. if err != nil {
  52. return err
  53. }
  54. for _, dev := range connectedUsbKvms {
  55. err := dezukvmManager.AddUsbKvmDevice(dev)
  56. if err != nil {
  57. return err
  58. }
  59. }
  60. err = dezukvmManager.StartAllUsbKvmDevices()
  61. if err != nil {
  62. return err
  63. }
  64. // ~Experimental
  65. // Handle root routing with CSRF protection
  66. handle_root_routing(listeningServerMux)
  67. // Handle program exit to close the HID controller
  68. c := make(chan os.Signal, 1)
  69. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  70. go func() {
  71. <-c
  72. log.Println("Shutting down DezuKVM...")
  73. if dezukvmManager != nil {
  74. dezukvmManager.Close()
  75. }
  76. if authManager != nil {
  77. authManager.Close()
  78. }
  79. log.Println("Shutdown complete.")
  80. os.Exit(0)
  81. }()
  82. // Register Auth related APIs
  83. register_auth_apis(listeningServerMux)
  84. // Register DezukVM related APIs
  85. register_ipkvm_apis(listeningServerMux)
  86. err = http.ListenAndServe(":9000", listeningServerMux)
  87. return err
  88. }
  89. func request_url_allow_unauthenticated(r *http.Request) bool {
  90. // Define a list of URL paths that can be accessed without authentication
  91. allowedPaths := []string{
  92. "/login.html",
  93. "/api/v1/login",
  94. "/img/",
  95. "/js/",
  96. "/css/",
  97. "/favicon.png",
  98. }
  99. requestPath := r.URL.Path
  100. for _, path := range allowedPaths {
  101. if strings.HasPrefix(requestPath, path) {
  102. return true
  103. }
  104. }
  105. return false
  106. }
  107. func handle_root_routing(mux *http.ServeMux) {
  108. // Root router: check login status, redirect to login.html if not authenticated
  109. mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  110. if !authManager.UserIsLoggedIn(r) && !request_url_allow_unauthenticated(r) {
  111. w.Header().Set("Location", "/login.html")
  112. w.WriteHeader(http.StatusFound)
  113. return
  114. }
  115. // Only inject for .html files
  116. path := r.URL.Path
  117. if path == "/" {
  118. path = "/index.html"
  119. }
  120. if strings.HasSuffix(path, ".html") {
  121. // Read the HTML file from disk
  122. targetFilePath := filepath.Join("www", filepath.Clean(path))
  123. content, err := os.ReadFile(targetFilePath)
  124. if err != nil {
  125. http.NotFound(w, r)
  126. return
  127. }
  128. htmlContent := string(content)
  129. // Replace CSRF token placeholder
  130. htmlContent = strings.ReplaceAll(htmlContent, "{{.csrfToken}}", csrf.Token(r))
  131. w.Header().Set("Content-Type", "text/html")
  132. w.Write([]byte(htmlContent))
  133. return
  134. }
  135. // Serve static files (img, js, css, etc.)
  136. targetFilePath := filepath.Join("www", filepath.Clean(path))
  137. http.ServeFile(w, r, targetFilePath)
  138. })
  139. }
  140. func register_auth_apis(mux *http.ServeMux) {
  141. // Check API for session validation
  142. mux.HandleFunc("/api/v1/check", func(w http.ResponseWriter, r *http.Request) {
  143. if r.Method != http.MethodPost {
  144. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  145. return
  146. }
  147. ok := authManager.UserIsLoggedIn(r)
  148. if !ok {
  149. http.Error(w, "Unauthorized", http.StatusUnauthorized)
  150. return
  151. }
  152. w.WriteHeader(http.StatusOK)
  153. w.Write([]byte("{\"status\":\"ok\"}"))
  154. })
  155. // Login API
  156. mux.HandleFunc("/api/v1/login", func(w http.ResponseWriter, r *http.Request) {
  157. if r.Method != http.MethodPost {
  158. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  159. return
  160. }
  161. err := authManager.LoginUser(w, r)
  162. if err != nil {
  163. http.Error(w, "Unauthorized", http.StatusUnauthorized)
  164. return
  165. }
  166. w.WriteHeader(http.StatusOK)
  167. w.Write([]byte("{\"status\":\"success\"}"))
  168. })
  169. }
  170. func register_ipkvm_apis(mux *http.ServeMux) {
  171. authManager.HandleFunc("/api/v1/stream/{uuid}/video", func(w http.ResponseWriter, r *http.Request) {
  172. instanceUUID := r.PathValue("uuid")
  173. fmt.Println("Requested video stream for instance UUID:", instanceUUID)
  174. dezukvmManager.HandleVideoStreams(w, r, instanceUUID)
  175. }, mux)
  176. authManager.HandleFunc("/api/v1/stream/{uuid}/audio", func(w http.ResponseWriter, r *http.Request) {
  177. instanceUUID := r.PathValue("uuid")
  178. dezukvmManager.HandleAudioStreams(w, r, instanceUUID)
  179. }, mux)
  180. authManager.HandleFunc("/api/v1/hid/{uuid}/events", func(w http.ResponseWriter, r *http.Request) {
  181. instanceUUID := r.PathValue("uuid")
  182. dezukvmManager.HandleHIDEvents(w, r, instanceUUID)
  183. }, mux)
  184. authManager.HandleFunc("/api/v1/mass_storage/switch", func(w http.ResponseWriter, r *http.Request) {
  185. if r.Method != http.MethodPost {
  186. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  187. return
  188. }
  189. instanceUUID, err := utils.PostPara(r, "uuid")
  190. if err != nil {
  191. http.Error(w, "Missing or invalid uuid parameter", http.StatusBadRequest)
  192. return
  193. }
  194. side, err := utils.PostPara(r, "side")
  195. if err != nil {
  196. http.Error(w, "Missing or invalid side parameter", http.StatusBadRequest)
  197. return
  198. }
  199. switch side {
  200. case "kvm":
  201. dezukvmManager.HandleMassStorageSideSwitch(w, r, instanceUUID, true)
  202. case "remote":
  203. dezukvmManager.HandleMassStorageSideSwitch(w, r, instanceUUID, false)
  204. default:
  205. http.Error(w, "Invalid side parameter", http.StatusBadRequest)
  206. }
  207. }, mux)
  208. authManager.HandleFunc("/api/v1/instances", func(w http.ResponseWriter, r *http.Request) {
  209. if r.Method == http.MethodGet {
  210. dezukvmManager.HandleListInstances(w, r)
  211. } else {
  212. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  213. }
  214. }, mux)
  215. }