mirror of
https://github.com/InazumaV/Ratte.git
synced 2026-02-03 20:20:14 +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