recover-sandcats.sh
Rama master · réplica de lectura del repositorio original.
#!/bin/bash
#
# This script recovers access to a sandcats.io subdomain by generating
# new keys and using the email-based recovery flow.
#
# Usage: ./recover-sandcats.sh [sandstorm-dir]
#
# If sandstorm-dir is not provided, defaults to /opt/sandstorm
set -euo pipefail
SANDSTORM_DIR="${1:-/opt/sandstorm}"
SANDCATS_BASE_DOMAIN="${SANDCATS_BASE_DOMAIN:-sandcats.io}"
SANDCATS_API_BASE="${SANDCATS_API_BASE:-https://sandcats.io}"
CURL_USER_AGENT="${CURL_USER_AGENT:-sandstorm-recover-script}"
error() {
if [ $# != 0 ]; then
echo -en '\e[0;31m' >&2
echo "$@" | (fold -s || cat) >&2
echo -en '\e[0m' >&2
fi
}
fail() {
local error_code="$1"
shift
echo "*** RECOVERY FAILED ***" >&2
echo ""
error "$@"
echo "" >&2
exit 1
}
prompt() {
local VALUE
echo -en '\e[1m' >&3
echo -n "$1 [$2]" >&3
echo -en '\e[0m ' >&3
read -u 3 VALUE
if [ -z "$VALUE" ]; then
VALUE=$2
fi
echo "$VALUE"
}
prompt-yesno() {
while true; do
local VALUE=$(prompt "$@")
case $VALUE in
y | Y | yes | YES | Yes )
return 0
;;
n | N | no | NO | No )
return 1
;;
esac
echo "*** Please answer \"yes\" or \"no\"."
done
}
dotdotdot_curl() {
echo -n '...' >&2
curl "$@"
echo -ne '\r' >&2
}
sandcats_generate_keys() {
local SANDCATS_DIR="$SANDSTORM_DIR/var/sandcats"
if [ -f "$SANDCATS_DIR/id_rsa.private_combined" ]; then
echo "Existing keys found in $SANDCATS_DIR"
if ! prompt-yesno "Generate new keys? (required for recovery)" "yes"; then
fail "E_NEED_NEW_KEYS" "Recovery requires generating new keys."
fi
# Back up existing keys
local BACKUP_DIR="$SANDCATS_DIR/backup.$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
mv "$SANDCATS_DIR/id_rsa" "$BACKUP_DIR/" 2>/dev/null || true
mv "$SANDCATS_DIR/id_rsa.pub" "$BACKUP_DIR/" 2>/dev/null || true
mv "$SANDCATS_DIR/id_rsa.private_combined" "$BACKUP_DIR/" 2>/dev/null || true
echo "Backed up existing keys to $BACKUP_DIR"
fi
echo -n 'Generating new keys...'
mkdir -p -m 0700 "$SANDCATS_DIR"
chmod 0700 "$SANDCATS_DIR"
openssl \
req \
-new \
-newkey rsa:4096 \
-days 3650 \
-nodes \
-x509 \
-subj "/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd" \
-keyout "$SANDCATS_DIR/id_rsa" \
-out "$SANDCATS_DIR/id_rsa.pub" \
2>/dev/null
cat "$SANDCATS_DIR/id_rsa" "$SANDCATS_DIR/id_rsa.pub" > "$SANDCATS_DIR/id_rsa.private_combined"
chmod 0640 "$SANDCATS_DIR/id_rsa" "$SANDCATS_DIR/id_rsa.pub" "$SANDCATS_DIR/id_rsa.private_combined"
echo " done."
}
sandcats_recover_domain() {
local SANDCATS_DIR="$SANDSTORM_DIR/var/sandcats"
DESIRED_SANDCATS_NAME=$(prompt "What Sandcats subdomain do you want to recover?" "")
if [ -z "$DESIRED_SANDCATS_NAME" ]; then
fail "E_NO_SUBDOMAIN" "You must provide a subdomain to recover."
fi
# Strip the base domain if provided
if [[ $DESIRED_SANDCATS_NAME =~ [.] ]]; then
echo ""
echo "You entered: $DESIRED_SANDCATS_NAME"
echo ""
echo "Please enter just the subdomain part, without any dots."
echo "For example, if your domain is 'myserver.sandcats.io', just enter 'myserver'."
echo ""
sandcats_recover_domain
return
fi
echo "OK. We will send a recovery token to the email address on file."
if ! prompt-yesno "OK to continue?" "yes"; then
echo "Aborted."
exit 0
fi
# Send recovery token request
local LOG_PATH="$SANDCATS_DIR/sendrecoverytoken-log"
mkdir -p "$SANDCATS_DIR"
touch "$LOG_PATH"
chmod 0640 "$LOG_PATH"
echo "Requesting recovery token..."
HTTP_STATUS=$(
dotdotdot_curl \
--silent \
--max-time 20 \
-A "$CURL_USER_AGENT" \
-X POST \
--data-urlencode "rawHostname=$DESIRED_SANDCATS_NAME" \
--output "$LOG_PATH" \
-w '%{http_code}' \
-H 'X-Sand: cats' \
-H "Accept: text/plain" \
"${SANDCATS_API_BASE}/sendrecoverytoken")
if [ "200" != "$HTTP_STATUS" ]; then
error "$(cat "$LOG_PATH")"
fail "E_SEND_TOKEN_FAILED" "Failed to request recovery token."
fi
cat "$LOG_PATH"
echo ''
TOKEN=$(prompt "Please enter the token that we sent to you by email." '')
if [ -z "$TOKEN" ]; then
fail "E_EMPTY_TOKEN" "Empty tokens are not valid."
fi
# Submit recovery token
LOG_PATH="$SANDCATS_DIR/recover-log"
touch "$LOG_PATH"
chmod 0640 "$LOG_PATH"
echo "Submitting recovery token..."
HTTP_STATUS=$(
dotdotdot_curl \
--silent \
--max-time 20 \
-A "$CURL_USER_AGENT" \
-X POST \
--data-urlencode "rawHostname=$DESIRED_SANDCATS_NAME" \
--data-urlencode "recoveryToken=$TOKEN" \
--output "$LOG_PATH" \
-w '%{http_code}' \
-H 'X-Sand: cats' \
-H "Accept: text/plain" \
--cert "$SANDCATS_DIR/id_rsa.private_combined" \
"${SANDCATS_API_BASE}/recover")
if [ "200" != "$HTTP_STATUS" ]; then
error "$(cat "$LOG_PATH")"
fail "E_RECOVER_FAILED" "Failed to recover domain."
fi
cat "$LOG_PATH"
echo ''
# Update DNS
LOG_PATH="$SANDCATS_DIR/update-log"
touch "$LOG_PATH"
chmod 0640 "$LOG_PATH"
echo "Updating DNS..."
HTTP_STATUS=$(
dotdotdot_curl \
--silent \
--max-time 20 \
-A "$CURL_USER_AGENT" \
-X POST \
--data-urlencode "rawHostname=$DESIRED_SANDCATS_NAME" \
--output "$LOG_PATH" \
-w '%{http_code}' \
-H 'X-Sand: cats' \
-H "Accept: text/plain" \
--cert "$SANDCATS_DIR/id_rsa.private_combined" \
"${SANDCATS_API_BASE}/update")
if [ "200" != "$HTTP_STATUS" ]; then
error "$(cat "$LOG_PATH")"
fail "E_UPDATE_FAILED" "Failed to update DNS."
fi
cat "$LOG_PATH"
echo ''
echo ""
echo "Congratulations! You have recovered ${DESIRED_SANDCATS_NAME}.${SANDCATS_BASE_DOMAIN}."
echo "Your credentials are in $SANDCATS_DIR; consider making a backup."
echo ""
echo "You may need to restart Sandstorm and/or renew your TLS certificate:"
echo " sudo sandstorm restart"
echo " sudo sandstorm renew-certificate"
}
main() {
if [ ! -t 1 ]; then
fail "E_NO_TTY" "This script is interactive. Please run it on a terminal."
fi
# Set up FD 3 for interactive input
exec 3<&1
if [ ! -d "$SANDSTORM_DIR" ]; then
fail "E_NO_DIR" "Sandstorm directory not found: $SANDSTORM_DIR"
fi
echo "Sandcats.io Domain Recovery"
echo "============================"
echo ""
echo "This script will help you recover access to a sandcats.io subdomain"
echo "that you previously registered. You will need access to the email"
echo "address you used when you registered the domain."
echo ""
sandcats_generate_keys
sandcats_recover_domain
}
main "$@"