Files
yggdrasil-go/main.go
2022-02-22 18:04:33 +08:00

193 lines
5.9 KiB
Go

/*
* Copyright (C) 2022. Gardel <sunxinao@hotmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"embed"
"encoding/pem"
"errors"
"github.com/gin-gonic/gin"
"gopkg.in/ini.v1"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"io/fs"
"io/ioutil"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"yggdrasil-go/model"
"yggdrasil-go/router"
"yggdrasil-go/util"
)
//go:embed assets/*
var f embed.FS
type MetaCfg struct {
ServerName string `ini:"server_name"`
ImplementationName string `ini:"implementation_name"`
ImplementationVersion string `ini:"implementation_version"`
SkinDomains []string `ini:"skin_domains"`
SkinRootUrl string `ini:"skin_root_url"`
}
func main() {
configFilePath := "config.ini"
cfg, err := ini.LooseLoad(configFilePath)
if err != nil {
log.Fatal("无法读取配置文件", err)
}
meta := MetaCfg{
ServerName: "A Mojang Yggdrasil Server",
ImplementationName: "go-yggdrasil-server",
ImplementationVersion: "v0.1",
SkinDomains: []string{".example.com", "localhost"},
SkinRootUrl: "http://localhost:8080",
}
err = cfg.Section("meta").MapTo(&meta)
if err != nil {
log.Fatal("无法读取配置文件", err)
}
pathSection := cfg.Section("paths")
privateKeyPath := pathSection.Key("private_key_file").MustString("private.pem")
publicKeyPath := pathSection.Key("public_key_file").MustString("public.pem")
databasePath := pathSection.Key("database_file").MustString("sqlite.db")
address := cfg.Section("server").Key("server_address").MustString(":8080")
_, err = os.Stat(configFilePath)
if err != nil && os.IsNotExist(err) {
log.Println("配置文件不存在,已使用默认配置")
_ = cfg.Section("meta").ReflectFrom(&meta)
err = cfg.SaveToIndent(configFilePath, " ")
if err != nil {
log.Println("警告: 无法保存配置文件", err)
}
}
checkRsaKeyFile(privateKeyPath, publicKeyPath)
publicKeyContent, err := ioutil.ReadFile(publicKeyPath)
if err != nil {
log.Fatal("无法读取公钥内容", err)
}
db, err := gorm.Open(sqlite.Open("file:"+databasePath+"?cache=shared"), &gorm.Config{
SkipDefaultTransaction: true,
})
if err != nil {
log.Fatal("无法连接数据库", err)
}
err = db.AutoMigrate(&model.User{}, &model.Texture{})
if err != nil {
log.Fatal("无法导入数据库", err)
}
serverMeta := router.ServerMeta{}
serverMeta.Meta.ServerName = meta.ServerName
serverMeta.Meta.ImplementationName = meta.ImplementationName
serverMeta.Meta.ImplementationVersion = meta.ImplementationVersion
serverMeta.Meta.FeatureNoMojangNamespace = true
serverMeta.Meta.Links.Homepage = meta.SkinRootUrl + "/profile/user.html"
serverMeta.Meta.Links.Register = meta.SkinRootUrl + "/profile/index.html"
serverMeta.SkinDomains = meta.SkinDomains
serverMeta.SignaturePublickey = string(publicKeyContent)
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
router.InitRouters(r, db, &serverMeta, meta.SkinRootUrl)
assetsFs, err := fs.Sub(f, "assets")
if err != nil {
log.Fatal(err)
}
r.StaticFS("/profile", http.FS(assetsFs))
srv := &http.Server{
Addr: address,
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
log.Printf("listen: %s\n", err)
}
}()
log.Printf("已启动, 地址: %s\n", srv.Addr)
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("关闭...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("强制关闭:", err)
}
log.Println("退出")
}
func checkRsaKeyFile(privateKeyPath string, publicKeyPath string) {
_, err := os.Stat(privateKeyPath)
if err != nil && os.IsNotExist(err) {
privatePem, err := os.OpenFile(privateKeyPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalln("无法创建私钥文件", err)
}
defer privatePem.Close()
publicPem, err := os.OpenFile(publicKeyPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Fatalln("无法创建公钥文件", err)
}
defer publicPem.Close()
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
util.PrivateKey = privateKey
if err != nil {
log.Fatalln("无法生成 RSA 密钥", err)
}
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
log.Fatalln("无法序列化 RSA 密钥", err)
}
err = pem.Encode(privatePem, &pem.Block{
Type: "PRIVATE",
Bytes: privateKeyBytes,
})
if err != nil {
log.Fatalln("无法写入私钥文件", err)
}
publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
err = pem.Encode(publicPem, &pem.Block{
Type: "PUBLIC",
Bytes: publicKeyBytes,
})
if err != nil {
log.Fatalln("无法写入公钥文件", err)
}
} else if err != nil {
log.Fatalln("无法打开私钥文件", err)
} else {
pemContent, err := os.ReadFile(privateKeyPath)
if err != nil {
log.Fatalln("无法打开私钥文件", err)
}
pemBlock, _ := pem.Decode(pemContent)
privateKeyI, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
if err != nil {
log.Fatalln("无法解析私钥文件", err)
}
util.PrivateKey = privateKeyI.(*rsa.PrivateKey)
}
}