| name | aether-dev |
| description | 开发环境部署与测试工具。支持手动部署分支到 dev、运行临时测试 Job、
绕过 CI 直接部署、查看 dev 环境日志等开发阶段操作。
使用场景:"部署这个分支到 dev"、"跑个测试 job"、"手动部署到开发环境"、"查看 dev 日志"
|
| argument-hint | <action> [args] |
| disable-model-invocation | false |
| user-invocable | true |
| allowed-tools | Bash, Read, Write, AskUserQuestion |
| dependencies | {"cli":{"required":true,"min_version":"0.7.0"}} |
Aether 开发环境工具 (aether-dev)
版本: 0.3.0 | 优先级: P1
前置检查
⚠️ 此 Skill 需要 aether CLI
source "${CLAUDE_PLUGIN_ROOT}/scripts/cli-functions.sh"
require_aether_cli || exit 1
快速开始
使用场景
- 部署当前分支到 dev 环境(不经过 CI)
- 运行临时测试 Job 验证功能
- CI 故障时手动部署到 dev
- 查看 dev 环境服务日志
- 清理 dev 环境中的测试 Job
不使用场景
- 部署到生产 → 使用
/aether:deploy
- 查看集群整体状态 → 使用
/aether:status
- 新项目接入 → 使用
/aether:init
子命令
1. deploy — 部署到 dev
/aether:dev deploy [service-name]
流程:
- 检测当前项目的部署配置(
deploy/nomad.hcl + Dockerfile)
- 确定部署模式(Docker / exec)
- Docker 模式:
- 在本地或 Runner 上构建镜像
- 推送到 Registry(tag 使用分支名或 commit SHA)
- 替换 nomad.hcl 中的镜像占位符
- exec 模式:
- 提交 Job 到 Nomad
- 等待部署完成
IMAGE="forgejo.10cg.pub/${ORG}/${PROJECT}:dev-$(git rev-parse --short HEAD)"
docker build -t ${IMAGE} .
docker push ${IMAGE}
sed "s|__IMAGE__|${IMAGE}|g" deploy/nomad.hcl | nomad job run -
rsync -avz --exclude '.git' ./ root@192.168.69.90:/opt/apps/${PROJECT}/
nomad job run deploy/nomad.hcl
与 CI 部署的区别:
| 维度 | CI 自动部署 | /aether:dev deploy |
|---|
| 触发 | push 到 main | 手动,任意分支 |
| 镜像 tag | commit SHA | dev-<short-sha> |
| 审批 | 无 | 无 |
| 用途 | 正式 dev 部署 | 临时测试、分支验证 |
2. run — 运行临时测试 Job
/aether:dev run [--docker|--exec] <command>
快速在集群上运行一个一次性任务,无需手写 nomad.hcl。
示例:
/aether:dev run --docker "nginx:alpine"
/aether:dev run --exec "/opt/apps/my-script/test.sh"
/aether:dev run --docker "my-api:dev-abc123" --port 3000
生成的临时 Job:
job "dev-temp-<timestamp>" {
region = "global"
datacenters = ["dc1"]
type = "batch"
constraint {
attribute = "${node.class}"
value = "<heavy_workload|light_exec>"
}
group "task" {
task "run" {
driver = "<docker|exec>"
config {
image = "<image>" # Docker 模式
command = "<command>" # exec 模式
}
resources {
cpu = 500
memory = 512
}
}
}
}
Job 名称带 dev-temp- 前缀,便于识别和清理。
3. logs — 查看 dev 服务日志
/aether:dev logs <service-name>
流程:
ALLOC_ID=$(curl -s "${NOMAD_ADDR}/v1/job/${SERVICE}/allocations" | \
jq -r '[.[] | select(.ClientStatus == "running")] | sort_by(.CreateTime) | last | .ID')
nomad alloc logs ${ALLOC_ID}
nomad alloc logs -stderr ${ALLOC_ID}
4. clean — 清理临时 Job
/aether:dev clean
清理所有 dev-temp-* 前缀的临时 Job:
curl -s "${NOMAD_ADDR}/v1/jobs" | jq -r '.[] | select(.Name | startswith("dev-temp-")) | .Name'
for JOB in $(curl -s "${NOMAD_ADDR}/v1/jobs" | jq -r '.[] | select(.Name | startswith("dev-temp-")) | .Name'); do
nomad job stop -purge ${JOB}
done
5. restart — 重启 dev 服务
/aether:dev restart <service-name>
重启服务的所有 allocation(不重新部署):
ALLOCS=$(curl -s "${NOMAD_ADDR}/v1/job/${SERVICE}/allocations" | \
jq -r '.[] | select(.ClientStatus == "running") | .ID')
for ALLOC in ${ALLOCS}; do
nomad alloc restart ${ALLOC}
done
错误处理
Docker 构建失败
if [ ! -f "Dockerfile" ]; then
echo "错误: 当前目录未找到 Dockerfile"
echo "修复:"
echo " 1. 运行 /aether:init 生成 Dockerfile"
echo " 2. 或手动创建: touch Dockerfile"
exit 1
fi
if ! docker build -t "${IMAGE}" . 2>&1; then
echo ""
echo "错误: Docker 镜像构建失败"
echo "常见原因:"
echo " - Dockerfile 语法错误(检查 FROM/RUN/COPY 指令)"
echo " - 依赖下载失败(检查网络连接或镜像源)"
echo " - COPY 的文件不存在(检查 .dockerignore 是否排除了必要文件)"
echo "修复: docker build -t ${IMAGE} . --no-cache --progress=plain"
exit 1
fi
Docker 推送失败
PUSH_OUTPUT=$(docker push "${IMAGE}" 2>&1)
PUSH_EXIT=$?
if [ $PUSH_EXIT -ne 0 ]; then
if echo "$PUSH_OUTPUT" | grep -qi "unauthorized\|authentication\|denied"; then
echo "错误: Registry 认证失败 (${AETHER_REGISTRY})"
echo "修复:"
echo " docker login ${AETHER_REGISTRY}"
echo " # 或检查凭据: cat ~/.docker/config.json"
elif echo "$PUSH_OUTPUT" | grep -qi "timeout\|connection refused\|no such host"; then
echo "错误: 无法连接到 Registry (${AETHER_REGISTRY})"
echo "修复:"
echo " 1. 检查 Registry 是否运行: curl -s https://${AETHER_REGISTRY}/v2/"
echo " 2. 检查 DNS 解析: nslookup ${AETHER_REGISTRY}"
echo " 3. 检查网络连通: ping ${AETHER_REGISTRY}"
else
echo "错误: 镜像推送失败"
echo "详情: ${PUSH_OUTPUT}"
fi
exit 1
fi
Nomad Job 提交失败
JOB_OUTPUT=$(nomad job run deploy/nomad.hcl 2>&1)
JOB_EXIT=$?
if [ $JOB_EXIT -ne 0 ]; then
if echo "$JOB_OUTPUT" | grep -qi "connection refused"; then
echo "错误: 无法连接到 Nomad (${NOMAD_ADDR})"
echo "修复: 检查 Nomad 服务状态或运行 /aether:setup 重新配置"
elif echo "$JOB_OUTPUT" | grep -qi "403\|permission\|token"; then
echo "错误: Nomad ACL 认证失败"
echo "修复: 检查 NOMAD_TOKEN 环境变量是否正确"
elif echo "$JOB_OUTPUT" | grep -qi "constraint"; then
echo "错误: 没有节点满足 Job 约束条件"
echo "修复:"
echo " 1. 检查节点状态: nomad node status"
echo " 2. 检查 nomad.hcl 中的 constraint 配置"
elif echo "$JOB_OUTPUT" | grep -qi "invalid\|parse error\|syntax"; then
echo "错误: nomad.hcl 配置文件语法错误"
echo "修复: nomad job validate deploy/nomad.hcl"
else
echo "错误: Job 提交失败"
echo "详情: ${JOB_OUTPUT}"
fi
exit 1
fi
网络超时
api_call() {
local url="$1"
local result
result=$(curl -s --connect-timeout 5 --max-time 15 "$url" 2>&1)
if [ $? -ne 0 ]; then
if echo "$result" | grep -qi "timed out"; then
echo "错误: 请求超时 ($url)"
echo "修复:"
echo " 1. 检查网络连通: ping $(echo $url | sed 's|https\?://||;s|/.*||;s|:.*||')"
echo " 2. 检查防火墙规则"
echo " 3. 增加超时重试: curl --connect-timeout 10 $url"
else
echo "错误: 网络请求失败 ($url)"
echo "详情: ${result}"
fi
return 1
fi
echo "$result"
}
rsync/exec 模式部署失败
RSYNC_OUTPUT=$(rsync -avz --exclude '.git' ./ root@${TARGET_NODE}:/opt/apps/${PROJECT}/ 2>&1)
RSYNC_EXIT=$?
if [ $RSYNC_EXIT -ne 0 ]; then
if echo "$RSYNC_OUTPUT" | grep -qi "permission denied\|ssh"; then
echo "错误: SSH 连接失败 (${TARGET_NODE})"
echo "修复:"
echo " 1. 测试 SSH: ssh root@${TARGET_NODE} 'echo ok'"
echo " 2. 检查密钥: ls -la ~/.ssh/id_ed25519"
echo " 3. 检查权限: chmod 600 ~/.ssh/id_ed25519"
elif echo "$RSYNC_OUTPUT" | grep -qi "No such file\|not found"; then
echo "错误: 目标路径不存在 (/opt/apps/${PROJECT}/)"
echo "修复: ssh root@${TARGET_NODE} 'mkdir -p /opt/apps/${PROJECT}'"
elif echo "$RSYNC_OUTPUT" | grep -qi "No space left"; then
echo "错误: 目标节点磁盘空间不足"
echo "修复: ssh root@${TARGET_NODE} 'df -h /opt/apps/'"
else
echo "错误: 文件同步失败"
echo "详情: ${RSYNC_OUTPUT}"
fi
exit 1
fi
前置条件:集群配置
执行前需要配置 Aether 集群入口,参考 /aether:setup。
配置读取
if [ -f ".aether/config.yaml" ]; then
NOMAD_ADDR=$(yq '.cluster.nomad_addr' .aether/config.yaml)
CONSUL_HTTP_ADDR=$(yq '.cluster.consul_addr' .aether/config.yaml)
AETHER_REGISTRY=$(yq '.cluster.registry' .aether/config.yaml)
fi
if [ -z "$NOMAD_ADDR" ] && [ -f "$HOME/.aether/config.yaml" ]; then
NOMAD_ADDR=$(yq '.cluster.nomad_addr' ~/.aether/config.yaml)
CONSUL_HTTP_ADDR=$(yq '.cluster.consul_addr' ~/.aether/config.yaml)
AETHER_REGISTRY=$(yq '.cluster.registry' ~/.aether/config.yaml)
fi
if [ -z "$NOMAD_ADDR" ]; then
echo "请先运行 /aether:setup 配置集群"
exit 1
fi
与其他 Skills 的关系
/aether:setup → 配置集群入口(首次使用)
↓
开发阶段 生产阶段
───────── ─────────
/aether:init → 项目接入
/aether:dev → 开发测试部署
/aether:status → 状态查询 → 状态查询
/aether:deploy → 生产部署
/aether:rollback → 生产回滚