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:
shishi
2026-08-09 10:12:57 +08:00
commit 2f79b22936
12 changed files with 1256 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
dist/
mock_deploy*/
*.sock
*.log
Executable
+80
View File
@@ -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/||"
+11
View File
@@ -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"
+33
View File
@@ -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)
}
}
+14
View File
@@ -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
+20
View File
@@ -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=
+347
View File
@@ -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
View File
@@ -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 环境下可能引发问题"},
}
}
+109
View File
@@ -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)
}
+16
View File
@@ -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
View File
@@ -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
+257
View File
@@ -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 }