Refactor: Rename NanoKVM to BatchuKVM and update server URL
This commit is contained in:
100
server/utils/cert.go
Normal file
100
server/utils/cert.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func GenerateCert() error {
|
||||
var (
|
||||
host = "localhost"
|
||||
ipAddress = []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")}
|
||||
validFor = time.Hour * 24 * 365 * 10
|
||||
certFile = "/etc/kvm/server.crt"
|
||||
keyFile = "/etc/kvm/server.key"
|
||||
)
|
||||
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
log.Errorf("failed to generate RSA private key: %v", err)
|
||||
return err
|
||||
}
|
||||
publicKey := &privateKey.PublicKey
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
log.Errorf("failed to generate serial number: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: host,
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(validFor),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: false,
|
||||
DNSNames: []string{host},
|
||||
IPAddresses: ipAddress,
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// generate certificate
|
||||
certOut, err := os.Create(certFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create %s: %v", certFile, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||
log.Errorf("failed to encode %s: %v", certFile, err)
|
||||
return err
|
||||
}
|
||||
|
||||
_ = certOut.Sync()
|
||||
_ = certOut.Close()
|
||||
log.Debugf("%s generated", certFile)
|
||||
|
||||
// generate private key
|
||||
keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) // 权限 0600
|
||||
if err != nil {
|
||||
log.Errorf("failed to create %s: %v", keyFile, err)
|
||||
return err
|
||||
}
|
||||
|
||||
privateBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
||||
if err != nil {
|
||||
log.Errorf("failed to marshal private key: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privateBytes}); err != nil {
|
||||
log.Errorf("failed to encode %s: %v", keyFile, err)
|
||||
return err
|
||||
}
|
||||
|
||||
_ = keyOut.Sync()
|
||||
_ = keyOut.Close()
|
||||
log.Debugf("%s generated", keyFile)
|
||||
|
||||
return nil
|
||||
}
|
||||
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
|
||||
})
|
||||
}
|
||||
30
server/utils/encrypt.go
Normal file
30
server/utils/encrypt.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/mervick/aes-everywhere/go/aes256"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SecretKey is only used to prevent the data from being transmitted in plaintext.
|
||||
const SecretKey = "NanoKVM-KOREA-TestKey-2512092155"
|
||||
|
||||
func Decrypt(ciphertext string) (string, error) {
|
||||
if ciphertext == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
decrypt := aes256.Decrypt(ciphertext, SecretKey)
|
||||
return decrypt, nil
|
||||
}
|
||||
|
||||
func DecodeDecrypt(data string) (string, error) {
|
||||
ciphertext, err := url.QueryUnescape(data)
|
||||
if err != nil {
|
||||
log.Errorf("decode ciphertext failed: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return Decrypt(ciphertext)
|
||||
}
|
||||
38
server/utils/hdmi.go
Normal file
38
server/utils/hdmi.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
HDMIDisableFile = "/etc/kvm/hdmi_disable"
|
||||
)
|
||||
|
||||
func PersistHDMIDisabled() {
|
||||
f, err := os.OpenFile(HDMIDisableFile, os.O_CREATE|os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
log.Error("failed to create hdmi disable file:", err)
|
||||
return
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func PersistHDMIEnabled() {
|
||||
if err := os.Remove(HDMIDisableFile); err != nil {
|
||||
log.Error("failed to remove hdmi disable file:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func IsHdmiDisabled() bool {
|
||||
if _, err := os.Stat(HDMIDisableFile); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false // HDMI is enabled
|
||||
}
|
||||
log.Error("failed to check hdmi disable file:", err)
|
||||
return false // Assume HDMI is enabled on error
|
||||
}
|
||||
return true // HDMI is disabled
|
||||
}
|
||||
56
server/utils/http.go
Normal file
56
server/utils/http.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Download(req *http.Request, target string) error {
|
||||
log.Debugf("downloading %s to %s", req.URL.String(), target)
|
||||
err := os.MkdirAll(filepath.Dir(target), 0o755)
|
||||
if err != nil {
|
||||
log.Errorf("create dir %s err: %s", filepath.Dir(target), err)
|
||||
return err
|
||||
}
|
||||
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("request error: %s", err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
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" && contentType != "application/gzip" {
|
||||
log.Debugf("unexpected content-type, it should be either octet-stream or (g)zip, but got: %s", contentType)
|
||||
return errors.New("unsupported content type")
|
||||
}
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
log.Errorf("download file to %s err: %s", target, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
77
server/utils/memory.go
Normal file
77
server/utils/memory.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const GoMemLimitFile = "/etc/kvm/GOMEMLIMIT"
|
||||
|
||||
func InitGoMemLimit() {
|
||||
if !IsGoMemLimitExist() {
|
||||
return
|
||||
}
|
||||
|
||||
limit, err := GetGoMemLimit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
debug.SetMemoryLimit(limit * 1024 * 1024)
|
||||
log.Debugf("set GOMEMLIMIT to %d MB", limit)
|
||||
}
|
||||
|
||||
func SetGoMemLimit(limit int64) error {
|
||||
memoryLimit := max(limit, 50)
|
||||
debug.SetMemoryLimit(memoryLimit * 1024 * 1024)
|
||||
|
||||
log.Debugf("set GOMEMLIMIT to %d MB", limit)
|
||||
|
||||
data := []byte(fmt.Sprintf("%d", limit))
|
||||
err := os.WriteFile(GoMemLimitFile, data, 0o644)
|
||||
if err != nil {
|
||||
log.Errorf("failed to write GOMEMLIMIT: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetGoMemLimit() (int64, error) {
|
||||
data, err := os.ReadFile(GoMemLimitFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed to read GOMEMLIMIT: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
content := strings.TrimSpace(string(data))
|
||||
limit, err := strconv.ParseInt(content, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("failed to parse GOMEMLIMIT: %s", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return limit, nil
|
||||
}
|
||||
|
||||
func DelGoMemLimit() error {
|
||||
debug.SetMemoryLimit(1024 * 1024 * 1024)
|
||||
|
||||
err := os.Remove(GoMemLimitFile)
|
||||
if err != nil {
|
||||
log.Errorf("failed to delete GOMEMLIMIT: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsGoMemLimitExist() bool {
|
||||
_, err := os.Stat(GoMemLimitFile)
|
||||
return err == nil
|
||||
}
|
||||
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)
|
||||
})
|
||||
}
|
||||
48
server/utils/permission.go
Normal file
48
server/utils/permission.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
func HasPermission(filePath string, perm os.FileMode) (bool, error) {
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
mode := fileInfo.Mode().Perm()
|
||||
if mode&perm == perm {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func AddPermission(filePath string, perm os.FileMode) error {
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mode := fileInfo.Mode() | perm
|
||||
err = os.Chmod(filePath, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsurePermission(filePath string, perm os.FileMode) error {
|
||||
hasPerm, err := HasPermission(filePath, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !hasPerm {
|
||||
err = AddPermission(filePath, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
82
server/utils/untar.go
Normal file
82
server/utils/untar.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func UnTarGz(srcFile string, destDir string) (string, error) {
|
||||
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fr, err := os.Open(srcFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = fr.Close()
|
||||
}()
|
||||
|
||||
gr, err := gzip.NewReader(fr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = gr.Close()
|
||||
}()
|
||||
|
||||
tr := tar.NewReader(gr)
|
||||
|
||||
targetFile := ""
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if targetFile == "" {
|
||||
parts := strings.Split(header.Name, "/")
|
||||
if len(parts) > 0 {
|
||||
targetFile = filepath.Join(destDir, parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
filename := filepath.Join(destDir, header.Name)
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(filename, os.FileMode(header.Mode)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
case tar.TypeReg:
|
||||
file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(file, tr); err != nil {
|
||||
_ = file.Close()
|
||||
return "", err
|
||||
}
|
||||
_ = file.Close()
|
||||
|
||||
case tar.TypeSymlink:
|
||||
if err := os.Symlink(header.Linkname, filename); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetFile, nil
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user