debian 开发,modiqi 修复 cf_load_profile 缺失问题。 9 个命令:cf-api/cf-zone/cf-dns/cf-cache/cf-ssl/cf-fw/cf-pagerule/cf-analytics/cf-hostname 支持多账号、域名自动解析 zone_id。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
156 lines
6.2 KiB
Bash
Executable file
156 lines
6.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# cf-dns — Cloudflare DNS 记录管理
|
|
# 用法: cf-dns list <domain> [-t type] [-n name] [--export]
|
|
# cf-dns add <domain> <type> <name> <content> [-l ttl] [--proxied]
|
|
# cf-dns update <domain> <record_id> <type> <name> <content> [-l ttl] [--proxied]
|
|
# cf-dns delete <domain> <record_id>
|
|
# cf-dns find <domain> <name> 按名称查找记录
|
|
# cf-dns import <domain> <file> 导入 BIND 格式 zone 文件
|
|
# cf-dns export <domain> 导出为 BIND 格式
|
|
# 选项: -p <profile> 指定 profile
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
source "$SCRIPT_DIR/cf-lib.sh"
|
|
|
|
cf_parse_profile "$@"
|
|
set -- "${CF_REMAINING_ARGS[@]}"
|
|
cf_load_profile
|
|
|
|
usage() {
|
|
cat >&2 <<'EOF'
|
|
用法: cf-dns <command> <domain> [args...]
|
|
命令:
|
|
list <domain> [-t type] [-n name] 列出 DNS 记录
|
|
add <domain> <type> <name> <content> 添加记录
|
|
[-l ttl] [--proxied] 可选: TTL(默认auto), 代理(默认关)
|
|
update <domain> <id> <type> <name> <content> 更新记录
|
|
[-l ttl] [--proxied|--no-proxied]
|
|
delete <domain> <record_id> 删除记录
|
|
find <domain> <name> 按名称查找
|
|
export <domain> 导出 BIND 格式
|
|
import <domain> <file> 导入 BIND zone 文件
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
[[ $# -lt 1 ]] && usage
|
|
|
|
CMD="$1"; shift
|
|
|
|
case "$CMD" in
|
|
list)
|
|
[[ $# -lt 1 ]] && { echo "用法: cf-dns list <domain> [-t type] [-n name]" >&2; exit 1; }
|
|
DOMAIN="$1"; shift
|
|
ZONE_ID=$(cf_resolve_zone "$DOMAIN")
|
|
TYPE="" NAME="" PAGE=1
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-t) TYPE="$2"; shift 2 ;;
|
|
-n) NAME="$2"; shift 2 ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
QUERY="/zones/$ZONE_ID/dns_records?per_page=100&page=$PAGE"
|
|
[[ -n "$TYPE" ]] && QUERY+="&type=$TYPE"
|
|
[[ -n "$NAME" ]] && QUERY+="&name=$NAME"
|
|
resp=$(cf_call GET "$QUERY")
|
|
cf_check_error "$resp"
|
|
echo "$resp" | jq -r '
|
|
["类型", "名称", "内容", "代理", "TTL", "ID"],
|
|
(.result[] | [.type, .name, (.content[:40]), (if .proxied then "✓" else "✗" end), (.ttl|tostring), .id]) |
|
|
@tsv' | column -t -s $'\t'
|
|
total=$(echo "$resp" | jq '.result_info.total_count')
|
|
echo -e "\n共 $total 条记录"
|
|
;;
|
|
|
|
add)
|
|
[[ $# -lt 4 ]] && { echo "用法: cf-dns add <domain> <type> <name> <content> [-l ttl] [--proxied]" >&2; exit 1; }
|
|
DOMAIN="$1" RTYPE="$2" RNAME="$3" RCONTENT="$4"; shift 4
|
|
ZONE_ID=$(cf_resolve_zone "$DOMAIN")
|
|
TTL=1 PROXIED=false
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-l) TTL="$2"; shift 2 ;;
|
|
--proxied) PROXIED=true; shift ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
DATA=$(jq -n --arg t "$RTYPE" --arg n "$RNAME" --arg c "$RCONTENT" \
|
|
--argjson ttl "$TTL" --argjson p "$PROXIED" \
|
|
'{type:$t, name:$n, content:$c, ttl:$ttl, proxied:$p}')
|
|
resp=$(cf_call POST "/zones/$ZONE_ID/dns_records" -d "$DATA")
|
|
cf_check_error "$resp"
|
|
echo "$resp" | jq '.result | "已添加: \(.type) \(.name) -> \(.content) (ID: \(.id))"' -r
|
|
;;
|
|
|
|
update)
|
|
[[ $# -lt 5 ]] && { echo "用法: cf-dns update <domain> <id> <type> <name> <content>" >&2; exit 1; }
|
|
DOMAIN="$1" RID="$2" RTYPE="$3" RNAME="$4" RCONTENT="$5"; shift 5
|
|
ZONE_ID=$(cf_resolve_zone "$DOMAIN")
|
|
TTL=1 PROXIED=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-l) TTL="$2"; shift 2 ;;
|
|
--proxied) PROXIED=true; shift ;;
|
|
--no-proxied) PROXIED=false; shift ;;
|
|
*) shift ;;
|
|
esac
|
|
done
|
|
DATA=$(jq -n --arg t "$RTYPE" --arg n "$RNAME" --arg c "$RCONTENT" --argjson ttl "$TTL" \
|
|
'{type:$t, name:$n, content:$c, ttl:$ttl}')
|
|
[[ -n "$PROXIED" ]] && DATA=$(echo "$DATA" | jq --argjson p "$PROXIED" '. + {proxied:$p}')
|
|
resp=$(cf_call PUT "/zones/$ZONE_ID/dns_records/$RID" -d "$DATA")
|
|
cf_check_error "$resp"
|
|
echo "$resp" | jq '.result | "已更新: \(.type) \(.name) -> \(.content)"' -r
|
|
;;
|
|
|
|
delete)
|
|
[[ $# -lt 2 ]] && { echo "用法: cf-dns delete <domain> <record_id>" >&2; exit 1; }
|
|
DOMAIN="$1" RID="$2"
|
|
ZONE_ID=$(cf_resolve_zone "$DOMAIN")
|
|
resp=$(cf_call DELETE "/zones/$ZONE_ID/dns_records/$RID")
|
|
cf_check_error "$resp"
|
|
echo "已删除记录: $RID"
|
|
;;
|
|
|
|
find)
|
|
[[ $# -lt 2 ]] && { echo "用法: cf-dns find <domain> <name>" >&2; exit 1; }
|
|
DOMAIN="$1" SEARCH="$2"
|
|
ZONE_ID=$(cf_resolve_zone "$DOMAIN")
|
|
# 如果搜索词不含域名后缀,自动补上
|
|
[[ "$SEARCH" != *"$DOMAIN"* ]] && SEARCH="${SEARCH}.${DOMAIN}"
|
|
resp=$(cf_call GET "/zones/$ZONE_ID/dns_records?name=$SEARCH")
|
|
cf_check_error "$resp"
|
|
count=$(echo "$resp" | jq '.result | length')
|
|
if [[ "$count" == "0" ]]; then
|
|
echo "未找到匹配 $SEARCH 的记录"
|
|
else
|
|
echo "$resp" | jq -r '
|
|
["类型", "名称", "内容", "代理", "TTL", "ID"],
|
|
(.result[] | [.type, .name, .content, (if .proxied then "✓" else "✗" end), (.ttl|tostring), .id]) |
|
|
@tsv' | column -t -s $'\t'
|
|
fi
|
|
;;
|
|
|
|
export)
|
|
[[ $# -lt 1 ]] && { echo "用法: cf-dns export <domain>" >&2; exit 1; }
|
|
DOMAIN="$1"
|
|
ZONE_ID=$(cf_resolve_zone "$DOMAIN")
|
|
cf_call GET "/zones/$ZONE_ID/dns_records/export"
|
|
;;
|
|
|
|
import)
|
|
[[ $# -lt 2 ]] && { echo "用法: cf-dns import <domain> <file>" >&2; exit 1; }
|
|
DOMAIN="$1" FILE="$2"
|
|
[[ ! -f "$FILE" ]] && { echo "错误: 文件不存在 $FILE" >&2; exit 1; }
|
|
ZONE_ID=$(cf_resolve_zone "$DOMAIN")
|
|
resp=$(cf_call POST "/zones/$ZONE_ID/dns_records/import" \
|
|
-H "Content-Type: multipart/form-data" \
|
|
-F "file=@$FILE")
|
|
cf_check_error "$resp"
|
|
echo "$resp" | jq '.result | "导入完成: 添加 \(.recs_added) 条, 总计 \(.total_records_parsed) 条"' -r
|
|
;;
|
|
|
|
*) usage ;;
|
|
esac
|