mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
Merge pull request #107 from Civil/refactoring
Slightly refactor nanokvm code
This commit is contained in:
@@ -1,30 +1,54 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
hwVersionFile = "/etc/kvm/hw"
|
||||
|
||||
gpioPower = "/sys/class/gpio/gpio503/value"
|
||||
gpioPowerLED = "/sys/class/gpio/gpio504/value"
|
||||
|
||||
gpioResetAlpha = "/sys/class/gpio/gpio507/value"
|
||||
gpioHDDLedAlpha = "/sys/class/gpio/gpio505/value"
|
||||
|
||||
gpioResetBeta = "/sys/class/gpio/gpio505/value"
|
||||
)
|
||||
|
||||
var (
|
||||
config Config
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
var defaultConfig = []byte(`proto: http
|
||||
port:
|
||||
http: 80
|
||||
https: 443
|
||||
cert:
|
||||
crt: server.crt
|
||||
key: server.key
|
||||
`)
|
||||
defaultConfig = &Config{
|
||||
Protocol: "http",
|
||||
Port: Port{
|
||||
Http: 80,
|
||||
Https: 443,
|
||||
},
|
||||
Cert: Cert{
|
||||
Crt: "server.crt",
|
||||
Key: "server.key",
|
||||
},
|
||||
Logger: LoggerConfig{
|
||||
Level: "info",
|
||||
File: "stdout",
|
||||
},
|
||||
Authentication: "enable",
|
||||
SecretKey: generateRandomString(),
|
||||
}
|
||||
)
|
||||
|
||||
func GetInstance() *Config {
|
||||
once.Do(read)
|
||||
@@ -40,48 +64,68 @@ func read() {
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if errors.As(err, &viper.ConfigFileNotFoundError{}) {
|
||||
create()
|
||||
fmt.Printf("File /etc/kvm/server.yaml not exists. Use default configuration.\n")
|
||||
log.Println("File /etc/kvm/server.yaml not found. Create a new one with default configuration.")
|
||||
} else {
|
||||
fmt.Printf("Read file /etc/kvm/server.yaml failed. Use default configuration.\n")
|
||||
log.Println("Failed to read config file /etc/kvm/server.yaml. Using default configuration.")
|
||||
}
|
||||
}
|
||||
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
panic(fmt.Sprintf("Can't read configuration file /etc/kvm/nanokvm.yaml.\n%s", err))
|
||||
log.Fatalf("Failed to parse configuration file /etc/kvm/server.yaml: %s", err)
|
||||
}
|
||||
|
||||
validate()
|
||||
|
||||
if config.SecretKey == "" {
|
||||
config.SecretKey = generateRandomString()
|
||||
config.SecretKey = defaultConfig.SecretKey
|
||||
}
|
||||
|
||||
if config.Authentication == "disable" {
|
||||
fmt.Println("NOTICE: Authentication is disabled! Please ensure your service is secure!")
|
||||
log.Println("NOTICE: Authentication is disabled! Please ensure your service is secure!")
|
||||
}
|
||||
|
||||
fmt.Printf("load config success\n")
|
||||
log.Println("config loaded successfully")
|
||||
config.HW.Version = getHwVersion()
|
||||
config.HW.GPIOPower = gpioPower
|
||||
config.HW.GPIOPowerLED = gpioPowerLED
|
||||
switch config.HW.Version {
|
||||
case HWVersionAlpha:
|
||||
config.HW.GPIOHDDLed = gpioHDDLedAlpha
|
||||
config.HW.GPIOReset = gpioResetAlpha
|
||||
case HWVersionBeta:
|
||||
config.HW.GPIOReset = gpioResetBeta
|
||||
default:
|
||||
log.Fatalf("Unsupported hardware version: %s", config.HW.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func create() {
|
||||
_ = os.MkdirAll("/etc/kvm", 0644)
|
||||
_ = os.MkdirAll("/etc/kvm", 0o644)
|
||||
|
||||
file, err := os.OpenFile("/etc/kvm/server.yaml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
file, err := os.OpenFile("/etc/kvm/server.yaml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
fmt.Printf("open config failed: %s\n", err)
|
||||
log.Printf("open config failed: %s", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
_, err = file.Write(defaultConfig)
|
||||
data, err := yaml.Marshal(defaultConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("save config failed: %s\n", err)
|
||||
log.Printf("failed to marshal default config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = file.Write(data)
|
||||
if err != nil {
|
||||
log.Printf("failed to save config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = file.Sync()
|
||||
if err != nil {
|
||||
fmt.Printf("sync config failed: %s\n", err)
|
||||
log.Printf("failed to sync config: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -93,19 +137,14 @@ func validate() {
|
||||
|
||||
_ = os.Remove("/etc/kvm/server.yaml")
|
||||
|
||||
if err := viper.ReadConfig(bytes.NewBuffer(defaultConfig)); err != nil {
|
||||
panic("load default config failed")
|
||||
}
|
||||
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
panic("Can't read configuration file /etc/kvm/server.yaml.")
|
||||
log.Fatalf("Failed to read configuration file /etc/kvm/server.yaml: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func generateRandomString() string {
|
||||
b := make([]byte, 64)
|
||||
_, err := rand.Read(b)
|
||||
|
||||
if err != nil {
|
||||
currentTime := time.Now().UnixNano()
|
||||
timeString := fmt.Sprintf("%d", currentTime)
|
||||
@@ -114,3 +153,15 @@ func generateRandomString() string {
|
||||
|
||||
return base64.URLEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func getHwVersion() HWVersion {
|
||||
content, err := os.ReadFile(hwVersionFile)
|
||||
if err == nil {
|
||||
version := strings.ReplaceAll(string(content), "\n", "")
|
||||
if version == "beta" {
|
||||
return HWVersionBeta
|
||||
}
|
||||
}
|
||||
|
||||
return HWVersionAlpha
|
||||
}
|
||||
|
||||
@@ -1,12 +1,45 @@
|
||||
package config
|
||||
|
||||
type HWVersion int
|
||||
|
||||
const (
|
||||
HWVersionAlpha HWVersion = iota
|
||||
HWVersionBeta
|
||||
)
|
||||
|
||||
func (h HWVersion) String() string {
|
||||
switch h {
|
||||
case HWVersionAlpha:
|
||||
return "Alpha"
|
||||
case HWVersionBeta:
|
||||
return "Beta"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Protocol string `yaml:"proto"`
|
||||
Port Port `yaml:"port"`
|
||||
Cert Cert `yaml:"cert"`
|
||||
Log string `yaml:"logger"`
|
||||
Authentication string `yaml:"authentication"`
|
||||
SecretKey string `yaml:"secretKey"`
|
||||
Protocol string `yaml:"proto"`
|
||||
Port Port `yaml:"port"`
|
||||
Cert Cert `yaml:"cert"`
|
||||
Logger LoggerConfig `yaml:"logger"`
|
||||
Authentication string `yaml:"authentication"`
|
||||
SecretKey string `yaml:"secretKey"`
|
||||
|
||||
HW HW `yaml:"-"`
|
||||
}
|
||||
|
||||
type HW struct {
|
||||
Version HWVersion `yaml:"-"`
|
||||
GPIOReset string `yaml:"-"`
|
||||
GPIOPower string `yaml:"-"`
|
||||
GPIOPowerLED string `yaml:"-"`
|
||||
GPIOHDDLed string `yaml:"-"`
|
||||
}
|
||||
|
||||
type LoggerConfig struct {
|
||||
Level string `yaml:"level"`
|
||||
File string `yaml:"file"`
|
||||
}
|
||||
|
||||
type Port struct {
|
||||
|
||||
@@ -14,6 +14,7 @@ require (
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/unrolled/secure v1.15.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -56,5 +57,4 @@ require (
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -3,8 +3,9 @@ package logger
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type formatter struct{}
|
||||
|
||||
@@ -1,21 +1,48 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"NanoKVM-Server/config"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func openLogFile(filename string) (*os.File, error) {
|
||||
absPath, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(absPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func Init() {
|
||||
conf := config.GetInstance()
|
||||
|
||||
level, err := logrus.ParseLevel(conf.Log)
|
||||
level, err := logrus.ParseLevel(conf.Logger.Level)
|
||||
if err != nil {
|
||||
level = logrus.ErrorLevel
|
||||
}
|
||||
|
||||
logrus.SetLevel(level)
|
||||
logrus.SetOutput(os.Stdout)
|
||||
if conf.Logger.File == "" || conf.Logger.File == "stdout" {
|
||||
logrus.SetOutput(os.Stdout)
|
||||
} else {
|
||||
fh, err := openLogFile(conf.Logger.File)
|
||||
if err != nil {
|
||||
logrus.Error("open log file failed:", err)
|
||||
logrus.SetOutput(os.Stdout)
|
||||
} else {
|
||||
logrus.SetOutput(fh)
|
||||
}
|
||||
}
|
||||
|
||||
logrus.SetReportCaller(true)
|
||||
logrus.SetFormatter(&formatter{})
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"NanoKVM-Server/config"
|
||||
"NanoKVM-Server/logger"
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/router"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
cors "github.com/rs/cors/wrapper/gin"
|
||||
)
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/config"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"NanoKVM-Server/config"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
@@ -38,7 +40,6 @@ func CheckToken() gin.HandlerFunc {
|
||||
|
||||
c.JSON(http.StatusUnauthorized, "unauthorized")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +65,6 @@ func ParseJWT(jwtToken string) (*Token, error) {
|
||||
secretKey := conf.SecretKey
|
||||
return []byte(secretKey), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("parse jwt error: %s", err)
|
||||
return nil, err
|
||||
|
||||
@@ -1,54 +1,49 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
)
|
||||
|
||||
var env = os.Getenv(gin.EnvGinMode)
|
||||
|
||||
// ValidateRequest Verification request parameters
|
||||
func ValidateRequest(req interface{}) (err error) {
|
||||
// ValidateRequest Validates request parameters.
|
||||
func ValidateRequest(req interface{}) error {
|
||||
validate := validator.New()
|
||||
|
||||
if err = validate.Struct(req); err != nil {
|
||||
if err := validate.Struct(req); err != nil {
|
||||
log.Errorf("validate request failed, err: %s", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
if env == "" || env == "debug" {
|
||||
log.Debugf("request: %+v\n", req)
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseQueryRequest Parsing GET requests
|
||||
func ParseQueryRequest(c *gin.Context, req interface{}) (err error) {
|
||||
// ParseQueryRequest Validates GET requests.
|
||||
func ParseQueryRequest(c *gin.Context, req interface{}) error {
|
||||
var err error
|
||||
if err = c.ShouldBindQuery(req); err != nil {
|
||||
log.Errorf("parse request failed, err: %s", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ValidateRequest(req); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
return ValidateRequest(req)
|
||||
}
|
||||
|
||||
// ParseFormRequest Parsing POST Requests
|
||||
func ParseFormRequest(c *gin.Context, req interface{}) (err error) {
|
||||
// ParseFormRequest Validates POST Requests.
|
||||
func ParseFormRequest(c *gin.Context, req interface{}) error {
|
||||
var err error
|
||||
if err = c.ShouldBind(req); err != nil {
|
||||
log.Errorf("parse request failed, err: %s", err)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ValidateRequest(req); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
return ValidateRequest(req)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
@@ -26,14 +27,14 @@ func (r *Response) Err(code int, msg string) {
|
||||
r.Msg = msg
|
||||
}
|
||||
|
||||
// OkRsp Successful response without data
|
||||
// OkRsp Successful response without data.
|
||||
func (r *Response) OkRsp(c *gin.Context) {
|
||||
r.Ok()
|
||||
|
||||
c.JSON(http.StatusOK, r)
|
||||
}
|
||||
|
||||
// OkRspWithData Successful response with data
|
||||
// OkRspWithData Successful response with data.
|
||||
func (r *Response) OkRspWithData(c *gin.Context, data interface{}) {
|
||||
r.Ok()
|
||||
r.Data = data
|
||||
@@ -41,7 +42,7 @@ func (r *Response) OkRspWithData(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, r)
|
||||
}
|
||||
|
||||
// ErrRsp Failed response
|
||||
// ErrRsp Failed response.
|
||||
func (r *Response) ErrRsp(c *gin.Context, code int, msg string) {
|
||||
r.Err(code, msg)
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/service/application"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func applicationRouter(r *gin.Engine) {
|
||||
log.Debugf("application router init")
|
||||
service := application.NewService()
|
||||
api := r.Group("/api").Use(middleware.CheckToken())
|
||||
|
||||
@@ -14,4 +17,5 @@ func applicationRouter(r *gin.Engine) {
|
||||
api.POST("/application/update", service.Update) // update application
|
||||
api.GET("/application/lib", service.GetLib) // check if lib exists
|
||||
api.POST("/application/lib", service.UpdateLib) // update lib
|
||||
log.Debugf("application router init done")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/service/auth"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func authRouter(r *gin.Engine) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/service/hid"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func hidRouter(r *gin.Engine) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/service/network"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func networkRouter(r *gin.Engine) {
|
||||
|
||||
@@ -2,10 +2,11 @@ package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gin-gonic/contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Init(r *gin.Engine) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/service/storage"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func storageRouter(r *gin.Engine) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/service/stream"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func streamRouter(r *gin.Engine) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/service/vm"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func vmRouter(r *gin.Engine) {
|
||||
@@ -12,9 +13,8 @@ func vmRouter(r *gin.Engine) {
|
||||
|
||||
api.GET("/vm/info", service.GetInfo) // get device information
|
||||
|
||||
api.POST("/vm/gpio", service.SetGpio) // update gpio
|
||||
api.GET("/vm/gpio", service.GetGpio) // get gpio
|
||||
|
||||
api.POST("/vm/gpio", service.SetGpio) // update gpio
|
||||
api.GET("/vm/gpio", service.GetGpio) // get gpio
|
||||
api.POST("/vm/screen", service.SetScreen) // update screen
|
||||
|
||||
api.GET("/vm/terminal", service.Terminal) // web terminal
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/service/ws"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func wsRouter(r *gin.Engine) {
|
||||
|
||||
@@ -1,45 +1,63 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTries = 3
|
||||
)
|
||||
|
||||
func (s *Service) GetVersion(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
log.Debugf("get version api triggered")
|
||||
|
||||
// current version
|
||||
currentVersion := "1.0.0"
|
||||
content, err := os.ReadFile(VersionFile)
|
||||
content, err := os.ReadFile(versionFile)
|
||||
if err == nil {
|
||||
currentVersion = strings.ReplaceAll(string(content), "\n", "")
|
||||
}
|
||||
log.Debugf("current version: %s", currentVersion)
|
||||
|
||||
// latest version
|
||||
url := fmt.Sprintf("%s?now=%d", VersionURL, time.Now().Unix())
|
||||
url := fmt.Sprintf("%s?now=%d", versionURL, time.Now().Unix())
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Debugf("get latest version failed: %v", err)
|
||||
rsp.ErrRsp(c, -2, "Unable to access sipeed.com. Please check your network.")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
rsp.ErrRsp(c, -3, "get version failed")
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Errorf("read body failed: %v", err)
|
||||
rsp.ErrRsp(c, -4, "read body failed")
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
latestVersion := strings.Replace(string(body), "\n", "", -1)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Errorf("server responded with status code: %d", resp.StatusCode)
|
||||
rsp.ErrRsp(c, -3, fmt.Sprintf("get version failed, server returns status code: %d, body: %s", resp.StatusCode, string(body)))
|
||||
return
|
||||
}
|
||||
|
||||
latestVersion := strings.ReplaceAll(string(body), "\n", "")
|
||||
|
||||
rsp.OkRspWithData(c, &proto.GetVersionRsp{
|
||||
Current: currentVersion,
|
||||
@@ -49,69 +67,109 @@ func (s *Service) GetVersion(c *gin.Context) {
|
||||
|
||||
func (s *Service) Update(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
log.Debugf("update application api triggered")
|
||||
|
||||
if err := updateApp(); err != nil {
|
||||
rsp.ErrRsp(c, -1, "update failed")
|
||||
log.Debugf("update failed: %s", err)
|
||||
rsp.ErrRsp(c, -1, fmt.Sprintf("update failed: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("update application success")
|
||||
|
||||
// Sleep for a second before restarting the device
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
_ = exec.Command("sh", "-c", "/etc/init.d/S95nanokvm restart").Run()
|
||||
}
|
||||
|
||||
func updateApp() error {
|
||||
cleanCmd := exec.Command("sh", "-c", fmt.Sprintf("rm -rf %s", Temporary))
|
||||
_ = cleanCmd.Run()
|
||||
_ = os.MkdirAll(Temporary, 0755)
|
||||
defer cleanCmd.Run()
|
||||
log.Debugf("update application")
|
||||
_ = os.RemoveAll(temporary)
|
||||
_ = os.MkdirAll(temporary, 0o755)
|
||||
defer func() {
|
||||
_ = os.RemoveAll(temporary)
|
||||
}()
|
||||
|
||||
if err := downloadLib(); err != nil {
|
||||
log.Errorf("download lib failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := downloadApp(); err != nil {
|
||||
log.Errorf("download app failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
commands := []string{
|
||||
fmt.Sprintf("mv -f %s/%s %s/latest/kvm_system/dl_lib/", Temporary, LibName, Temporary), // move lib
|
||||
fmt.Sprintf("rm -rf %s && mv %s %s", Backup, Workspace, Backup), // backup old version
|
||||
fmt.Sprintf("rm -rf %s && mv %s/latest %s", Workspace, Temporary, Workspace), // update
|
||||
fmt.Sprintf("chmod -R 755 %s", Workspace), // modify permission
|
||||
err := utils.MoveFile(temporary+"/"+libName, temporary+"/latest/kvm_system/dl_lib/"+libName) // move lib
|
||||
if err != nil {
|
||||
log.Errorf("rename lib failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
err := exec.Command("sh", "-c", command).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// backup old version
|
||||
err = os.RemoveAll(backup)
|
||||
if err != nil {
|
||||
log.Errorf("remove backup failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = utils.MoveFilesRecursively(workspace, backup)
|
||||
if err != nil {
|
||||
log.Errorf("backuping old libraries failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// update
|
||||
err = utils.MoveFilesRecursively(temporary+"/latest", workspace)
|
||||
if err != nil {
|
||||
log.Errorf("failed to move update back in place: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// modify permissions
|
||||
err = utils.ChmodRecursively(workspace, 0o755)
|
||||
if err != nil {
|
||||
log.Errorf("chmod failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadApp() error {
|
||||
url := fmt.Sprintf("%s?now=%d", ApplicationURL, time.Now().Unix())
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Errorf("new request err: %s", err)
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
log.Debugf("downloading latest application...")
|
||||
url := fmt.Sprintf("%s?now=%d", applicationURL, time.Now().Unix())
|
||||
for i := range maxTries {
|
||||
log.Debugf("attempt #%d/%d", i+1, maxTries)
|
||||
if i > 0 {
|
||||
time.Sleep(time.Second * 3) // wait for 3 seconds before retrying the download attempt
|
||||
}
|
||||
|
||||
zipFile := fmt.Sprintf("%s/latest.zip", Temporary)
|
||||
err = utils.Download(req, zipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Errorf("new request err: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
command := fmt.Sprintf("unzip %s -d %s", zipFile, Temporary)
|
||||
err = exec.Command("sh", "-c", command).Run()
|
||||
if err != nil {
|
||||
log.Errorf("unzip app failed: %s", err)
|
||||
return err
|
||||
}
|
||||
zipFile := temporary + "/latest.zip"
|
||||
log.Debugf("update will be saved to: %s", zipFile)
|
||||
err = utils.Download(req, zipFile)
|
||||
if err != nil {
|
||||
log.Errorf("downloading latest application failed, try again...")
|
||||
continue
|
||||
}
|
||||
|
||||
return nil
|
||||
err = utils.Unzip(zipFile, temporary)
|
||||
if err != nil {
|
||||
log.Errorf("unzip app failed: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
)
|
||||
|
||||
func (s *Service) GetLib(c *gin.Context) {
|
||||
@@ -39,18 +42,17 @@ func (s *Service) UpdateLib(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
cleanCmd := exec.Command("sh", "-c", fmt.Sprintf("rm -rf %s", Temporary))
|
||||
_ = cleanCmd.Run()
|
||||
_ = os.MkdirAll(Temporary, 0755)
|
||||
defer cleanCmd.Run()
|
||||
_ = os.MkdirAll(temporary, 0o755)
|
||||
defer func() {
|
||||
_ = os.RemoveAll(temporary)
|
||||
}()
|
||||
|
||||
if err := downloadLib(); err != nil {
|
||||
rsp.ErrRsp(c, -1, "download lib failed")
|
||||
return
|
||||
}
|
||||
|
||||
command := fmt.Sprintf("mv -f %s/%s %s/", Temporary, LibName, LibDir) // update lib
|
||||
err := exec.Command("sh", "-c", command).Run()
|
||||
err := utils.MoveFile(temporary+"/"+libName, libDir+"/") // update lib
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -2, "update lib failed")
|
||||
return
|
||||
@@ -63,7 +65,7 @@ func (s *Service) UpdateLib(c *gin.Context) {
|
||||
}
|
||||
|
||||
func isLibExist() (bool, error) {
|
||||
libPath := fmt.Sprintf("%s/%s", LibDir, LibName)
|
||||
libPath := fmt.Sprintf("%s/%s", libDir, libName)
|
||||
_, err := os.Stat(libPath)
|
||||
|
||||
if err == nil {
|
||||
@@ -78,22 +80,37 @@ func isLibExist() (bool, error) {
|
||||
}
|
||||
|
||||
func downloadLib() error {
|
||||
log.Debugf("downloading libs...")
|
||||
content, err := os.ReadFile("/device_key")
|
||||
if err != nil {
|
||||
log.Errorf("read devcie key err: %s", err)
|
||||
log.Errorf("error reading device key: %s", err)
|
||||
return err
|
||||
}
|
||||
deviceKey := strings.ReplaceAll(string(content), "\n", "")
|
||||
|
||||
url := fmt.Sprintf("%s?uid=%s", LibURL, deviceKey)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Errorf("new request err: %s", err)
|
||||
return err
|
||||
for i := range maxTries {
|
||||
log.Debugf("attempt #%d/%d", i+1, maxTries)
|
||||
if i > 0 {
|
||||
time.Sleep(time.Second * 3) // wait for 3 seconds before retrying the download attempt
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
url := fmt.Sprintf("%s?uid=%s", libURL, deviceKey)
|
||||
req, err = http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Errorf("error creating new request: %s", err)
|
||||
continue
|
||||
}
|
||||
req.Header.Set("token", "MaixVision2024")
|
||||
|
||||
target := fmt.Sprintf("%s/%s", temporary, libName)
|
||||
|
||||
err = utils.Download(req, target)
|
||||
if err != nil {
|
||||
log.Errorf("downloading lib failed: %s", err)
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
req.Header.Set("token", "MaixVision2024")
|
||||
|
||||
target := fmt.Sprintf("%s/%s", Temporary, LibName)
|
||||
|
||||
return utils.Download(req, target)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package application
|
||||
|
||||
const (
|
||||
VersionURL = "https://cdn.sipeed.com/nanokvm/latest"
|
||||
ApplicationURL = "https://cdn.sipeed.com/nanokvm/latest.zip"
|
||||
LibURL = "https://maixvision.sipeed.com/api/v1/nanokvm/encryption"
|
||||
versionURL = "https://cdn.sipeed.com/nanokvm/latest"
|
||||
applicationURL = "https://cdn.sipeed.com/nanokvm/latest.zip"
|
||||
libURL = "https://maixvision.sipeed.com/api/v1/nanokvm/encryption"
|
||||
|
||||
Temporary = "/tmp/kvmcache"
|
||||
Workspace = "/kvmapp"
|
||||
Backup = "/root/old"
|
||||
LibDir = "/kvmapp/kvm_system/dl_lib"
|
||||
LibName = "libmaixcam_lib.so"
|
||||
VersionFile = "/kvmapp/version"
|
||||
temporary = "/tmp/kvmcache"
|
||||
workspace = "/kvmapp"
|
||||
backup = "/root/old"
|
||||
libDir = "/kvmapp/kvm_system/dl_lib"
|
||||
libName = "libmaixcam_lib.so"
|
||||
versionFile = "/kvmapp/version"
|
||||
)
|
||||
|
||||
type Service struct{}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/utils"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/utils"
|
||||
)
|
||||
|
||||
const AccountFile = "/etc/kvm/pwd"
|
||||
@@ -56,14 +58,18 @@ func setAccount(username string, password string) error {
|
||||
Username: username,
|
||||
Password: password,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to marshal account information to json: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(AccountFile), 0644)
|
||||
err = os.MkdirAll(filepath.Dir(AccountFile), 0o644)
|
||||
if err != nil {
|
||||
log.Errorf("create directory %s failed: %s", AccountFile, err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(AccountFile, account, 0644)
|
||||
err = os.WriteFile(AccountFile, account, 0o644)
|
||||
if err != nil {
|
||||
log.Errorf("write password failed: %s", err)
|
||||
return err
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/config"
|
||||
"NanoKVM-Server/middleware"
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (s *Service) Login(c *gin.Context) {
|
||||
|
||||
36
server/service/hid/hid.go
Normal file
36
server/service/hid/hid.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package hid
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
hid *Hid
|
||||
hidOnce sync.Once
|
||||
)
|
||||
|
||||
type Hid struct {
|
||||
g0 *os.File
|
||||
g1 *os.File
|
||||
g2 *os.File
|
||||
kbMutex sync.Mutex
|
||||
mouseMutex sync.Mutex
|
||||
}
|
||||
|
||||
func (h *Hid) Lock() {
|
||||
h.kbMutex.Lock()
|
||||
h.mouseMutex.Lock()
|
||||
}
|
||||
|
||||
func (h *Hid) Unlock() {
|
||||
h.kbMutex.Unlock()
|
||||
h.mouseMutex.Unlock()
|
||||
}
|
||||
|
||||
func GetHid() *Hid {
|
||||
hidOnce.Do(func() {
|
||||
hid = &Hid{}
|
||||
})
|
||||
return hid
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package hid
|
||||
|
||||
func Keyboard(queue <-chan []int) {
|
||||
func (h *Hid) Keyboard(queue <-chan []int) {
|
||||
for event := range queue {
|
||||
writeKeyboard(event)
|
||||
h.kbMutex.Lock()
|
||||
h.writeKeyboard(event)
|
||||
h.kbMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func writeKeyboard(event []int) {
|
||||
func (h *Hid) writeKeyboard(event []int) {
|
||||
var data []byte
|
||||
|
||||
if event[0] > 0 {
|
||||
@@ -17,28 +19,28 @@ func writeKeyboard(event []int) {
|
||||
data = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
}
|
||||
|
||||
Write(Hidg0, data)
|
||||
h.Write(h.g0, data)
|
||||
}
|
||||
|
||||
func getModifier(event []int) byte {
|
||||
var modifier byte = 0x00
|
||||
|
||||
if event[1] == 1 {
|
||||
modifier = modifier | ModifierLCtrl
|
||||
modifier |= ModifierLCtrl
|
||||
}
|
||||
|
||||
if event[2] == 1 {
|
||||
modifier = modifier | ModifierLShift
|
||||
modifier |= ModifierLShift
|
||||
}
|
||||
|
||||
if event[3] == 1 {
|
||||
modifier = modifier | ModifierLAlt
|
||||
modifier |= ModifierLAlt
|
||||
} else if event[3] == 2 {
|
||||
modifier = modifier | ModifierRAlt
|
||||
modifier |= ModifierRAlt
|
||||
}
|
||||
|
||||
if event[4] == 1 {
|
||||
modifier = modifier | ModifierLGUI
|
||||
modifier |= ModifierLGUI
|
||||
}
|
||||
|
||||
return modifier
|
||||
|
||||
@@ -3,31 +3,34 @@ package hid
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Mouse(queue <-chan []int) {
|
||||
func (h *Hid) Mouse(queue <-chan []int) {
|
||||
for event := range queue {
|
||||
h.mouseMutex.Lock()
|
||||
switch event[0] {
|
||||
case MouseDown:
|
||||
mouseDown(event)
|
||||
h.mouseDown(event)
|
||||
case MouseUp:
|
||||
mouseUp()
|
||||
h.mouseUp()
|
||||
case MouseMoveAbsolute:
|
||||
mouseMoveAbsolute(event)
|
||||
h.mouseMoveAbsolute(event)
|
||||
case MouseMoveRelative:
|
||||
mouseMoveRelative(event)
|
||||
h.mouseMoveRelative(event)
|
||||
case MouseScroll:
|
||||
scroll(event)
|
||||
h.scroll(event)
|
||||
default:
|
||||
log.Debugf("invalid mouse event: %+v", event)
|
||||
}
|
||||
h.mouseMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func mouseDown(event []int) {
|
||||
func (h *Hid) mouseDown(event []int) {
|
||||
var button byte
|
||||
|
||||
switch event[1] {
|
||||
@@ -43,52 +46,52 @@ func mouseDown(event []int) {
|
||||
}
|
||||
|
||||
data := []byte{button, 0, 0, 0}
|
||||
writeWithTimeout(Hidg1, data)
|
||||
h.writeWithTimeout(h.g1, data)
|
||||
}
|
||||
|
||||
func mouseUp() {
|
||||
func (h *Hid) mouseUp() {
|
||||
data := []byte{0, 0, 0, 0}
|
||||
writeWithTimeout(Hidg1, data)
|
||||
h.writeWithTimeout(h.g1, data)
|
||||
}
|
||||
|
||||
func scroll(event []int) {
|
||||
func (h *Hid) scroll(event []int) {
|
||||
direction := 0x01
|
||||
if event[3] > 0 {
|
||||
direction = -0x1
|
||||
}
|
||||
|
||||
data := []byte{0, 0, 0, byte(direction)}
|
||||
writeWithTimeout(Hidg1, data)
|
||||
h.writeWithTimeout(h.g1, data)
|
||||
}
|
||||
|
||||
func mouseMoveAbsolute(event []int) {
|
||||
func (h *Hid) mouseMoveAbsolute(event []int) {
|
||||
x := make([]byte, 2)
|
||||
y := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(x, uint16(event[2]))
|
||||
binary.LittleEndian.PutUint16(y, uint16(event[3]))
|
||||
|
||||
data := []byte{0, x[0], x[1], y[0], y[1], 0}
|
||||
writeWithTimeout(Hidg2, data)
|
||||
|
||||
h.writeWithTimeout(h.g2, data)
|
||||
}
|
||||
|
||||
func mouseMoveRelative(event []int) {
|
||||
func (h *Hid) mouseMoveRelative(event []int) {
|
||||
data := []byte{byte(event[1]), byte(event[2]), byte(event[3]), 0}
|
||||
writeWithTimeout(Hidg1, data)
|
||||
h.writeWithTimeout(h.g1, data)
|
||||
}
|
||||
|
||||
func writeWithTimeout(file *os.File, data []byte) {
|
||||
func (h *Hid) writeWithTimeout(file *os.File, data []byte) {
|
||||
deadline := time.Now().Add(9 * time.Millisecond)
|
||||
_ = file.SetWriteDeadline(deadline)
|
||||
|
||||
_, err := file.Write(data)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrClosed) {
|
||||
Open()
|
||||
switch {
|
||||
case errors.Is(err, os.ErrClosed):
|
||||
log.Debugf("hid already closed, reopen it...")
|
||||
} else if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
h.Open()
|
||||
case errors.Is(err, os.ErrDeadlineExceeded):
|
||||
log.Debugf("write to hid timeout")
|
||||
} else {
|
||||
default:
|
||||
log.Errorf("write to hid failed: %s", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,39 +2,44 @@ package hid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
Hidg0 *os.File
|
||||
Hidg1 *os.File
|
||||
Hidg2 *os.File
|
||||
)
|
||||
|
||||
func Open() {
|
||||
Close()
|
||||
|
||||
func (h *Hid) OpenNoLock() {
|
||||
var err error
|
||||
h.CloseNoLock()
|
||||
|
||||
Hidg0, err = os.OpenFile("/dev/hidg0", os.O_WRONLY, 0666)
|
||||
h.g0, err = os.OpenFile("/dev/hidg0", os.O_WRONLY, 0o666)
|
||||
if err != nil {
|
||||
log.Errorf("open /dev/hidg0 failed: %s", err)
|
||||
}
|
||||
|
||||
Hidg1, err = os.OpenFile("/dev/hidg1", os.O_WRONLY, 0666)
|
||||
h.g1, err = os.OpenFile("/dev/hidg1", os.O_WRONLY, 0o666)
|
||||
if err != nil {
|
||||
log.Errorf("open /dev/hidg1 failed: %s", err)
|
||||
}
|
||||
|
||||
Hidg2, err = os.OpenFile("/dev/hidg2", os.O_WRONLY, 0666)
|
||||
h.g2, err = os.OpenFile("/dev/hidg2", os.O_WRONLY, 0o666)
|
||||
if err != nil {
|
||||
log.Errorf("open /dev/hidg2 failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Close() {
|
||||
for _, file := range []*os.File{Hidg0, Hidg1, Hidg2} {
|
||||
func (h *Hid) Open() {
|
||||
h.kbMutex.Lock()
|
||||
defer h.kbMutex.Unlock()
|
||||
h.mouseMutex.Lock()
|
||||
defer h.mouseMutex.Unlock()
|
||||
|
||||
h.CloseNoLock()
|
||||
|
||||
h.OpenNoLock()
|
||||
}
|
||||
|
||||
func (h *Hid) CloseNoLock() {
|
||||
for _, file := range []*os.File{h.g0, h.g1, h.g2} {
|
||||
if file != nil {
|
||||
_ = file.Sync()
|
||||
_ = file.Close()
|
||||
@@ -42,12 +47,21 @@ func Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func Write(file *os.File, data []byte) {
|
||||
func (h *Hid) Close() {
|
||||
h.kbMutex.Lock()
|
||||
defer h.kbMutex.Unlock()
|
||||
h.mouseMutex.Lock()
|
||||
defer h.mouseMutex.Unlock()
|
||||
|
||||
h.CloseNoLock()
|
||||
}
|
||||
|
||||
func (h *Hid) Write(file *os.File, data []byte) {
|
||||
_, err := file.Write(data)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrClosed) {
|
||||
Open()
|
||||
log.Debugf("hid already closed, reopen it...")
|
||||
h.Open()
|
||||
} else {
|
||||
log.Errorf("write to hid failed: %s", err)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package hid
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"time"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
)
|
||||
|
||||
type Char struct {
|
||||
@@ -30,6 +32,7 @@ func (s *Service) Paste(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
s.hid.kbMutex.Lock()
|
||||
for _, char := range req.Content {
|
||||
key, ok := charMap[char]
|
||||
if !ok {
|
||||
@@ -43,18 +46,19 @@ func (s *Service) Paste(c *gin.Context) {
|
||||
// only handle shift. Need to handle all modifiers?
|
||||
if key.Modifiers > 0 {
|
||||
keyShift := []byte{0x00, 0x00, byte(225), 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
Write(Hidg0, keyShift)
|
||||
s.hid.Write(s.hid.g0, keyShift)
|
||||
}
|
||||
|
||||
Write(Hidg0, keyDown)
|
||||
Write(Hidg0, keyUp)
|
||||
s.hid.Write(s.hid.g0, keyDown)
|
||||
s.hid.Write(s.hid.g0, keyUp)
|
||||
|
||||
if key.Modifiers > 0 {
|
||||
Write(Hidg0, keyUp)
|
||||
s.hid.Write(s.hid.g0, keyUp)
|
||||
}
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
s.hid.kbMutex.Unlock()
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("hid paste success, total %d", len(req.Content))
|
||||
|
||||
@@ -1,29 +1,75 @@
|
||||
package hid
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os/exec"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
)
|
||||
|
||||
func (s *Service) Reset(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
s.hid.kbMutex.Lock()
|
||||
defer s.hid.kbMutex.Unlock()
|
||||
|
||||
// reset USB
|
||||
commands := []string{
|
||||
"echo > /sys/kernel/config/usb_gadget/g0/UDC",
|
||||
"ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC",
|
||||
f, err := os.Open("/sys/kernel/config/usb_gadget/g0/UDC")
|
||||
if err != nil {
|
||||
log.Errorf("open /sys/kernel/config/usb_gadget/g0/UDC failed: %s", err)
|
||||
rsp.ErrRsp(c, -1, "open usb gadget file failed")
|
||||
return
|
||||
}
|
||||
err = f.Truncate(0)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
log.Errorf("truncate /sys/kernel/config/usb_gadget/g0/UDC failed: %s", err)
|
||||
rsp.ErrRsp(c, -1, "truncate usb gadget file failed")
|
||||
return
|
||||
}
|
||||
_, err = f.Seek(0, 0)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
log.Errorf("seek to 0 failed: %s", err)
|
||||
rsp.ErrRsp(c, -1, "seek to 0 in usb gadget file failed")
|
||||
return
|
||||
}
|
||||
_, err = f.WriteString("\n")
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
log.Errorf("write to /sys/kernel/config/usb_gadget/g0/UDC failed: %s", err)
|
||||
rsp.ErrRsp(c, -1, "write to usb gadget file failed")
|
||||
return
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
devices, err := os.ReadDir("/sys/class/udc/")
|
||||
if err != nil {
|
||||
log.Errorf("read udc directory failed: %s", err)
|
||||
rsp.ErrRsp(c, -1, "read udc directory failed")
|
||||
return
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
err := exec.Command("sh", "-c", command).Run()
|
||||
f, err = os.Open("/sys/kernel/config/usb_gadget/g0/UDC")
|
||||
if err != nil {
|
||||
log.Errorf("open /sys/kernel/config/usb_gadget/g0/UDC failed: %s", err)
|
||||
rsp.ErrRsp(c, -1, "open usb gadget file failed")
|
||||
return
|
||||
}
|
||||
for _, device := range devices {
|
||||
_, err = f.WriteString(device.Name() + "\n")
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -2, "execute command failed")
|
||||
_ = f.Close()
|
||||
log.Errorf("write to /sys/kernel/config/usb_gadget/g0/UDC failed: %s", err)
|
||||
rsp.ErrRsp(c, -1, "write to usb gadget file failed")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_ = f.Close()
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("reset hid success")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package hid
|
||||
|
||||
type Service struct{}
|
||||
type Service struct {
|
||||
hid *Hid
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{}
|
||||
return &Service{
|
||||
hid: GetHid(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -15,13 +11,41 @@ import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
TailscalePath = "/usr/bin/tailscale"
|
||||
TailscaledPath = "/usr/sbin/tailscaled"
|
||||
tailscalePath = "/usr/bin/tailscale"
|
||||
tailscaledPath = "/usr/sbin/tailscaled"
|
||||
|
||||
backendStateRunning = "Running"
|
||||
backendStateNeedsLogin = "NeedsLogin"
|
||||
backendStateStopped = "Stopped"
|
||||
|
||||
responseStateRunning = "running"
|
||||
responseStateNeedsLogin = "notLogin"
|
||||
responseStateStopped = "stopped"
|
||||
responseStateNotInstalled = "notInstall"
|
||||
)
|
||||
|
||||
var backendToResponseStates = map[string]string{
|
||||
backendStateRunning: responseStateRunning,
|
||||
backendStateNeedsLogin: responseStateNeedsLogin,
|
||||
backendStateStopped: responseStateStopped,
|
||||
}
|
||||
|
||||
func stateToJson(state string) string {
|
||||
if resp, ok := backendToResponseStates[state]; ok {
|
||||
return resp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type TailscaleStatus struct {
|
||||
BackendState string `json:"BackendState"`
|
||||
|
||||
@@ -43,48 +67,52 @@ func (s *Service) InstallTailscale(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
const (
|
||||
downloadUrl = "http://cdn.sipeed.com/nanokvm/resources/tailscale_riscv64.zip"
|
||||
workspace = "/root/.tailscale"
|
||||
)
|
||||
|
||||
var (
|
||||
zipFile = fmt.Sprintf("%s/tailscale_riscv64.zip", workspace)
|
||||
tailscale = fmt.Sprintf("%s/tailscale_riscv64/tailscale", workspace)
|
||||
tailscaled = fmt.Sprintf("%s/tailscale_riscv64/tailscaled", workspace)
|
||||
)
|
||||
|
||||
// download
|
||||
_ = os.MkdirAll(workspace, 0755)
|
||||
defer exec.Command("sh", "-c", fmt.Sprintf("rm -rf %s", workspace)).Run()
|
||||
_ = os.MkdirAll(workspace, 0o755)
|
||||
defer func() {
|
||||
_ = os.RemoveAll(workspace)
|
||||
}()
|
||||
|
||||
req, err := http.NewRequest("GET", downloadUrl, nil)
|
||||
if err != nil {
|
||||
log.Errorf("new request err: %s", err)
|
||||
log.Errorf("failed to create new request: %s", err)
|
||||
rsp.ErrRsp(c, -1, "request failed")
|
||||
return
|
||||
}
|
||||
|
||||
err = utils.Download(req, zipFile)
|
||||
if err != nil {
|
||||
log.Errorf("download failed: %s", err)
|
||||
rsp.ErrRsp(c, -2, "download failed")
|
||||
return
|
||||
}
|
||||
|
||||
// install
|
||||
command := fmt.Sprintf("unzip %s -d %s", zipFile, workspace)
|
||||
err = exec.Command("sh", "-c", command).Run()
|
||||
err = utils.Unzip(zipFile, workspace)
|
||||
if err != nil {
|
||||
log.Errorf("unzip failed: %s", err)
|
||||
rsp.ErrRsp(c, -3, "unzip failed")
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Rename(tailscale, TailscalePath)
|
||||
err = utils.MoveFile(tailscale, tailscalePath)
|
||||
if err != nil {
|
||||
log.Debugf("rename %s failed: %s", tailscale, err)
|
||||
log.Errorf("rename %s failed: %s", tailscale, err)
|
||||
}
|
||||
err = os.Rename(tailscaled, TailscaledPath)
|
||||
err = utils.MoveFile(tailscaled, tailscaledPath)
|
||||
if err != nil {
|
||||
log.Debugf("rename %s failed: %s", tailscaled, err)
|
||||
log.Errorf("rename %s failed: %s", tailscaled, err)
|
||||
}
|
||||
|
||||
_ = runTailscale()
|
||||
@@ -107,9 +135,9 @@ func (s *Service) LoginTailscale(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if status.BackendState == "Running" {
|
||||
if status.BackendState == backendStateRunning {
|
||||
rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{
|
||||
Status: "running",
|
||||
Status: responseStateRunning,
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -121,13 +149,17 @@ func (s *Service) LoginTailscale(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
defer stderr.Close()
|
||||
defer func() {
|
||||
_ = stderr.Close()
|
||||
}()
|
||||
|
||||
go cmd.Run()
|
||||
go func() {
|
||||
_ = cmd.Run()
|
||||
}()
|
||||
|
||||
url := parseLoginUrl(stderr)
|
||||
rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{
|
||||
Status: "notLogin",
|
||||
Status: responseStateNeedsLogin,
|
||||
Url: url,
|
||||
})
|
||||
log.Debugf("tailscale login url: %s", url)
|
||||
@@ -152,26 +184,20 @@ func (s *Service) GetTailscaleStatus(c *gin.Context) {
|
||||
var data proto.GetTailscaleStatusRsp
|
||||
|
||||
if exist := isTailscaleExist(); !exist {
|
||||
data.Status = "notInstall"
|
||||
data.Status = responseStateNotInstalled
|
||||
rsp.OkRspWithData(c, data)
|
||||
return
|
||||
}
|
||||
|
||||
status, err := getTailscaleStatus()
|
||||
if err != nil {
|
||||
data.Status = "notLogin"
|
||||
data.Status = responseStateNeedsLogin
|
||||
rsp.OkRspWithData(c, data)
|
||||
return
|
||||
}
|
||||
|
||||
switch status.BackendState {
|
||||
case "NeedsLogin":
|
||||
data.Status = "notLogin"
|
||||
case "Running":
|
||||
data.Status = "running"
|
||||
case "Stopped":
|
||||
data.Status = "stopped"
|
||||
default:
|
||||
data.Status = stateToJson(status.BackendState)
|
||||
if data.Status == "" {
|
||||
rsp.ErrRsp(c, -1, "unknown state")
|
||||
return
|
||||
}
|
||||
@@ -215,11 +241,12 @@ func (s *Service) UpdateTailscaleStatus(c *gin.Context) {
|
||||
|
||||
data := &proto.UpdateTailscaleStatusRsp{}
|
||||
|
||||
if status.BackendState == "Running" {
|
||||
data.Status = "running"
|
||||
} else if status.BackendState == "Stopped" {
|
||||
data.Status = "stopped"
|
||||
} else {
|
||||
switch status.BackendState {
|
||||
case backendStateRunning:
|
||||
data.Status = responseStateRunning
|
||||
case backendStateStopped:
|
||||
data.Status = responseStateStopped
|
||||
default:
|
||||
rsp.ErrRsp(c, -4, "unknown tailscale status")
|
||||
return
|
||||
}
|
||||
@@ -229,15 +256,15 @@ func (s *Service) UpdateTailscaleStatus(c *gin.Context) {
|
||||
}
|
||||
|
||||
func isTailscaleExist() bool {
|
||||
_, err1 := os.Stat(TailscalePath)
|
||||
_, err2 := os.Stat(TailscaledPath)
|
||||
_, err1 := os.Stat(tailscalePath)
|
||||
_, err2 := os.Stat(tailscaledPath)
|
||||
|
||||
return err1 == nil && err2 == nil
|
||||
}
|
||||
|
||||
func runTailscale() error {
|
||||
for _, filePath := range []string{TailscalePath, TailscaledPath} {
|
||||
if err := utils.EnsurePermission(filePath, 0100); err != nil {
|
||||
for _, filePath := range []string{tailscalePath, tailscaledPath} {
|
||||
if err := utils.EnsurePermission(filePath, 0o100); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -288,7 +315,7 @@ func parseLoginUrl(r io.Reader) string {
|
||||
}
|
||||
|
||||
if strings.Contains(line, "https") {
|
||||
reg := regexp.MustCompile("\\s+")
|
||||
reg := regexp.MustCompile(`\s+`)
|
||||
return reg.ReplaceAllString(line, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -83,7 +85,7 @@ func (s *Service) DeleteMac(c *gin.Context) {
|
||||
}
|
||||
|
||||
data := strings.Join(newMacs, "\n")
|
||||
err = os.WriteFile(WolHistory, []byte(data), 0644)
|
||||
err = os.WriteFile(WolHistory, []byte(data), 0o644)
|
||||
if err != nil {
|
||||
log.Errorf("write %s failed: %s", WolHistory, err)
|
||||
rsp.ErrRsp(c, -3, "write failed")
|
||||
@@ -99,18 +101,20 @@ func saveMac(mac string) {
|
||||
return
|
||||
}
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(WolHistory), 0644)
|
||||
err := os.MkdirAll(filepath.Dir(WolHistory), 0o644)
|
||||
if err != nil {
|
||||
log.Errorf("create dir failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(WolHistory, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
file, err := os.OpenFile(WolHistory, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
log.Errorf("open %s failed: %s", WolHistory, err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
content := fmt.Sprintf("%s\n", mac)
|
||||
_, err = file.WriteString(content)
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
ImageDirectory = "/data"
|
||||
ImageNone = "/dev/mmcblk0p3"
|
||||
MountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file"
|
||||
imageDirectory = "/data"
|
||||
imageNone = "/dev/mmcblk0p3"
|
||||
mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file"
|
||||
)
|
||||
|
||||
func (s *Service) GetImages(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
var images []string
|
||||
|
||||
err := filepath.Walk(ImageDirectory, func(path string, info os.FileInfo, err error) error {
|
||||
err := filepath.Walk(imageDirectory, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -34,7 +36,6 @@ func (s *Service) GetImages(c *gin.Context) {
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -2, "get images failed")
|
||||
return
|
||||
@@ -57,11 +58,11 @@ func (s *Service) MountImage(c *gin.Context) {
|
||||
|
||||
image := req.File
|
||||
if image == "" {
|
||||
image = ImageNone
|
||||
image = imageNone
|
||||
}
|
||||
|
||||
// mount
|
||||
if err := os.WriteFile(MountDevice, []byte(image), 0666); err != nil {
|
||||
if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil {
|
||||
log.Errorf("mount file %s failed: %s", image, err)
|
||||
rsp.ErrRsp(c, -2, "mount image failed")
|
||||
return
|
||||
@@ -88,14 +89,14 @@ func (s *Service) MountImage(c *gin.Context) {
|
||||
func (s *Service) GetMountedImage(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
content, err := os.ReadFile(MountDevice)
|
||||
content, err := os.ReadFile(mountDevice)
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -2, "read failed")
|
||||
return
|
||||
}
|
||||
|
||||
image := strings.ReplaceAll(string(content), "\n", "")
|
||||
if image == ImageNone {
|
||||
if image == imageNone {
|
||||
image = ""
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package stream
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
FrameDetect = "/etc/kvm/frame_detact"
|
||||
FrameDetectTmp = "/etc/kvm/frame_detact_tmp"
|
||||
frameDetect = "/etc/kvm/frame_detact"
|
||||
frameDetectTmp = "/etc/kvm/frame_detact_tmp"
|
||||
)
|
||||
|
||||
type UpdateFrameDetectRsp struct {
|
||||
@@ -43,15 +45,17 @@ func (s *Service) UpdateFrameDetect(c *gin.Context) {
|
||||
}
|
||||
|
||||
if isEnabled {
|
||||
_ = os.Remove(FrameDetect)
|
||||
_ = os.Remove(FrameDetectTmp)
|
||||
_ = os.Remove(frameDetect)
|
||||
_ = os.Remove(frameDetectTmp)
|
||||
} else {
|
||||
file, err2 := os.OpenFile(FrameDetect, os.O_CREATE|os.O_TRUNC, 0644)
|
||||
file, err2 := os.OpenFile(frameDetect, os.O_CREATE|os.O_TRUNC, 0o644)
|
||||
if err2 != nil {
|
||||
rsp.ErrRsp(c, -3, "enable frame detect failed")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
isEnabled, err = isFrameDetectEnabled()
|
||||
@@ -69,7 +73,7 @@ func (s *Service) UpdateFrameDetect(c *gin.Context) {
|
||||
func (s *Service) StopFrameDetect(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
exist, err := isFileExist(FrameDetect)
|
||||
exist, err := isFileExist(frameDetect)
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -1, "unknown frame status")
|
||||
return
|
||||
@@ -80,7 +84,7 @@ func (s *Service) StopFrameDetect(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Rename(FrameDetect, FrameDetectTmp)
|
||||
err = os.Rename(frameDetect, frameDetectTmp)
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -2, "stop operation failed")
|
||||
return
|
||||
@@ -88,7 +92,7 @@ func (s *Service) StopFrameDetect(c *gin.Context) {
|
||||
|
||||
go func() {
|
||||
time.Sleep(20 * time.Second)
|
||||
_ = os.Rename(FrameDetectTmp, FrameDetect)
|
||||
_ = os.Rename(frameDetectTmp, frameDetect)
|
||||
log.Debugf("frame detect started")
|
||||
}()
|
||||
|
||||
@@ -97,7 +101,7 @@ func (s *Service) StopFrameDetect(c *gin.Context) {
|
||||
}
|
||||
|
||||
func isFrameDetectEnabled() (bool, error) {
|
||||
exist, err := isFileExist(FrameDetect)
|
||||
exist, err := isFileExist(frameDetect)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -106,7 +110,7 @@ func isFrameDetectEnabled() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
exist, err = isFileExist(FrameDetectTmp)
|
||||
exist, err = isFileExist(frameDetectTmp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package stream
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (s *Service) Mjpeg(c *gin.Context) {
|
||||
@@ -14,7 +15,9 @@ func (s *Service) Mjpeg(c *gin.Context) {
|
||||
c.String(http.StatusInternalServerError, "Failed to connect to MJPEG server")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.String(resp.StatusCode, "MJPEG server returned an error")
|
||||
@@ -23,5 +26,9 @@ func (s *Service) Mjpeg(c *gin.Context) {
|
||||
|
||||
c.Header("Content-Type", resp.Header.Get("Content-Type"))
|
||||
|
||||
_, _ = io.Copy(c.Writer, resp.Body)
|
||||
_, err = io.Copy(c.Writer, resp.Body)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Failed to copy MJPEG stream")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
HW_VERSION_FILE = "/etc/kvm/hw"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
GPIO_PWR = "/sys/class/gpio/gpio503/value"
|
||||
GPIO_PWR_LED = "/sys/class/gpio/gpio504/value"
|
||||
|
||||
GPIO_RST_ALPHA = "/sys/class/gpio/gpio507/value"
|
||||
GPIO_HDD_LED_ALPHA = "/sys/class/gpio/gpio505/value"
|
||||
|
||||
GPIO_RST_BETA = "/sys/class/gpio/gpio505/value"
|
||||
"NanoKVM-Server/config"
|
||||
"NanoKVM-Server/proto"
|
||||
)
|
||||
|
||||
func (s *Service) SetGpio(c *gin.Context) {
|
||||
@@ -27,23 +18,19 @@ func (s *Service) SetGpio(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
if err := proto.ParseFormRequest(c, &req); err != nil {
|
||||
rsp.ErrRsp(c, -1, "invalid arguments")
|
||||
rsp.ErrRsp(c, -1, fmt.Sprintf("invalid arguments: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
version := getHwVersion()
|
||||
device := ""
|
||||
|
||||
if req.Type == "power" {
|
||||
device = GPIO_PWR
|
||||
} else if req.Type == "reset" {
|
||||
if version == "alpha" {
|
||||
device = GPIO_RST_ALPHA
|
||||
} else {
|
||||
device = GPIO_RST_BETA
|
||||
}
|
||||
} else {
|
||||
rsp.ErrRsp(c, -2, "invalid power event")
|
||||
switch req.Type {
|
||||
case "power":
|
||||
device = s.config.HW.GPIOPower
|
||||
case "reset":
|
||||
device = s.config.HW.GPIOReset
|
||||
default:
|
||||
rsp.ErrRsp(c, -2, fmt.Sprintf("invalid power event: %s", req.Type))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -55,27 +42,30 @@ func (s *Service) SetGpio(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err := writeGpio(device, duration); err != nil {
|
||||
rsp.ErrRsp(c, -3, "operation failed")
|
||||
rsp.ErrRsp(c, -3, fmt.Sprintf("operation failed: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("set gpio %s success", device)
|
||||
log.Debugf("gpio %s set sucessfully", device)
|
||||
rsp.OkRsp(c)
|
||||
}
|
||||
|
||||
func (s *Service) GetGpio(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
pwr, err := readGpio(GPIO_PWR_LED)
|
||||
pwr, err := readGpio(s.config.HW.GPIOPowerLED)
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -2, "read led failed")
|
||||
rsp.ErrRsp(c, -2, fmt.Sprintf("failed to read power led: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
hdd := false
|
||||
version := getHwVersion()
|
||||
if version == "alpha" {
|
||||
hdd, err = readGpio(GPIO_HDD_LED_ALPHA)
|
||||
if s.config.HW.Version == config.HWVersionAlpha {
|
||||
hdd, err = readGpio(s.config.HW.GPIOHDDLed)
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -2, fmt.Sprintf("failed to read hdd led: %s", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
data := &proto.GetGpioRsp{
|
||||
@@ -85,28 +75,15 @@ func (s *Service) GetGpio(c *gin.Context) {
|
||||
rsp.OkRspWithData(c, data)
|
||||
}
|
||||
|
||||
// get hardware version: alpha or beta
|
||||
func getHwVersion() string {
|
||||
content, err := os.ReadFile(HW_VERSION_FILE)
|
||||
if err == nil {
|
||||
version := strings.ReplaceAll(string(content), "\n", "")
|
||||
if version == "beta" {
|
||||
return "beta"
|
||||
}
|
||||
}
|
||||
|
||||
return "alpha"
|
||||
}
|
||||
|
||||
func writeGpio(device string, duration time.Duration) error {
|
||||
if err := os.WriteFile(device, []byte("1"), 0666); err != nil {
|
||||
if err := os.WriteFile(device, []byte("1"), 0o666); err != nil {
|
||||
log.Errorf("write gpio %s failed: %s", device, err)
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
|
||||
if err := os.WriteFile(device, []byte("0"), 0666); err != nil {
|
||||
if err := os.WriteFile(device, []byte("0"), 0o666); err != nil {
|
||||
log.Errorf("write gpio %s failed: %s", device, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
)
|
||||
|
||||
var imageVersionMap = map[string]string{
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
)
|
||||
|
||||
var screenFileMap = map[string]string{
|
||||
@@ -30,7 +32,7 @@ func (s *Service) SetScreen(c *gin.Context) {
|
||||
}
|
||||
|
||||
data := fmt.Sprintf("%d", req.Value)
|
||||
err := os.WriteFile(file, []byte(data), 0666)
|
||||
err := os.WriteFile(file, []byte(data), 0o666)
|
||||
if err != nil {
|
||||
log.Errorf("write kvm %s failed: %s", file, err)
|
||||
rsp.ErrRsp(c, -3, "update screen failed")
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
)
|
||||
|
||||
const ScriptDirectory = "/etc/kvm/scripts"
|
||||
@@ -29,7 +31,6 @@ func (s *Service) GetScripts(c *gin.Context) {
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("get scripts failed: %s", err)
|
||||
rsp.ErrRsp(c, -1, "get scripts failed")
|
||||
@@ -58,7 +59,7 @@ func (s *Service) UploadScript(c *gin.Context) {
|
||||
}
|
||||
|
||||
if _, err = os.Stat(ScriptDirectory); err != nil {
|
||||
_ = os.MkdirAll(ScriptDirectory, 0755)
|
||||
_ = os.MkdirAll(ScriptDirectory, 0o755)
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("%s/%s", ScriptDirectory, header.Filename)
|
||||
@@ -68,7 +69,7 @@ func (s *Service) UploadScript(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
_ = utils.EnsurePermission(target, 0100)
|
||||
_ = utils.EnsurePermission(target, 0o100)
|
||||
|
||||
data := &proto.UploadScriptRsp{
|
||||
File: header.Filename,
|
||||
@@ -103,7 +104,12 @@ func (s *Service) RunScript(c *gin.Context) {
|
||||
} else {
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
go cmd.Run()
|
||||
go func() {
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Errorf("run script %s in background failed: %s", req.Name, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
package vm
|
||||
|
||||
type Service struct{}
|
||||
import (
|
||||
"NanoKVM-Server/config"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{}
|
||||
return &Service{
|
||||
config: config.GetInstance(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/utils"
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"NanoKVM-Server/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -161,10 +163,12 @@ func (s *SshClient) bridgeWSAndSSH() {
|
||||
|
||||
s.client, err = ssh.Dial("tcp", s.addr, config)
|
||||
if err != nil {
|
||||
//log.Errorf("init ssh failed: %s", err)
|
||||
// log.Errorf("init ssh failed: %s", err)
|
||||
return
|
||||
}
|
||||
defer s.client.Close()
|
||||
defer func() {
|
||||
_ = s.client.Close()
|
||||
}()
|
||||
|
||||
s.session, err = s.client.NewSession()
|
||||
if err != nil {
|
||||
@@ -185,7 +189,9 @@ func (s *SshClient) bridgeWSAndSSH() {
|
||||
log.Errorf("open ssh session in failed: %s", err)
|
||||
return
|
||||
}
|
||||
defer s.sessionIn.Close()
|
||||
defer func() {
|
||||
_ = s.sessionIn.Close()
|
||||
}()
|
||||
|
||||
if err = s.session.RequestPty("xterm", size.Height, size.Width, terminalModes); err != nil {
|
||||
log.Errorf("oen session request pty failed: %s", err)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/service/hid"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/service/hid"
|
||||
)
|
||||
|
||||
const (
|
||||
VirtualNetwork = "/boot/usb.rndis0"
|
||||
VirtualDisk = "/boot/usb.disk0"
|
||||
virtualNetwork = "/boot/usb.rndis0"
|
||||
virtualDisk = "/boot/usb.disk0"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,8 +48,8 @@ var (
|
||||
func (s *Service) GetVirtualDevice(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
network, _ := isDeviceExist(VirtualNetwork)
|
||||
disk, _ := isDeviceExist(VirtualDisk)
|
||||
network, _ := isDeviceExist(virtualNetwork)
|
||||
disk, _ := isDeviceExist(virtualDisk)
|
||||
|
||||
rsp.OkRspWithData(c, &proto.GetVirtualDeviceRsp{
|
||||
Network: network,
|
||||
@@ -68,8 +70,9 @@ func (s *Service) UpdateVirtualDevice(c *gin.Context) {
|
||||
var device string
|
||||
var commands []string
|
||||
|
||||
if req.Device == "network" {
|
||||
device = VirtualNetwork
|
||||
switch req.Device {
|
||||
case "network":
|
||||
device = virtualNetwork
|
||||
|
||||
exist, _ := isDeviceExist(device)
|
||||
if !exist {
|
||||
@@ -77,8 +80,8 @@ func (s *Service) UpdateVirtualDevice(c *gin.Context) {
|
||||
} else {
|
||||
commands = unmountNetworkCommands
|
||||
}
|
||||
} else if req.Device == "disk" {
|
||||
device = VirtualDisk
|
||||
case "disk":
|
||||
device = virtualDisk
|
||||
|
||||
exist, _ := isDeviceExist(device)
|
||||
if !exist {
|
||||
@@ -86,13 +89,18 @@ func (s *Service) UpdateVirtualDevice(c *gin.Context) {
|
||||
} else {
|
||||
commands = unmountDiskCommands
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
rsp.ErrRsp(c, -2, "invalid arguments")
|
||||
return
|
||||
}
|
||||
|
||||
hid.Close()
|
||||
defer hid.Open()
|
||||
h := hid.GetHid()
|
||||
h.Lock()
|
||||
h.CloseNoLock()
|
||||
defer func() {
|
||||
h.OpenNoLock()
|
||||
h.Unlock()
|
||||
}()
|
||||
|
||||
for _, command := range commands {
|
||||
err := exec.Command("sh", "-c", command).Run()
|
||||
|
||||
@@ -2,11 +2,12 @@ package ws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/service/hid"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"NanoKVM-Server/service/hid"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -17,6 +19,7 @@ const (
|
||||
|
||||
type WsClient struct {
|
||||
conn *websocket.Conn
|
||||
hid *hid.Hid
|
||||
keyboard chan []int
|
||||
mouse chan []int
|
||||
watcher chan struct{}
|
||||
@@ -39,6 +42,7 @@ func (s *Service) Connect(c *gin.Context) {
|
||||
log.Debug("websocket connected")
|
||||
|
||||
client := &WsClient{
|
||||
hid: hid.GetHid(),
|
||||
conn: conn,
|
||||
keyboard: make(chan []int, 200),
|
||||
mouse: make(chan []int, 200),
|
||||
@@ -51,10 +55,10 @@ func (s *Service) Connect(c *gin.Context) {
|
||||
func (c *WsClient) Start() {
|
||||
defer c.Clean()
|
||||
|
||||
hid.Open()
|
||||
c.hid.Open()
|
||||
|
||||
go hid.Keyboard(c.keyboard)
|
||||
go hid.Mouse(c.mouse)
|
||||
go c.hid.Keyboard(c.keyboard)
|
||||
go c.hid.Mouse(c.mouse)
|
||||
|
||||
go c.Watch()
|
||||
|
||||
@@ -97,7 +101,7 @@ func (c *WsClient) Watch() {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
var fileModMap = map[string]time.Time{
|
||||
fileModMap := map[string]time.Time{
|
||||
StreamState: time.Unix(0, 0),
|
||||
}
|
||||
|
||||
@@ -133,7 +137,7 @@ func (c *WsClient) Clean() {
|
||||
|
||||
close(c.watcher)
|
||||
|
||||
hid.Close()
|
||||
c.hid.Close()
|
||||
|
||||
log.Debug("websocket disconnected")
|
||||
}
|
||||
|
||||
23
server/utils/chmod.go
Normal file
23
server/utils/chmod.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func ChmodRecursively(path string, mode uint32) error {
|
||||
return filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
err = os.Chmod(path, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/mervick/aes-everywhere/go/aes256"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const EncryptSecretKey = "nanokvm-sipeed-2024"
|
||||
|
||||
@@ -2,34 +2,48 @@ package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Download(req *http.Request, target string) error {
|
||||
out, err := os.OpenFile(target, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
log.Debugf("downloading %s to %s", req.URL.String(), target)
|
||||
err := os.MkdirAll(filepath.Dir(target), 0o755)
|
||||
if err != nil {
|
||||
log.Errorf("create file %s err: %s", target, err)
|
||||
log.Errorf("create dir %s err: %s", filepath.Dir(target), err)
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
out, err := os.OpenFile(target, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755)
|
||||
if err != nil {
|
||||
log.Errorf("cannot create file '%s', error: %s", target, err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = out.Close()
|
||||
}()
|
||||
|
||||
resp, err := (&http.Client{}).Do(req)
|
||||
if err != nil {
|
||||
log.Errorf("download file err: %s", err)
|
||||
log.Errorf("request error: %s", err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New("request error")
|
||||
log.Errorf("request failed, status code: %d", resp.StatusCode)
|
||||
return errors.New("update website is inaccessible right now")
|
||||
}
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if contentType != "application/octet-stream" && contentType != "application/zip" {
|
||||
return errors.New("download error")
|
||||
log.Debugf("unexpected content-type, it should be either octet-stream or zip, but got: %s", contentType)
|
||||
return errors.New("unsupported content type")
|
||||
}
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
|
||||
78
server/utils/move-file.go
Normal file
78
server/utils/move-file.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MoveFile(src, dst string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
err := os.Rename(src, dst)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "invalid cross-device link") {
|
||||
return MoveFileCrossFS(src, dst)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MoveFileCrossFS(src, dst string) error {
|
||||
tmp := dst + ".tmp"
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpFile, err := os.Create(tmp)
|
||||
if err != nil {
|
||||
_ = srcFile.Close()
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(tmpFile, srcFile)
|
||||
if err != nil {
|
||||
_ = srcFile.Close()
|
||||
_ = tmpFile.Close()
|
||||
return err
|
||||
}
|
||||
_ = srcFile.Close()
|
||||
_ = tmpFile.Close()
|
||||
fi, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(tmp, fi.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.Remove(src)
|
||||
err = os.Rename(tmp, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MoveFilesRecursively(src, dst string) error {
|
||||
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileName := strings.Replace(path, src, "", 1)
|
||||
dstName := dst + fileName
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
return os.MkdirAll(dstName, fileInfo.Mode())
|
||||
}
|
||||
return MoveFile(path, dstName)
|
||||
})
|
||||
}
|
||||
61
server/utils/unzip.go
Normal file
61
server/utils/unzip.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func Unzip(filename string, dest string) error {
|
||||
r, err := zip.OpenReader(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = r.Close()
|
||||
}()
|
||||
|
||||
for _, f := range r.File {
|
||||
dstPath := filepath.Join(dest, filepath.Clean("/"+f.Name))
|
||||
if f.FileInfo().IsDir() {
|
||||
err = os.MkdirAll(dstPath, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = unzipFile(dstPath, f)
|
||||
if err == nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unzipFile(dstPath string, f *zip.File) error {
|
||||
err := os.MkdirAll(filepath.Dir(dstPath), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = out.Close()
|
||||
}()
|
||||
|
||||
archivedFile, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = io.Copy(out, archivedFile); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.Chmod(dstPath, f.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export function update() {
|
||||
return http.request({
|
||||
method: 'post',
|
||||
url: '/api/application/update',
|
||||
timeout: 5 * 60 * 1000
|
||||
timeout: 15 * 60 * 1000
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user