67 lines
5.0 KiB
Bash
Executable File
67 lines
5.0 KiB
Bash
Executable File
#!/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(","))"'
|