Files
MirageServer/controller/cockpit.go
2023-09-01 12:34:05 +08:00

1012 lines
26 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controller
import (
"bytes"
"crypto/rand"
"embed"
"encoding/json"
"fmt"
"html/template"
"io/fs"
"net"
"net/http"
"net/netip"
"strings"
"time"
"github.com/go-webauthn/webauthn/protocol"
webauthn "github.com/go-webauthn/webauthn/webauthn"
"github.com/gorilla/mux"
"github.com/patrickmn/go-cache"
"github.com/robfig/cron/v3"
"github.com/rs/zerolog/log"
"go4.org/netipx"
"golang.org/x/sync/errgroup"
"gorm.io/gorm"
"tailscale.com/types/key"
"tailscale.com/version"
)
type Cockpit struct {
App *Mirage
db *gorm.DB
Addr string
serviceState bool
CtrlChn chan CtrlMsg
MsgChn chan CtrlMsg
// hasAdmin bool
author *webauthn.WebAuthn
superAdmin *MirageSuperAdmin
authCache *cache.Cache
BuildCron *cron.Cron
}
type CtrlMsg struct {
Msg string
Err error
SysCfg *Config
}
type MirageSuperAdmin struct {
cred AdminCredential
}
func (user *MirageSuperAdmin) WebAuthnID() []byte {
return []byte("MirageSuperAdmin")
}
func (user *MirageSuperAdmin) WebAuthnName() string {
return "MirageSuperAdmin"
}
func (user *MirageSuperAdmin) WebAuthnDisplayName() string {
return "蜃境超级管理员"
}
func (user *MirageSuperAdmin) WebAuthnIcon() string {
return ""
}
func (user *MirageSuperAdmin) WebAuthnCredentials() []webauthn.Credential {
return []webauthn.Credential{webauthn.Credential(user.cred)}
}
// NewCockpit 创建一个新的Cockpit实例
// @sysAddr 监听地址
// @ctrlChn 对外控制通道
// @db 数据库实例
// @return Cockpit实例
// @return 错误信息如果没有错误则为nil
func NewCockpit(sysAddr string, ctrlChn, msgChn chan CtrlMsg, db *gorm.DB) (*Cockpit, error) {
cockpit := &Cockpit{
db: db,
Addr: sysAddr,
serviceState: false,
CtrlChn: ctrlChn,
MsgChn: msgChn,
BuildCron: cron.New(),
}
cockpit.authCache = cache.New(0, 0)
// cockpit.superAdmin, cockpit.hasAdmin = cockpit.GetAdmin()
cockpit.superAdmin = cockpit.GetSuperAdmin()
cockpit.BuildCron.AddFunc("CRON_TZ=Asia/Shanghai 00 02 * * *", cockpit.BuildLinuxClient)
return cockpit, nil
}
// isAdminSet 检查是否已经设置了管理员凭证
// @return 如果已经设置了管理员凭证则返回true否则返回false
/* TODEL
func (c *Cockpit) GetAdmin() (*MirageSuperAdmin, bool) {
if sysadmin := c.GetSysAdmin(); sysadmin != nil {
return &MirageSuperAdmin{
cred: sysCfg.AdminCredential,
}, true
}
return nil, false
}
*/
func (c *Cockpit) GetSuperAdmin() *MirageSuperAdmin {
sysadmin := []SysAdmin{}
err := c.db.Find(&sysadmin).Error
if err != nil || sysadmin == nil || len(sysadmin) == 0 {
return nil
}
return &MirageSuperAdmin{
cred: sysadmin[0].AdminCredential,
}
}
func (c *Cockpit) GetAdmin() *SysAdmin {
sysadmin := []SysAdmin{}
err := c.db.Find(&sysadmin).Error
if err != nil || sysadmin == nil || len(sysadmin) == 0 {
return nil
}
return &sysadmin[0]
}
func (c *Cockpit) GetSysCfg() *SysConfig {
cfg := []SysConfig{}
err := c.db.Find(&cfg).Error
if err != nil || cfg == nil || len(cfg) == 0 {
return nil
}
if cfg[0].NaviDeployKey == "" {
pri, pub, err := genSSHKeypair()
if err != nil {
log.Fatal().Msg(err.Error())
}
cfg[0].NaviDeployPub = pub
cfg[0].NaviDeployKey = pri
err = c.db.Save(&cfg[0]).Error
if err != nil {
log.Fatal().Msg(err.Error())
}
}
return &cfg[0]
}
//go:embed cockpit_html
var cockpitFS embed.FS
// createRouter 创建路由
// @return 路由实例
func (c *Cockpit) createRouter() *mux.Router {
router := mux.NewRouter()
cockpitDir, err := fs.Sub(cockpitFS, "cockpit_html")
if err != nil {
log.Fatal().Msg(err.Error())
}
cockpit_router := router.PathPrefix("/cockpit").Subrouter()
cockpit_router.Use(c.Auth)
cockpit_router.HandleFunc("/api/regAdmin", c.RegisterAdmin).Methods(http.MethodPost)
cockpit_router.HandleFunc("/api/revokeAdmin", c.RevokeAdmin).Methods(http.MethodPost)
cockpit_router.HandleFunc("/api/login", c.Login).Methods(http.MethodPost)
cockpit_router.HandleFunc("/api/setting/general", c.SetSettingGeneral).Methods(http.MethodPost)
cockpit_router.HandleFunc("/api/service/start", c.DoServiceStart).Methods(http.MethodPost)
cockpit_router.HandleFunc("/api/service/stop", c.DoServiceStop).Methods(http.MethodPost)
cockpit_router.HandleFunc("/api/tenants", c.CAPIPostTenants).Methods(http.MethodPost)
cockpit_router.HandleFunc("/api/publish/{os}", c.CAPIPublishClient).Methods(http.MethodPost)
cockpit_router.HandleFunc("/api/derp/add", c.CAPIAddDERP).Methods(http.MethodPost)
cockpit_router.HandleFunc("/api/logout", c.Logout).Methods(http.MethodGet)
cockpit_router.HandleFunc("/api/service/state", c.GetServiceState).Methods(http.MethodGet)
cockpit_router.HandleFunc("/api/setting/general", c.GetSettingGeneral).Methods(http.MethodGet)
cockpit_router.HandleFunc("/api/tenants", c.CAPIGetTenant).Methods(http.MethodGet)
cockpit_router.HandleFunc("/api/publish", c.GetPublishInfo).Methods(http.MethodGet)
cockpit_router.HandleFunc("/api/derp/query", c.CAPIQueryDERP).Methods(http.MethodGet)
cockpit_router.PathPrefix("/api/derp/{id}").HandlerFunc(c.CAPIDelNaviNode).Methods(http.MethodDelete)
cockpit_router.PathPrefix("").Handler(http.StripPrefix("/cockpit", http.FileServer(http.FS(cockpitDir))))
router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.ErrMessage(w, r, 404, "你迷失在蜃境中了吗?这里什么都没有")
})
return router
}
// Auth 验证用户是否已经登录
func (c *Cockpit) Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/cockpit/assets/") || strings.HasPrefix(r.URL.Path, "/cockpit/imgs/") {
next.ServeHTTP(w, r)
return
}
// If admin credential is not set, allow user to register admin credential
if c.superAdmin == nil {
if r.URL.Path == "/cockpit/api/regAdmin" {
next.ServeHTTP(w, r)
return
}
// If user is not trying to register admin credential, return noadmin error
if strings.Contains(r.URL.Path, "/cockpit/api/") {
c.doAPIResponse(w, "noadmin", nil)
return
}
next.ServeHTTP(w, r) // If user is not trying to access API, allow user to
return
}
if r.URL.Path == "/cockpit/api/login" {
next.ServeHTTP(w, r)
return
}
// If user is not trying to login, check if user is logged in
authCookie, err := r.Cookie("mirage_cockpit_auth")
AuthCode, ok := c.authCache.Get("AuthCode")
if err != nil || !ok || authCookie.Value != AuthCode.(string) {
// If user is not logged in, return unauthorized error
if strings.Contains(r.URL.Path, "/cockpit/api/") {
c.doAPIResponse(w, "unauthorized", nil)
return
}
next.ServeHTTP(w, r) // If user is not trying to access API, allow user to
return
}
next.ServeHTTP(w, r) // If user is logged in, allow user to access API
})
}
// RevokeAdmin 注销超级管理员
func (c *Cockpit) RevokeAdmin(
w http.ResponseWriter,
r *http.Request,
) {
if c.superAdmin == nil {
c.doAPIResponse(w, "超级管理员不存在", nil)
return
}
sysAdmin := c.GetAdmin()
err := c.db.Delete(&sysAdmin).Error
if err != nil {
c.doAPIResponse(w, "解绑超级管理员失败", nil)
return
}
c.superAdmin = nil
c.doAPIResponse(w, "", "ok")
}
// RegisterAdmin 注册超级管理员
func (c *Cockpit) RegisterAdmin(
w http.ResponseWriter,
r *http.Request,
) {
if r.URL.Query().Get("phase") == "response" { // 注册响应
response, err := protocol.ParseCredentialCreationResponseBody(r.Body)
if err != nil {
c.doAPIResponse(w, "解析注册响应失败", nil)
return
}
sessionInt, ok := c.authCache.Get("MirageSuperAdmin")
if !ok || sessionInt == nil {
c.doAPIResponse(w, "注册会话不存在或已过期", nil)
return
}
session := sessionInt.(*webauthn.SessionData)
credential, err := c.author.CreateCredential(c.superAdmin, *session, response)
if err != nil {
c.doAPIResponse(w, "创建超管凭证失败", nil)
return
}
// newSysCfg := c.GetSysCfg()
currentSysadmin := c.GetAdmin()
if currentSysadmin == nil {
newSysCfg := c.GetSysCfg()
if newSysCfg == nil {
dexSecret := c.GenAuthCode()
machineKey := key.NewMachine()
machineKeyStr, err := machineKey.MarshalText()
if err != nil {
c.doAPIResponse(w, "创建机器密钥失败", nil)
return
}
newSysCfg = &SysConfig{
DexSecret: dexSecret,
ServerKey: string(machineKeyStr),
}
c.db.Save(newSysCfg)
newSysCfg = c.GetSysCfg()
if newSysCfg.DexSecret == "" || newSysCfg.ServerKey == "" {
c.doAPIResponse(w, "创建dex认证随机数失败", nil)
return
}
if newSysCfg.ServerKey == "" {
c.doAPIResponse(w, "创建服务器私钥失败", nil)
return
}
}
if newSysCfg.DexSecret == "" {
dexSecret := c.GenAuthCode()
newSysCfg.DexSecret = dexSecret
c.db.Save(newSysCfg)
newSysCfg = c.GetSysCfg()
if newSysCfg.DexSecret == "" {
c.doAPIResponse(w, "创建dex认证随机数失败", nil)
return
}
}
if newSysCfg.ServerKey == "" {
machineKey := key.NewMachine()
machineKeyStr, err := machineKey.MarshalText()
if err != nil {
c.doAPIResponse(w, "创建机器密钥失败", nil)
return
}
newSysCfg.ServerKey = string(machineKeyStr)
c.db.Save(newSysCfg)
newSysCfg = c.GetSysCfg()
if newSysCfg.ServerKey == "" {
c.doAPIResponse(w, "创建服务器私钥失败", nil)
return
}
}
currentSysadmin = &SysAdmin{}
}
currentSysadmin.AdminCredential = AdminCredential(*credential)
c.db.Save(currentSysadmin)
// c.superAdmin, c.hasAdmin = c.GetAdmin()
c.superAdmin = c.GetSuperAdmin()
if c.superAdmin == nil {
c.doAPIResponse(w, "保存超管凭证失败", nil)
return
}
c.doAPIResponse(w, "", "ok")
return
} else { // 注册请求
//if c.author == nil {
wconfig := &webauthn.Config{
RPDisplayName: "蜃境网络", // Display Name for your site
RPID: r.Host, // Generally the FQDN for your site
RPOrigins: r.Header["Origin"], //[]string{"https://" + serverURL}, // The origin URLs allowed for WebAuthn requests
}
webAuthor, err := webauthn.New(wconfig)
if err != nil {
c.doAPIResponse(w, "创建WebAuthn验证器失败", nil)
return
}
c.author = webAuthor
//}
options, webAuthSession, err := c.author.BeginRegistration(c.superAdmin)
c.authCache.Set("MirageSuperAdmin", webAuthSession, 5*time.Minute)
if err != nil {
c.doAPIResponse(w, "启动超管注册失败", nil)
return
}
c.doAPIResponse(w, "", options)
}
}
func (c *Cockpit) Login(
w http.ResponseWriter,
r *http.Request,
) {
if r.URL.Query().Get("phase") == "response" { // 登录响应
response, err := protocol.ParseCredentialRequestResponseBody(r.Body)
if err != nil {
c.doAPIResponse(w, "解析登录响应失败", nil)
return
}
sessionInt, ok := c.authCache.Get("MirageSuperAdmin")
if !ok || sessionInt == nil {
c.doAPIResponse(w, "登录会话不存在或已过期", nil)
return
}
_, err = c.author.ValidateLogin(c.superAdmin, *sessionInt.(*webauthn.SessionData), response)
if err != nil {
c.doAPIResponse(w, "登录验证失败", nil)
return
}
// 登录成功,生成 authCode 并返回给客户端
authCode := c.GenAuthCode()
c.authCache.Set("AuthCode", authCode, 5*time.Hour)
authCookie := &http.Cookie{
Name: "mirage_cockpit_auth",
Value: authCode,
Domain: r.URL.Host,
Path: "/",
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Expires: time.Now().Add(1 * time.Hour),
}
http.SetCookie(w, authCookie)
c.doAPIResponse(w, "", "ok")
} else { // 登录请求
//if c.author == nil {
wconfig := &webauthn.Config{
RPDisplayName: "蜃境网络", // Display Name for your site
RPID: r.Host, // Generally the FQDN for your site
RPOrigins: r.Header["Origin"], //[]string{"https://" + serverURL}, // The origin URLs allowed for WebAuthn requests
}
webAuthor, err := webauthn.New(wconfig)
if err != nil {
c.doAPIResponse(w, "创建WebAuthn验证器失败", nil)
return
}
c.author = webAuthor
//}
options, session, err := c.author.BeginLogin(c.superAdmin)
if err != nil {
c.doAPIResponse(w, "启动超管登录失败", nil)
return
}
c.authCache.Set("MirageSuperAdmin", session, 5*time.Minute)
c.doAPIResponse(w, "", options)
}
}
// Logout 登出
func (c *Cockpit) Logout(
w http.ResponseWriter,
r *http.Request,
) {
c.authCache.Delete("AuthCode")
authCookie := &http.Cookie{
Name: "mirage_cockpit_auth",
Value: "",
Domain: r.URL.Host,
Path: "/",
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
MaxAge: -1,
}
http.SetCookie(w, authCookie)
c.doAPIResponse(w, "", "ok")
}
type svcStateData struct {
ControllerVer string `json:"ctrlver"`
IsRunning bool `json:"isRunning"`
}
// GetServiceState 获取服务状态
func (c *Cockpit) GetServiceState(
w http.ResponseWriter,
r *http.Request,
) {
c.doAPIResponse(w, "", svcStateData{
ControllerVer: version.Long(),
IsRunning: c.serviceState,
})
}
// DoServiceStart 启动服务
func (c *Cockpit) DoServiceStart(
w http.ResponseWriter,
r *http.Request,
) {
if c.serviceState {
c.doAPIResponse(w, "", true)
return
}
if cfg, ok := c.CheckCfgValid(); ok {
c.serviceState = true
c.CtrlChn <- CtrlMsg{
Msg: "start",
SysCfg: cfg,
}
c.doAPIResponse(w, "", true)
return
}
c.doAPIResponse(w, "配置不完整", false)
}
// DoServiceStop 停止服务
func (c *Cockpit) DoServiceStop(
w http.ResponseWriter,
r *http.Request,
) {
if !c.serviceState {
c.doAPIResponse(w, "", true)
return
}
c.serviceState = false
c.CtrlChn <- CtrlMsg{
Msg: "stop",
}
c.doAPIResponse(w, "", false)
}
// CheckCfgValid 检查配置是否有效
func (c *Cockpit) CheckCfgValid() (cfg *Config, ok bool) {
var err error
if sysCfg := c.GetSysCfg(); sysCfg != nil {
cfg, err = sysCfg.toSrvConfig()
if err != nil {
return
}
if cfg.ServerURL == "" || cfg.Addr == "" || cfg.IPPrefixes == nil || cfg.BaseDomain == "" { //|| cfg.DERPURL == "" {
return
}
/*
if cfg.OIDC.Issuer == "" || cfg.OIDC.ClientID == "" || cfg.OIDC.ClientSecret == "" {
return
}
if cfg.IDaaS.App == "" || cfg.IDaaS.ClientID == "" || cfg.IDaaS.ClientKey == "" || cfg.IDaaS.Instance == "" || cfg.IDaaS.OrgID == "" {
return
}
if cfg.SMS.ID == "" || cfg.SMS.Key == "" || cfg.SMS.Sign == "" || cfg.SMS.Template == "" {
return
}
*/
ok = true
}
return
}
// GetConfiguration 获取系统配置
func (c *Cockpit) GetSettingGeneral(
w http.ResponseWriter,
r *http.Request,
) {
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
gCfg := sysCfg.toGeneralCfg()
c.doAPIResponse(w, "", gCfg)
}
// SetConfiguration 设置系统配置
func (c *Cockpit) SetSettingGeneral(
w http.ResponseWriter,
r *http.Request,
) {
reqData := make(map[string]interface{})
json.NewDecoder(r.Body).Decode(&reqData)
reqState, ok := reqData["state"].(string)
if !ok {
c.doAPIResponse(w, "用户请求state解析失败", nil)
return
}
switch reqState {
case "set-mipv4":
mipv4, ok := reqData["mipv4"].(string)
if !ok {
c.doAPIResponse(w, "用户请求mipv4解析失败", nil)
return
}
mipv4Prefix, err := netip.ParsePrefix(mipv4)
if err != nil {
c.doAPIResponse(w, "MIPv4格式有误", nil)
return
}
mipv4Prefix, ok = netipx.RangeOfPrefix(mipv4Prefix).Prefix()
if !ok {
c.doAPIResponse(w, "MIPv4格式有误", nil)
return
}
if !mipv4Prefix.Addr().Is4() {
c.doAPIResponse(w, "MIPv4格式有误", nil)
return
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.Mip4 = IPPrefix(mipv4Prefix)
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-mipv6":
mipv6, ok := reqData["mipv6"].(string)
if !ok {
c.doAPIResponse(w, "用户请求mipv6解析失败", nil)
return
}
mipv6Prefix, err := netip.ParsePrefix(mipv6)
if err != nil {
c.doAPIResponse(w, "MIPv6格式有误", nil)
return
}
mipv6Prefix, ok = netipx.RangeOfPrefix(mipv6Prefix).Prefix()
if !ok {
c.doAPIResponse(w, "MIPv6格式有误", nil)
return
}
if !mipv6Prefix.Addr().Is6() {
c.doAPIResponse(w, "MIPv6格式有误", nil)
return
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.Mip6 = IPPrefix(mipv6Prefix)
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-srvaddr":
srvaddr, ok := reqData["srvaddr"].(string)
if !ok {
c.doAPIResponse(w, "用户请求srvaddr解析失败", nil)
return
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.Addr = srvaddr
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-serverurl":
serverURL, ok := reqData["ServerURL"].(string)
if !ok {
c.doAPIResponse(w, "用户请求ServerURL解析失败", nil)
return
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.ServerURL = serverURL
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-basedomain":
baseDomain, ok := reqData["BaseDomain"].(string)
if !ok {
c.doAPIResponse(w, "用户请求BaseDomain解析失败", nil)
return
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.Basedomain = baseDomain
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
/*
case "set-derpurl":
derpURL, ok := reqData["DERPURL"].(string)
if !ok {
c.doAPIResponse(w, "用户请求DERPURL解析失败", nil)
return
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.DerpUrl = derpURL
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
*/
case "set-routeaccessduemachine":
SubnetAccessDueMachine, ok := reqData["SubnetAccessDueMachine"].(bool)
if !ok {
c.doAPIResponse(w, "用户请求SubnetAccessDueMachine解析失败", nil)
return
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.RouteAccessDueMachine = SubnetAccessDueMachine
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-es":
esURL, ok := reqData["ESURL"].(string)
if !ok {
c.doAPIResponse(w, "用户请求ESURL解析失败", nil)
return
}
esKey, ok := reqData["ESKey"].(string)
if !ok {
c.doAPIResponse(w, "用户请求ESKey解析失败", nil)
return
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.EsUrl = esURL
sysCfg.EsKey = esKey
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-wxscanurl":
wxScanURL, ok := reqData["WXScanURL"].(string)
if !ok {
c.doAPIResponse(w, "用户请求WXScanURL解析失败", nil)
return
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.WXScanURL = wxScanURL
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-sms":
smsCfgInt, ok := reqData["SMS"].(map[string]interface{})
if !ok {
c.doAPIResponse(w, "用户请求SMS解析失败", nil)
return
}
smsCfg := SMSConfig{
ID: smsCfgInt["id"].(string),
Key: smsCfgInt["key"].(string),
Sign: smsCfgInt["sign"].(string),
Template: smsCfgInt["template"].(string),
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.SMSConfig = smsCfg
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-idaas":
idaasCfgInt, ok := reqData["IDaaS"].(map[string]interface{})
if !ok {
c.doAPIResponse(w, "用户请求IDaaS解析失败", nil)
return
}
idaasCfg := ALIConfig{
App: idaasCfgInt["app"].(string),
ClientID: idaasCfgInt["id"].(string),
ClientKey: idaasCfgInt["key"].(string),
Instance: idaasCfgInt["instance"].(string),
OrgID: idaasCfgInt["org"].(string),
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.IdaasConfig = idaasCfg
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-microsoft":
MSInt, ok := reqData["Microsoft"].(map[string]interface{})
if !ok {
c.doAPIResponse(w, "用户请求Microsoft解析失败", nil)
return
}
MSCfg := MicrosoftCfg{
ApplicationID: MSInt["app_id"].(string),
ClientID: MSInt["client_id"].(string),
ClientSecret: MSInt["client_secret"].(string),
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.MicrosoftCfg = MSCfg
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-github":
GHInt, ok := reqData["Github"].(map[string]interface{})
if !ok {
c.doAPIResponse(w, "用户请求Github解析失败", nil)
return
}
GHCfg := GithubCfg{
ClientID: GHInt["client_id"].(string),
ClientSecret: GHInt["client_secret"].(string),
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.GithubCfg = GHCfg
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-google":
GgInt, ok := reqData["Google"].(map[string]interface{})
if !ok {
c.doAPIResponse(w, "用户请求Google解析失败", nil)
return
}
GgCfg := GoogleCfg{
ClientID: GgInt["client_id"].(string),
ClientSecret: GgInt["client_secret"].(string),
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.GoogleCfg = GgCfg
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
case "set-apple":
AppInt, ok := reqData["Apple"].(map[string]interface{})
if !ok {
c.doAPIResponse(w, "用户请求Github解析失败", nil)
return
}
AppCfg := AppleCfg{
ClientID: AppInt["client_id"].(string),
TeamID: AppInt["team_id"].(string),
KeyID: AppInt["key_id"].(string),
PrivateKey: AppInt["private_key"].(string),
}
sysCfg := c.GetSysCfg()
if sysCfg == nil {
c.doAPIResponse(w, "获取系统配置失败", nil)
return
}
sysCfg.AppleCfg = AppCfg
if err := c.db.Save(sysCfg).Error; err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
default:
c.doAPIResponse(w, "用户请求state不存在", nil)
return
}
if c.serviceState {
newCfg, err := c.GetSysCfg().toSrvConfig()
if err != nil {
c.doAPIResponse(w, "更新系统配置失败", nil)
return
}
c.CtrlChn <- CtrlMsg{
Msg: "update-config",
SysCfg: newCfg,
}
}
c.GetSettingGeneral(w, r)
}
// GenAuthCode 生成 authCode
func (c *Cockpit) GenAuthCode() string {
const letterBytes = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, 25)
b[0] = 'm'
b[1] = 'n'
b[2] = '-'
index := make([]byte, 22)
rand.Read(index)
for i := 0; i < 22; i++ {
b[i+3] = letterBytes[index[i]&63]
}
return string(b)
}
// API调用的统一响应发报
// @msg 响应状态成功时data不为nil则忽略自动设置为success否则拼接error-{msg}
// @data 响应数据key值为data的json对象
func (c *Cockpit) doAPIResponse(writer http.ResponseWriter, msg string, data interface{}) {
res := APIResponse{}
if msg == "" {
res.Status = "success"
res.Data = data
} else {
res.Status = "error-" + msg
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
err := json.NewEncoder(writer).Encode(&res)
if err != nil {
log.Error().
Caller().
Err(err).
Msg("Failed to write response")
}
}
// cgao6: 用这个向前端返回错误页面
func (c *Cockpit) ErrMessage(
w http.ResponseWriter,
r *http.Request,
code int,
msg string,
) {
errT := template.Must(template.New("err").Parse(errTemplate))
config := map[string]interface{}{
"ErrCode": code,
"ErrMsg": msg,
}
var payload bytes.Buffer
if err := errT.Execute(&payload, config); err != nil {
log.Error().
Str("handler", "ErrMessage").
Err(err).
Msg("Could not render Error template")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)
_, err := w.Write([]byte("Could not render Error template"))
if err != nil {
log.Error().
Caller().
Err(err).
Msg("Failed to write response")
}
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(code)
_, err := w.Write(payload.Bytes())
if err != nil {
log.Error().
Caller().
Err(err).
Msg("Failed to write response")
}
}
func (c *Cockpit) Run() error {
errorGroup := new(errgroup.Group)
router := c.createRouter()
httpServer := &http.Server{
Addr: c.Addr,
Handler: router,
ReadTimeout: HTTPReadTimeout,
WriteTimeout: 0,
}
httpListener, err := net.Listen("tcp", c.Addr)
if err != nil {
return fmt.Errorf("failed to bind to TCP address: %w", err)
}
errorGroup.Go(func() error { return httpServer.Serve(httpListener) })
// 启动buildCron
c.BuildCron.Start()
msgFunc := func(c *Cockpit) {
for {
msg := <-c.MsgChn
switch msg.Msg {
case "error":
log.Info().Msg("received service fatal error message, should be stopped")
c.serviceState = false
}
}
}
errorGroup.Go(func() error {
msgFunc(c)
return nil
})
if sysCfg, ok := c.CheckCfgValid(); ok {
c.serviceState = true
// 启动定时任务
c.CtrlChn <- CtrlMsg{
Msg: "start",
Err: nil,
SysCfg: sysCfg,
}
}
return errorGroup.Wait()
}