#!/bin/bash # Selectel DNS v2 для зоны loop.ru # loopctl dns show metrosamara -> текущая запись # loopctl dns ttl metrosamara 60 -> сменить TTL # loopctl dns cname metrosamara l3.clusters.loop.ru -> сменить CNAME (TTL сохраняется), нет записи — создать # Доступ в DNS_ENV (по умолчанию ~/.selectel-dns.env, chmod 600), имена как в rc.sh из панели Selectel: # export OS_USER_DOMAIN_NAME=<номер аккаунта> OS_PROJECT_NAME=<проект> OS_USERNAME=<пользователь> OS_PASSWORD=<пароль> set -euo pipefail . "$(dirname "$0")/../lib/common.sh" # файл с паролем не исполняем (source печатал пароль при ошибке в строке), а разбираем: [export ]KEY=VALUE while IFS= read -r l; do l=${l#export }; [[ $l =~ ^(OS_[A-Z_]+)=(.*)$ ]] || continue v=${BASH_REMATCH[2]}; v=${v#[\"\']}; v=${v%[\"\']}; printf -v "${BASH_REMATCH[1]}" '%s' "$v" done < "$DNS_ENV" ZONE=${ZONE:-loop.ru} API=https://api.selectel.ru/domains/v2 CMD=${1:?show|ttl|cname} [ "$CMD" = show ] || confirm "Изменю DNS: ${2:-} $CMD ${3:-}." || exit 1; NAME=${2:?поддомен, например metrosamara}; VAL=${3:-} FQDN="$NAME.$ZONE." AUTH=$(curl -sS --connect-timeout 15 --retry 3 --retry-all-errors -D - https://cloud.api.selcloud.ru/identity/v3/auth/tokens \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg a "$OS_USER_DOMAIN_NAME" --arg p "${OS_PROJECT_NAME:-}" --arg pid "${OS_PROJECT_ID:-}" --arg u "$OS_USERNAME" --arg pw "$OS_PASSWORD" \ '{auth:{identity:{methods:["password"],password:{user:{name:$u,domain:{name:$a},password:$pw}}}, scope:{project:(if $pid!="" then {id:$pid} else {name:$p,domain:{name:$a}} end)}}}')") TOKEN=$(echo "$AUTH" | awk 'tolower($1)=="x-subject-token:"{print $2}' | tr -d '\r') [ -n "$TOKEN" ] || { echo "не получил токен: $(echo "$AUTH" | head -1) $(echo "$AUTH" | tail -1 | cut -c1-300)" echo "аккаунт=$OS_USER_DOMAIN_NAME проект=${OS_PROJECT_NAME:-} пользователь=$OS_USERNAME пароль=${#OS_PASSWORD} симв." # вход без проекта: если тут 201, то логин/пароль верные, а нет доступа к проекту UT=$(curl -sS -D - -o /dev/null https://cloud.api.selcloud.ru/identity/v3/auth/tokens \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg a "$OS_USER_DOMAIN_NAME" --arg u "$OS_USERNAME" --arg pw "$OS_PASSWORD" \ '{auth:{identity:{methods:["password"],password:{user:{name:$u,domain:{name:$a},password:$pw}}}}}')" \ | awk 'tolower($1)=="x-subject-token:"{print $2}' | tr -d '\r') [ -n "$UT" ] || { echo "логин/пароль не подходят"; exit 1; } echo "логин/пароль верные; доступные проекты (OS_PROJECT_ID / OS_PROJECT_NAME):" curl -sS -H "X-Auth-Token: $UT" https://cloud.api.selcloud.ru/identity/v3/auth/projects | jq -r '.projects[]|" \(.id) \(.name)"' exit 1; } api() { curl -sS --fail-with-body --connect-timeout 15 --retry 3 --retry-all-errors -H "X-Auth-Token: $TOKEN" -H 'Content-Type: application/json' "$@"; } ZID=$(api "$API/zones?filter=$ZONE" | jq -r --arg z "$ZONE." '.result[]|select(.name==$z).id') [ -n "$ZID" ] || { echo "зона $ZONE не найдена"; exit 1; } RR=$(api "$API/zones/$ZID/rrset?name=$FQDN&limit=1000" | jq -c --arg n "$FQDN" '[.result[]|select(.name==$n)]') echo "$RR" | jq -r '.[]|"\(.name) \(.ttl) \(.type) \([.records[].content]|join(","))"' [ "$CMD" = show ] && exit 0 if [ "$CMD" = cname ] && [ "$(echo "$RR" | jq length)" = 0 ]; then # своей записи нет (домен резолвится через wildcard *.loop.ru) -> создаём CNAME [ -n "$VAL" ] || { echo "не указано значение"; exit 1; } echo "своей записи нет (wildcard), создаю: $FQDN CNAME ${VAL%.}. TTL 3600" api -X POST "$API/zones/$ZID/rrset" -w '%{http_code}\n' \ -d "$(jq -nc --arg n "$FQDN" --arg c "${VAL%.}." '{name:$n, type:"CNAME", ttl:3600, records:[{content:$c}]}')" api "$API/zones/$ZID/rrset?name=$FQDN" | jq -r --arg n "$FQDN" '.result[]|select(.name==$n)|"стало: \(.name) \(.ttl) \(.type) \([.records[].content]|join(","))"' exit 0 fi [ "$(echo "$RR" | jq length)" = 1 ] && [ "$(echo "$RR" | jq -r '.[0].type')" = CNAME ] || { echo "ожидалась одна CNAME-запись, стоп"; exit 1; } RID=$(echo "$RR" | jq -r '.[0].id') [ -n "$VAL" ] || { echo "не указано значение"; exit 1; } case $CMD in ttl) BODY=$(echo "$RR" | jq -c --argjson t "${VAL:?ttl}" '{ttl:$t, records:.[0].records}') ;; cname) BODY=$(echo "$RR" | jq -c --arg c "${VAL%.}." '{ttl:.[0].ttl, records:[{content:$c}]}') ;; *) echo "show|ttl|cname"; exit 1 ;; esac api -X PATCH "$API/zones/$ZID/rrset/$RID" -d "$BODY" -w '%{http_code}\n' api "$API/zones/$ZID/rrset?name=$FQDN" | jq -r --arg n "$FQDN" '.result[]|select(.name==$n)|"стало: \(.name) \(.ttl) \(.type) \([.records[].content]|join(","))"'