feat: 新增 file/svc/archive/sys/log/fw 分组并扩充 net、proc
- file:findbig 按大小、findrecent 按修改时间、findname 按名称查找文件 - svc:systemctl / launchctl / sc 统一接口,status、enable、disable、logs - archive:按扩展名自动识别格式并解压,支持 tar.gz/tgz/tar.bz2/tar.xz/tar/zip/gz - sysinfo:OS、CPU、内存、磁盘一览 - log:journalctl / log show / wevtutil 搜索关键字与查看最近日志 - fw:ufw / firewalld / iptables / ALF / netsh 统一接口,status、allow、deny - net 新增 ping、trace(mtr 优先)、dns(dig 优先)、curl HTTP 诊断 - proc 新增 topmem、topcpu、killname - 危险操作(svc enable/disable、proc killname、fw allow/deny)需 --yes 确认 - 注册表、executor 派发表、cli 分组说明与 README 同步更新
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// runProcTopMem implements `kopsc proc topmem`: show top N processes by memory.
|
||||
func runProcTopMem(ctx context.Context, req BuiltinRequest) error {
|
||||
opts, err := parseProcTopArgs(req.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
procs, err := listProcessesWithMem(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Slice(procs, func(i, j int) bool { return procs[i].mem > procs[j].mem })
|
||||
printProcTop(req, procs, opts.count, "内存")
|
||||
return nil
|
||||
}
|
||||
|
||||
// runProcTopCPU implements `kopsc proc topcpu`: show top N processes by CPU.
|
||||
func runProcTopCPU(ctx context.Context, req BuiltinRequest) error {
|
||||
opts, err := parseProcTopArgs(req.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
procs, err := listProcessesWithCPU(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Slice(procs, func(i, j int) bool { return procs[i].cpu > procs[j].cpu })
|
||||
printProcTop(req, procs, opts.count, "CPU")
|
||||
return nil
|
||||
}
|
||||
|
||||
type procTopOptions struct {
|
||||
count int
|
||||
}
|
||||
|
||||
func parseProcTopArgs(args []string) (procTopOptions, error) {
|
||||
opts := procTopOptions{count: 10}
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
switch {
|
||||
case arg == "--":
|
||||
remaining := args[i+1:]
|
||||
if len(remaining) > 0 {
|
||||
n, err := strconv.Atoi(remaining[0])
|
||||
if err != nil || n < 1 {
|
||||
return opts, fmt.Errorf("数量需要正整数,收到 %q", remaining[0])
|
||||
}
|
||||
opts.count = n
|
||||
}
|
||||
i = len(args)
|
||||
case arg == "-n" || arg == "--count":
|
||||
if i+1 >= len(args) {
|
||||
return opts, fmt.Errorf("%s 缺少数量", arg)
|
||||
}
|
||||
i++
|
||||
n, err := strconv.Atoi(args[i])
|
||||
if err != nil || n < 1 {
|
||||
return opts, fmt.Errorf("--count 需要正整数,收到 %q", args[i])
|
||||
}
|
||||
opts.count = n
|
||||
case strings.HasPrefix(arg, "--count="):
|
||||
n, err := strconv.Atoi(strings.TrimPrefix(arg, "--count="))
|
||||
if err != nil || n < 1 {
|
||||
return opts, fmt.Errorf("--count 需要正整数")
|
||||
}
|
||||
opts.count = n
|
||||
case strings.HasPrefix(arg, "-") && arg != "-":
|
||||
return opts, fmt.Errorf("未知选项 %q", arg)
|
||||
default:
|
||||
n, err := strconv.Atoi(arg)
|
||||
if err != nil || n < 1 {
|
||||
return opts, fmt.Errorf("数量需要正整数,收到 %q", arg)
|
||||
}
|
||||
opts.count = n
|
||||
}
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
type procEntry struct {
|
||||
PID int
|
||||
Name string
|
||||
Command string
|
||||
mem float64
|
||||
cpu float64
|
||||
}
|
||||
|
||||
func listProcessesWithMem(ctx context.Context) ([]procEntry, error) {
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
||||
return listProcMemPS(ctx)
|
||||
}
|
||||
return listProcMemTasklist(ctx)
|
||||
}
|
||||
|
||||
func listProcessesWithCPU(ctx context.Context) ([]procEntry, error) {
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
||||
return listProcCPUPS(ctx)
|
||||
}
|
||||
return listProcCPUTasklist(ctx)
|
||||
}
|
||||
|
||||
func listProcMemPS(ctx context.Context) ([]procEntry, error) {
|
||||
out, err := exec.CommandContext(ctx, "ps", "-eo", "pid=,rss=,comm=,args=").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ps 执行失败: %w", err)
|
||||
}
|
||||
var procs []procEntry
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 3 {
|
||||
continue
|
||||
}
|
||||
pid, _ := strconv.Atoi(fields[0])
|
||||
rss, _ := strconv.ParseFloat(fields[1], 64)
|
||||
name := fields[2]
|
||||
command := strings.Join(fields[3:], " ")
|
||||
if pid > 0 {
|
||||
procs = append(procs, procEntry{PID: pid, Name: name, Command: command, mem: rss})
|
||||
}
|
||||
}
|
||||
return procs, nil
|
||||
}
|
||||
|
||||
func listProcCPUPS(ctx context.Context) ([]procEntry, error) {
|
||||
out, err := exec.CommandContext(ctx, "ps", "-eo", "pid=,%cpu=,comm=,args=").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ps 执行失败: %w", err)
|
||||
}
|
||||
var procs []procEntry
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 3 {
|
||||
continue
|
||||
}
|
||||
pid, _ := strconv.Atoi(fields[0])
|
||||
cpu, _ := strconv.ParseFloat(fields[1], 64)
|
||||
name := fields[2]
|
||||
command := strings.Join(fields[3:], " ")
|
||||
if pid > 0 {
|
||||
procs = append(procs, procEntry{PID: pid, Name: name, Command: command, cpu: cpu})
|
||||
}
|
||||
}
|
||||
return procs, nil
|
||||
}
|
||||
|
||||
func listProcMemTasklist(ctx context.Context) ([]procEntry, error) {
|
||||
out, err := exec.CommandContext(ctx, "tasklist", "/fo", "csv", "/nh").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tasklist 执行失败: %w", err)
|
||||
}
|
||||
var procs []procEntry
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
fields := strings.Split(strings.TrimSpace(line), ",")
|
||||
if len(fields) < 5 {
|
||||
continue
|
||||
}
|
||||
name := strings.Trim(fields[0], `"`)
|
||||
pid, _ := strconv.Atoi(strings.Trim(fields[1], `"`))
|
||||
memStr := strings.Trim(fields[4], ` "K`)
|
||||
memStr = strings.ReplaceAll(memStr, ",", "")
|
||||
mem, _ := strconv.ParseFloat(memStr, 64)
|
||||
if pid > 0 {
|
||||
procs = append(procs, procEntry{PID: pid, Name: name, mem: mem})
|
||||
}
|
||||
}
|
||||
return procs, nil
|
||||
}
|
||||
|
||||
func listProcCPUTasklist(ctx context.Context) ([]procEntry, error) {
|
||||
return nil, fmt.Errorf("Windows 下 tasklist 不报告 CPU 使用率,请使用任务管理器")
|
||||
}
|
||||
|
||||
func printProcTop(req BuiltinRequest, procs []procEntry, count int, label string) {
|
||||
if len(procs) == 0 {
|
||||
fmt.Fprintln(req.Stdout, "没有获取到进程信息")
|
||||
return
|
||||
}
|
||||
if count > len(procs) {
|
||||
count = len(procs)
|
||||
}
|
||||
w := req.Stdout
|
||||
fmt.Fprintf(w, "=== 占用 %s 前 %d 的进程 ===\n\n", label, count)
|
||||
fmt.Fprintf(w, "%-8s %-8s %-20s %s\n", "PID", label, "进程", "命令")
|
||||
for i := 0; i < count; i++ {
|
||||
p := procs[i]
|
||||
memStr := ""
|
||||
if label == "内存" {
|
||||
memStr = humanSize(int64(p.mem * 1024))
|
||||
} else {
|
||||
memStr = fmt.Sprintf("%.1f%%", p.cpu)
|
||||
}
|
||||
cmd := p.Command
|
||||
if len(cmd) > 50 {
|
||||
cmd = cmd[:50] + "..."
|
||||
}
|
||||
fmt.Fprintf(w, "%-8d %-8s %-20s %s\n", p.PID, memStr, p.Name, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// runProcKillName implements `kopsc proc killname <name>`: kill processes by name.
|
||||
func runProcKillName(ctx context.Context, req BuiltinRequest) error {
|
||||
opts, err := parseProcKillNameArgs(req.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
procs, err := listProcesses(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var targets []processInfo
|
||||
for _, p := range procs {
|
||||
if strings.EqualFold(p.Name, opts.name) || strings.Contains(strings.ToLower(p.Command), strings.ToLower(opts.name)) {
|
||||
targets = append(targets, p)
|
||||
}
|
||||
}
|
||||
|
||||
if len(targets) == 0 {
|
||||
fmt.Fprintf(req.Stdout, "没有找到名为 %q 的进程\n", opts.name)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(req.Stdout, "找到 %d 个匹配进程:\n", len(targets))
|
||||
for _, p := range targets {
|
||||
fmt.Fprintf(req.Stdout, " %d %s %s\n", p.PID, p.Name, p.Command)
|
||||
}
|
||||
|
||||
if !opts.confirmed {
|
||||
fmt.Fprintf(req.Stdout, "\n这是破坏性操作,未执行。加 --yes 确认终止以上进程\n")
|
||||
return &ExitError{Code: 2}
|
||||
}
|
||||
|
||||
for _, p := range targets {
|
||||
if err := killPID(p.PID); err != nil {
|
||||
fmt.Fprintf(req.Stderr, "终止 PID %d 失败: %v\n", p.PID, err)
|
||||
} else {
|
||||
fmt.Fprintf(req.Stdout, "已终止 PID %d (%s)\n", p.PID, p.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type procKillNameOptions struct {
|
||||
name string
|
||||
confirmed bool
|
||||
}
|
||||
|
||||
func parseProcKillNameArgs(args []string) (procKillNameOptions, error) {
|
||||
var opts procKillNameOptions
|
||||
var positional []string
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
switch {
|
||||
case arg == "--":
|
||||
positional = append(positional, args[i+1:]...)
|
||||
i = len(args)
|
||||
case arg == "-y" || arg == "--yes":
|
||||
opts.confirmed = true
|
||||
case strings.HasPrefix(arg, "-") && arg != "-":
|
||||
return opts, fmt.Errorf("未知选项 %q", arg)
|
||||
default:
|
||||
positional = append(positional, arg)
|
||||
}
|
||||
}
|
||||
if len(positional) == 0 {
|
||||
return opts, fmt.Errorf("缺少进程名;用法: kopsc proc killname <进程名>")
|
||||
}
|
||||
opts.name = positional[0]
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func killPID(pid int) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
return exec.Command("taskkill", "/PID", strconv.Itoa(pid), "/F").Run()
|
||||
}
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return proc.Kill()
|
||||
}
|
||||
Reference in New Issue
Block a user