Files
2026-10-27 14:29:43 +03:00

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(","))"'