Files
kenaito-kopsc/README.md
T
odboy bd6f20d7c5 feat: 新增 proc nohup 后台运行、docker save/load 内置实现,移除 Podman 支持
- proc.nohup:后台运行命令并将 stdout/stderr 追加写入日志,
  Unix 下新会话、Windows 下 DETACHED_PROCESS 脱离终端
- docker.save:等价 docker save <镜像> -o <镜像名>.tar,文件名按
  仓库名+标签生成(alpine:3.20 → alpine_3.20.tar),传镜像 ID 时
  先解析出对应名称;docker.load 改为归档文件作位置参数
- 移除 Podman 支持,所有容器命令只走 docker
2026-11-01 07:47:57 +08:00

329 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# kopsc
跨平台命令助手。用一条平台无关的意图命令,在受支持的平台上执行对应的底层命令:
```bash
$ kopsc docker clearsafe --dry-run
[dry-run] 未执行任何命令
将执行: docker system prune -f
平台: linux/amd64, distro=centos, runtimes=docker
方式: 直接执行
命中: 变体 #1(2 分)Linux 上的 Docker 运行时
依据: 匹配 os=linux, runtime=docker (+2)
```
在 Linux 上它执行 `docker system prune -f`,macOS / Windows 上使用 Docker Desktop 的同一命令。
你只记住一个意图,kopsc 负责挑出当前平台可用的那条命令。
## 构建
本机构建:
```bash
go build -o bin/kopsc ./cmd/kopsc
```
### 跨平台编译脚本
`scripts/build` 是纯 Go 编写的构建脚本,用 `go run` 运行,不依赖 make / bash,在 Windows、macOS、Linux 上行为一致:
```bash
go run ./scripts/build # 默认编译全部 6 个目标到 dist/
go run ./scripts/build -host # 只编译当前 OS/ARCH
go run ./scripts/build -targets linux/amd64,windows/arm64
go run ./scripts/build -version 1.2.3 # 指定版本(默认 git describe --tags --always --dirty)
go run ./scripts/build -out bin -no-strip # 自定义输出目录、保留符号表
```
默认目标矩阵:`linux/{amd64,arm64}`、`darwin/{amd64,arm64}`、`windows/{amd64,arm64}`。
脚本统一设置 `CGO_ENABLED=0`(纯 Go 静态产物)与 `-trimpath`,产物命名为
`kopsc-<version>-<os>-<arch>[.exe]`,任一目标失败即以非零码退出。
等价的单条命令写法:
```bash
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath \
-ldflags "-s -w -X main.version=1.0.0" -o dist/kopsc-linux-amd64 ./cmd/kopsc
```
## 用法
```bash
kopsc docker ps # 列出运行中的容器
kopsc docker psall # 含已停止的容器
kopsc docker logs -f web # 查看日志(容器名必填,选项可透传)
kopsc docker exec -it web sh # 进入容器
kopsc docker compose up # 启动 Compose 服务
kopsc docker clearall --yes # 深度清理(破坏性命令,必须显式 --yes)
kopsc docker save alpine:3.20 # 自动保存为 alpine_3.20.tar(传镜像 ID 会先解析出名称)
kopsc docker load alpine_3.20.tar # 等价 docker load < alpine_3.20.tar
kopsc docker clearsafe # 直接执行当前平台对应的清理命令
kopsc docker clearsafe --dry-run # 只打印将执行的命令与判定依据
kopsc docker clearsafe -h # 该意图的说明、当前平台命中结果与选项
kopsc docker clearsafe -- --filter until=24h # -- 之后的参数原样透传给底层命令
kopsc diagnose web # 自动识别 Docker 容器或 K8s Pod,给出根因
kopsc diagnose api-0 -n prod # K8s:本命令里 -n 表示 namespace
kopsc diagnose api-0 --kind k8s --context staging
kopsc diagnose web --lines 200 --no-events
kopsc net pids 8080 # 等价 lsof -ti:8080,输出占用端口的 PID
kopsc net ports # 列出监听中的端口
kopsc disk usage /var # 磁盘占用排行(du -x --max-depth=1 -h | sort -hr 的跨平台版)
kopsc disk usage --path /var # 同上,用命名参数指定路径
kopsc disk usage /var --dry-run # 预览(内置命令同样支持)
kopsc proc nohup ./server # 后台运行命令,输出写日志(等价 nohup ./server > server.out 2>&1 &)
kopsc list # 全部命令及当前平台可用性
kopsc explain docker.clearsafe # 匹配过程、全部候选及各自被选中/拒绝的原因
kopsc platform # 探测到的 OS/ARCH/发行版/运行时
kopsc version # 版本与平台
```
### 磁盘占用排行(disk.usage)
`kopsc disk usage <路径>` 等价于 `du -x --max-depth=1 -h <路径> | sort -hr`,但由 kopsc
内置实现,因此 Linux、macOS、Windows 输出完全一致,不依赖 `du`、`sort` 或 PowerShell:
```text
$ kopsc disk usage /var
1.2G /var/lib
800.0M /var/cache
4.0K /var/tmp
0 /var/opt
总计 2.0G /var
```
- 路径**必填**:位置参数(`/var`)或 `--path /var`、`--path=/var` 均可,一次一个。
- 统计每个直接子项:普通文件按自身大小,目录递归累计其下普通文件的大小。
- 不跟随符号链接;Unix 上通过设备号实现 `-x`(不跨文件系统),Windows 上不跟随
reparse point,目录遍历本身不会跨越卷。
- 输出按大小降序,大小采用与 `du -h` 相同的二进制单位(1.0K、1.5M、2.0G)。
- 末尾输出该路径的合计行。
### 选项解析规则
叶子命令禁用了 cobra 的 flag 解析,自行处理:
| 参数 | 行为 |
| --- | --- |
| `-n`, `--dry-run`, `--dry-run=true` | 置为预览模式,不执行。**例外**:`kopsc diagnose` 中 `-n` 表示 namespace,预览请用 `--dry-run` |
| `--dry-run=false` | 关闭预览模式 |
| `-y`, `--yes` | 确认执行破坏性命令;未提供时只打印影响范围并以退出码 2 结束 |
| `-h`, `--help` | 打印该命令的帮助 |
| `--` | 其后的所有参数原样透传给底层命令(并全部计入位置参数) |
| 其它任何参数 | 原样透传给底层命令(包括未知 flag) |
因此底层命令的 flag 不会与 kopsc 自身的选项冲突。
需要容器名/镜像名的命令会做必填校验(例如 `kopsc docker logs` 缺容器名时直接报错,
并给出 `kopsc docker logs <容器>` 的用法),避免把参数错误留给底层命令才发现。
## 内置命令
### Docker(`kopsc docker <子命令>`)
| 分组 | 命令 |
| --- | --- |
| 只读 | `ps`、`psall`、`images`、`stats`、`df`、`info`、`version`、`logs`、`inspect`、`networks`、`volumes` |
| 生命周期 | `start`、`stop`、`restart`、`rm`、`rmi`、`exec`、`pull`、`tag`、`save`、`load`、`cp` |
| 清理 | `clearsafe`、`pruneimages`、`prunecontainers`、`prunenets`、`prunebuilder`、`prunevolumes`、`clearall` |
| Compose | `compose up`、`compose down`、`compose ps`、`compose logs` |
- 所有 Docker 命令在三个平台都调用 `docker`(macOS / Windows 上即 Docker Desktop)。
- `logs`、`inspect`、`start`、`stop`、`restart`、`rm`、`rmi`、`exec`、`pull`、`tag`、`save`、`load`、`cp`
需要位置参数,缺失时直接报错并给出用法。
- 破坏性命令只有 `clearall`(`system prune -a -f`)与 `prunevolumes`(`volume prune -f`):
未带 `--yes` 时只打印影响范围与将执行的命令,并以退出码 2 结束。
- `docker compose` 对应 Compose v2。
- `docker clearsafe` 的安全契约:只加 `-f` 以便无人值守执行,**不**加 `-a`(不清理全部未使用镜像),
**不**加 `--volumes`(不删除卷),仅清理停止的容器、未使用的网络与悬空镜像。
### 镜像导出与导入(docker save / docker load)
`kopsc docker save <镜像名|镜像ID>` 等价于 `docker save <镜像> -o <镜像名>.tar`,归档文件自动命名:
```bash
kopsc docker save alpine:3.20 # → alpine_3.20.tar
kopsc docker save 9c6f07244728 # 按 ID 查到 alpine:3.20,同样保存为 alpine_3.20.tar
kopsc docker save sha256:9c6f07244728 --platform linux/arm64
kopsc docker load alpine_3.20.tar # 等价 docker load < alpine_3.20.tar
kopsc docker load a.tar b.tar # 一次导入多个
```
- 文件名取自镜像的**仓库名 + 标签**:标签前的冒号、路径分隔符 `/` 等无法用于文件名的字符改写为下划线,
例如 `registry.example.com:5000/team/app:1.0` → `registry.example.com_5000_team_app_1.0.tar`;
未打标签的镜像没有名称可依据,直接报错并提示先 `docker tag`。
- 传入镜像 ID(可省略 `sha256:` 前缀,也可用唯一前缀)时,先向运行时查询它对应的镜像名称,再据此命名。
同一 ID 有多个名称时要求直接指定名称,避免文件名含糊。
- `--platform` 等运行时选项原样透传给底层运行时。
- `docker load` 的归档文件是**位置参数**(对应原来的 `docker load < xxx.tar`),可一次给出多个;
`-i/--input` 仍然可用,传 `-` 仍表示从标准输入读取。
### 容器/Pod 诊断(diagnose)
`kopsc diagnose <名称>` 自动判断目标是 Docker 容器还是 Kubernetes Pod,输出状态、根因、依据、建议,并附最近日志与事件。只读,不修改任何对象。
```text
目标: web
来源: Docker (docker)
状态: exited ExitCode=137 OOMKilled=true RestartCount=3
镜像: nginx:1.25
判定: ❌ 容器因内存超限被 OOM kill
依据: State.Status=exited, ExitCode=137, OOMKilled=true, RestartCount=3
建议: 提高容器的内存限制,或降低应用峰值内存;可用 kopsc docker stats 观察
最近日志 (50 行):
...
最近事件 (1h):
...
```
| 参数 | 说明 |
| --- | --- |
| `--kind docker\|k8s` | 强制来源;默认自动识别:先 `docker inspect`,未命中再 `kubectl get pod` |
| `-n`, `-N`, `--namespace` | **仅 diagnose**:Kubernetes 命名空间。默认取 kubeconfig 当前上下文,未设置则为 `default` |
| `--context` | kubectl 上下文 |
| `-c`, `--container` | 多容器 Pod 只看指定容器(默认输出全部容器) |
| `--lines N` | 日志行数,默认 50 |
| `--no-logs` / `--no-events` | 跳过日志 / 事件采集 |
| `--dry-run` | 只预览将执行的诊断动作,不调用 docker/kubectl |
> `-n` 只在 `kopsc diagnose` 中表示 namespace;其它命令里 `-n` 仍是 `--dry-run` 的简写。
> Docker 模式下传 `--namespace`/`--context` 会直接报错,不会静默忽略。
判定覆盖:Docker 的 OOMKilled、ExitCode 137/143/126/127、正常退出、重启循环、unhealthy、created/paused/dead;
Kubernetes 的 CrashLoopBackOff、ImagePullBackOff/ErrImagePull、CreateContainerConfigError、OOMKilled、
Pending(结合 FailedScheduling/FailedMount 事件)、Evicted 等。
### 端口占用查询(net.pids)
`kopsc net pids <端口>` 等价 `lsof -ti:<端口>`:输出占用该端口的进程 PID,每行一个、升序、去重;没有进程占用时无输出并正常退出。
```bash
kopsc net pids 8080
kopsc net pids --port 8080
kopsc net pids 8080 --dry-run
```
底层按平台自动选择,但对外的命令与输出保持一致:
| 平台 | 依次尝试的底层来源 |
| --- | --- |
| Linux | `lsof -ti:<port>` → `ss -H -t -u -n -p`(解析 `pid=`) → 读 `/proc/net/*` 与 `/proc/*/fd`(纯内置,零外部依赖) |
| macOS | `lsof -ti:<port>`(系统自带,无 `/proc` 回退) |
| Windows | `netstat -ano`(解析本地地址与 PID) |
- 端口必须是 1–65535;非法端口直接报错,不执行任何外部命令。
- `lsof` 无匹配时退出码为 1 且无输出,kopsc 视为“没有进程占用”,返回 0。
- 只读命令,不需要 `--yes`。
### 其它
| 命令 | 说明 | 平台变体 |
| --- | --- | --- |
| `diagnose` | 容器/Pod 状态根因诊断(自动识别 Docker 与 Kubernetes) | Linux/macOS/Windows 均使用 kopsc 内置实现 |
| `net.pids` | 查找占用指定端口的进程 PID(等价 `lsof -ti:<端口>`) | Linux `lsof` → `ss` → `/proc`;macOS `lsof`;Windows `netstat -ano`。三平台命令与输出一致(PID 升序、每行一个) |
| `disk.usage` | 目录占用排行(等价 `du -x --max-depth=1 -h <路径> \| sort -hr`) | Linux/macOS/Windows 均使用 kopsc 内置实现,保证输出一致 |
| `net.ports` | 列出监听中的端口 | Linux `ss -tulnp`、macOS `lsof -iTCP -sTCP:LISTEN -nP`、Windows `netstat -ano` |
| `pkg.refreshindex` | 刷新包管理器索引 | centos `dnf makecache`、ID_LIKE rhel/fedora `dnf makecache`、ID_LIKE debian/ubuntu `apt-get update`、alpine `apk update`、macOS `brew update`、Windows `winget source update` |
| `proc.nohup` | 后台运行命令,输出写入日志(等价 `nohup <命令> > output.log 2>&1 &`) | Linux/macOS 内置实现(新会话 + 日志追加)、Windows 内置实现(`DETACHED_PROCESS` + 日志追加) |
### 后台运行(proc.nohup)
`kopsc proc nohup <命令> [参数...]` 等价于 `nohup <命令> > output.log 2>&1 &`:启动命令后立刻返回,关闭终端不会终止它。
```bash
kopsc proc nohup ./server -port 8080 # 日志写到 ./server.out(同 nohup.out)
kopsc proc nohup -o /var/log/server.log ./server # 指定日志
kopsc proc nohup -C /srv/app --env=APP_ENV=prod ./run.sh # 指定工作目录与环境变量
kopsc proc nohup -w ./migrate.sh # 等它结束,并回显日志、透传退出码
kopsc proc nohup --dry-run ./server # 只打印判定依据
```
- 标准输入接到空设备;标准输出与标准错误**都追加**写入日志文件(`>>` 语义),并自动创建日志目录。
- Unix 下子进程进入新会话、无控制终端(相当于 `setsid`),Windows 下以 `DETACHED_PROCESS` 启动。
- 选项只在**命令之前**解析:`kopsc proc nohup ffmpeg -i in.mp4` 中的 `-i` 原样给 ffmpeg,
不会被 kopsc 截获(因此命令自身的 `-n`/`--dry-run` 不会触发 kopsc 的预览)。
需要预览时把 `--dry-run` 写在命令之前、或用 `--` 分隔。
- 不经过 shell,`>`、`|`、`&&` 需要显式写 shell:`kopsc proc nohup sh -c 'a | b > c'`。
| 选项 | 说明 |
| --- | --- |
| `-o`, `--out`, `--output` | 日志文件,默认 `<程序名>.out` |
| `-C`, `--dir`, `--chdir` | 子进程工作目录(相对日志路径也基于它),默认当前目录 |
| `-E`, `--env NAME=VALUE` | 追加环境变量,可重复 |
| `-q`, `--quiet` | 不打印启动摘要(PID、日志路径) |
| `-w`, `--wait` | 前台等待命令退出,回显其写入的日志内容并透传退出码 |
| `--detach` / `--no-detach` | 默认脱离会话;`--no-detach` 保留当前会话(类似只加 `&`) |
## 平台识别
`kopsc platform` 展示探测结果,来源:
- OS/ARCH:编译目标 `runtime.GOOS` / `runtime.GOARCH`
- Linux 发行版:`/etc/os-release` 的 `ID` 与 `ID_LIKE`(文件缺失时视为未知,不报错)
- 容器运行时:`exec.LookPath` 探测 `docker`
## 匹配规则
每个命令由若干**变体**组成,变体声明它适用的环境约束(`OS`、`Arch`、`Distro`、
`DistroLike`、`Runtime`,留空表示不限制)。
1. 变体声明的每一个约束都必须满足,否则该变体被排除。
2. 满足的变体按特异性打分:`Distro` 精确命中 +3,`ID_LIKE` 命中 +2,`Runtime` 命中 +2,无约束的通用变体 0 分。
3. 取最高分。并列时先按运行时偏好(有运行时约束者优先,且 `docker` 在前),再按声明约束的数量(更具体者优先)。
4. 仍并列则报歧义错误;无任何变体匹配则报无匹配错误,并提示 `kopsc explain <命令>`。
5. 取 `--dry-run` 或 `explain` 可看到每个候选的得分与被拒原因。
例如 CentOS 上执行 `kopsc explain docker.clearsafe`:
```text
✓ #0 2 分 匹配 os=linux, runtime=docker (+2) -> docker system prune -f
✗ #1 0 分 os=darwin 不匹配(当前 linux) -> docker system prune -f
✗ #2 0 分 os=windows 不匹配(当前 linux) -> docker system prune -f
```
## 新增命令或平台变体
内置映射位于 [`internal/registry/builtin.go`](internal/registry/builtin.go)(纯 Go 注册表,无外部配置文件)。
新增一个变体即向对应 `Command.Variants` 追加一项:
```go
{
Match: Match{OS: "linux", Distro: "rocky", Runtime: "docker"},
Argv: []string{"docker", "system", "prune", "-f"},
Note: "Rocky Linux 上的 Docker",
},
```
- `Argv[0]` 是程序,其余是参数;需要 shell 语义时设 `Shell: true`(Linux/macOS 用 `sh -c`,Windows 用 `cmd /c`)。
- 当平台没有可移植的系统命令时,用 `Builtin` 代替 `Argv`(二者互斥):
`Builtin: "disk.usage"` 指向 [`internal/executor`](internal/executor) 中注册的实现,
这样三平台共用同一份逻辑。新增内置动作需在 `executor.builtins` 中注册。
- 新增命令时给 `Command` 填 `ID`(点号分隔,决定 CLI 层级)、`Summary`、`Action`,别名不得重复。
- 提交前跑 `go test ./...`:`registry.Validate()` 与快照测试会拦截重复 ID/别名、空变体、空 argv。
## 开发
```bash
gofmt -l . # 应无输出
go vet ./...
go test ./...
```
包结构:
```text
cmd/kopsc 入口,注入版本号
internal/platform 环境探测(可注入,便于测试)
internal/registry 命令模型、校验、内置注册表
internal/resolve 变体打分与选择
internal/executor 进程执行、内置动作、dry-run、退出码透传
internal/diagnose Docker 与 Kubernetes 的状态根因诊断(内置动作)
internal/cli cobra 命令树与辅助命令
scripts/build 跨平台构建脚本(go run ./scripts/build)
```