Files
odboy 400812d114 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 同步更新
2026-11-02 22:42:18 +08:00

290 lines
7.6 KiB
Go

package executor
import (
"context"
"fmt"
"os/exec"
"runtime"
"strconv"
"strings"
)
// runNetPing implements `kopsc net ping <host>`: connectivity check.
func runNetPing(ctx context.Context, req BuiltinRequest) error {
opts, err := parseNetPingArgs(req.Args)
if err != nil {
return err
}
args := []string{}
if opts.count > 0 {
if runtime.GOOS == "windows" {
args = append(args, "-n", strconv.Itoa(opts.count))
} else {
args = append(args, "-c", strconv.Itoa(opts.count))
}
}
if opts.interval > 0 {
if runtime.GOOS != "windows" {
args = append(args, "-i", strconv.Itoa(opts.interval))
}
}
args = append(args, opts.host)
cmd := exec.CommandContext(ctx, "ping", args...)
cmd.Stdout = req.Stdout
cmd.Stderr = req.Stderr
return cmd.Run()
}
type netPingOptions struct {
host string
count int
interval int
}
func parseNetPingArgs(args []string) (netPingOptions, error) {
opts := netPingOptions{count: 4}
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 == "-c" || 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("次数需要正整数,收到 %q", args[i])
}
opts.count = n
case arg == "-i" || arg == "--interval":
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("间隔需要正整数,收到 %q", args[i])
}
opts.interval = n
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 net ping <主机>")
}
opts.host = positional[0]
return opts, nil
}
// runNetTrace implements `kopsc net trace <host>`: route tracing.
func runNetTrace(ctx context.Context, req BuiltinRequest) error {
if len(req.Args) == 0 {
return fmt.Errorf("缺少目标主机;用法: kopsc net trace <主机>")
}
host := req.Args[0]
// Try mtr first (best output), then traceroute, then tracert on Windows.
if path, err := exec.LookPath("mtr"); err == nil {
cmd := exec.CommandContext(ctx, path, "--report", "--report-wide", host)
cmd.Stdout = req.Stdout
cmd.Stderr = req.Stderr
return cmd.Run()
}
if runtime.GOOS == "windows" {
cmd := exec.CommandContext(ctx, "tracert", host)
cmd.Stdout = req.Stdout
cmd.Stderr = req.Stderr
return cmd.Run()
}
if path, err := exec.LookPath("traceroute"); err == nil {
cmd := exec.CommandContext(ctx, path, host)
cmd.Stdout = req.Stdout
cmd.Stderr = req.Stderr
return cmd.Run()
}
return fmt.Errorf("未找到 traceroute 或 mtr,请安装其中之一")
}
// runNetDNS implements `kopsc net dns <host>`: DNS lookup.
func runNetDNS(ctx context.Context, req BuiltinRequest) error {
opts, err := parseNetDNSArgs(req.Args)
if err != nil {
return err
}
// Try dig first, then nslookup, then host.
if path, err := exec.LookPath("dig"); err == nil {
args := []string{opts.host}
if opts.recordType != "" {
args = []string{opts.host, opts.recordType}
}
if opts.server != "" {
args = append(args, "@"+opts.server)
}
args = append(args, "+noall", "+answer")
cmd := exec.CommandContext(ctx, path, args...)
cmd.Stdout = req.Stdout
cmd.Stderr = req.Stderr
return cmd.Run()
}
if path, err := exec.LookPath("nslookup"); err == nil {
args := []string{opts.host}
if opts.server != "" {
args = []string{opts.host, opts.server}
}
cmd := exec.CommandContext(ctx, path, args...)
cmd.Stdout = req.Stdout
cmd.Stderr = req.Stderr
return cmd.Run()
}
if path, err := exec.LookPath("host"); err == nil {
args := []string{opts.host}
if opts.server != "" {
args = append(args, opts.server)
}
cmd := exec.CommandContext(ctx, path, args...)
cmd.Stdout = req.Stdout
cmd.Stderr = req.Stderr
return cmd.Run()
}
return fmt.Errorf("未找到 dig、nslookup 或 host,请安装其中之一")
}
type netDNSOptions struct {
host string
recordType string
server string
}
func parseNetDNSArgs(args []string) (netDNSOptions, error) {
var opts netDNSOptions
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 == "-t" || arg == "--type":
if i+1 >= len(args) {
return opts, fmt.Errorf("%s 缺少记录类型", arg)
}
i++
opts.recordType = strings.ToUpper(args[i])
case strings.HasPrefix(arg, "--type="):
opts.recordType = strings.ToUpper(strings.TrimPrefix(arg, "--type="))
case arg == "-s" || arg == "--server":
if i+1 >= len(args) {
return opts, fmt.Errorf("%s 缺少 DNS 服务器", arg)
}
i++
opts.server = args[i]
case strings.HasPrefix(arg, "--server="):
opts.server = strings.TrimPrefix(arg, "--server=")
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 net dns <域名>")
}
opts.host = positional[0]
return opts, nil
}
// runNetCurl implements `kopsc net curl <url>`: HTTP diagnostics showing
// status code, time breakdown, and redirect chain.
func runNetCurl(ctx context.Context, req BuiltinRequest) error {
opts, err := parseNetCurlArgs(req.Args)
if err != nil {
return err
}
if _, err := exec.LookPath("curl"); err != nil {
return fmt.Errorf("net curl 需要 curl,请先安装")
}
args := []string{"-sS", "-o", "/dev/null", "-w",
"HTTP 状态码: %{http_code}\n" +
"重定向次数: %{num_redirects}\n" +
"DNS 解析: %{time_namelookup}s\n" +
"TCP 连接: %{time_connect}s\n" +
"TLS 握手: %{time_appconnect}s\n" +
"首字节: %{time_starttransfer}s\n" +
"总耗时: %{time_total}s\n" +
"下载大小: %{size_download} bytes\n" +
"下载速度: %{speed_download} bytes/s\n",
}
if opts.follow {
args = append(args, "-L")
}
if opts.method != "" {
args = append(args, "-X", opts.method)
}
for _, h := range opts.headers {
args = append(args, "-H", h)
}
args = append(args, opts.url)
cmd := exec.CommandContext(ctx, "curl", args...)
cmd.Stdout = req.Stdout
cmd.Stderr = req.Stderr
return cmd.Run()
}
type netCurlOptions struct {
url string
follow bool
method string
headers []string
}
func parseNetCurlArgs(args []string) (netCurlOptions, error) {
var opts netCurlOptions
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 == "-L" || arg == "--follow":
opts.follow = true
case arg == "-X" || arg == "--method":
if i+1 >= len(args) {
return opts, fmt.Errorf("%s 缺少 HTTP 方法", arg)
}
i++
opts.method = strings.ToUpper(args[i])
case arg == "-H" || arg == "--header":
if i+1 >= len(args) {
return opts, fmt.Errorf("%s 缺少 header 值", arg)
}
i++
opts.headers = append(opts.headers, args[i])
case strings.HasPrefix(arg, "-") && arg != "-":
return opts, fmt.Errorf("未知选项 %q", arg)
default:
positional = append(positional, arg)
}
}
if len(positional) == 0 {
return opts, fmt.Errorf("缺少 URL;用法: kopsc net curl <URL>")
}
opts.url = positional[0]
return opts, nil
}