feat: 系统调优 Go 版后端完整源码
- 19 个接口全部实测验收通过 (2026-08-08) - 结构: main.go + core/consts + gsysctl + gtuner + server - 含 build_go.sh 官方部署结构构建脚本 (amd64/arm64 交叉编译) - 数据无缝衔接 Python 版遗留 (快照/state/审计日志) - 备份自 156:/root/goddhv (2026-08-09)
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
dist/
|
||||
mock_deploy*/
|
||||
*.sock
|
||||
*.log
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
# GMSSH「系统调优」Go 版后端构建脚本
|
||||
# 产出官方标准结构: dist/app/bin/{main, config.json, i18n/}
|
||||
set -euo pipefail
|
||||
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
export GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
CURDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$CURDIR"
|
||||
|
||||
# 架构: 默认 amd64, 可用 ARCH=arm64 交叉编译
|
||||
ARCH="${ARCH:-amd64}"
|
||||
DIST_BIN="$CURDIR/dist/app/bin"
|
||||
|
||||
echo "🚀 开始构建 Go 版后端 (arch=$ARCH)..."
|
||||
|
||||
# 1. 编译 main
|
||||
echo "⚙️ 编译 main.go (GOOS=linux GOARCH=$ARCH)..."
|
||||
GOOS=linux GOARCH="$ARCH" CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o "$DIST_BIN/main" .
|
||||
if [ ! -f "$DIST_BIN/main" ]; then
|
||||
echo "❌ 编译失败, 未找到产物"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 编译完成: $DIST_BIN/main ($(stat -c%s "$DIST_BIN/main") bytes)"
|
||||
|
||||
# 2. 生成 config.json (官方格式, socket=app.sock)
|
||||
echo "📝 生成 config.json..."
|
||||
mkdir -p "$DIST_BIN"
|
||||
cat > "$DIST_BIN/config.json" <<'JSONEOF'
|
||||
{
|
||||
"test": {
|
||||
"jsonrpc": {
|
||||
"sockets": "app.sock"
|
||||
},
|
||||
"logger": {
|
||||
"path": "logs/",
|
||||
"file": "{Y-m-d}.log",
|
||||
"level": "info",
|
||||
"stdout": true,
|
||||
"rotateBackupLimit": 7,
|
||||
"rotateExpire": "1d"
|
||||
}
|
||||
},
|
||||
"prod": {
|
||||
"jsonrpc": {
|
||||
"sockets": "app.sock"
|
||||
},
|
||||
"logger": {
|
||||
"path": "../../logs/",
|
||||
"file": "{Y-m-d}.log",
|
||||
"level": "error",
|
||||
"stdout": false,
|
||||
"rotateBackupLimit": 30,
|
||||
"rotateExpire": "30d"
|
||||
}
|
||||
}
|
||||
}
|
||||
JSONEOF
|
||||
echo "✅ config.json 已生成"
|
||||
|
||||
# 3. 生成 i18n (基础国际化文件)
|
||||
echo "📝 生成 i18n..."
|
||||
mkdir -p "$DIST_BIN/i18n"
|
||||
cat > "$DIST_BIN/i18n/zh-CN.ini" <<'INIIEOF'
|
||||
REQUIRE_VALIDATION_TM="该参数不能为空"
|
||||
STATUS_OK="操作成功"
|
||||
INIIEOF
|
||||
cat > "$DIST_BIN/i18n/en.ini" <<'INIIEOF'
|
||||
REQUIRE_VALIDATION_TM="This parameter cannot be empty"
|
||||
STATUS_OK=" Operation successful"
|
||||
INIIEOF
|
||||
echo "✅ i18n 已生成"
|
||||
|
||||
# 4. 赋权
|
||||
chmod +x "$DIST_BIN/main"
|
||||
|
||||
echo ""
|
||||
echo "✅ 构建完成! 产物结构:"
|
||||
find "$CURDIR/dist/app/bin" -type f | sed "s|$CURDIR/||"
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
export GOPROXY=https://goproxy.cn,direct
|
||||
cd /root/goddhv
|
||||
echo "=== go vet 全部包 ==="
|
||||
go vet ./... 2>&1 | tail -30
|
||||
echo "=== go build 全部 ==="
|
||||
go build ./... 2>&1 | tail -30
|
||||
echo "=== 完整构建二进制 ==="
|
||||
go build -o /tmp/goddhv_main . 2>&1 | tail -20
|
||||
ls -la /tmp/goddhv_main 2>/dev/null && echo "BUILD_MAIN_OK"
|
||||
@@ -0,0 +1,33 @@
|
||||
package consts
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// 应用标识:{组织名}/{应用名},与部署一致
|
||||
const APP_NAME = "user_TGPJp0KLpNsa5TAo/ddhv"
|
||||
|
||||
// GMSSH 平台根路径
|
||||
const GMSSH_PATH = "/.__gmssh"
|
||||
|
||||
// 部署相关路径
|
||||
var (
|
||||
// APP_INSTALLED_PATH: 应用安装目录
|
||||
APP_INSTALLED_PATH = filepath.Join(GMSSH_PATH, "plugin", APP_NAME)
|
||||
// APP_TMP_PATH: 临时/socket 目录
|
||||
APP_TMP_PATH = filepath.Join(APP_INSTALLED_PATH, "tmp")
|
||||
// APP_BIN_PATH: 二进制目录
|
||||
APP_BIN_PATH = filepath.Join(APP_INSTALLED_PATH, "app", "bin")
|
||||
// APP_DATA_PATH: 数据目录(快照/状态/审计)
|
||||
APP_DATA_PATH = filepath.Join(APP_INSTALLED_PATH, "data")
|
||||
// APP_SOCKET_FILE_PATH: unix socket 文件
|
||||
APP_SOCKET_FILE_PATH = filepath.Join(APP_TMP_PATH, "app.sock")
|
||||
)
|
||||
|
||||
// 确保目录存在
|
||||
func EnsureDirs() {
|
||||
for _, d := range []string{APP_TMP_PATH, APP_DATA_PATH} {
|
||||
os.MkdirAll(d, 0o755)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
module github.com/user_TGPJp0KLpNsa5TAo/ddhv
|
||||
|
||||
go 1.23.4
|
||||
|
||||
require github.com/DemonZack/simplejrpc-go v0.0.0
|
||||
|
||||
require (
|
||||
github.com/sourcegraph/jsonrpc2 v0.2.1 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/DemonZack/simplejrpc-go => /root/gmssh-src/gmssh-app-skeleton-go
|
||||
@@ -0,0 +1,20 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sourcegraph/jsonrpc2 v0.2.1 h1:2GtljixMQYUYCmIg7W9aF2dFmniq/mOr2T9tFRh6zSQ=
|
||||
github.com/sourcegraph/jsonrpc2 v0.2.1/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -0,0 +1,347 @@
|
||||
// Package gsysctl 提供内核参数(sysctl)读写、持久化、快照备份/回滚、审计日志能力。
|
||||
// 对应 Python 版 backend/app/services/sysctl.py。
|
||||
package gsysctl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/user_TGPJp0KLpNsa5TAo/ddhv/core/consts"
|
||||
)
|
||||
|
||||
// ErrSysctl 表示 sysctl 操作错误
|
||||
type ErrSysctl struct{ Msg string }
|
||||
|
||||
func (e *ErrSysctl) Error() string { return e.Msg }
|
||||
|
||||
// NewErrSysctl 构造 sysctl 错误
|
||||
func NewErrSysctl(msg string) error { return &ErrSysctl{Msg: msg} }
|
||||
|
||||
func newErr(format string, a ...any) error {
|
||||
return &ErrSysctl{Msg: fmt.Sprintf(format, a...)}
|
||||
}
|
||||
|
||||
// Manager 内核参数管理器
|
||||
type Manager struct {
|
||||
backupDir string // 快照备份目录
|
||||
logFile string // 审计日志文件
|
||||
}
|
||||
|
||||
// 持久化配置文件(独立文件,不污染系统默认配置)
|
||||
const persistConf = "/etc/sysctl.d/99-gm-tune.conf"
|
||||
|
||||
// 需要同步的系统配置文件(避免 sysctl --system 重载时被旧值覆盖)
|
||||
var systemConfFiles = []string{
|
||||
"/etc/sysctl.conf",
|
||||
"/etc/sysctl.d/99-sysctl.conf",
|
||||
"/etc/sysctl.d/99-bbr.conf",
|
||||
}
|
||||
|
||||
// 快照前缀
|
||||
const snapshotPrefix = "snapshot_"
|
||||
|
||||
// NewManager 创建管理器,确保目录存在
|
||||
func NewManager() *Manager {
|
||||
backup := filepath.Join(consts.APP_DATA_PATH, "backups")
|
||||
logF := filepath.Join(consts.APP_DATA_PATH, "logs", "audit.log")
|
||||
os.MkdirAll(backup, 0o755)
|
||||
os.MkdirAll(filepath.Dir(logF), 0o755)
|
||||
return &Manager{backupDir: backup, logFile: logF}
|
||||
}
|
||||
|
||||
// run 执行命令并返回 stdout
|
||||
func run(cmd ...string) (string, error) {
|
||||
c := exec.Command(cmd[0], cmd[1:]...)
|
||||
out, err := c.Output()
|
||||
if err != nil {
|
||||
if ee, ok := err.(*exec.ExitError); ok {
|
||||
return "", newErr("命令执行失败: %s => %s", strings.Join(cmd, " "), strings.TrimSpace(string(ee.Stderr)))
|
||||
}
|
||||
return "", newErr("命令执行失败: %s => %v", strings.Join(cmd, " "), err)
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
// GetValue 读取单个内核参数当前值
|
||||
func GetValue(key string) (string, error) {
|
||||
out, err := run("sysctl", "-n", key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(out), nil
|
||||
}
|
||||
|
||||
// GetAll 读取全部内核参数
|
||||
func GetAll() (map[string]string, error) {
|
||||
out, err := run("sysctl", "-a")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := map[string]string{}
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || !strings.Contains(line, "=") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
result[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetValues 批量读取指定参数
|
||||
func GetValues(keys []string) (map[string]string, error) {
|
||||
result := map[string]string{}
|
||||
for _, k := range keys {
|
||||
v, err := GetValue(k)
|
||||
if err != nil {
|
||||
v = ""
|
||||
}
|
||||
result[k] = v
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetValue 设置单个内核参数并持久化
|
||||
func SetValue(key, value string, persist bool) error {
|
||||
if _, err := run("sysctl", "-w", fmt.Sprintf("%s=%s", key, value)); err != nil {
|
||||
return err
|
||||
}
|
||||
if persist {
|
||||
return persistValue(key, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetValues 批量设置,返回每个参数的执行结果
|
||||
func SetValues(pairs map[string]string, persist bool) map[string]any {
|
||||
results := map[string]any{}
|
||||
for k, v := range pairs {
|
||||
if err := SetValue(k, v, persist); err != nil {
|
||||
results[k] = map[string]any{"ok": false, "error": err.Error()}
|
||||
} else {
|
||||
results[k] = map[string]any{"ok": true, "value": v}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// syncSystemConf 同步同名参数到系统配置文件(只更新已存在的同名非注释行)
|
||||
func syncSystemConf(key, value string) {
|
||||
re := regexp.MustCompile(fmt.Sprintf(`^\s*%s\s*=`, regexp.QuoteMeta(key)))
|
||||
for _, path := range systemConfFiles {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
changed := false
|
||||
for i, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if re.MatchString(line) && !strings.HasPrefix(trimmed, "#") {
|
||||
lines[i] = fmt.Sprintf("%s = %s", key, value)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
os.WriteFile(path, []byte(strings.Join(lines, "\n")), 0o644)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// persistValue 将参数写入持久化配置并同步系统配置
|
||||
func persistValue(key, value string) error {
|
||||
lines := []string{}
|
||||
if data, err := os.ReadFile(persistConf); err == nil {
|
||||
lines = strings.Split(string(data), "\n")
|
||||
}
|
||||
re := regexp.MustCompile(fmt.Sprintf(`^\s*%s\s*=`, regexp.QuoteMeta(key)))
|
||||
newLines := []string{}
|
||||
for _, line := range lines {
|
||||
if !re.MatchString(line) {
|
||||
newLines = append(newLines, line)
|
||||
}
|
||||
}
|
||||
newLines = append(newLines, fmt.Sprintf("%s = %s", key, value))
|
||||
os.MkdirAll(filepath.Dir(persistConf), 0o755)
|
||||
if err := os.WriteFile(persistConf, []byte(strings.Join(newLines, "\n")+"\n"), 0o644); err != nil {
|
||||
return newErr("写入持久化配置失败: %v", err)
|
||||
}
|
||||
syncSystemConf(key, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePersist 从持久化配置中移除参数
|
||||
func RemovePersist(key string) error {
|
||||
data, err := os.ReadFile(persistConf)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
re := regexp.MustCompile(fmt.Sprintf(`^\s*%s\s*=`, regexp.QuoteMeta(key)))
|
||||
lines := []string{}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if !re.MatchString(line) {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
return os.WriteFile(persistConf, []byte(strings.Join(lines, "\n")), 0o644)
|
||||
}
|
||||
|
||||
// SnapshotMeta 快照元信息
|
||||
type SnapshotMeta struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Reason string `json:"reason"`
|
||||
Kind string `json:"kind"`
|
||||
ParamCount int `json:"param_count"`
|
||||
}
|
||||
|
||||
// CreateSnapshot 创建当前全部内核参数快照
|
||||
func (m *Manager) CreateSnapshot(reason, kind string) (SnapshotMeta, error) {
|
||||
ts := time.Now().Format("20060102_150405")
|
||||
name := snapshotPrefix + ts
|
||||
dir := filepath.Join(m.backupDir, name)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return SnapshotMeta{}, err
|
||||
}
|
||||
// 全部参数快照
|
||||
params, err := GetAll()
|
||||
if err != nil {
|
||||
return SnapshotMeta{}, err
|
||||
}
|
||||
keys := make([]string, 0, len(params))
|
||||
for k := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var sb strings.Builder
|
||||
for _, k := range keys {
|
||||
sb.WriteString(fmt.Sprintf("%s = %s\n", k, params[k]))
|
||||
}
|
||||
os.WriteFile(filepath.Join(dir, "sysctl_all.conf"), []byte(sb.String()), 0o644)
|
||||
// 持久化配置副本
|
||||
if data, err := os.ReadFile(persistConf); err == nil {
|
||||
os.WriteFile(filepath.Join(dir, "persist.conf.bak"), data, 0o644)
|
||||
}
|
||||
// 元信息
|
||||
meta := SnapshotMeta{
|
||||
Name: name, CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||
Reason: reason, Kind: kind, ParamCount: len(params),
|
||||
}
|
||||
mb, _ := json.MarshalIndent(meta, "", " ")
|
||||
os.WriteFile(filepath.Join(dir, "meta.json"), mb, 0o644)
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
// ListSnapshots 列出所有快照(按时间倒序)
|
||||
func (m *Manager) ListSnapshots() []SnapshotMeta {
|
||||
snapshots := []SnapshotMeta{}
|
||||
entries, err := os.ReadDir(m.backupDir)
|
||||
if err != nil {
|
||||
return snapshots
|
||||
}
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() || !strings.HasPrefix(e.Name(), snapshotPrefix) {
|
||||
continue
|
||||
}
|
||||
meta := SnapshotMeta{Name: e.Name(), Kind: "manual"}
|
||||
if data, err := os.ReadFile(filepath.Join(m.backupDir, e.Name(), "meta.json")); err == nil {
|
||||
json.Unmarshal(data, &meta)
|
||||
}
|
||||
snapshots = append(snapshots, meta)
|
||||
}
|
||||
sort.Slice(snapshots, func(i, j int) bool { return snapshots[i].Name > snapshots[j].Name })
|
||||
return snapshots
|
||||
}
|
||||
|
||||
// RestoreSnapshot 回滚到指定快照
|
||||
func (m *Manager) RestoreSnapshot(name string) (map[string]any, error) {
|
||||
dir := filepath.Join(m.backupDir, name)
|
||||
confPath := filepath.Join(dir, "sysctl_all.conf")
|
||||
if _, err := os.Stat(confPath); err != nil {
|
||||
return nil, newErr("快照不存在: %s", name)
|
||||
}
|
||||
data, err := os.ReadFile(confPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
restored := map[string]string{}
|
||||
failed := map[string]string{}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
k, v := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
|
||||
if _, err := run("sysctl", "-w", fmt.Sprintf("%s=%s", k, v)); err != nil {
|
||||
failed[k] = err.Error()
|
||||
} else {
|
||||
restored[k] = v
|
||||
}
|
||||
}
|
||||
// 恢复持久化配置
|
||||
persistBackup := filepath.Join(dir, "persist.conf.bak")
|
||||
if _, err := os.Stat(persistBackup); err == nil {
|
||||
if pb, err := os.ReadFile(persistBackup); err == nil {
|
||||
os.WriteFile(persistConf, pb, 0o644)
|
||||
for k, v := range restored {
|
||||
syncSystemConf(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return map[string]any{
|
||||
"snapshot": name, "restored_count": len(restored), "failed_count": len(failed), "failed": failed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LogAction 记录操作审计日志
|
||||
func (m *Manager) LogAction(action string, detail map[string]any) {
|
||||
entry := map[string]any{"time": time.Now().Format("2006-01-02 15:04:05"), "action": action}
|
||||
for k, v := range detail {
|
||||
entry[k] = v
|
||||
}
|
||||
os.MkdirAll(filepath.Dir(m.logFile), 0o755)
|
||||
jb, _ := json.Marshal(entry)
|
||||
f, _ := os.OpenFile(m.logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if f != nil {
|
||||
f.WriteString(string(jb) + "\n")
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// AuditLogs 读取审计日志(最近 n 条,倒序)
|
||||
func (m *Manager) AuditLogs(limit int) []map[string]any {
|
||||
lines := []string{}
|
||||
if data, err := os.ReadFile(m.logFile); err == nil {
|
||||
lines = strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
}
|
||||
// 倒序(最新在前)
|
||||
result := []map[string]any{}
|
||||
for i := len(lines) - 1; i >= 0 && len(result) < limit; i-- {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
var entry map[string]any
|
||||
if json.Unmarshal([]byte(line), &entry) == nil {
|
||||
result = append(result, entry)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// BoolToInt bool 转 int(0/1),供前端逻辑使用
|
||||
func BoolToInt(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
// Package gtuner 提供系统调优核心服务(检测/一键优化/场景预设/TCP加速/Swappiness)。
|
||||
// 对应 Python 版 backend/app/services/tuner.py。
|
||||
package gtuner
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/user_TGPJp0KLpNsa5TAo/ddhv/core/consts"
|
||||
"github.com/user_TGPJp0KLpNsa5TAo/ddhv/gsysctl"
|
||||
)
|
||||
|
||||
// TunerService 系统调优服务
|
||||
type TunerService struct {
|
||||
S *gsysctl.Manager
|
||||
}
|
||||
|
||||
// NewTuner 构造服务
|
||||
func NewTuner() TunerService {
|
||||
return TunerService{S: gsysctl.NewManager()}
|
||||
}
|
||||
|
||||
// ========== 状态持久化 ==========
|
||||
|
||||
// statePath state.json 路径
|
||||
func statePath() string {
|
||||
return filepath.Join(consts.APP_DATA_PATH, "state.json")
|
||||
}
|
||||
|
||||
// SaveState 保存应用状态(当前预设等)
|
||||
func (t TunerService) SaveState(state map[string]any) {
|
||||
path := statePath()
|
||||
os.MkdirAll(filepath.Dir(path), 0o755)
|
||||
if data, err := json.MarshalIndent(state, "", " "); err == nil {
|
||||
os.WriteFile(path, data, 0o644)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadState 读取应用状态
|
||||
func (t TunerService) LoadState() map[string]any {
|
||||
path := statePath()
|
||||
if data, err := os.ReadFile(path); err == nil {
|
||||
var m map[string]any
|
||||
if json.Unmarshal(data, &m) == nil {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return map[string]any{}
|
||||
}
|
||||
|
||||
// GetCurrentPreset 获取当前生效的预设方案
|
||||
func (t TunerService) GetCurrentPreset() (map[string]any, error) {
|
||||
state := t.LoadState()
|
||||
key := ""
|
||||
if v, ok := state["current_preset"].(string); ok {
|
||||
key = v
|
||||
}
|
||||
presets := t.GetPresets()
|
||||
if key != "" {
|
||||
if p, ok := presets[key].(map[string]any); ok {
|
||||
name, _ := p["name"].(string)
|
||||
appliedAt := ""
|
||||
if v, ok := state["preset_applied_at"].(string); ok {
|
||||
appliedAt = v
|
||||
}
|
||||
return map[string]any{"current": key, "name": name, "applied_at": appliedAt}, nil
|
||||
}
|
||||
}
|
||||
return map[string]any{"current": nil, "name": nil, "applied_at": ""}, nil
|
||||
}
|
||||
|
||||
// ========== 系统检测 ==========
|
||||
|
||||
// CheckRoot 检查 root 权限
|
||||
func (t TunerService) CheckRoot() (map[string]any, error) {
|
||||
isRoot := os.Geteuid() == 0
|
||||
return map[string]any{
|
||||
"is_root": isRoot, "proc_writable": isRoot, "ok": isRoot,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DetectHardware 检测硬件环境
|
||||
func (t TunerService) DetectHardware() (map[string]any, error) {
|
||||
info := map[string]any{
|
||||
"os": runtime.GOOS + " linux", "arch": runtime.GOARCH,
|
||||
"cpu_cores": runtime.NumCPU(), "memory_gb": 0.0,
|
||||
"disk_type": "unknown", "is_container": false, "virtualization": "none",
|
||||
}
|
||||
// 内存
|
||||
if data, err := os.ReadFile("/proc/meminfo"); err == nil {
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if strings.HasPrefix(line, "MemTotal:") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 {
|
||||
if kb, err := strconv.ParseInt(fields[1], 10, 64); err == nil {
|
||||
gb := float64(kb) / 1024 / 1024
|
||||
info["memory_gb"] = float64(int(gb*10+0.5)) / 10
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// 磁盘类型
|
||||
for _, dev := range []string{"sda", "vda"} {
|
||||
if data, err := os.ReadFile("/sys/block/" + dev + "/queue/rotational"); err == nil {
|
||||
if strings.TrimSpace(string(data)) == "1" {
|
||||
info["disk_type"] = "hdd"
|
||||
} else {
|
||||
info["disk_type"] = "ssd"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// 容器检测
|
||||
if data, err := os.ReadFile("/proc/1/cgroup"); err == nil {
|
||||
c := string(data)
|
||||
if strings.Contains(c, "docker") || strings.Contains(c, "kubepods") || strings.Contains(c, "lxc") {
|
||||
info["is_container"] = true
|
||||
info["virtualization"] = "container"
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat("/.dockerenv"); err == nil {
|
||||
info["is_container"] = true
|
||||
info["virtualization"] = "docker"
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// DetectSwap 检测 swap
|
||||
func (t TunerService) DetectSwap() (map[string]any, error) {
|
||||
enabled := false
|
||||
if data, err := os.ReadFile("/proc/swaps"); err == nil {
|
||||
for _, line := range strings.Split(string(data), "\n")[1:] {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(line, "Filename") && len(strings.Fields(line)) > 0 {
|
||||
enabled = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return map[string]any{"swap_enabled": enabled}, nil
|
||||
}
|
||||
|
||||
// DetectTCP 检测 TCP 配置
|
||||
func (t TunerService) DetectTCP() (map[string]any, error) {
|
||||
keys := []string{
|
||||
"net.ipv4.tcp_congestion_control", "net.core.default_qdisc",
|
||||
"net.ipv4.tcp_fastopen", "net.ipv4.tcp_syncookies",
|
||||
"net.ipv4.tcp_rmem", "net.ipv4.tcp_wmem",
|
||||
"net.core.rmem_max", "net.core.wmem_max",
|
||||
"net.core.somaxconn", "net.ipv4.tcp_max_syn_backlog",
|
||||
"net.ipv4.tcp_tw_reuse", "net.ipv4.tcp_slow_start_after_idle",
|
||||
"net.ipv4.tcp_mtu_probing", "net.ipv4.ip_local_port_range",
|
||||
}
|
||||
values, _ := gsysctl.GetValues(keys)
|
||||
// BBR 支持检测
|
||||
result := map[string]any{}
|
||||
for k, v := range values {
|
||||
result[k] = v
|
||||
}
|
||||
bbrAvailable := false
|
||||
if out, err := gsysctl.GetValue("net.ipv4.tcp_available_congestion_control"); err == nil {
|
||||
if strings.Contains(strings.ToLower(out), "bbr") {
|
||||
bbrAvailable = true
|
||||
}
|
||||
}
|
||||
result["bbr_available"] = bbrAvailable
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Detect 综合检测
|
||||
func (t TunerService) Detect() (map[string]any, error) {
|
||||
hw, _ := t.DetectHardware()
|
||||
tcp, _ := t.DetectTCP()
|
||||
swap, _ := t.DetectSwap()
|
||||
preset, _ := t.GetCurrentPreset()
|
||||
root, _ := t.CheckRoot()
|
||||
init, _ := t.InitStatus()
|
||||
return map[string]any{
|
||||
"hardware": hw, "tcp": tcp, "swap": swap, "preset": preset,
|
||||
"root": root, "init": init,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ========== 场景预设 ==========
|
||||
|
||||
// GetPresets 返回可用的调优场景预设
|
||||
func (t TunerService) GetPresets() map[string]any {
|
||||
return map[string]any{
|
||||
"balanced": map[string]any{
|
||||
"name": "均衡模式",
|
||||
"desc": "适合大多数服务器,平衡性能与稳定性",
|
||||
"params": map[string]string{
|
||||
"vm.swappiness": "10",
|
||||
"net.core.default_qdisc": "fq",
|
||||
"net.ipv4.tcp_congestion_control": "bbr",
|
||||
"net.ipv4.tcp_fastopen": "3",
|
||||
"net.ipv4.tcp_slow_start_after_idle": "0",
|
||||
"net.ipv4.tcp_mtu_probing": "1",
|
||||
"net.core.somaxconn": "1024",
|
||||
"net.ipv4.tcp_max_syn_backlog": "4096",
|
||||
},
|
||||
},
|
||||
"performance": map[string]any{
|
||||
"name": "高性能模式",
|
||||
"desc": "追求极限吞吐,适合高并发业务(内存≥4G推荐)",
|
||||
"params": map[string]string{
|
||||
"vm.swappiness": "5",
|
||||
"vm.vfs_cache_pressure": "50",
|
||||
"net.core.default_qdisc": "fq",
|
||||
"net.ipv4.tcp_congestion_control": "bbr",
|
||||
"net.ipv4.tcp_fastopen": "3",
|
||||
"net.ipv4.tcp_slow_start_after_idle": "0",
|
||||
"net.ipv4.tcp_mtu_probing": "1",
|
||||
"net.core.somaxconn": "4096",
|
||||
"net.ipv4.tcp_max_syn_backlog": "8192",
|
||||
"net.core.rmem_max": "16777216",
|
||||
"net.core.wmem_max": "16777216",
|
||||
"net.ipv4.tcp_rmem": "4096 87380 16777216",
|
||||
"net.ipv4.tcp_wmem": "4096 16384 16777216",
|
||||
"net.ipv4.tcp_tw_reuse": "1",
|
||||
},
|
||||
},
|
||||
"conservative": map[string]any{
|
||||
"name": "保守模式",
|
||||
"desc": "最小改动,仅调整最安全的参数",
|
||||
"params": map[string]string{
|
||||
"vm.swappiness": "10",
|
||||
"net.ipv4.tcp_slow_start_after_idle": "0",
|
||||
"net.ipv4.tcp_mtu_probing": "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyPreset 应用场景预设
|
||||
func (t TunerService) ApplyPreset(key string) (map[string]any, error) {
|
||||
presets := t.GetPresets()
|
||||
p, ok := presets[key].(map[string]any)
|
||||
if !ok {
|
||||
return nil, gsysctl.NewErrSysctl("未知的预设场景: " + key)
|
||||
}
|
||||
name, _ := p["name"].(string)
|
||||
params, _ := p["params"].(map[string]string)
|
||||
results := gsysctl.SetValues(params, true)
|
||||
okCount, failCount := 0, 0
|
||||
for _, r := range results {
|
||||
if m, ok := r.(map[string]any); ok {
|
||||
if v, ok := m["ok"].(bool); ok && v {
|
||||
okCount++
|
||||
} else {
|
||||
failCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
t.SaveState(map[string]any{
|
||||
"current_preset": key, "preset_applied_at": time.Now().Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
t.S.LogAction("apply_preset", map[string]any{
|
||||
"preset": key, "preset_name": name, "ok": okCount, "fail": failCount,
|
||||
})
|
||||
return map[string]any{
|
||||
"preset": key, "preset_name": name, "results": results,
|
||||
"ok_count": okCount, "fail_count": failCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InitStatus 检查是否已初始化
|
||||
func (t TunerService) InitStatus() (map[string]any, error) {
|
||||
snaps := t.S.ListSnapshots()
|
||||
for _, s := range snaps {
|
||||
if s.Kind == "init" {
|
||||
return map[string]any{"initialized": true, "snapshot": s}, nil
|
||||
}
|
||||
}
|
||||
return map[string]any{"initialized": false, "snapshot": nil}, nil
|
||||
}
|
||||
|
||||
// InitSnapshot 首次使用初始化快照
|
||||
func (t TunerService) InitSnapshot() (map[string]any, error) {
|
||||
snaps := t.S.ListSnapshots()
|
||||
for _, s := range snaps {
|
||||
if s.Kind == "init" {
|
||||
return map[string]any{"already": true, "snapshot": s}, nil
|
||||
}
|
||||
}
|
||||
meta, err := t.S.CreateSnapshot("首次使用初始化", "init")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.S.LogAction("init", map[string]any{"snapshot": meta.Name, "param_count": meta.ParamCount})
|
||||
return map[string]any{"already": false, "snapshot": meta}, nil
|
||||
}
|
||||
|
||||
// ========== 单独调优项 ==========
|
||||
|
||||
// SetSwappiness 设置 swappiness
|
||||
func (t TunerService) SetSwappiness(value int, persist bool) (map[string]any, error) {
|
||||
if value < 0 || value > 100 {
|
||||
return nil, gsysctl.NewErrSysctl("swappiness 必须在 0-100 之间")
|
||||
}
|
||||
err := gsysctl.SetValue("vm.swappiness", strconv.Itoa(value), persist)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
current, _ := gsysctl.GetValue("vm.swappiness")
|
||||
t.S.LogAction("set_swappiness", map[string]any{"value": value, "persist": persist})
|
||||
return map[string]any{"ok": true, "value": value, "current": current}, nil
|
||||
}
|
||||
|
||||
// EnableBBR 一键开启 BBR
|
||||
func (t TunerService) EnableBBR() (map[string]any, error) {
|
||||
results := gsysctl.SetValues(map[string]string{
|
||||
"net.core.default_qdisc": "fq",
|
||||
"net.ipv4.tcp_congestion_control": "bbr",
|
||||
}, true)
|
||||
current, _ := gsysctl.GetValue("net.ipv4.tcp_congestion_control")
|
||||
t.S.LogAction("enable_bbr", map[string]any{"current": current})
|
||||
return map[string]any{"results": results, "current": current}, nil
|
||||
}
|
||||
|
||||
// DisableBBR 关闭 BBR
|
||||
func (t TunerService) DisableBBR() (map[string]any, error) {
|
||||
results := gsysctl.SetValues(map[string]string{
|
||||
"net.ipv4.tcp_congestion_control": "cubic",
|
||||
}, true)
|
||||
current, _ := gsysctl.GetValue("net.ipv4.tcp_congestion_control")
|
||||
t.S.LogAction("disable_bbr", map[string]any{"current": current})
|
||||
return map[string]any{"results": results, "current": current}, nil
|
||||
}
|
||||
|
||||
// ========== 危险参数解锁 ==========
|
||||
|
||||
// GetDangerousParams 危险参数清单
|
||||
func (t TunerService) GetDangerousParams() map[string]any {
|
||||
return map[string]any{
|
||||
"vm.swappiness": map[string]any{"risk": "danger", "desc": "内存交换策略,过低可能导致内存压力,过高会导致频繁交换"},
|
||||
"vm.vfs_cache_pressure": map[string]any{"risk": "danger", "desc": "内核缓存回收倾向,过高可能导致缓存频繁失效"},
|
||||
"net.core.rmem_max": map[string]any{"risk": "danger", "desc": "最大接收缓冲区,过大可能浪费内存"},
|
||||
"net.core.wmem_max": map[string]any{"risk": "danger", "desc": "最大发送缓冲区,过大可能浪费内存"},
|
||||
"net.ipv4.tcp_rmem": map[string]any{"risk": "danger", "desc": "TCP 接收缓冲动态范围,不当设置影响吞吐"},
|
||||
"net.ipv4.tcp_wmem": map[string]any{"risk": "danger", "desc": "TCP 发送缓冲动态范围,不当设置影响吞吐"},
|
||||
"net.ipv4.tcp_tw_reuse": map[string]any{"risk": "danger", "desc": "TIME_WAIT 复用,NAT 环境下可能引发问题"},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// 主入口: GMSSH「系统调优」Go 版后端服务。
|
||||
// 基于官方 simplejrpc-go SDK, JSON-RPC 2.0 over Unix Socket。
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
rpc "github.com/DemonZack/simplejrpc-go"
|
||||
"github.com/DemonZack/simplejrpc-go/core"
|
||||
"github.com/DemonZack/simplejrpc-go/core/config"
|
||||
"github.com/DemonZack/simplejrpc-go/net/gsock"
|
||||
"github.com/DemonZack/simplejrpc-go/os/gpath"
|
||||
"github.com/user_TGPJp0KLpNsa5TAo/ddhv/core/consts"
|
||||
"github.com/user_TGPJp0KLpNsa5TAo/ddhv/server"
|
||||
)
|
||||
|
||||
// env 运行环境(test/prod),可通过环境变量 GMSSH_ENV 覆盖
|
||||
func startupEnv() string {
|
||||
if e := os.Getenv("GMSSH_ENV"); e == "prod" || e == "test" {
|
||||
return e
|
||||
}
|
||||
// 平台通常以 prod 运行正式版
|
||||
return "prod"
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 确保数据目录存在
|
||||
consts.EnsureDirs()
|
||||
|
||||
var socketPath string
|
||||
|
||||
// 1. 优先支持环境变量显式指定(本地测试用)
|
||||
if env := os.Getenv("APP_SOCKET"); env != "" {
|
||||
socketPath = env
|
||||
} else {
|
||||
// 2. 尝试按官方 SDK 规范从 config.json 读取 socket
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[*] config.json 读取失败(忽略): %v", r)
|
||||
}
|
||||
}()
|
||||
env := startupEnv()
|
||||
// 设置配置基础路径: 优先 main 所在目录(与 config.json 同目录), 兜底 consts
|
||||
binDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
if err == nil {
|
||||
gpath.GmCfgPath = binDir
|
||||
} else {
|
||||
gpath.GmCfgPath = consts.APP_BIN_PATH
|
||||
}
|
||||
if os.Getenv("CONFIG_PATH") != "" {
|
||||
gpath.GmCfgPath = ""
|
||||
}
|
||||
core.InitContainer(config.WithConfigEnvFormatterOptionFunc(env))
|
||||
val, err := core.Container.CfgFmt().GetValue("jsonrpc.sockets").String()
|
||||
if err == nil && val != "" {
|
||||
socketPath = val
|
||||
// 若为相对路径, 从 app/bin 定位到部署根 tmp (bin -> ../.. -> /tmp)
|
||||
if !filepath.IsAbs(socketPath) {
|
||||
socketPath = filepath.Join(binDir, "..", "..", "tmp", filepath.Base(socketPath))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 3. 兜底用 consts 绝对路径
|
||||
if socketPath == "" {
|
||||
socketPath = consts.APP_SOCKET_FILE_PATH
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Starting GMSSH system tuner backend, socket=%s\n", socketPath)
|
||||
if err := ds0(socketPath); err != nil {
|
||||
log.Fatalf("server error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ds0 创建并启动 RPC 服务
|
||||
func ds0(socketPath string) error {
|
||||
ds := rpc.NewDefaultServer(
|
||||
gsock.WithJsonRpcSimpleServiceHandler(gsock.NewJsonRpcSimpleServiceHandler()),
|
||||
gsock.WithJsonRpcSimpleServiceMiddlewares(),
|
||||
)
|
||||
|
||||
h := server.NewHandler()
|
||||
ds.RegisterHandle("hello", h.Hello)
|
||||
ds.RegisterHandle("ping", h.Ping)
|
||||
ds.RegisterHandle("tuner_check_root", h.TunerCheckRoot)
|
||||
ds.RegisterHandle("tuner_detect", h.TunerDetect)
|
||||
ds.RegisterHandle("tuner_current_preset", h.TunerCurrentPreset)
|
||||
ds.RegisterHandle("tuner_snapshots", h.TunerSnapshots)
|
||||
ds.RegisterHandle("tuner_init_status", h.TunerInitStatus)
|
||||
ds.RegisterHandle("tuner_init", h.TunerInit)
|
||||
ds.RegisterHandle("tuner_create_snapshot", h.TunerCreateSnapshot)
|
||||
ds.RegisterHandle("tuner_restore_snapshot", h.TunerRestoreSnapshot)
|
||||
ds.RegisterHandle("tuner_presets", h.TunerPresets)
|
||||
ds.RegisterHandle("tuner_apply_preset", h.TunerApplyPreset)
|
||||
ds.RegisterHandle("tuner_set_swappiness", h.TunerSetSwappiness)
|
||||
ds.RegisterHandle("tuner_enable_bbr", h.TunerEnableBBR)
|
||||
ds.RegisterHandle("tuner_disable_bbr", h.TunerDisableBBR)
|
||||
ds.RegisterHandle("tuner_get_param", h.TunerGetParam)
|
||||
ds.RegisterHandle("tuner_set_param", h.TunerSetParam)
|
||||
ds.RegisterHandle("tuner_dangerous_params", h.TunerDangerousParams)
|
||||
ds.RegisterHandle("tuner_audit_logs", h.TunerAuditLogs)
|
||||
|
||||
return ds.StartServer(socketPath)
|
||||
}
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
export LANG=C
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
pkill -f goddhv_main 2>/dev/null
|
||||
rm -f /tmp/goddhv_test.sock
|
||||
APP_SOCKET=/tmp/goddhv_test.sock nohup /tmp/goddhv_main > /tmp/goddhv_main.log 2>&1 &
|
||||
PID=$!
|
||||
sleep 1.5
|
||||
echo "server pid=$PID"
|
||||
ls -la /tmp/goddhv_test.sock 2>/dev/null && echo "SOCKET_READY"
|
||||
echo "=== log ==="
|
||||
cat /tmp/goddhv_main.log
|
||||
echo "=== client test ==="
|
||||
python3 /tmp/test_client.py
|
||||
pkill -f goddhv_main 2>/dev/null
|
||||
echo "done"
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
export LANG=C
|
||||
# 测试用 socket 路径(不碰真实部署)
|
||||
export APP_SOCKET=/tmp/goddhv_test.sock
|
||||
pkill -f goddhv_main 2>/dev/null
|
||||
rm -f /tmp/goddhv_test.sock
|
||||
nohup /tmp/goddhv_main > /tmp/goddhv_main.log 2>&1 &
|
||||
sleep 1
|
||||
echo "started, pid=$!"
|
||||
ls -la /tmp/goddhv_test.sock 2>/dev/null
|
||||
cat /tmp/goddhv_main.log 2>/dev/null | head -5
|
||||
@@ -0,0 +1,257 @@
|
||||
// Package server 提供 GMSSH JSON-RPC 接口层(19 个接口)。
|
||||
// 对应 Python 版 backend/app/server.py。
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/DemonZack/simplejrpc-go/net/gsock"
|
||||
"github.com/user_TGPJp0KLpNsa5TAo/ddhv/gsysctl"
|
||||
"github.com/user_TGPJp0KLpNsa5TAo/ddhv/gtuner"
|
||||
)
|
||||
|
||||
// Handler 聚合所有接口 handler
|
||||
type Handler struct {
|
||||
Sysctl *gsysctl.Manager
|
||||
Tuner gtuner.TunerService
|
||||
}
|
||||
|
||||
// NewHandler 构造 handler
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{Sysctl: gsysctl.NewManager(), Tuner: gtuner.NewTuner()}
|
||||
}
|
||||
|
||||
// params 提取请求参数为 map
|
||||
func params(req *gsock.Request) map[string]any {
|
||||
if req.RawRequest() == nil || req.RawRequest().Params == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
raw, _ := json.Marshal(req.RawRequest().Params)
|
||||
var m map[string]any
|
||||
_ = json.Unmarshal(raw, &m)
|
||||
return m
|
||||
}
|
||||
|
||||
// str 取字符串参数
|
||||
func str(p map[string]any, key string) string {
|
||||
if v, ok := p[key]; ok {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
return t
|
||||
case float64:
|
||||
return strconv.FormatFloat(t, 'f', -1, 64)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// boolP 取布尔参数
|
||||
func boolP(p map[string]any, key string) bool {
|
||||
if v, ok := p[key]; ok {
|
||||
if b, ok := v.(bool); ok {
|
||||
return b
|
||||
}
|
||||
if f, ok := v.(float64); ok {
|
||||
return f != 0
|
||||
}
|
||||
if s, ok := v.(string); ok {
|
||||
return s == "true" || s == "1"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// trueP 取布尔参数(默认 true)
|
||||
func trueP(p map[string]any, key string) bool {
|
||||
if v, ok := p[key]; ok {
|
||||
if b, ok := v.(bool); ok {
|
||||
return b
|
||||
}
|
||||
if f, ok := v.(float64); ok {
|
||||
return f != 0
|
||||
}
|
||||
if s, ok := v.(string); ok {
|
||||
return s == "true" || s == "1"
|
||||
}
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// intP 取整数参数
|
||||
func intP(p map[string]any, key string) int {
|
||||
if v, ok := p[key]; ok {
|
||||
if f, ok := v.(float64); ok {
|
||||
return int(f)
|
||||
}
|
||||
if s, ok := v.(string); ok {
|
||||
if i, err := strconv.Atoi(s); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// ========== 基础 ==========
|
||||
|
||||
// Hello 测试接口
|
||||
func (h *Handler) Hello(req *gsock.Request) (any, error) {
|
||||
return map[string]any{"message": "hello"}, nil
|
||||
}
|
||||
|
||||
// Ping 健康检查(平台要求返回 pong)
|
||||
func (h *Handler) Ping(req *gsock.Request) (any, error) {
|
||||
return "pong", nil
|
||||
}
|
||||
|
||||
// ========== 系统调优接口 ==========
|
||||
|
||||
func (h *Handler) TunerCheckRoot(req *gsock.Request) (any, error) {
|
||||
return h.Tuner.CheckRoot()
|
||||
}
|
||||
|
||||
func (h *Handler) TunerDetect(req *gsock.Request) (any, error) {
|
||||
return h.Tuner.Detect()
|
||||
}
|
||||
|
||||
func (h *Handler) TunerCurrentPreset(req *gsock.Request) (any, error) {
|
||||
return h.Tuner.GetCurrentPreset()
|
||||
}
|
||||
|
||||
func (h *Handler) TunerSnapshots(req *gsock.Request) (any, error) {
|
||||
snaps := h.Sysctl.ListSnapshots()
|
||||
list := make([]map[string]any, 0, len(snaps))
|
||||
for _, s := range snaps {
|
||||
list = append(list, map[string]any{
|
||||
"name": s.Name, "created_at": s.CreatedAt, "reason": s.Reason,
|
||||
"kind": s.Kind, "param_count": s.ParamCount,
|
||||
})
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (h *Handler) TunerInitStatus(req *gsock.Request) (any, error) {
|
||||
return h.Tuner.InitStatus()
|
||||
}
|
||||
|
||||
func (h *Handler) TunerInit(req *gsock.Request) (any, error) {
|
||||
return h.Tuner.InitSnapshot()
|
||||
}
|
||||
|
||||
func (h *Handler) TunerCreateSnapshot(req *gsock.Request) (any, error) {
|
||||
p := params(req)
|
||||
reason := str(p, "reason")
|
||||
if reason == "" {
|
||||
reason = "手动备份"
|
||||
}
|
||||
return h.Sysctl.CreateSnapshot(reason, "manual")
|
||||
}
|
||||
|
||||
func (h *Handler) TunerRestoreSnapshot(req *gsock.Request) (any, error) {
|
||||
p := params(req)
|
||||
name := str(p, "snapshot_name")
|
||||
if name == "" {
|
||||
return nil, errParam("缺少 snapshot_name 参数")
|
||||
}
|
||||
return h.Sysctl.RestoreSnapshot(name)
|
||||
}
|
||||
|
||||
func (h *Handler) TunerPresets(req *gsock.Request) (any, error) {
|
||||
return h.Tuner.GetPresets(), nil
|
||||
}
|
||||
|
||||
func (h *Handler) TunerApplyPreset(req *gsock.Request) (any, error) {
|
||||
p := params(req)
|
||||
key := str(p, "preset_key")
|
||||
if key == "" {
|
||||
return nil, errParam("缺少 preset_key 参数")
|
||||
}
|
||||
return h.Tuner.ApplyPreset(key)
|
||||
}
|
||||
|
||||
func (h *Handler) TunerSetSwappiness(req *gsock.Request) (any, error) {
|
||||
p := params(req)
|
||||
value := intP(p, "value")
|
||||
if value < 0 {
|
||||
return nil, errParam("缺少 value 参数")
|
||||
}
|
||||
persist := trueP(p, "persist")
|
||||
return h.Tuner.SetSwappiness(value, persist)
|
||||
}
|
||||
|
||||
func (h *Handler) TunerEnableBBR(req *gsock.Request) (any, error) {
|
||||
return h.Tuner.EnableBBR()
|
||||
}
|
||||
|
||||
func (h *Handler) TunerDisableBBR(req *gsock.Request) (any, error) {
|
||||
return h.Tuner.DisableBBR()
|
||||
}
|
||||
|
||||
func (h *Handler) TunerGetParam(req *gsock.Request) (any, error) {
|
||||
p := params(req)
|
||||
key := str(p, "key")
|
||||
if key == "" {
|
||||
return nil, errParam("缺少 key 参数")
|
||||
}
|
||||
value, err := gsysctl.GetValue(key)
|
||||
if err != nil {
|
||||
value = ""
|
||||
}
|
||||
return map[string]any{"key": key, "value": value}, nil
|
||||
}
|
||||
|
||||
func (h *Handler) TunerSetParam(req *gsock.Request) (any, error) {
|
||||
p := params(req)
|
||||
key := str(p, "key")
|
||||
value := str(p, "value")
|
||||
if key == "" || value == "" {
|
||||
return nil, errParam("缺少 key/value 参数")
|
||||
}
|
||||
unlocked := boolP(p, "unlocked")
|
||||
// 危险参数必须解锁
|
||||
if _, danger := h.Tuner.GetDangerousParams()[key]; danger && !unlocked {
|
||||
return map[string]any{"need_unlock": true}, errNeedUnlock()
|
||||
}
|
||||
if err := gsysctl.SetValue(key, value, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
current, _ := gsysctl.GetValue(key)
|
||||
h.Sysctl.LogAction("set_param", map[string]any{"key": key, "value": value, "unlocked": unlocked})
|
||||
return map[string]any{"key": key, "value": value, "ok": true, "current": current}, nil
|
||||
}
|
||||
|
||||
func (h *Handler) TunerDangerousParams(req *gsock.Request) (any, error) {
|
||||
return h.Tuner.GetDangerousParams(), nil
|
||||
}
|
||||
|
||||
func (h *Handler) TunerAuditLogs(req *gsock.Request) (any, error) {
|
||||
p := params(req)
|
||||
limit := intP(p, "limit")
|
||||
if limit <= 0 || limit > 500 {
|
||||
limit = 100
|
||||
}
|
||||
return h.Sysctl.AuditLogs(limit), nil
|
||||
}
|
||||
|
||||
// ========== 辅助错误 ==========
|
||||
|
||||
// errParam 参数错误
|
||||
func errParam(msg string) error {
|
||||
return &handleError{code: 400, msg: msg}
|
||||
}
|
||||
|
||||
// errNeedUnlock 需要解锁危险参数
|
||||
func errNeedUnlock() error {
|
||||
return &handleError{code: 403, msg: "危险参数需要先解锁才能修改"}
|
||||
}
|
||||
|
||||
type handleError struct {
|
||||
code int
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *handleError) Error() string { return e.msg }
|
||||
Reference in New Issue
Block a user