Skip to content

Commit b0d92be

Browse files
committed
Add Cloudflare API auto-DNS record creation
New option in Step 3 and --add-domain to automatically create all 7 DNS records via Cloudflare API. Includes step-by-step instructions for getting an API token. Falls back to manual setup if preferred.
1 parent fbdad32 commit b0d92be

1 file changed

Lines changed: 230 additions & 42 deletions

File tree

dnstm-setup.sh

Lines changed: 230 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3660,41 +3660,208 @@ step_ask_domain() {
36603660
print_ok "Using domain: ${BOLD}${DOMAIN}${NC}"
36613661
}
36623662

3663+
# ─── Cloudflare API: Auto-create DNS records ────────────────────────────────────
3664+
3665+
# Create all required DNS records via Cloudflare API.
3666+
# Args: $1=API token, $2=domain, $3=server IP
3667+
cloudflare_create_dns_records() {
3668+
local api_token="$1"
3669+
local domain="$2"
3670+
local server_ip="$3"
3671+
local cf_api="https://api.cloudflare.com/client/v4"
3672+
3673+
# Step 1: Get Zone ID
3674+
print_info "Looking up Cloudflare Zone ID for ${domain}..."
3675+
local zone_resp
3676+
zone_resp=$(curl -s -X GET "${cf_api}/zones?name=${domain}" \
3677+
-H "Authorization: Bearer ${api_token}" \
3678+
-H "Content-Type: application/json" --max-time 15 2>/dev/null || true)
3679+
3680+
if [[ -z "$zone_resp" ]]; then
3681+
print_fail "Could not connect to Cloudflare API"
3682+
return 1
3683+
fi
3684+
3685+
local zone_id
3686+
zone_id=$(echo "$zone_resp" | jq -r '.result[0].id // empty' 2>/dev/null || true)
3687+
if [[ -z "$zone_id" ]]; then
3688+
local cf_err
3689+
cf_err=$(echo "$zone_resp" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "Invalid response")
3690+
print_fail "Could not find zone for ${domain}: ${cf_err}"
3691+
print_info "Make sure the domain is added to your Cloudflare account and the API token has Zone:Read permission"
3692+
return 1
3693+
fi
3694+
print_ok "Zone ID: ${zone_id}"
3695+
3696+
# Helper: create or skip a DNS record
3697+
local created=0 skipped=0 failed=0
3698+
_cf_create_record() {
3699+
local rtype="$1" rname="$2" rcontent="$3" proxied="${4:-false}"
3700+
local full_name="${rname}.${domain}"
3701+
3702+
# Check if record already exists
3703+
local existing
3704+
existing=$(curl -s -X GET "${cf_api}/zones/${zone_id}/dns_records?name=${full_name}&type=${rtype}" \
3705+
-H "Authorization: Bearer ${api_token}" \
3706+
-H "Content-Type: application/json" --max-time 10 2>/dev/null || true)
3707+
local count
3708+
count=$(echo "$existing" | jq '.result | length' 2>/dev/null || echo "0")
3709+
3710+
if [[ "$count" -gt 0 ]]; then
3711+
echo -e " ${DIM}[skip]${NC} ${rtype} ${rname} — already exists"
3712+
skipped=$((skipped + 1))
3713+
return 0
3714+
fi
3715+
3716+
# Create the record
3717+
local payload
3718+
if [[ "$rtype" == "A" ]]; then
3719+
payload=$(jq -n --arg t "$rtype" --arg n "$full_name" --arg c "$rcontent" --argjson p "$proxied" \
3720+
'{type: $t, name: $n, content: $c, ttl: 3600, proxied: $p}')
3721+
else
3722+
payload=$(jq -n --arg t "$rtype" --arg n "$full_name" --arg c "$rcontent" \
3723+
'{type: $t, name: $n, content: $c, ttl: 3600}')
3724+
fi
3725+
3726+
local create_resp
3727+
create_resp=$(curl -s -X POST "${cf_api}/zones/${zone_id}/dns_records" \
3728+
-H "Authorization: Bearer ${api_token}" \
3729+
-H "Content-Type: application/json" \
3730+
-d "$payload" --max-time 10 2>/dev/null || true)
3731+
3732+
local success
3733+
success=$(echo "$create_resp" | jq -r '.success // false' 2>/dev/null || echo "false")
3734+
if [[ "$success" == "true" ]]; then
3735+
echo -e " ${GREEN}[created]${NC} ${rtype} ${rname}${rcontent}"
3736+
created=$((created + 1))
3737+
else
3738+
local err_msg
3739+
err_msg=$(echo "$create_resp" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "API error")
3740+
echo -e " ${RED}[failed]${NC} ${rtype} ${rname}: ${err_msg}"
3741+
failed=$((failed + 1))
3742+
fi
3743+
}
3744+
3745+
# Step 2: Create A record
3746+
echo ""
3747+
print_info "Creating DNS records..."
3748+
echo ""
3749+
_cf_create_record "A" "ns" "$server_ip" "false"
3750+
3751+
# Step 3: Create NS records
3752+
local ns_target="ns.${domain}"
3753+
local subdomains=("t" "d" "n" "s" "ds" "z")
3754+
for sub in "${subdomains[@]}"; do
3755+
_cf_create_record "NS" "$sub" "$ns_target"
3756+
done
3757+
3758+
echo ""
3759+
print_ok "Done: ${created} created, ${skipped} skipped, ${failed} failed"
3760+
3761+
if [[ $failed -gt 0 ]]; then
3762+
print_warn "Some records failed — check your Cloudflare dashboard"
3763+
return 1
3764+
fi
3765+
return 0
3766+
}
3767+
36633768
# ─── STEP 3: Show DNS Records ──────────────────────────────────────────────────
36643769

