mirror of
https://github.com/InazumaV/Ratte.git
synced 2026-02-04 12:40:12 +00:00
Initial commit
This commit is contained in:
11
conf/acme.go
Normal file
11
conf/acme.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package conf
|
||||
|
||||
type ACME struct {
|
||||
Name string
|
||||
Mode string `json:"CertMode"` // file, http, dns
|
||||
RejectUnknownSni bool `json:"RejectUnknownSni"`
|
||||
Provider string `json:"Provider"` // alidns, cloudflare, gandi, godaddy....
|
||||
Email string `json:"Email"`
|
||||
DNSEnv map[string]string `json:"DNSEnv"`
|
||||
Storage string `json:"Storage"`
|
||||
}
|
||||
7
conf/common.go
Normal file
7
conf/common.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package conf
|
||||
|
||||
import "strings"
|
||||
|
||||
func IsHttpUrl(url string) bool {
|
||||
return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")
|
||||
}
|
||||
75
conf/conf.go
Normal file
75
conf/conf.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
trim "Ratte/common/json"
|
||||
"Ratte/common/watcher"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type Conf struct {
|
||||
// internal fields
|
||||
path string
|
||||
watcherHandle EventHandler
|
||||
errorHandler ErrorHandler
|
||||
configWatcher watcher.Watcher
|
||||
coreDataWatchers map[int]watcher.Watcher
|
||||
|
||||
// config fields
|
||||
Log Log `json:"Log,omitempty"`
|
||||
Watcher Watcher `json:"Watcher,omitempty"`
|
||||
Core []Core `json:"Core,omitempty"`
|
||||
Acme []ACME `json:"Acme,omitempty"`
|
||||
Panel []Panel `json:"Panel,omitempty"`
|
||||
Node []Node `json:"Node,omitempty"`
|
||||
}
|
||||
|
||||
func New(path string) *Conf {
|
||||
return &Conf{
|
||||
path: path,
|
||||
Watcher: Watcher{
|
||||
WatchLocalConfig: true,
|
||||
WatchRemoteConfig: true,
|
||||
},
|
||||
Log: newLog(),
|
||||
Core: make([]Core, 0),
|
||||
Acme: make([]ACME, 0),
|
||||
Panel: make([]Panel, 0),
|
||||
Node: make([]Node, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conf) Load(data []byte) error {
|
||||
if len(data) >= 0 {
|
||||
err := json.Unmarshal(data, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode json error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if IsHttpUrl(c.path) {
|
||||
rsp, err := http.Get(c.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rsp.Body.Close()
|
||||
err = json.NewDecoder(trim.NewTrimNodeReader(rsp.Body)).Decode(&c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode json error: %w", err)
|
||||
}
|
||||
} else {
|
||||
f, err := os.Open(c.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
err = json.NewDecoder(trim.NewTrimNodeReader(f)).Decode(&c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode json error: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
50
conf/conf_test.go
Normal file
50
conf/conf_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConf_Load_Local(t *testing.T) {
|
||||
c := New("./config.json5")
|
||||
err := c.Load(nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(c)
|
||||
}
|
||||
|
||||
func TestConf_Load_Remote(t *testing.T) {
|
||||
c := New("http://127.0.0.1:9000/config.json5")
|
||||
err := c.Load(nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConf_Watch(t *testing.T) {
|
||||
c := New("./config.json5")
|
||||
err := c.Load(nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
t.Log(c)
|
||||
c.SetEventHandler(func(event uint, target ...string) {
|
||||
switch event {
|
||||
case ConfigFileChangedEvent:
|
||||
t.Log("Event:", "ConfigFileChangedEvent", "target:", target)
|
||||
case CoreDataPathChangedEvent:
|
||||
t.Log("Event:", "CoreDataPathChangedEvent", "target:", target)
|
||||
}
|
||||
})
|
||||
c.SetErrorHandler(func(err error) {
|
||||
t.Error(err)
|
||||
})
|
||||
err = c.Watch()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log("press any key to done.")
|
||||
fmt.Scan()
|
||||
}
|
||||
22
conf/core.go
Normal file
22
conf/core.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package conf
|
||||
|
||||
import "github.com/goccy/go-json"
|
||||
|
||||
type Core struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
Path string `json:"Path,omitempty"`
|
||||
DataPath string `json:"DataPath,omitempty"`
|
||||
Config json.RawMessage `json:"Config,omitempty"`
|
||||
}
|
||||
type _core Core
|
||||
|
||||
func (c *Core) UnmarshalJSON(data []byte) error {
|
||||
err := json.Unmarshal(data, (*_core)(c))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(c.Config) == 0 {
|
||||
c.Config = data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
19
conf/log.go
Normal file
19
conf/log.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package conf
|
||||
|
||||
type Log struct {
|
||||
Level string `json:"Level,omitempty"`
|
||||
Output string `json:"Output,omitempty"`
|
||||
MaxBackups int `json:"MaxBackups,omitempty"`
|
||||
MaxSize int `json:"MaxSize,omitempty"`
|
||||
MaxAge int `json:"MaxAge,omitempty"`
|
||||
}
|
||||
|
||||
func newLog() Log {
|
||||
return Log{
|
||||
Level: "info",
|
||||
Output: "",
|
||||
MaxBackups: 3,
|
||||
MaxSize: 100,
|
||||
MaxAge: 28,
|
||||
}
|
||||
}
|
||||
111
conf/node.go
Normal file
111
conf/node.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type rawNodeConfig struct {
|
||||
Name string `json:"Name"`
|
||||
RemoteRaw json.RawMessage `json:"Remote"`
|
||||
OptRaw json.RawMessage `json:"Options"`
|
||||
TriggerRaw json.RawMessage `json:"Trigger"`
|
||||
}
|
||||
|
||||
type Remote struct {
|
||||
APIHost string `json:"ApiHost"`
|
||||
NodeID int `json:"NodeID"`
|
||||
Key string `json:"ApiKey"`
|
||||
NodeType string `json:"NodeType"`
|
||||
Timeout int `json:"Timeout"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Core string `json:"Core"`
|
||||
Panel string `json:"Panel"`
|
||||
Acme string `json:"Acme"`
|
||||
Cert Cert `json:"Cert"`
|
||||
Expand map[string]interface{} `json:"Other"`
|
||||
}
|
||||
|
||||
type Trigger struct {
|
||||
PullNodeCron any `json:"PullNodeCron"`
|
||||
PullUserCron any `json:"PullUserCron"`
|
||||
ReportUserCron any `json:"ReportUserCron"`
|
||||
RenewCertCron any `json:"RenewCertCron"`
|
||||
}
|
||||
|
||||
type Cert struct {
|
||||
Domain string `json:"Domain"`
|
||||
CertPath string `json:"Cert"`
|
||||
KeyPath string `json:"Key"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Name string `json:"Name"`
|
||||
Remote Remote `json:"-"`
|
||||
Trigger Trigger `json:"-"`
|
||||
Options Options `json:"-"`
|
||||
}
|
||||
|
||||
func (n *Node) UnmarshalJSON(data []byte) (err error) {
|
||||
rn := rawNodeConfig{}
|
||||
err = json.Unmarshal(data, &rn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.Remote = Remote{
|
||||
APIHost: "http://127.0.0.1",
|
||||
Timeout: 30,
|
||||
}
|
||||
if len(rn.RemoteRaw) > 0 {
|
||||
err = json.Unmarshal(rn.RemoteRaw, &n.Remote)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = json.Unmarshal(data, &n.Remote)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
n.Options = Options{}
|
||||
if len(rn.OptRaw) > 0 {
|
||||
err = json.Unmarshal(rn.OptRaw, &n.Options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = json.Unmarshal(data, &n.Options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
n.Trigger = Trigger{
|
||||
PullNodeCron: 60,
|
||||
PullUserCron: 60,
|
||||
ReportUserCron: 60,
|
||||
RenewCertCron: "0 2 * * *",
|
||||
}
|
||||
if len(rn.TriggerRaw) > 0 {
|
||||
err = json.Unmarshal(rn.OptRaw, &n.Trigger)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = json.Unmarshal(data, &n.Trigger)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(rn.Name) > 0 {
|
||||
n.Name = rn.Name
|
||||
} else {
|
||||
n.Name = fmt.Sprintf("{T:%s;A:%s;I:%d;}",
|
||||
n.Remote.NodeType,
|
||||
n.Remote.APIHost,
|
||||
n.Remote.NodeID)
|
||||
}
|
||||
return
|
||||
}
|
||||
6
conf/panel.go
Normal file
6
conf/panel.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package conf
|
||||
|
||||
type Panel struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
78
conf/watcher.go
Normal file
78
conf/watcher.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"Ratte/common/watcher"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigFileChangedEvent = 0
|
||||
CoreDataPathChangedEvent = 1
|
||||
)
|
||||
|
||||
type EventHandler func(event uint, target ...string)
|
||||
type ErrorHandler func(err error)
|
||||
|
||||
type Watcher struct {
|
||||
WatchLocalConfig bool `json:"WatchLocalConfig,omitempty"`
|
||||
WatchRemoteConfig bool `json:"WatchRemoteConfig,omitempty"`
|
||||
WatchCoreDataPath bool `json:"WatchCoreDataPath,omitempty"`
|
||||
RemoteInterval uint `json:"Interval,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Conf) SetEventHandler(w EventHandler) {
|
||||
c.watcherHandle = w
|
||||
}
|
||||
|
||||
func (c *Conf) SetErrorHandler(w ErrorHandler) {
|
||||
c.errorHandler = w
|
||||
}
|
||||
|
||||
func (c *Conf) Watch() error {
|
||||
if c.watcherHandle != nil {
|
||||
return errors.New("no watch handler")
|
||||
}
|
||||
if IsHttpUrl(c.path) {
|
||||
if c.Watcher.WatchRemoteConfig {
|
||||
w := watcher.NewHTTPWatcher(c.path, c.Watcher.RemoteInterval)
|
||||
c.configWatcher = w
|
||||
}
|
||||
} else {
|
||||
if !c.Watcher.WatchLocalConfig {
|
||||
w := watcher.NewLocalWatcher(path.Dir(c.path), []string{path.Base(c.path)})
|
||||
c.configWatcher = w
|
||||
}
|
||||
}
|
||||
if c.Watcher.WatchLocalConfig || c.Watcher.WatchRemoteConfig {
|
||||
c.configWatcher.SetErrorHandler(watcher.ErrorHandler(c.errorHandler))
|
||||
c.configWatcher.SetEventHandler(func(_ string) error {
|
||||
c.watcherHandle(ConfigFileChangedEvent)
|
||||
return nil
|
||||
})
|
||||
err := c.configWatcher.Watch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("watch config err:%w", err)
|
||||
}
|
||||
}
|
||||
if !c.Watcher.WatchCoreDataPath {
|
||||
return nil
|
||||
}
|
||||
|
||||
watchers := make(map[int]*watcher.LocalWatcher, len(c.Core))
|
||||
for i, co := range c.Core {
|
||||
w := watcher.NewLocalWatcher(co.DataPath, []string{"*"})
|
||||
w.SetErrorHandler(watcher.ErrorHandler(c.errorHandler))
|
||||
w.SetEventHandler(func(_ string) error {
|
||||
c.watcherHandle(CoreDataPathChangedEvent, c.Core[i].Name)
|
||||
return nil
|
||||
})
|
||||
err := w.Watch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("watch core %s err:%w", co.Name, err)
|
||||
}
|
||||
watchers[i] = w
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user