Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df5165d4a3 | ||
|
|
868731c95e | ||
|
|
6990166dfc | ||
|
|
fa07a6f0e4 | ||
|
|
d428a7db2e | ||
|
|
8163d0d9d4 | ||
|
|
3831950780 | ||
|
|
9220cf82aa | ||
|
|
4a61527952 | ||
|
|
48bb6b8864 | ||
|
|
58b30fa41a | ||
|
|
2d73157a60 |
@@ -0,0 +1,34 @@
|
||||
name: Quality check
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
web-ui:
|
||||
name: Web UI
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
cache-dependency-path: web/frontend/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
working-directory: web/frontend
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
working-directory: web/frontend
|
||||
@@ -1,8 +1,12 @@
|
||||
# goodBaby v2
|
||||
|
||||
摇篮系统 —— 为独居青年准备的「死人开关」(Dead Man's Switch)。
|
||||
  
|
||||
|
||||
一个人在大城市生活,最怕的是出了事都没人知道。goodBaby 让你定期回来报一声平安;一旦超过设定的期限没有签到,它就会替你把预先托付的留言送出去:给家人的邮件、给朋友的 QQ / 钉钉消息、一条 B 站动态,或是公开你的 GitHub 仓库,把作品留给世界。
|
||||
根据《中国心血管健康与疾病报告2023》,中国 每年 猝死总人数(含心源性和非心源性)或达80万-100万,18-35岁人群占比为38%,也就是说,每天18-35岁人群有近1000人猝死
|
||||
|
||||
伴随着中国人口老龄化加剧,年轻人的牵挂越来越多,面对着可能的猝死,我们或许真的需要做好准备
|
||||
|
||||
所以我写了goodBaby来解决这个问题,他和apple app store里的“死了么”形式上有些接近,不过goodBaby支持了更多好玩的通知方式,比如bilibili动态,github仓库,邮件,OneBot,还可以方便的自托管
|
||||
|
||||
[](https://app.rainyun.com/apps/rca/store/7125/cat_)
|
||||
|
||||
@@ -39,12 +43,10 @@ docker compose up -d
|
||||
docker run -d --name goodbaby -p 8088:8088 -v ./data:/app/data ghcr.io/ssdomei232/goodbaby:latest
|
||||
```
|
||||
|
||||
可用标签:`latest`、`v2.2.0`(完整版本)、`2.2`(次版本)、`2`(主版本)。
|
||||
|
||||
### 下载预编译二进制
|
||||
|
||||
[Releases](https://github.com/ssdomei232/goodBaby/releases) 提供 Linux / Windows / macOS 的 amd64 与 arm64 产物,
|
||||
解压后直接运行即可(前端已内嵌,无需额外部署)。压缩包旁的 `checksums.txt` 可校验完整性:
|
||||
解压后直接运行即可。压缩包旁的 `checksums.txt` 可校验完整性:
|
||||
|
||||
```bash
|
||||
sha256sum -c checksums.txt --ignore-missing
|
||||
@@ -55,7 +57,7 @@ sha256sum -c checksums.txt --ignore-missing
|
||||
需要 Go 1.25+ 与 Node.js 20+:
|
||||
|
||||
```bash
|
||||
# 1. 构建前端(产物输出到 web/dist,会被 go:embed 打进二进制)
|
||||
# 1. 构建前端
|
||||
cd web/frontend && npm install && npm run build && cd ../..
|
||||
|
||||
# 2. 构建后端
|
||||
@@ -97,16 +99,7 @@ cd web/frontend && npm run dev
|
||||
|
||||
### 管理员后台
|
||||
|
||||
**第一个注册的用户自动成为管理员**(老版本升级上来时,注册最早的用户会被自动提升)。
|
||||
|
||||
管理员在「设置」页面会多出一个「系统配置」区块,可以直接修改并立即生效,无需重启:
|
||||
|
||||
* 开放注册
|
||||
* 检查间隔(保存后会自动重排定时任务)
|
||||
* 规则重试时长
|
||||
* 日志保留条数
|
||||
|
||||
监听地址、数据库、会话密钥这类启动项不在后台开放,只能改配置文件。
|
||||
**第一个注册的用户自动成为管理员**。
|
||||
|
||||
## 数据库
|
||||
|
||||
@@ -126,6 +119,7 @@ cd web/frontend && npm run dev
|
||||
* [OneBot (QQ)](docs/onebot-config.md)
|
||||
* [钉钉机器人](docs/dingtalk-config.md)
|
||||
* [GitHub](docs/github-config.md)
|
||||
* [饭碗警告](docs/fwalert-config.md)
|
||||
|
||||
## 开发:新增一种规则类型
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# 饭碗警告配置
|
||||
|
||||
规则类型: `fwalert`。
|
||||
|
||||
定时器到期后,请求饭碗警告的 Webhook 地址
|
||||
|
||||
饭碗警告: 比较简单的(不用实名认证)支持电话和短信通知的第三方平台: `https://fwalert.com/`,价格不算最低,但比较方便
|
||||
|
||||
## 规则配置 (Rule Config)
|
||||
|
||||
```json
|
||||
{
|
||||
"webhook_url": "https://fwalert.com/xxx-xxx-xxx-xxx-xxx",
|
||||
"msg": "消息内容"
|
||||
}
|
||||
```
|
||||
|
||||
## 饭碗警告平台配置
|
||||
|
||||
goodbaby 在请求饭碗警告时将`msg`放在请求体中,参照下面和官方教程设置:
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| `变量名` | `message` |
|
||||
| `变量来源` | `请求体(body)` |
|
||||
| `提取规则` | `序列化数据` |
|
||||
| `键` | `message` |
|
||||
| `通知简述` | 随便填 |
|
||||
| `通知正文` | `{{message}}` |
|
||||
| `联系方式类型` | 参照官方文档添加并选择 |
|
||||
+1
-1
@@ -37,7 +37,7 @@ git push origin v2.2.0
|
||||
|
||||
编译时通过 ldflags 注入,启动日志里能看到:
|
||||
|
||||
```
|
||||
```log
|
||||
goodBaby v2.2.0 (a1b2c3d, 2026-07-27T07:00:00Z) 启动中...
|
||||
```
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
)
|
||||
|
||||
// DingTalkExecutor 钉钉机器人执行器
|
||||
//
|
||||
// 之前只注册了校验器却没有执行器,导致钉钉规则可以创建但永远不会被执行。
|
||||
type DingTalkExecutor struct{}
|
||||
|
||||
func (e *DingTalkExecutor) GetType() string {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package fwalert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
// send 发送消息
|
||||
func send(config *FwAlertRuleConfig) error {
|
||||
restyClient := resty.New()
|
||||
req := restyClient.R()
|
||||
|
||||
req.SetBody(FwAlertRequest{
|
||||
Message: config.Msg,
|
||||
})
|
||||
|
||||
resp, err := req.Execute(resty.MethodPost, config.WebhookURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return fmt.Errorf("发送饭碗警告失败: %s", resp.Status())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package fwalert
|
||||
|
||||
const RuleTypeFwalert = "fwalert"
|
||||
|
||||
type FwAlertRuleConfig struct {
|
||||
WebhookURL string `json:"webhook_url"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
type FwAlertRequest struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package fwalert
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/ssdomei232/goodBaby/internal/meta"
|
||||
)
|
||||
|
||||
type FwalertRuleValidator struct{}
|
||||
|
||||
func (v *FwalertRuleValidator) GetType() string {
|
||||
return RuleTypeFwalert
|
||||
}
|
||||
|
||||
func (v *FwalertRuleValidator) Validate(configJSON string) error {
|
||||
var config FwAlertRuleConfig
|
||||
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
|
||||
return fmt.Errorf("解析饭碗警告规则配置失败: %v", err)
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(config.WebhookURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("饭碗警告规则配置中 webhook_url 格式不正确: %v", err)
|
||||
}
|
||||
if parsed.Scheme != "http" && parsed.Scheme != "https" {
|
||||
return fmt.Errorf("饭碗警告规则配置中 webhook_url 必须是 HTTP 或 HTTPS URL")
|
||||
}
|
||||
|
||||
if config.Msg == "" {
|
||||
return fmt.Errorf("饭碗警告规则配置中 msg 不能为空")
|
||||
}
|
||||
if len([]rune(config.Msg)) > 1000 {
|
||||
return fmt.Errorf("饭碗警告内容过长")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *FwalertRuleValidator) Meta() meta.RuleMeta {
|
||||
return meta.RuleMeta{
|
||||
Type: RuleTypeFwalert,
|
||||
Label: "发送饭碗警告",
|
||||
Description: "触发时发送饭碗警告消息。",
|
||||
Docs: "docs/fwalert-config.md",
|
||||
AccountType: "",
|
||||
Fields: []meta.Field{
|
||||
{Key: "webhook_url", Label: "Webhook URL", Type: meta.FieldString, Required: true, Help: "饭碗警告的触发地址", Placeholder: "https://fwalert.com/xxx-xxx-xxx-xxx-xxx"},
|
||||
{Key: "msg", Label: "消息内容", Type: meta.FieldTextarea, Required: true, Help: "最大长度 1000 个字符"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ParseRuleConfig 解析饭碗警告规则配置
|
||||
func ParseRuleConfig(configJSON string) (*FwAlertRuleConfig, error) {
|
||||
var config FwAlertRuleConfig
|
||||
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
|
||||
return nil, fmt.Errorf("解析饭碗警告规则配置失败: %v", err)
|
||||
}
|
||||
return &config, nil
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package fwalert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/ssdomei232/goodBaby/internal/retry"
|
||||
"github.com/ssdomei232/goodBaby/model"
|
||||
)
|
||||
|
||||
// FwalertExecutor 饭碗警告执行器
|
||||
type FwalertExecutor struct{}
|
||||
|
||||
func (e *FwalertExecutor) GetType() string {
|
||||
return RuleTypeFwalert
|
||||
}
|
||||
|
||||
func (e *FwalertExecutor) Execute(ctx context.Context, rule *model.Rule) error {
|
||||
log.Printf("执行饭碗警告规则: %s (ID: %d)", rule.Name, rule.ID)
|
||||
|
||||
config, err := ParseRuleConfig(rule.ConfigJson)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return retry.Do(ctx, func() error {
|
||||
return send(config)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package rainyun
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ssdomei232/goodBaby/internal/meta"
|
||||
rain "github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/common"
|
||||
"github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/user"
|
||||
)
|
||||
|
||||
// RainyunAccountConfigValidator Rainyun账号配置验证器
|
||||
type RainyunAccountConfigValidator struct{}
|
||||
|
||||
func (v *RainyunAccountConfigValidator) GetType() string {
|
||||
return AccountType
|
||||
}
|
||||
|
||||
func (v *RainyunAccountConfigValidator) Validate(config string) error {
|
||||
// 解析配置
|
||||
cfg, err := parseAccount(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.APIKey == "" {
|
||||
return fmt.Errorf("雨云账号配置中APIKey不能为空")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseAccount(config string) (*RainyunAccount, error) {
|
||||
var cfg RainyunAccount
|
||||
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
|
||||
return nil, fmt.Errorf("解析雨云账号配置失败: %v", err)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func (v *RainyunAccountConfigValidator) Test(config string) error {
|
||||
cfg, err := parseAccount(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := rain.NewClient(cfg.APIKey)
|
||||
userClient := user.Client{Client: client}
|
||||
_, err = userClient.GetUserInfo()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("测试雨云账号配置失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *RainyunAccountConfigValidator) Meta() meta.AccountMeta {
|
||||
return meta.AccountMeta{
|
||||
Type: AccountType,
|
||||
Label: "雨云",
|
||||
Description: "对接雨云,方便各位代理/运维删库跑路",
|
||||
Fields: []meta.Field{
|
||||
{Key: "api_key", Label: "API Key", Type: meta.FieldPassword, Required: true, Secret: true, Placeholder: "xxxxxxxxx"},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package rainyun
|
||||
|
||||
const (
|
||||
// AccountType Rainyun 账号类型标识
|
||||
AccountType = "rainyun"
|
||||
// RuleType 发送 Rainyun 消息的规则类型标识
|
||||
RuleType = "rainyun-workorder"
|
||||
)
|
||||
|
||||
type RainyunWorkOrderRule struct {
|
||||
Title string `json:"title"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
type RainyunAccount struct {
|
||||
APIKey string `json:"api_key"`
|
||||
}
|
||||
|
||||
type RainyunRunAwayRule struct {
|
||||
IK string `json:"ik"`
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package rainyun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ssdomei232/goodBaby/internal/retry"
|
||||
"github.com/ssdomei232/goodBaby/model"
|
||||
rain "github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/common"
|
||||
"github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/rcs"
|
||||
"github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/rgs"
|
||||
"github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/workorder"
|
||||
)
|
||||
|
||||
func SendRainyunWorkorderMsg(ctx context.Context, rule *model.Rule) error {
|
||||
rainyunConfig, err := getRainyunConfigFromRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rainyunAccount, err := getRainyunAccountFromRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = retry.Do(ctx, func() error {
|
||||
return sendRainyunWorkorderMsg(rainyunConfig, rainyunAccount)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendRainyunWorkorderMsg(rainyunRuleConfig *RainyunWorkOrderRule, rainyunAccount *RainyunAccount) error {
|
||||
client := rain.NewClient(rainyunAccount.APIKey)
|
||||
workorderClient := workorder.Client{Client: client}
|
||||
|
||||
_, err := workorderClient.CreateWorkOrder(&workorder.CreateWorkerorderRequest{
|
||||
Content: rainyunRuleConfig.Msg,
|
||||
IsAuthed: false,
|
||||
IsUrgent: 1,
|
||||
Title: rainyunRuleConfig.Title,
|
||||
Type: "reward",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RainyunRunAway(ctx context.Context, rule *model.Rule) error {
|
||||
rainyunAccount, err := getRainyunAccountFromRule(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := rain.NewClient(rainyunAccount.APIKey)
|
||||
|
||||
var fails []string
|
||||
total := 0
|
||||
|
||||
// 重装所有RCS
|
||||
rcsClient := rcs.Client{Client: client}
|
||||
options := rain.EncodingStandardQueryParameters(1, 20)
|
||||
rcsList, err := rcsClient.GetRcsList(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rcsList.Data.TotalRecords == 0 {
|
||||
return nil
|
||||
} else if rcsList.Data.TotalRecords > 20 {
|
||||
rcsTotal := rcsList.Data.TotalRecords
|
||||
options := rain.EncodingStandardQueryParameters(1, rcsTotal)
|
||||
rcsList, err = rcsClient.GetRcsList(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, rcs := range rcsList.Data.Records {
|
||||
total++
|
||||
err := retry.Do(context.Background(), func() error {
|
||||
return reinstallOneRCS(rcs.ID, &rcsClient)
|
||||
})
|
||||
if err != nil {
|
||||
fails = append(fails, fmt.Sprintf("RCS %d: %v", rcs.ID, err))
|
||||
}
|
||||
}
|
||||
|
||||
// 重装所有RGS
|
||||
rgsClient := rgs.Client{Client: client}
|
||||
rgsOptions := rain.EncodingStandardQueryParameters(1, 20)
|
||||
rgsList, err := rgsClient.GetRgsList(rgsOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rgsList.Data.TotalRecords == 0 {
|
||||
return nil
|
||||
} else if rgsList.Data.TotalRecords > 20 {
|
||||
rgsTotal := rgsList.Data.TotalRecords
|
||||
rgsOptions := rain.EncodingStandardQueryParameters(1, rgsTotal)
|
||||
rgsList, err = rgsClient.GetRgsList(rgsOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, rgs := range rgsList.Data.Records {
|
||||
total++
|
||||
err := retry.Do(context.Background(), func() error {
|
||||
return reinstallOneRGS(rgs.ID, &rgsClient)
|
||||
})
|
||||
if err != nil {
|
||||
fails = append(fails, fmt.Sprintf("RGS %d: %v", rgs.ID, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(fails) > 0 {
|
||||
return fmt.Errorf("%d/%d 台云服务器重装失败: %s", len(fails), total, strings.Join(fails, "; "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package rainyun
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ssdomei232/goodBaby/internal/meta"
|
||||
)
|
||||
|
||||
// Rainyun规则验证器
|
||||
type RainyunWorkorderRuleValidator struct{}
|
||||
|
||||
func (v *RainyunWorkorderRuleValidator) GetType() string {
|
||||
return RuleType
|
||||
}
|
||||
|
||||
func (v *RainyunWorkorderRuleValidator) Validate(configJSON string) error {
|
||||
var config RainyunWorkOrderRule
|
||||
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
|
||||
return fmt.Errorf("解析雨云规则配置失败: %v", err)
|
||||
}
|
||||
|
||||
if config.Title == "" {
|
||||
return fmt.Errorf("雨云规则配置中 title 不能为空")
|
||||
}
|
||||
|
||||
if config.Msg == "" {
|
||||
return fmt.Errorf("雨云规则配置中 msg 不能为空")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *RainyunWorkorderRuleValidator) Meta() meta.RuleMeta {
|
||||
return meta.RuleMeta{
|
||||
Type: RuleType,
|
||||
Label: "发送雨云工单",
|
||||
Description: "触发时发送雨云工单",
|
||||
AccountType: AccountType,
|
||||
Fields: []meta.Field{
|
||||
{Key: "title", Label: "工单标题", Type: meta.FieldTextarea, Required: true},
|
||||
{Key: "msg", Label: "工单内容", Type: meta.FieldTextarea, Required: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type RainyunRunAwayRuleValidator struct{}
|
||||
|
||||
func (v *RainyunRunAwayRuleValidator) GetType() string {
|
||||
return "rainyun-runaway"
|
||||
}
|
||||
|
||||
func (v *RainyunRunAwayRuleValidator) Validate(configJSON string) error {
|
||||
var config RainyunRunAwayRule
|
||||
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
|
||||
return fmt.Errorf("解析雨云规则配置失败: %v", err)
|
||||
}
|
||||
|
||||
if config.IK != "我已知晓" {
|
||||
return fmt.Errorf("请输入正确内容")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *RainyunRunAwayRuleValidator) Meta() meta.RuleMeta {
|
||||
return meta.RuleMeta{
|
||||
Type: "rainyun-runaway",
|
||||
Label: "重置雨云账号中所有云服务器(一键跑路)",
|
||||
Description: "触发时会重装雨云账号下所有云服务器来实现跑路",
|
||||
AccountType: AccountType,
|
||||
Fields: []meta.Field{
|
||||
{Key: "ik", Label: "免责声明", Type: meta.FieldTextarea, Required: true, Placeholder: "该操作引发的损失及法律责任归属账号所有人,本软件一律不承担,请输入\"我已知晓\""},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package rainyun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/ssdomei232/goodBaby/model"
|
||||
)
|
||||
|
||||
// RainyunExecutor 雨云执行器
|
||||
type RainyunWorkorderExecutor struct{}
|
||||
|
||||
func (e *RainyunWorkorderExecutor) GetType() string {
|
||||
return RuleType
|
||||
}
|
||||
|
||||
func (e *RainyunWorkorderExecutor) Execute(ctx context.Context, rule *model.Rule) error {
|
||||
log.Printf("执行雨云工单规则: %s", rule.Name)
|
||||
|
||||
return SendRainyunWorkorderMsg(ctx, rule)
|
||||
}
|
||||
|
||||
// RainyunRunAwayExecutor 雨云跑路执行器
|
||||
type RainyunRunAwayExecutor struct{}
|
||||
|
||||
func (e *RainyunRunAwayExecutor) GetType() string {
|
||||
return "rainyun-runaway"
|
||||
}
|
||||
|
||||
func (e *RainyunRunAwayExecutor) Execute(ctx context.Context, rule *model.Rule) error {
|
||||
log.Printf("执行雨云跑路规则: %s", rule.Name)
|
||||
return RainyunRunAway(ctx, rule)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package rainyun
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ssdomei232/goodBaby/handler/db"
|
||||
"github.com/ssdomei232/goodBaby/model"
|
||||
"github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/public"
|
||||
"github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/rcs"
|
||||
"github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/rgs"
|
||||
)
|
||||
|
||||
// 从 Rule 中获取 RainyunConfig
|
||||
func getRainyunConfigFromRule(rule *model.Rule) (*RainyunWorkOrderRule, error) {
|
||||
var rainyunConfig RainyunWorkOrderRule
|
||||
if err := json.Unmarshal([]byte(rule.ConfigJson), &rainyunConfig); err != nil {
|
||||
return nil, fmt.Errorf("解析Rainyun规则配置失败: %w", err)
|
||||
}
|
||||
return &rainyunConfig, nil
|
||||
}
|
||||
|
||||
// 从 Rule 中获取 RainyunAccount
|
||||
func getRainyunAccountFromRule(rule *model.Rule) (*RainyunAccount, error) {
|
||||
var rainyunAccount RainyunAccount
|
||||
if err := db.LoadAccountConfig(rule.AccountID, &rainyunAccount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rainyunAccount, nil
|
||||
}
|
||||
|
||||
func reinstallOneRCS(rcsID int, rcsClient *rcs.Client) error {
|
||||
rcsDetail, err := rcsClient.GetRcsDetails(rcsID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rcsOSList, err := public.GetRcsOSList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, os := range rcsOSList.Data {
|
||||
if os.Region == rcsDetail.Data.Data.Node.Region && os.OsType == "linux" {
|
||||
rcsReinstallRequest := &rcs.ReinstallRcsRequest{
|
||||
OsID: os.ID,
|
||||
ResetOsd: true,
|
||||
}
|
||||
_, err := rcsClient.ReinstallRcs(rcsID, rcsReinstallRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reinstallOneRGS(rgsID int, rgsClient *rgs.Client) error {
|
||||
rgsDetail, err := rgsClient.GetRgsDetails(rgsID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rgsOSList, err := public.GetRgsOSList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, os := range rgsOSList.Data {
|
||||
if os.Region == rgsDetail.Data.Data.Node.Region && os.OsType == "linux" {
|
||||
rgsReinstallRequest := &rcs.ReinstallRcsRequest{
|
||||
OsID: os.ID,
|
||||
ResetOsd: true,
|
||||
}
|
||||
_, err := rgsClient.Reinstallgs(rgsID, rgsReinstallRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -9,11 +9,14 @@ require (
|
||||
github.com/gin-contrib/sessions v1.0.4
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-resty/resty/v2 v2.17.2
|
||||
github.com/google/go-github/v84 v84.0.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/ssdomei232/napcat-http-go-sdk v1.0.0
|
||||
github.com/ssdomei232/rainyun-go-sdk/v2 v2.0.6
|
||||
github.com/wneessen/go-mail v0.7.2
|
||||
golang.org/x/crypto v0.48.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
@@ -29,7 +32,6 @@ require (
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.2 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
@@ -68,7 +70,6 @@ require (
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gorm.io/driver/postgres v1.6.0 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
|
||||
@@ -123,6 +123,8 @@ github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/ssdomei232/napcat-http-go-sdk v1.0.0 h1:BY3iqqlZw5letiCwTmfMrhyqsx0a96eL5LpDuinnOSE=
|
||||
github.com/ssdomei232/napcat-http-go-sdk v1.0.0/go.mod h1:yPsSaMs3OyKMVPXwIs5ZB5G532vRi1GvL/WiWUEwblU=
|
||||
github.com/ssdomei232/rainyun-go-sdk/v2 v2.0.6 h1:tjratVKpPk3lc2bw+oVnIzVZG79mFlvUBez+FbX86pQ=
|
||||
github.com/ssdomei232/rainyun-go-sdk/v2 v2.0.6/go.mod h1:Wf9X0qEpKQ1/uD4iUGGoPLHydiCxULqCVk+JCoVqdIg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"github.com/ssdomei232/goodBaby/drivers/bilibili"
|
||||
"github.com/ssdomei232/goodBaby/drivers/dingtalk"
|
||||
"github.com/ssdomei232/goodBaby/drivers/email"
|
||||
"github.com/ssdomei232/goodBaby/drivers/fwalert"
|
||||
"github.com/ssdomei232/goodBaby/drivers/github"
|
||||
"github.com/ssdomei232/goodBaby/drivers/onebot"
|
||||
"github.com/ssdomei232/goodBaby/drivers/rainyun"
|
||||
"github.com/ssdomei232/goodBaby/model"
|
||||
)
|
||||
|
||||
@@ -76,6 +78,9 @@ func InitExecutorRegistry() *ExecutorRegistry {
|
||||
registry.Register(&github.GithubMakeRepoPublicExecutor{})
|
||||
registry.Register(&onebot.OneBotExecutor{})
|
||||
registry.Register(&dingtalk.DingTalkExecutor{})
|
||||
registry.Register(&fwalert.FwalertExecutor{})
|
||||
registry.Register(&rainyun.RainyunWorkorderExecutor{})
|
||||
registry.Register(&rainyun.RainyunRunAwayExecutor{})
|
||||
// 未来添加新规则类型时,在这里注册即可
|
||||
|
||||
globalExecutorRegistry = registry
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/ssdomei232/goodBaby/drivers/email"
|
||||
"github.com/ssdomei232/goodBaby/drivers/github"
|
||||
"github.com/ssdomei232/goodBaby/drivers/onebot"
|
||||
"github.com/ssdomei232/goodBaby/drivers/rainyun"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -24,6 +25,7 @@ func InitValidatorRegistry() *ValidatorRegistry {
|
||||
r.Register(&email.EmailAccountConfigValidator{})
|
||||
r.Register(&github.GitHubAccountConfigValidator{})
|
||||
r.Register(&onebot.OneBotAccountConfigValidator{})
|
||||
r.Register(&rainyun.RainyunAccountConfigValidator{})
|
||||
|
||||
registry = r
|
||||
})
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"github.com/ssdomei232/goodBaby/drivers/bilibili"
|
||||
"github.com/ssdomei232/goodBaby/drivers/dingtalk"
|
||||
"github.com/ssdomei232/goodBaby/drivers/email"
|
||||
"github.com/ssdomei232/goodBaby/drivers/fwalert"
|
||||
"github.com/ssdomei232/goodBaby/drivers/github"
|
||||
"github.com/ssdomei232/goodBaby/drivers/onebot"
|
||||
"github.com/ssdomei232/goodBaby/drivers/rainyun"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -27,6 +29,9 @@ func InitValidatorRegistry() *ValidatorRegistry {
|
||||
r.Register(&onebot.OneBotRuleValidator{})
|
||||
r.Register(&dingtalk.DingTalkRuleValidator{})
|
||||
r.Register(&github.GithubMakeRepositoryPublicRuleValidator{})
|
||||
r.Register(&fwalert.FwalertRuleValidator{})
|
||||
r.Register(&rainyun.RainyunWorkorderRuleValidator{})
|
||||
r.Register(&rainyun.RainyunRunAwayRuleValidator{})
|
||||
|
||||
registry = r
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user