- 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 同步更新
270 lines
7.4 KiB
Go
270 lines
7.4 KiB
Go
package executor
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// runLogSearch implements `kopsc log search <keyword>`: search system logs.
|
|
func runLogSearch(ctx context.Context, req BuiltinRequest) error {
|
|
opts, err := parseLogSearchArgs(req.Args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch runtime.GOOS {
|
|
case "linux":
|
|
return logSearchLinux(ctx, opts, req)
|
|
case "darwin":
|
|
return logSearchDarwin(ctx, opts, req)
|
|
case "windows":
|
|
return logSearchWindows(ctx, opts, req)
|
|
default:
|
|
return fmt.Errorf("不支持的操作系统: %s", runtime.GOOS)
|
|
}
|
|
}
|
|
|
|
type logSearchOptions struct {
|
|
keyword string
|
|
since string
|
|
unit string
|
|
lines int
|
|
}
|
|
|
|
func parseLogSearchArgs(args []string) (logSearchOptions, error) {
|
|
opts := logSearchOptions{lines: 100}
|
|
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 == "--since":
|
|
if i+1 >= len(args) {
|
|
return opts, fmt.Errorf("--since 缺少时长,例如 --since 1h")
|
|
}
|
|
i++
|
|
opts.since = args[i]
|
|
case strings.HasPrefix(arg, "--since="):
|
|
opts.since = strings.TrimPrefix(arg, "--since=")
|
|
case arg == "-u" || arg == "--unit":
|
|
if i+1 >= len(args) {
|
|
return opts, fmt.Errorf("%s 缺少服务名", arg)
|
|
}
|
|
i++
|
|
opts.unit = args[i]
|
|
case strings.HasPrefix(arg, "--unit="):
|
|
opts.unit = strings.TrimPrefix(arg, "--unit=")
|
|
case arg == "-n" || arg == "--lines":
|
|
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("--lines 需要正整数,收到 %q", args[i])
|
|
}
|
|
opts.lines = n
|
|
case strings.HasPrefix(arg, "--lines="):
|
|
n, err := strconv.Atoi(strings.TrimPrefix(arg, "--lines="))
|
|
if err != nil || n < 1 {
|
|
return opts, fmt.Errorf("--lines 需要正整数")
|
|
}
|
|
opts.lines = 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 log search <关键字>")
|
|
}
|
|
opts.keyword = positional[0]
|
|
return opts, nil
|
|
}
|
|
|
|
func logSearchLinux(ctx context.Context, opts logSearchOptions, req BuiltinRequest) error {
|
|
args := []string{"--no-pager", "-n", strconv.Itoa(opts.lines), "--grep=" + opts.keyword}
|
|
if opts.since != "" {
|
|
since, err := normalizeSince(opts.since)
|
|
if err != nil {
|
|
return fmt.Errorf("--since: %w", err)
|
|
}
|
|
args = append(args, "--since="+since)
|
|
}
|
|
if opts.unit != "" {
|
|
args = append(args, "-u", opts.unit)
|
|
}
|
|
cmd := exec.CommandContext(ctx, "journalctl", args...)
|
|
cmd.Stdout = req.Stdout
|
|
cmd.Stderr = req.Stderr
|
|
return cmd.Run()
|
|
}
|
|
|
|
func logSearchDarwin(ctx context.Context, opts logSearchOptions, req BuiltinRequest) error {
|
|
predicate := fmt.Sprintf(`eventMessage CONTAINS[c] "%s"`, opts.keyword)
|
|
if opts.unit != "" {
|
|
predicate = fmt.Sprintf(`(subsystem == "%s" OR process == "%s") AND %s`, opts.unit, opts.unit, predicate)
|
|
}
|
|
since := "1h"
|
|
if opts.since != "" {
|
|
since = opts.since
|
|
}
|
|
cmd := exec.CommandContext(ctx, "log", "show", "--predicate", predicate, "--last", since, "--style", "compact")
|
|
cmd.Stdout = req.Stdout
|
|
cmd.Stderr = req.Stderr
|
|
return cmd.Run()
|
|
}
|
|
|
|
func logSearchWindows(ctx context.Context, opts logSearchOptions, req BuiltinRequest) error {
|
|
args := []string{"qe", "System", "/q:*[System[Provider]]", "/c:" + strconv.Itoa(opts.lines), "/rd:true", "/f:text"}
|
|
cmd := exec.CommandContext(ctx, "wevtutil", args...)
|
|
var stdout strings.Builder
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = req.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
scanner := bufio.NewScanner(strings.NewReader(stdout.String()))
|
|
count := 0
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if strings.Contains(strings.ToLower(line), strings.ToLower(opts.keyword)) {
|
|
fmt.Fprintln(req.Stdout, line)
|
|
count++
|
|
}
|
|
}
|
|
if count == 0 {
|
|
fmt.Fprintf(req.Stdout, "没有找到包含 %q 的日志\n", opts.keyword)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// normalizeSince converts shorthand like "1h", "30m" to journalctl --since format.
|
|
func normalizeSince(s string) (string, error) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return "", fmt.Errorf("时长不能为空")
|
|
}
|
|
if d, err := time.ParseDuration(s); err == nil {
|
|
t := time.Now().Add(-d)
|
|
return t.Format("2006-01-02 15:04:05"), nil
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// runLogLast implements `kopsc log last`: show recent system log entries.
|
|
func runLogLast(ctx context.Context, req BuiltinRequest) error {
|
|
opts, err := parseLogLastArgs(req.Args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch runtime.GOOS {
|
|
case "linux":
|
|
return logLastLinux(ctx, opts, req)
|
|
case "darwin":
|
|
return logLastDarwin(ctx, opts, req)
|
|
case "windows":
|
|
return logLastWindows(ctx, opts, req)
|
|
default:
|
|
return fmt.Errorf("不支持的操作系统: %s", runtime.GOOS)
|
|
}
|
|
}
|
|
|
|
type logLastOptions struct {
|
|
lines int
|
|
unit string
|
|
}
|
|
|
|
func parseLogLastArgs(args []string) (logLastOptions, error) {
|
|
opts := logLastOptions{lines: 50}
|
|
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.lines = n
|
|
}
|
|
i = len(args)
|
|
case arg == "-n" || arg == "--lines":
|
|
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("--lines 需要正整数,收到 %q", args[i])
|
|
}
|
|
opts.lines = n
|
|
case strings.HasPrefix(arg, "--lines="):
|
|
n, err := strconv.Atoi(strings.TrimPrefix(arg, "--lines="))
|
|
if err != nil || n < 1 {
|
|
return opts, fmt.Errorf("--lines 需要正整数")
|
|
}
|
|
opts.lines = n
|
|
case arg == "-u" || arg == "--unit":
|
|
if i+1 >= len(args) {
|
|
return opts, fmt.Errorf("%s 缺少服务名", arg)
|
|
}
|
|
i++
|
|
opts.unit = args[i]
|
|
case strings.HasPrefix(arg, "--unit="):
|
|
opts.unit = strings.TrimPrefix(arg, "--unit=")
|
|
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.lines = n
|
|
}
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
func logLastLinux(ctx context.Context, opts logLastOptions, req BuiltinRequest) error {
|
|
args := []string{"--no-pager", "-n", strconv.Itoa(opts.lines)}
|
|
if opts.unit != "" {
|
|
args = append(args, "-u", opts.unit)
|
|
}
|
|
cmd := exec.CommandContext(ctx, "journalctl", args...)
|
|
cmd.Stdout = req.Stdout
|
|
cmd.Stderr = req.Stderr
|
|
return cmd.Run()
|
|
}
|
|
|
|
func logLastDarwin(ctx context.Context, opts logLastOptions, req BuiltinRequest) error {
|
|
predicate := `eventMessage != ""`
|
|
if opts.unit != "" {
|
|
predicate = fmt.Sprintf(`subsystem == "%s" OR process == "%s"`, opts.unit, opts.unit)
|
|
}
|
|
cmd := exec.CommandContext(ctx, "log", "show", "--predicate", predicate, "--last", "5m", "--style", "compact")
|
|
cmd.Stdout = req.Stdout
|
|
cmd.Stderr = req.Stderr
|
|
return cmd.Run()
|
|
}
|
|
|
|
func logLastWindows(ctx context.Context, opts logLastOptions, req BuiltinRequest) error {
|
|
cmd := exec.CommandContext(ctx, "wevtutil", "qe", "System",
|
|
"/c:"+strconv.Itoa(opts.lines), "/rd:true", "/f:text")
|
|
cmd.Stdout = req.Stdout
|
|
cmd.Stderr = req.Stderr
|
|
return cmd.Run()
|
|
}
|