mirror of
https://github.com/InazumaV/Ratte.git
synced 2026-02-04 04:30:09 +00:00
Initial commit
This commit is contained in:
40
acme/acme.go
Normal file
40
acme/acme.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Ratte/conf"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Acme struct {
|
||||||
|
client *lego.Client
|
||||||
|
c *conf.ACME
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAcme(c *conf.ACME) (*Acme, error) {
|
||||||
|
user, err := NewLegoUser(
|
||||||
|
path.Join(c.Storage, "user", fmt.Sprintf("user-%s.json", c.Email)),
|
||||||
|
c.Email)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create user error: %s", err)
|
||||||
|
}
|
||||||
|
lc := lego.NewConfig(user)
|
||||||
|
//c.CADirURL = "http://192.168.99.100:4000/directory"
|
||||||
|
lc.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
client, err := lego.NewClient(lc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l := Acme{
|
||||||
|
client: client,
|
||||||
|
c: c,
|
||||||
|
}
|
||||||
|
err = l.SetProvider()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("set provider error: %s", err)
|
||||||
|
}
|
||||||
|
return &l, nil
|
||||||
|
}
|
||||||
119
acme/cert.go
Normal file
119
acme/cert.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Ratte/common/file"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkPath(p string) error {
|
||||||
|
if !file.IsExist(path.Dir(p)) {
|
||||||
|
err := os.MkdirAll(path.Dir(p), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create dir error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Acme) SetProvider() error {
|
||||||
|
switch l.c.Provider {
|
||||||
|
case "http":
|
||||||
|
err := l.client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "dns":
|
||||||
|
for k, v := range l.c.DNSEnv {
|
||||||
|
os.Setenv(k, v)
|
||||||
|
}
|
||||||
|
p, err := dns.NewDNSChallengeProviderByName(l.c.Provider)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create dns challenge provider error: %s", err)
|
||||||
|
}
|
||||||
|
err = l.client.Challenge.SetDNS01Provider(p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("set dns provider error: %s", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported provider %s", l.c.Provider)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Acme) CreateCert(certPath, keyPath, domain string) (err error) {
|
||||||
|
if certPath == "" || keyPath == "" {
|
||||||
|
return fmt.Errorf("cert file path or key file path not exist")
|
||||||
|
}
|
||||||
|
if file.IsExist(certPath) && file.IsExist(keyPath) {
|
||||||
|
return l.RenewCert(certPath, keyPath, domain)
|
||||||
|
}
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: []string{domain},
|
||||||
|
Bundle: true,
|
||||||
|
}
|
||||||
|
certificates, err := l.client.Certificate.Obtain(request)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("obtain certificate error: %s", err)
|
||||||
|
}
|
||||||
|
err = l.writeCert(certPath, keyPath, certificates)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Acme) RenewCert(certPath, keyPath, domain string) error {
|
||||||
|
file, err := os.ReadFile(certPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read cert file error: %s", err)
|
||||||
|
}
|
||||||
|
if e, err := l.CheckCert(file); !e {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("check cert error: %s", err)
|
||||||
|
}
|
||||||
|
res, err := l.client.Certificate.Renew(certificate.Resource{
|
||||||
|
Domain: domain,
|
||||||
|
Certificate: file,
|
||||||
|
}, true, false, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = l.writeCert(certPath, keyPath, res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Acme) CheckCert(file []byte) (bool, error) {
|
||||||
|
cert, err := certcrypto.ParsePEMCertificate(file)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
notAfter := int(time.Until(cert.NotAfter).Hours() / 24.0)
|
||||||
|
if notAfter > 30 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
func (l *Acme) writeCert(cert, key string, certificates *certificate.Resource) error {
|
||||||
|
err := checkPath(cert)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("check path error: %s", err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(cert, certificates.Certificate, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = checkPath(key)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("check path error: %s", err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(key, certificates.PrivateKey, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
129
acme/user.go
Normal file
129
acme/user.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Ratte/common/file"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Email string `json:"Email"`
|
||||||
|
Registration *registration.Resource `json:"Registration"`
|
||||||
|
key crypto.PrivateKey
|
||||||
|
KeyEncoded string `json:"Key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetEmail() string {
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
func (u *User) GetRegistration() *registration.Resource {
|
||||||
|
return u.Registration
|
||||||
|
}
|
||||||
|
func (u *User) GetPrivateKey() crypto.PrivateKey {
|
||||||
|
return u.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLegoUser(path string, email string) (*User, error) {
|
||||||
|
var user User
|
||||||
|
if file.IsExist(path) {
|
||||||
|
err := user.Load(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if user.Email != email {
|
||||||
|
user.Registration = nil
|
||||||
|
user.Email = email
|
||||||
|
err := registerUser(&user, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user.Email = email
|
||||||
|
err := registerUser(&user, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerUser(user *User, path string) error {
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("generate key error: %s", err)
|
||||||
|
}
|
||||||
|
user.key = privateKey
|
||||||
|
c := lego.NewConfig(user)
|
||||||
|
client, err := lego.NewClient(c)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create lego client error: %s", err)
|
||||||
|
}
|
||||||
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.Registration = reg
|
||||||
|
err = user.Save(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("save user error: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodePrivate(privKey *ecdsa.PrivateKey) (string, error) {
|
||||||
|
encoded, err := x509.MarshalECPrivateKey(privKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: encoded})
|
||||||
|
return string(pemEncoded), nil
|
||||||
|
}
|
||||||
|
func (u *User) Save(path string) error {
|
||||||
|
err := checkPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("check path error: %s", err)
|
||||||
|
}
|
||||||
|
u.KeyEncoded, _ = EncodePrivate(u.key.(*ecdsa.PrivateKey))
|
||||||
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = json.NewEncoder(f).Encode(u)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal json error: %s", err)
|
||||||
|
}
|
||||||
|
u.KeyEncoded = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) DecodePrivate(pemEncodedPriv string) (*ecdsa.PrivateKey, error) {
|
||||||
|
blockPriv, _ := pem.Decode([]byte(pemEncodedPriv))
|
||||||
|
x509EncodedPriv := blockPriv.Bytes
|
||||||
|
privateKey, err := x509.ParseECPrivateKey(x509EncodedPriv)
|
||||||
|
return privateKey, err
|
||||||
|
}
|
||||||
|
func (u *User) Load(path string) error {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open file error: %s", err)
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(f).Decode(u)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshal json error: %s", err)
|
||||||
|
}
|
||||||
|
u.key, err = u.DecodePrivate(u.KeyEncoded)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decode private key error: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
32
cmd/ratte/ratte.go
Normal file
32
cmd/ratte/ratte.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
pre "github.com/x-cray/logrus-prefixed-formatter"
|
||||||
|
)
|
||||||
|
|
||||||
|
var version string
|
||||||
|
var buildDate string
|
||||||
|
|
||||||
|
var command = &cobra.Command{
|
||||||
|
Use: "Ratte",
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
err := command.Execute()
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("err", err).Error("Execute command failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFormatter(&pre.TextFormatter{
|
||||||
|
TimestampFormat: "01-02 15:04:05",
|
||||||
|
FullTimestamp: true,
|
||||||
|
})
|
||||||
|
log.Info("Ratte")
|
||||||
|
log.Info("Version: ", version)
|
||||||
|
log.Info("Build date: ", buildDate)
|
||||||
|
Execute()
|
||||||
|
}
|
||||||
150
cmd/ratte/run.go
Normal file
150
cmd/ratte/run.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Ratte/acme"
|
||||||
|
"Ratte/conf"
|
||||||
|
"Ratte/handler"
|
||||||
|
"Ratte/trigger"
|
||||||
|
"github.com/Yuzuki616/Ratte-Interface/core"
|
||||||
|
"github.com/Yuzuki616/Ratte-Interface/panel"
|
||||||
|
"github.com/Yuzuki616/Ratte-Interface/plugin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var runCommand = cobra.Command{
|
||||||
|
Use: "server",
|
||||||
|
Short: "Run Ratte",
|
||||||
|
Run: runHandle,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
}
|
||||||
|
var config string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
runCommand.PersistentFlags().
|
||||||
|
StringVarP(&config, "config", "c",
|
||||||
|
"./config.json5", "config file path")
|
||||||
|
command.AddCommand(&runCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cores map[string]*plugin.Client[core.Core]
|
||||||
|
panels map[string]*plugin.Client[panel.Panel]
|
||||||
|
acmes map[string]*acme.Acme
|
||||||
|
handlers []*handler.Handler
|
||||||
|
triggers []*trigger.Trigger
|
||||||
|
)
|
||||||
|
|
||||||
|
func runHandle(_ *cobra.Command, _ []string) {
|
||||||
|
c := conf.New(config)
|
||||||
|
log.WithField("path", config).Info("Load config...")
|
||||||
|
err := c.Load(nil)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatal("Load config failed")
|
||||||
|
}
|
||||||
|
log.WithField("path", config).Info("Loaded.")
|
||||||
|
|
||||||
|
log.Info("Init core plugin...")
|
||||||
|
// new cores
|
||||||
|
cores = make(map[string]*plugin.Client[core.Core], len(c.Core))
|
||||||
|
for _, co := range c.Core {
|
||||||
|
c, err := plugin.NewClient[core.Core](&plugin.Config{
|
||||||
|
Type: plugin.CoreType,
|
||||||
|
Cmd: exec.Command(co.Path),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).WithField("core", co.Name).Fatal("New core failed")
|
||||||
|
}
|
||||||
|
err = c.Caller().Start(co.DataPath, co.Config)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).WithField("core", co.Name).Fatal("Start core failed")
|
||||||
|
}
|
||||||
|
cores[co.Name] = c
|
||||||
|
}
|
||||||
|
log.Info("Done.")
|
||||||
|
|
||||||
|
log.Info("Init panel plugin...")
|
||||||
|
// new panels
|
||||||
|
panels = make(map[string]*plugin.Client[panel.Panel], len(c.Panel))
|
||||||
|
for _, p := range c.Panel {
|
||||||
|
pn, err := plugin.NewClient[panel.Panel](&plugin.Config{
|
||||||
|
Type: plugin.PanelType,
|
||||||
|
Cmd: exec.Command(p.Path),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).WithField("panel", p.Name).Fatal("New panel failed")
|
||||||
|
}
|
||||||
|
panels[p.Name] = pn
|
||||||
|
}
|
||||||
|
log.Info("Done.")
|
||||||
|
|
||||||
|
log.Info("Init acme...")
|
||||||
|
// new acme
|
||||||
|
acmes = make(map[string]*acme.Acme)
|
||||||
|
for _, a := range c.Acme {
|
||||||
|
ac, err := acme.NewAcme(&a)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatal("New acme failed")
|
||||||
|
}
|
||||||
|
acmes[a.Name] = ac
|
||||||
|
}
|
||||||
|
log.Info("Done.")
|
||||||
|
|
||||||
|
log.Info("Starting...")
|
||||||
|
// new node
|
||||||
|
triggers = make([]*trigger.Trigger, 0, len(c.Node))
|
||||||
|
handlers = make([]*handler.Handler, len(c.Node))
|
||||||
|
for _, nd := range c.Node {
|
||||||
|
var co core.Core
|
||||||
|
var pl panel.Panel
|
||||||
|
var ac *acme.Acme
|
||||||
|
if c, ok := cores[nd.Options.Core]; ok {
|
||||||
|
co = c.Caller()
|
||||||
|
} else {
|
||||||
|
log.WithField("core", nd.Options.Core).Fatal("Couldn't find core")
|
||||||
|
}
|
||||||
|
if p, ok := panels[nd.Options.Panel]; ok {
|
||||||
|
pl = p.Caller()
|
||||||
|
} else {
|
||||||
|
log.WithField("panel", nd.Options.Panel).Fatal("Couldn't find panel")
|
||||||
|
}
|
||||||
|
if a, ok := acmes[nd.Options.Acme]; ok {
|
||||||
|
ac = a
|
||||||
|
} else {
|
||||||
|
log.WithField("acme", nd.Options.Acme).Fatal("Couldn't find acme")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := handler.New(co, pl, nd.Name, ac, log.WithFields(
|
||||||
|
map[string]interface{}{
|
||||||
|
"node": nd.Name,
|
||||||
|
"service": "handler",
|
||||||
|
},
|
||||||
|
), &nd.Options)
|
||||||
|
handlers = append(handlers, h)
|
||||||
|
tr, err := trigger.NewTrigger(log.WithFields(
|
||||||
|
map[string]interface{}{
|
||||||
|
"node": nd.Name,
|
||||||
|
"service": "trigger",
|
||||||
|
},
|
||||||
|
), &nd.Trigger, h, pl, &nd.Remote)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatal("New trigger failed")
|
||||||
|
}
|
||||||
|
triggers = append(triggers, tr)
|
||||||
|
err = tr.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatal("Start trigger failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info("Started.")
|
||||||
|
|
||||||
|
runtime.GC()
|
||||||
|
sig := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM)
|
||||||
|
<-sig
|
||||||
|
}
|
||||||
8
common/file/file.go
Normal file
8
common/file/file.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func IsExist(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err == nil || !os.IsNotExist(err)
|
||||||
|
}
|
||||||
112
common/json/trim.go
Normal file
112
common/json/trim.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrimNodeReader struct {
|
||||||
|
r io.Reader
|
||||||
|
br *bytes.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNL(c byte) bool {
|
||||||
|
return c == '\n' || c == '\r'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWS(c byte) bool {
|
||||||
|
return c == ' ' || c == '\t' || isNL(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeComment(s []byte, i int) int {
|
||||||
|
if i < len(s) && s[i] == '/' {
|
||||||
|
s[i-1] = ' '
|
||||||
|
for ; i < len(s) && !isNL(s[i]); i += 1 {
|
||||||
|
s[i] = ' '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i < len(s) && s[i] == '*' {
|
||||||
|
s[i-1] = ' '
|
||||||
|
s[i] = ' '
|
||||||
|
for ; i < len(s); i += 1 {
|
||||||
|
if s[i] != '*' {
|
||||||
|
s[i] = ' '
|
||||||
|
} else {
|
||||||
|
s[i] = ' '
|
||||||
|
i++
|
||||||
|
if i < len(s) {
|
||||||
|
if s[i] == '/' {
|
||||||
|
s[i] = ' '
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func prep(r io.Reader) (s []byte, err error) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
_, err = io.Copy(buf, r)
|
||||||
|
s = buf.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for i < len(s) {
|
||||||
|
switch s[i] {
|
||||||
|
case '"':
|
||||||
|
i += 1
|
||||||
|
for i < len(s) {
|
||||||
|
if s[i] == '"' {
|
||||||
|
i += 1
|
||||||
|
break
|
||||||
|
} else if s[i] == '\\' {
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
case '/':
|
||||||
|
i = consumeComment(s, i+1)
|
||||||
|
case ',':
|
||||||
|
j := i
|
||||||
|
for {
|
||||||
|
i += 1
|
||||||
|
if i >= len(s) {
|
||||||
|
break
|
||||||
|
} else if s[i] == '}' || s[i] == ']' {
|
||||||
|
s[j] = ' '
|
||||||
|
break
|
||||||
|
} else if s[i] == '/' {
|
||||||
|
i = consumeComment(s, i+1)
|
||||||
|
} else if !isWS(s[i]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read acts as a proxy for the underlying reader and cleans p
|
||||||
|
// of comments and trailing commas preceeding ] and }
|
||||||
|
// comments are delimitted by // up until the end the line
|
||||||
|
func (st *TrimNodeReader) Read(p []byte) (n int, err error) {
|
||||||
|
if st.br == nil {
|
||||||
|
var s []byte
|
||||||
|
if s, err = prep(st.r); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st.br = bytes.NewReader(s)
|
||||||
|
}
|
||||||
|
return st.br.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTrimNodeReader New returns an io.Reader acting as proxy to r
|
||||||
|
func NewTrimNodeReader(r io.Reader) io.Reader {
|
||||||
|
return &TrimNodeReader{r: r}
|
||||||
|
}
|
||||||
10
common/maps/maps.go
Normal file
10
common/maps/maps.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package maps
|
||||||
|
|
||||||
|
func Merge[k comparable, v any](m1 map[k]v, m2 ...map[k]v) map[k]v {
|
||||||
|
for _, m2v := range m2 {
|
||||||
|
for k2, v2 := range m2v {
|
||||||
|
m1[k2] = v2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m1
|
||||||
|
}
|
||||||
18
common/slices/slice.go
Normal file
18
common/slices/slice.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package slices
|
||||||
|
|
||||||
|
func Range[t any](sl []t, handle func(i int, v t) (_break bool)) {
|
||||||
|
for i := range sl {
|
||||||
|
b := handle(i, sl[i])
|
||||||
|
if b {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RangeToNew[old, new any](sl []old, handle func(i int, v old) new) []new {
|
||||||
|
ns := make([]new, len(sl))
|
||||||
|
for i := range ns {
|
||||||
|
ns[i] = handle(i, sl[i])
|
||||||
|
}
|
||||||
|
return ns
|
||||||
|
}
|
||||||
75
common/watcher/http.go
Normal file
75
common/watcher/http.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package watcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPWatcher struct {
|
||||||
|
hash [32]byte
|
||||||
|
url string
|
||||||
|
interval uint
|
||||||
|
handler EventHandler
|
||||||
|
errorHandler ErrorHandler
|
||||||
|
close chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPWatcher(url string, interval uint) *HTTPWatcher {
|
||||||
|
return &HTTPWatcher{
|
||||||
|
url: url,
|
||||||
|
interval: interval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *HTTPWatcher) handle() error {
|
||||||
|
rsp, err := http.Get(w.url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("request error: %w", err)
|
||||||
|
}
|
||||||
|
defer rsp.Body.Close()
|
||||||
|
b, err := io.ReadAll(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read body error: %w", err)
|
||||||
|
}
|
||||||
|
h := sha256.Sum256(b)
|
||||||
|
if bytes.Equal(w.hash[:], h[:]) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.hash = h
|
||||||
|
err = w.handler(w.url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("handle error: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *HTTPWatcher) SetEventHandler(handler EventHandler) {
|
||||||
|
w.handler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *HTTPWatcher) SetErrorHandler(handler ErrorHandler) {
|
||||||
|
w.errorHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *HTTPWatcher) Watch() error {
|
||||||
|
go func() {
|
||||||
|
for range time.Tick(time.Duration(w.interval) * time.Second) {
|
||||||
|
select {
|
||||||
|
case <-w.close:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
w.errorHandler(w.handle())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *HTTPWatcher) Close() error {
|
||||||
|
close(w.close)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
85
common/watcher/local.go
Normal file
85
common/watcher/local.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package watcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LocalWatcher struct {
|
||||||
|
dir string
|
||||||
|
filenames []string
|
||||||
|
handler EventHandler
|
||||||
|
errorHandler ErrorHandler
|
||||||
|
watcher *fsnotify.Watcher
|
||||||
|
close chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLocalWatcher(dir string, filenames []string) *LocalWatcher {
|
||||||
|
return &LocalWatcher{
|
||||||
|
dir: dir,
|
||||||
|
filenames: filenames,
|
||||||
|
close: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LocalWatcher) SetEventHandler(handler EventHandler) {
|
||||||
|
w.handler = handler
|
||||||
|
}
|
||||||
|
func (w *LocalWatcher) SetErrorHandler(handler ErrorHandler) {
|
||||||
|
w.errorHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LocalWatcher) handle(e fsnotify.Event) error {
|
||||||
|
if (!e.Has(fsnotify.Write)) && (!e.Has(fsnotify.Create)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
name := path.Base(e.Name)
|
||||||
|
file := ""
|
||||||
|
for _, filename := range w.filenames {
|
||||||
|
ok, _ := path.Match(filename, name)
|
||||||
|
if ok {
|
||||||
|
file = filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(file) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := w.handler(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LocalWatcher) Watch() error {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("new watcher error: %s", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer watcher.Close()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case e := <-watcher.Events:
|
||||||
|
err := w.handle(e)
|
||||||
|
if err != nil {
|
||||||
|
w.errorHandler(err)
|
||||||
|
}
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
if err != nil {
|
||||||
|
w.errorHandler(err)
|
||||||
|
}
|
||||||
|
case <-w.close:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return watcher.Add(w.dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LocalWatcher) Close() error {
|
||||||
|
close(w.close)
|
||||||
|
return w.watcher.Close()
|
||||||
|
}
|
||||||
11
common/watcher/watcher.go
Normal file
11
common/watcher/watcher.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package watcher
|
||||||
|
|
||||||
|
type EventHandler func(filename string) error
|
||||||
|
type ErrorHandler func(err error)
|
||||||
|
|
||||||
|
type Watcher interface {
|
||||||
|
SetEventHandler(handler EventHandler)
|
||||||
|
SetErrorHandler(handler ErrorHandler)
|
||||||
|
Watch() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
196
go.mod
Normal file
196
go.mod
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
module Ratte
|
||||||
|
|
||||||
|
go 1.22.0
|
||||||
|
|
||||||
|
toolchain go1.23.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Yuzuki616/Ratte-Interface v0.0.0-20240911204230-d6204ff1f3e0
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
|
github.com/go-acme/lego/v4 v4.18.0
|
||||||
|
github.com/goccy/go-json v0.10.3
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
github.com/spf13/cobra v1.8.0
|
||||||
|
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||||
|
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||||
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||||
|
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect
|
||||||
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.27.18 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.18 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/lightsail v1.38.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/route53 v1.40.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect
|
||||||
|
github.com/aws/smithy-go v1.20.2 // indirect
|
||||||
|
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/civo/civogo v0.3.11 // indirect
|
||||||
|
github.com/cloudflare/cloudflare-go v0.97.0 // indirect
|
||||||
|
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/deepmap/oapi-codegen v1.9.1 // indirect
|
||||||
|
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||||
|
github.com/dnsimple/dnsimple-go v1.7.0 // indirect
|
||||||
|
github.com/exoscale/egoscale v0.102.3 // indirect
|
||||||
|
github.com/fatih/color v1.16.0 // indirect
|
||||||
|
github.com/fatih/structs v1.1.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 // indirect
|
||||||
|
github.com/go-errors/errors v1.0.1 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.1 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-resty/resty/v2 v2.11.0 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
||||||
|
github.com/gofrs/flock v0.10.0 // indirect
|
||||||
|
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
|
||||||
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||||
|
github.com/gophercloud/gophercloud v1.12.0 // indirect
|
||||||
|
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-plugin v1.6.1 // indirect
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||||
|
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
|
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||||
|
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||||
|
github.com/linode/linodego v1.28.0 // indirect
|
||||||
|
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
||||||
|
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
|
github.com/miekg/dns v1.1.59 // indirect
|
||||||
|
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||||
|
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||||
|
github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect
|
||||||
|
github.com/nrdcg/desec v0.8.0 // indirect
|
||||||
|
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||||
|
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||||
|
github.com/nrdcg/goinwx v0.10.0 // indirect
|
||||||
|
github.com/nrdcg/mailinabox v0.2.0 // indirect
|
||||||
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
|
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||||
|
github.com/nrdcg/porkbun v0.3.0 // indirect
|
||||||
|
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||||
|
github.com/oklog/run v1.0.0 // indirect
|
||||||
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||||
|
github.com/oracle/oci-go-sdk/v65 v65.63.1 // indirect
|
||||||
|
github.com/ovh/go-ovh v1.5.1 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/pquerna/otp v1.4.0 // indirect
|
||||||
|
github.com/sacloud/api-client-go v0.2.10 // indirect
|
||||||
|
github.com/sacloud/go-http v0.1.8 // indirect
|
||||||
|
github.com/sacloud/iaas-api-go v1.12.0 // indirect
|
||||||
|
github.com/sacloud/packages-go v0.0.10 // indirect
|
||||||
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 // indirect
|
||||||
|
github.com/selectel/domains-go v1.1.0 // indirect
|
||||||
|
github.com/selectel/go-selvpcclient/v3 v3.1.1 // indirect
|
||||||
|
github.com/shopspring/decimal v1.3.1 // indirect
|
||||||
|
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||||
|
github.com/softlayer/softlayer-go v1.1.5 // indirect
|
||||||
|
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
|
||||||
|
github.com/sony/gobreaker v0.5.0 // indirect
|
||||||
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898 // indirect
|
||||||
|
github.com/transip/gotransip/v6 v6.23.0 // indirect
|
||||||
|
github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect
|
||||||
|
github.com/vinyldns/go-vinyldns v0.9.16 // indirect
|
||||||
|
github.com/vultr/govultr/v3 v3.9.0 // indirect
|
||||||
|
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e // indirect
|
||||||
|
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
|
go.uber.org/ratelimit v0.3.0 // indirect
|
||||||
|
golang.org/x/crypto v0.24.0 // indirect
|
||||||
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
|
golang.org/x/net v0.26.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.21.0 // indirect
|
||||||
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
|
golang.org/x/term v0.21.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
|
google.golang.org/api v0.172.0 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||||
|
google.golang.org/grpc v1.63.1 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/ns1/ns1-go.v2 v2.7.13 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/dolthub/maphashlatest => github.com/Yuzuki616/maphash v0.0.0-20240910225326-accabd8aa54a
|
||||||
11
handler/cert.go
Normal file
11
handler/cert.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func (h *Handler) RenewCertHandle() error {
|
||||||
|
err := h.acme.RenewCert(h.Options.Cert.CertPath, h.Options.Cert.KeyPath, h.Options.Cert.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("renew cert error: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
48
handler/handler.go
Normal file
48
handler/handler.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Ratte/acme"
|
||||||
|
"Ratte/conf"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
import "github.com/Yuzuki616/Ratte-Interface/core"
|
||||||
|
import "github.com/Yuzuki616/Ratte-Interface/panel"
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
c core.Core
|
||||||
|
p panel.Panel
|
||||||
|
nodeName string
|
||||||
|
acme *acme.Acme
|
||||||
|
l *logrus.Entry
|
||||||
|
userList []panel.UserInfo
|
||||||
|
userHash map[string]struct{}
|
||||||
|
nodeAdded atomic.Bool
|
||||||
|
*conf.Options
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(
|
||||||
|
c core.Core,
|
||||||
|
p panel.Panel,
|
||||||
|
nodeName string,
|
||||||
|
ac *acme.Acme,
|
||||||
|
l *logrus.Entry,
|
||||||
|
opts *conf.Options) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
c: c,
|
||||||
|
p: p,
|
||||||
|
nodeName: nodeName,
|
||||||
|
userList: make([]panel.UserInfo, 0),
|
||||||
|
userHash: make(map[string]struct{}),
|
||||||
|
acme: ac,
|
||||||
|
l: l,
|
||||||
|
Options: opts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Close() error {
|
||||||
|
if h.nodeAdded.Load() {
|
||||||
|
return h.c.DelNode(h.nodeName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
50
handler/node.go
Normal file
50
handler/node.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Ratte/common/maps"
|
||||||
|
"fmt"
|
||||||
|
"github.com/Yuzuki616/Ratte-Interface/core"
|
||||||
|
"github.com/Yuzuki616/Ratte-Interface/panel"
|
||||||
|
"github.com/Yuzuki616/Ratte-Interface/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) PullNodeHandle(n *panel.NodeInfo) error {
|
||||||
|
if h.nodeAdded.Load() {
|
||||||
|
err := h.c.DelNode(h.nodeName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("del node error: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := h.acme.CreateCert(h.Cert.CertPath, h.Cert.KeyPath, h.Cert.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create cert error: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := h.c.AddNode(&core.AddNodeParams{
|
||||||
|
NodeInfo: core.NodeInfo{
|
||||||
|
CommonNodeInfo: params.CommonNodeInfo{
|
||||||
|
Type: n.Type,
|
||||||
|
VMess: n.VMess,
|
||||||
|
Shadowsocks: n.Shadowsocks,
|
||||||
|
Trojan: n.Trojan,
|
||||||
|
Hysteria: n.Hysteria,
|
||||||
|
Other: n.Other,
|
||||||
|
ExpandParams: params.ExpandParams{
|
||||||
|
OtherOptions: maps.Merge(n.OtherOptions, h.Expand),
|
||||||
|
CustomData: n.CustomData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TlsOptions: core.TlsOptions{
|
||||||
|
CertPath: h.Cert.CertPath,
|
||||||
|
KeyPath: h.Cert.KeyPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("add node error: %w", err)
|
||||||
|
}
|
||||||
|
if h.nodeAdded.Load() {
|
||||||
|
h.nodeAdded.Store(true)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
87
handler/user.go
Normal file
87
handler/user.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Ratte/common/slices"
|
||||||
|
"fmt"
|
||||||
|
"github.com/Yuzuki616/Ratte-Interface/core"
|
||||||
|
"github.com/Yuzuki616/Ratte-Interface/panel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func compareUserList(old, new []panel.UserInfo) (deleted []string, added []panel.UserInfo) {
|
||||||
|
tmp := map[string]struct{}{}
|
||||||
|
tmp2 := map[string]struct{}{}
|
||||||
|
for i := range old {
|
||||||
|
tmp[old[i].GetHashOrKey()] = struct{}{}
|
||||||
|
}
|
||||||
|
l := len(tmp)
|
||||||
|
for i := range new {
|
||||||
|
e := new[i].GetHashOrKey()
|
||||||
|
tmp[e] = struct{}{}
|
||||||
|
tmp2[e] = struct{}{}
|
||||||
|
if l != len(tmp) {
|
||||||
|
added = append(added, new[i])
|
||||||
|
l++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmp = nil
|
||||||
|
l = len(tmp2)
|
||||||
|
for i := range old {
|
||||||
|
tmp2[old[i].GetHashOrKey()] = struct{}{}
|
||||||
|
if l != len(tmp2) {
|
||||||
|
deleted = append(deleted, old[i].Name)
|
||||||
|
l++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deleted, added
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) PullUserHandle(users []panel.UserInfo) error {
|
||||||
|
del, add := compareUserList(h.userList, users)
|
||||||
|
cas := slices.RangeToNew[panel.UserInfo, core.UserInfo](add, func(_ int, v panel.UserInfo) core.UserInfo {
|
||||||
|
return core.UserInfo(v.UserInfo)
|
||||||
|
})
|
||||||
|
err := h.c.AddUsers(&core.AddUsersParams{
|
||||||
|
NodeName: h.nodeName,
|
||||||
|
Users: cas,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("add user error: %w", err)
|
||||||
|
}
|
||||||
|
h.l.Infof("Added %d users", len(users))
|
||||||
|
err = h.c.DelUsers(&core.DelUsersParams{
|
||||||
|
NodeName: h.nodeName,
|
||||||
|
Users: del,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("del user error: %w", err)
|
||||||
|
}
|
||||||
|
h.l.Infof("Deleted %d users", len(users))
|
||||||
|
h.userList = users
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) ReportUserHandle(id int) error {
|
||||||
|
var err error
|
||||||
|
req := &core.GetUserTrafficParams{NodeName: h.nodeName}
|
||||||
|
var users []panel.UserTrafficInfo
|
||||||
|
slices.Range(h.userList, func(_ int, v panel.UserInfo) bool {
|
||||||
|
req.Username = v.Name
|
||||||
|
rsp := h.c.GetUserTraffic(req)
|
||||||
|
if rsp.Err != nil {
|
||||||
|
err = rsp.Err
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if rsp.Up == 0 && rsp.Down == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
err = h.p.ReportUserTraffic(&panel.ReportUserTrafficParams{
|
||||||
|
Id: id,
|
||||||
|
Users: users,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("report user error: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
1
log/log.go
Normal file
1
log/log.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package log
|
||||||
103
trigger/handle.go
Normal file
103
trigger/handle.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package trigger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Trigger) addCronHandle(cron any, job cron.FuncJob) (cron.EntryID, error) {
|
||||||
|
switch cron.(type) {
|
||||||
|
case string:
|
||||||
|
return t.c.AddJob(cron.(string), job)
|
||||||
|
case int:
|
||||||
|
return t.c.Schedule(newSchedule(cron.(int)), job), nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown cron type: %T", cron)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) hashEqualsOrStore(name, hash string) bool {
|
||||||
|
if h, ok := t.hashs[name]; ok {
|
||||||
|
if h == hash {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
t.hashs[name] = hash
|
||||||
|
} else {
|
||||||
|
t.hashs[name] = hash
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) pullNodeHandle() {
|
||||||
|
t.l.Info("Run pull node task...")
|
||||||
|
defer t.l.Info("Run pull node task done.")
|
||||||
|
// get node info
|
||||||
|
nn := t.p.GetNodeInfo(t.remoteId)
|
||||||
|
if nn.Err != nil {
|
||||||
|
t.l.WithError(nn.Err).Error("Get node info failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t.hashEqualsOrStore("pullNode", nn.GetHash()) {
|
||||||
|
t.l.Debug("Node is not changed, skip")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.l.Debug("Node is changed, triggering handler...")
|
||||||
|
// update node handler
|
||||||
|
err := t.h.PullNodeHandle(&nn.NodeInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.l.WithError(err).Error("Pull node failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// done
|
||||||
|
t.l.Debug("trigger handler done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) pullUserHandle() {
|
||||||
|
t.l.Info("Run pull user task...")
|
||||||
|
defer t.l.Info("Run pull user task done.")
|
||||||
|
// get user info
|
||||||
|
nu := t.p.GetUserList(t.remoteId)
|
||||||
|
if nu.Err != nil {
|
||||||
|
t.l.WithError(nu.Err).Error("Get user list failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if t.hashEqualsOrStore("pullUser", nu.GetHash()) {
|
||||||
|
t.l.Debug("Node is not changed, skip")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.l.Debug("user list is changed, triggering handler...")
|
||||||
|
// triggering update user list handler
|
||||||
|
err := t.h.PullUserHandle(nu.Users)
|
||||||
|
if err != nil {
|
||||||
|
t.l.WithError(err).Error("Pull user handle failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// done
|
||||||
|
t.l.Debug("trigger handler done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) reportUserHandle() {
|
||||||
|
t.l.Info("Run report user task...")
|
||||||
|
defer t.l.Info("Run pull user task done.")
|
||||||
|
// triggering report user handler
|
||||||
|
err := t.h.ReportUserHandle()
|
||||||
|
if err != nil {
|
||||||
|
t.l.WithError(err).Error("Report user handle failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) renewCertCron() {
|
||||||
|
t.l.Info("Run renew cert task...")
|
||||||
|
defer t.l.Info("Run renew cert task done.")
|
||||||
|
// triggering renew cert handler
|
||||||
|
err := t.h.RenewCertHandle()
|
||||||
|
if err != nil {
|
||||||
|
t.l.WithError(err).Error("Renew cert handle failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// done
|
||||||
|
}
|
||||||
17
trigger/schedule.go
Normal file
17
trigger/schedule.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package trigger
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Schedule struct {
|
||||||
|
interval int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSchedule(interval int) *Schedule {
|
||||||
|
return &Schedule{
|
||||||
|
interval: interval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schedule) Next(t time.Time) time.Time {
|
||||||
|
return t.Add(time.Duration(s.interval) * time.Second)
|
||||||
|
}
|
||||||
88
trigger/trigger.go
Normal file
88
trigger/trigger.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package trigger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Ratte/conf"
|
||||||
|
"Ratte/handler"
|
||||||
|
"fmt"
|
||||||
|
"github.com/Yuzuki616/Ratte-Interface/panel"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Trigger struct {
|
||||||
|
l *logrus.Entry
|
||||||
|
c *cron.Cron
|
||||||
|
h *handler.Handler
|
||||||
|
p panel.Panel
|
||||||
|
remoteId int
|
||||||
|
remoteC *conf.Remote
|
||||||
|
hashs map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrigger(
|
||||||
|
l *logrus.Entry,
|
||||||
|
tc *conf.Trigger,
|
||||||
|
h *handler.Handler,
|
||||||
|
p panel.Panel,
|
||||||
|
rm *conf.Remote,
|
||||||
|
) (*Trigger, error) {
|
||||||
|
tr := &Trigger{
|
||||||
|
l: l,
|
||||||
|
c: cron.New(),
|
||||||
|
h: h,
|
||||||
|
p: p,
|
||||||
|
remoteC: rm,
|
||||||
|
}
|
||||||
|
|
||||||
|
// add pull node cron task
|
||||||
|
_, err := tr.addCronHandle(tc.PullNodeCron, tr.pullNodeHandle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add pull user cron task
|
||||||
|
_, err = tr.addCronHandle(tc.PullUserCron, tr.pullUserHandle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add report user cron task
|
||||||
|
_, err = tr.addCronHandle(tc.ReportUserCron, tr.reportUserHandle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add renew cert cron task
|
||||||
|
_, err = tr.addCronHandle(tc.RenewCertCron, tr.renewCertCron)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) Start() error {
|
||||||
|
r := t.remoteC
|
||||||
|
rsp := t.p.AddRemote(&panel.AddRemoteParams{
|
||||||
|
Baseurl: r.APIHost,
|
||||||
|
NodeId: r.NodeID,
|
||||||
|
NodeType: r.NodeType,
|
||||||
|
Timeout: r.Timeout,
|
||||||
|
})
|
||||||
|
if rsp.Err != nil {
|
||||||
|
return rsp.Err
|
||||||
|
}
|
||||||
|
t.remoteId = rsp.RemoteId
|
||||||
|
t.pullNodeHandle()
|
||||||
|
t.pullUserHandle()
|
||||||
|
t.c.Start()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trigger) Close() error {
|
||||||
|
t.c.Stop()
|
||||||
|
err := t.p.DelRemote(t.remoteId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("del remote err: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user