36653770
step_dns_records() {
36663771
print_step 3 "DNS Records (Cloudflare)"
36673772

3668-
print_info "Create these DNS records in your Cloudflare dashboard:"
36693773
echo ""
3670-
print_box \
3671-
"Record 1: Type: A | Name: ns | Value: ${SERVER_IP}" \
3672-
" Proxy: OFF (DNS Only - grey cloud)" \
3673-
"" \
3674-
"Record 2: Type: NS | Name: t | Value: ns.${DOMAIN}" \
3675-
"Record 3: Type: NS | Name: d | Value: ns.${DOMAIN}" \
3676-
"Record 4: Type: NS | Name: s | Value: ns.${DOMAIN}" \
3677-
"Record 5: Type: NS | Name: ds | Value: ns.${DOMAIN}" \
3678-
"Record 6: Type: NS | Name: n | Value: ns.${DOMAIN}" \
3679-
"Record 7: Type: NS | Name: z | Value: ns.${DOMAIN}"
3680-
3774+
echo -e " ${BOLD}How do you want to set up DNS records?${NC}"
36813775
echo ""
3682-
print_warn "IMPORTANT: The A record MUST be DNS Only (grey cloud, NOT orange)"
3683-
print_warn "IMPORTANT: The A record name must be \"ns\" (not \"tns\")"
3684-
echo ""
3685-
echo " Subdomain purposes:"
3686-
echo " t = Slipstream + SOCKS tunnel"
3687-
echo " d = DNSTT + SOCKS tunnel"
3688-
echo " n = NoizDNS + SOCKS tunnel (DPI-resistant)"
3689-
echo " s = Slipstream + SSH tunnel"
3690-
echo " ds = DNSTT + SSH tunnel"
3691-
echo " z = NoizDNS + SSH tunnel (DPI-resistant)"
3776+
echo -e " ${BOLD}1)${NC} Automatic (Cloudflare API) ${DIM}— enter API token, records created for you${NC}"
3777+
echo -e " ${BOLD}2)${NC} Manual ${DIM}— create records yourself in Cloudflare dashboard${NC}"
36923778
echo ""
3779+
local dns_choice
3780+
dns_choice=$(prompt_input "Select (1-2)" "1")
36933781

3694-
if ! prompt_yn "Have you created these DNS records in Cloudflare?" "n"; then
3782+
if [[ "$dns_choice" == "1" ]]; then
3783+
# Automatic via Cloudflare API
36953784
echo ""
3696-
print_info "Please create the DNS records and re-run this script."
3697-
exit 0
3785+
echo -e " ${BOLD}${YELLOW}How to get a Cloudflare API Token:${NC}"
3786+
echo ""
3787+
echo -e " ${BOLD}1.${NC} Go to: ${GREEN}https://dash.cloudflare.com/profile/api-tokens${NC}"
3788+
echo -e " ${BOLD}2.${NC} Click ${BOLD}Create Token${NC}"
3789+
echo -e " ${BOLD}3.${NC} Select the ${BOLD}Edit zone DNS${NC} template"
3790+
echo -e " ${BOLD}4.${NC} Under ${BOLD}Zone Resources${NC}, select your domain (or All Zones)"
3791+
echo -e " ${BOLD}5.${NC} Click ${BOLD}Continue to summary${NC}${BOLD}Create Token${NC}"
3792+
echo -e " ${BOLD}6.${NC} Copy the token (you'll only see it once!)"
3793+
echo ""
3794+
echo -e " ${DIM}Required permissions: Zone:DNS:Edit + Zone:Zone:Read${NC}"
3795+
echo -e " ${DIM}The 'Edit zone DNS' template includes both automatically${NC}"
3796+
echo ""
3797+
3798+
local cf_token
3799+
cf_token=$(prompt_input "Paste your Cloudflare API Token here")
3800+
cf_token=$(echo "$cf_token" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
3801+
3802+
if [[ -z "$cf_token" ]]; then
3803+
print_fail "API token cannot be empty"
3804+
exit 1
3805+
fi
3806+
3807+
# Validate token
3808+
print_info "Validating API token..."
3809+
local verify_resp
3810+
verify_resp=$(curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
3811+
-H "Authorization: Bearer ${cf_token}" \
3812+
-H "Content-Type: application/json" --max-time 10 2>/dev/null || true)
3813+
local token_status
3814+
token_status=$(echo "$verify_resp" | jq -r '.result.status // empty' 2>/dev/null || true)
3815+
3816+
if [[ "$token_status" != "active" ]]; then
3817+
print_fail "API token is invalid or expired"
3818+
print_info "Check your token at: https://dash.cloudflare.com/profile/api-tokens"
3819+
exit 1
3820+
fi
3821+
print_ok "API token is valid"
3822+
3823+
# Create DNS records
3824+
if cloudflare_create_dns_records "$cf_token" "$DOMAIN" "$SERVER_IP"; then
3825+
print_ok "All DNS records created successfully"
3826+
else
3827+
echo ""
3828+
if ! prompt_yn "Some records failed. Continue anyway?" "n"; then
3829+
exit 1
3830+
fi
3831+
fi
3832+
else
3833+
# Manual setup
3834+
print_info "Create these DNS records in your Cloudflare dashboard:"
3835+
echo ""
3836+
print_box \
3837+
"Record 1: Type: A | Name: ns | Value: ${SERVER_IP}" \
3838+
" Proxy: OFF (DNS Only - grey cloud)" \
3839+
"" \
3840+
"Record 2: Type: NS | Name: t | Value: ns.${DOMAIN}" \
3841+
"Record 3: Type: NS | Name: d | Value: ns.${DOMAIN}" \
3842+
"Record 4: Type: NS | Name: s | Value: ns.${DOMAIN}" \
3843+
"Record 5: Type: NS | Name: ds | Value: ns.${DOMAIN}" \
3844+
"Record 6: Type: NS | Name: n | Value: ns.${DOMAIN}" \
3845+
"Record 7: Type: NS | Name: z | Value: ns.${DOMAIN}"
3846+
3847+
echo ""
3848+
print_warn "IMPORTANT: The A record MUST be DNS Only (grey cloud, NOT orange)"
3849+
print_warn "IMPORTANT: The A record name must be \"ns\" (not \"tns\")"
3850+
echo ""
3851+
echo " Subdomain purposes:"
3852+
echo " t = Slipstream + SOCKS tunnel"
3853+
echo " d = DNSTT + SOCKS tunnel"
3854+
echo " n = NoizDNS + SOCKS tunnel (DPI-resistant)"
3855+
echo " s = Slipstream + SSH tunnel"
3856+
echo " ds = DNSTT + SSH tunnel"
3857+
echo " z = NoizDNS + SSH tunnel (DPI-resistant)"
3858+
echo ""
3859+
3860+
if ! prompt_yn "Have you created these DNS records in Cloudflare?" "n"; then
3861+
echo ""
3862+
print_info "Please create the DNS records and re-run this script."
3863+
exit 0
3864+
fi
36983865
fi
36993866

37003867
echo ""
@@ -4949,30 +5116,51 @@ do_add_domain() {
49495116
print_ok "Domain: ${DOMAIN}"
49505117
echo ""
49515118

4952-
# DNS record instructions
5119+
# DNS record setup
49535120
print_header "DNS Records for ${DOMAIN}"
49545121

4955-
print_info "Create these records in Cloudflare for ${BOLD}${DOMAIN}${NC}:"
49565122
echo ""
4957-
print_box \
4958-
"Record 1: Type: A | Name: ns | Value: ${SERVER_IP}" \
4959-
" Proxy: OFF (DNS Only - grey cloud)" \
4960-
"" \
4961-
"Record 2: Type: NS | Name: t | Value: ns.${DOMAIN}" \
4962-
"Record 3: Type: NS | Name: d | Value: ns.${DOMAIN}" \
4963-
"Record 4: Type: NS | Name: s | Value: ns.${DOMAIN}" \
4964-
"Record 5: Type: NS | Name: ds | Value: ns.${DOMAIN}" \
4965-
"Record 6: Type: NS | Name: n | Value: ns.${DOMAIN}" \
4966-
"Record 7: Type: NS | Name: z | Value: ns.${DOMAIN}"
4967-
5123+
echo -e " ${BOLD}How do you want to set up DNS records?${NC}"
49685124
echo ""
4969-
print_warn "IMPORTANT: The A record MUST be DNS Only (grey cloud, NOT orange)"
5125+
echo -e " ${BOLD}1)${NC} Automatic (Cloudflare API)"
5126+
echo -e " ${BOLD}2)${NC} Manual (create in dashboard)"
49705127
echo ""
5128+
local dns_choice
5129+
dns_choice=$(prompt_input "Select (1-2)" "2")
49715130

4972-
if ! prompt_yn "Have you created these DNS records in Cloudflare?" "n"; then
5131+
if [[ "$dns_choice" == "1" ]]; then
5132+
local cf_token
5133+
cf_token=$(prompt_input "Cloudflare API Token")
5134+
cf_token=$(echo "$cf_token" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
5135+
if [[ -n "$cf_token" ]]; then
5136+
cloudflare_create_dns_records "$cf_token" "$DOMAIN" "$SERVER_IP" || true
5137+
else
5138+
print_fail "API token cannot be empty"
5139+
exit 1
5140+
fi
5141+
else
5142+
print_info "Create these records in Cloudflare for ${BOLD}${DOMAIN}${NC}:"
49735143
echo ""
4974-
print_info "Please create the DNS records and re-run: sudo bash $0 --add-domain"
4975-
exit 0
5144+
print_box \
5145+
"Record 1: Type: A | Name: ns | Value: ${SERVER_IP}" \
5146+
" Proxy: OFF (DNS Only - grey cloud)" \
5147+
"" \
5148+
"Record 2: Type: NS | Name: t | Value: ns.${DOMAIN}" \
5149+
"Record 3: Type: NS | Name: d | Value: ns.${DOMAIN}" \
5150+
"Record 4: Type: NS | Name: s | Value: ns.${DOMAIN}" \
5151+
"Record 5: Type: NS | Name: ds | Value: ns.${DOMAIN}" \
5152+
"Record 6: Type: NS | Name: n | Value: ns.${DOMAIN}" \
5153+
"Record 7: Type: NS | Name: z | Value: ns.${DOMAIN}"
5154+
5155+
echo ""
5156+
print_warn "IMPORTANT: The A record MUST be DNS Only (grey cloud, NOT orange)"
5157+
echo ""
5158+
5159+
if ! prompt_yn "Have you created these DNS records in Cloudflare?" "n"; then
5160+
echo ""
5161+
print_info "Please create the DNS records and re-run: sudo bash $0 --add-domain"
5162+
exit 0
5163+
fi
49765164
fi
49775165

49785166
echo ""

0 commit comments

Comments
 (0)