Refactor: Rename NanoKVM to BatchuKVM and update server URL

This commit is contained in:
2025-12-09 20:35:38 +09:00
commit 8cf674c9e5
396 changed files with 54380 additions and 0 deletions

122
server/config/config.go Normal file
View File

@@ -0,0 +1,122 @@
package config
import (
"bytes"
"errors"
"log"
"os"
"sync"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
var (
instance Config
once sync.Once
)
func GetInstance() *Config {
once.Do(initialize)
return &instance
}
func initialize() {
if err := readByFile(); err != nil {
if errors.As(err, &viper.ConfigFileNotFoundError{}) {
create()
}
if err = readByDefault(); err != nil {
log.Fatalf("Failed to read default configuration!")
}
log.Println("using default configuration")
}
if err := validate(); err != nil {
log.Fatalf("Failed to validate configuration!")
}
if err := viper.Unmarshal(&instance); err != nil {
log.Fatalf("Failed to parse configuration: %s", err)
}
checkDefaultValue()
if instance.Authentication == "disable" {
log.Println("NOTICE: Authentication is disabled! Please ensure your service is secure!")
}
log.Println("config loaded successfully")
}
func readByFile() error {
viper.SetConfigName("server")
viper.SetConfigType("yaml")
viper.AddConfigPath("/etc/kvm/")
return viper.ReadInConfig()
}
func readByDefault() error {
data, err := yaml.Marshal(defaultConfig)
if err != nil {
log.Printf("failed to marshal default config: %s", err)
return err
}
return viper.ReadConfig(bytes.NewBuffer(data))
}
// Create configuration file.
func create() {
var (
file *os.File
data []byte
err error
)
_ = os.MkdirAll("/etc/kvm", 0o644)
file, err = os.OpenFile("/etc/kvm/server.yaml", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
log.Printf("open config failed: %s", err)
return
}
defer func() {
_ = file.Close()
}()
if data, err = yaml.Marshal(defaultConfig); err != nil {
log.Printf("failed to marshal default config: %s", err)
return
}
if _, err = file.Write(data); err != nil {
log.Printf("failed to save config: %s", err)
return
}
if err = file.Sync(); err != nil {
log.Printf("failed to sync config: %s", err)
return
}
log.Println("create file /etc/kvm/server.yaml with default configuration")
}
// Validate the configuration. This is to ensure compatibility with earlier versions.
func validate() error {
if viper.GetInt("port.http") > 0 && viper.GetInt("port.https") > 0 {
return nil
}
_ = os.Remove("/etc/kvm/server.yaml")
log.Println("delete empty configuration file")
create()
return readByDefault()
}

50
server/config/default.go Normal file
View File

@@ -0,0 +1,50 @@
package config
var defaultConfig = &Config{
Proto: "http",
Port: Port{
Http: 80,
Https: 443,
},
Cert: Cert{
Crt: "server.crt",
Key: "server.key",
},
Logger: Logger{
Level: "info",
File: "stdout",
},
JWT: JWT{
SecretKey: "",
RefreshTokenDuration: 2678400,
RevokeTokensOnLogout: true,
},
Stun: "stun.l.google.com:19302",
Turn: Turn{
TurnAddr: "",
TurnUser: "",
TurnCred: "",
},
Authentication: "enable",
}
func checkDefaultValue() {
if instance.JWT.SecretKey == "" {
instance.JWT.SecretKey = generateRandomSecretKey()
instance.JWT.RevokeTokensOnLogout = true
}
if instance.JWT.RefreshTokenDuration == 0 {
instance.JWT.RefreshTokenDuration = 2678400
}
if instance.Stun == "" {
instance.Stun = "stun.l.google.com:19302"
}
if instance.Authentication == "" {
instance.Authentication = "enable"
}
instance.Hardware = getHardware()
}

45
server/config/file.go Normal file
View File

@@ -0,0 +1,45 @@
package config
import (
"os"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
const ConfigurationFile = "/etc/kvm/server.yaml"
func Read() (*Config, error) {
data, err := os.ReadFile(ConfigurationFile)
if err != nil {
log.Errorf("failed to read config: %v", err)
return nil, err
}
var conf Config
if err := yaml.Unmarshal(data, &conf); err != nil {
log.Fatalf("failed to unmarshal config: %v", err)
return nil, err
}
log.Debugf("read %s successfully", ConfigurationFile)
return &conf, nil
}
func Write(conf *Config) error {
data, err := yaml.Marshal(&conf)
if err != nil {
log.Errorf("failed to marshal config: %v", err)
return err
}
err = os.WriteFile(ConfigurationFile, data, 0644)
if err != nil {
log.Errorf("failed to write config: %v", err)
return err
}
log.Debugf("write to %s successfully", ConfigurationFile)
return nil
}

95
server/config/hardware.go Normal file
View File

@@ -0,0 +1,95 @@
package config
import (
"os"
"strings"
log "github.com/sirupsen/logrus"
)
type HWVersion int
const (
HWVersionAlpha HWVersion = iota
HWVersionBeta
HWVersionPcie
HWVersionFile = "/etc/kvm/hw"
)
var HWAlpha = Hardware{
Version: HWVersionAlpha,
GPIOReset: "/sys/class/gpio/gpio507/value",
GPIOPower: "/sys/class/gpio/gpio503/value",
GPIOPowerLED: "/sys/class/gpio/gpio504/value",
GPIOHDDLed: "/sys/class/gpio/gpio505/value",
}
var HWBeta = Hardware{
Version: HWVersionBeta,
GPIOReset: "/sys/class/gpio/gpio505/value",
GPIOPower: "/sys/class/gpio/gpio503/value",
GPIOPowerLED: "/sys/class/gpio/gpio504/value",
GPIOHDDLed: "",
}
var HWPcie = Hardware{
Version: HWVersionPcie,
GPIOReset: "/sys/class/gpio/gpio505/value",
GPIOPower: "/sys/class/gpio/gpio503/value",
GPIOPowerLED: "/sys/class/gpio/gpio504/value",
GPIOHDDLed: "",
}
func (h HWVersion) String() string {
switch h {
case HWVersionAlpha:
return "Alpha"
case HWVersionBeta:
return "Beta"
case HWVersionPcie:
return "PCIE"
default:
return "Unknown"
}
}
func GetHwVersion() HWVersion {
content, err := os.ReadFile(HWVersionFile)
if err != nil {
return HWVersionAlpha
}
version := strings.ReplaceAll(string(content), "\n", "")
switch version {
case "alpha":
return HWVersionAlpha
case "beta":
return HWVersionBeta
case "pcie":
return HWVersionPcie
default:
return HWVersionAlpha
}
}
func getHardware() (h Hardware) {
version := GetHwVersion()
switch version {
case HWVersionAlpha:
h = HWAlpha
case HWVersionBeta:
h = HWBeta
case HWVersionPcie:
h = HWPcie
default:
h = HWAlpha
log.Errorf("Unsupported hardware version: %s", version)
}
return
}

28
server/config/jwt.go Normal file
View File

@@ -0,0 +1,28 @@
package config
import (
"crypto/rand"
"encoding/base64"
"fmt"
"time"
)
// RegenerateSecretKey regenerate secret key when logout
func RegenerateSecretKey() {
if instance.JWT.RevokeTokensOnLogout {
instance.JWT.SecretKey = generateRandomSecretKey()
}
}
// Generate random string for secret key.
func generateRandomSecretKey() string {
b := make([]byte, 64)
_, err := rand.Read(b)
if err != nil {
currentTime := time.Now().UnixNano()
timeString := fmt.Sprintf("%d", currentTime)
return fmt.Sprintf("%064s", timeString)
}
return base64.URLEncoding.EncodeToString(b)
}

49
server/config/types.go Normal file
View File

@@ -0,0 +1,49 @@
package config
type Config struct {
Proto string `yaml:"proto"`
Port Port `yaml:"port"`
Cert Cert `yaml:"cert"`
Logger Logger `yaml:"logger"`
Authentication string `yaml:"authentication"`
JWT JWT `yaml:"jwt"`
Stun string `yaml:"stun"`
Turn Turn `yaml:"turn"`
Hardware Hardware `yaml:"-"`
}
type Logger struct {
Level string `yaml:"level"`
File string `yaml:"file"`
}
type Port struct {
Http int `yaml:"http"`
Https int `yaml:"https"`
}
type Cert struct {
Crt string `yaml:"crt"`
Key string `yaml:"key"`
}
type JWT struct {
SecretKey string `yaml:"secretKey"`
RefreshTokenDuration uint64 `yaml:"refreshTokenDuration"`
RevokeTokensOnLogout bool `yaml:"revokeTokensOnLogout"`
}
type Turn struct {
TurnAddr string `yaml:"turnAddr"`
TurnUser string `yaml:"turnUser"`
TurnCred string `yaml:"turnCred"`
}
type Hardware struct {
Version HWVersion `yaml:"-"`
GPIOReset string `yaml:"-"`
GPIOPower string `yaml:"-"`
GPIOPowerLED string `yaml:"-"`
GPIOHDDLed string `yaml:"-"`
}