From 78da2612494c291b2378f1b8c5bd18002ecb3832 Mon Sep 17 00:00:00 2001 From: Jake Gold <52801504+Jacob2161@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:16:54 -0800 Subject: [PATCH] Add pdsadmin script (#38) * add pdsadmin helper script --------- Co-authored-by: Devin Ivy --- pdsadmin.sh | 31 ++++++ pdsadmin/account.sh | 169 +++++++++++++++++++++++++++++++++ pdsadmin/create-invite-code.sh | 18 ++++ pdsadmin/help.sh | 42 ++++++++ pdsadmin/request-crawl.sh | 33 +++++++ pdsadmin/update.sh | 41 ++++++++ 6 files changed, 334 insertions(+) create mode 100644 pdsadmin.sh create mode 100644 pdsadmin/account.sh create mode 100644 pdsadmin/create-invite-code.sh create mode 100644 pdsadmin/help.sh create mode 100644 pdsadmin/request-crawl.sh create mode 100644 pdsadmin/update.sh diff --git a/pdsadmin.sh b/pdsadmin.sh new file mode 100644 index 0000000..ec8ad19 --- /dev/null +++ b/pdsadmin.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +#PDSADMIN_BASE_URL="https://raw.githubusercontent.com/bluesky-social/pds/main/pdsadmin" +PDSADMIN_BASE_URL="https://raw.githubusercontent.com/bluesky-social/pds/jake/add-pdsadmin/pdsadmin" + +# Command to run. +COMMAND="${1:-help}" +shift || true + +# Ensure the user is root, since it's required for most commands. +if [[ "${EUID}" -ne 0 ]]; then + echo "ERROR: This script must be run as root" + exit 1 +fi + +# Download the script, if it exists. +SCRIPT_URL="${PDSADMIN_BASE_URL}/${COMMAND}.sh" +SCRIPT_FILE="$(mktemp /tmp/pdsadmin.${COMMAND}.XXXXXX)" + +if ! curl --fail --silent --show-error --location --output "${SCRIPT_FILE}" "${SCRIPT_URL}"; then + echo "ERROR: ${COMMAND} not found" + exit 2 +fi + +chmod +x "${SCRIPT_FILE}" +if "${SCRIPT_FILE}" "$@"; then + rm --force "${SCRIPT_FILE}" +fi diff --git a/pdsadmin/account.sh b/pdsadmin/account.sh new file mode 100644 index 0000000..e9994e9 --- /dev/null +++ b/pdsadmin/account.sh @@ -0,0 +1,169 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +PDS_ENV_FILE="/pds/pds.env" +source "${PDS_ENV_FILE}" + +curl_cmd() { + curl --fail --silent --show-error "$@" +} + +curl_cmd_post() { + curl --fail --silent --show-error --request POST --header "Content-Type: application/json" "$@" +} + +curl_cmd_post_nofail() { + curl --silent --show-error --request POST --header "Content-Type: application/json" "$@" +} + +SUBCOMMAND="${1:-}" + +if [[ "${SUBCOMMAND}" == "list" ]]; then + DIDS=$(curl_cmd \ + "https://${PDS_HOSTNAME}/xrpc/com.atproto.sync.listRepos?limit=100" | jq --raw-output '.repos[].did' + ) + OUTPUT='[{"handle":"Handle","email":"Email","did":"DID"}' + for did in $DIDS; do + ITEM=$(curl_cmd \ + --user "admin:${PDS_ADMIN_PASSWORD}" \ + "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.getAccountInfo?did=$did" + ) + OUTPUT="${OUTPUT},${ITEM}" + done + OUTPUT="${OUTPUT}]" + echo $OUTPUT | jq --raw-output '.[] | [.handle, .email, .did] | @tsv' | column -t +elif [[ "${SUBCOMMAND}" == "create" ]]; then + EMAIL="${2:-}" + HANDLE="${3:-}" + + if [[ "${EMAIL}" == "" || "${HANDLE}" == "" ]]; then + echo "ERROR: missing EMAIL and/or HANDLE parameters." >/dev/stderr + echo "Usage: $0 ${SUBCOMMAND} " >/dev/stderr + exit 1 + fi + + PASSWORD=$(openssl rand -base64 30 | tr -d "=+/" | cut -c1-24) + INVITE_CODE=$(curl_cmd_post \ + --user "admin:${PDS_ADMIN_PASSWORD}" \ + --data '{"useCount": 1}' \ + https://${PDS_HOSTNAME}/xrpc/com.atproto.server.createInviteCode | jq --raw-output '.code' + ) + RESULT=$(curl_cmd_post_nofail \ + --data "{\"email\":\"${EMAIL}\", \"handle\":\"${HANDLE}\", \"password\":\"${PASSWORD}\", \"inviteCode\":\"${INVITE_CODE}\"}" \ + https://${PDS_HOSTNAME}/xrpc/com.atproto.server.createAccount + ) + + DID=$(echo $RESULT | jq --raw-output '.did') + if [[ "${DID}" != did:* ]]; then + ERR=$(echo $RESULT | jq --raw-output '.message') + echo "ERROR: ${ERR}" >/dev/stderr + echo "Usage: $0 ${SUBCOMMAND} " >/dev/stderr + exit 1 + fi + + echo "Account created for ${HANDLE}.\nYour password is below, which we'll only show you once.\n" + echo "DID: ${DID}" + echo "Password: ${PASSWORD}" +elif [[ "${SUBCOMMAND}" == "delete" ]]; then + DID="${2:-}" + + if [[ "${DID}" == "" ]]; then + echo "ERROR: missing DID parameter." >/dev/stderr + echo "Usage: $0 ${SUBCOMMAND} " >/dev/stderr + exit 1 + fi + + if [[ "${DID}" != did:* ]]; then + echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr + echo "Usage: $0 ${SUBCOMMAND} " >/dev/stderr + exit 1 + fi + + echo "This action is permanent." + read -r -p "Are you sure you'd like to delete ${DID}? [y/N] " response + if [[ ! "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then + exit 0 + fi + + curl_cmd_post \ + --user "admin:${PDS_ADMIN_PASSWORD}" \ + --data "{\"did\": \"${DID}\"}" \ + https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.deleteAccount >/dev/null + + echo "${DID} deleted" +elif [[ "${SUBCOMMAND}" == "takedown" ]]; then + DID="${2:-}" + TAKEDOWN_REF="$(date +%s)" + + if [[ "${DID}" == "" ]]; then + echo "ERROR: missing DID parameter." >/dev/stderr + echo "Usage: $0 ${SUBCOMMAND} " >/dev/stderr + exit 1 + fi + + if [[ "${DID}" != did:* ]]; then + echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr + echo "Usage: $0 ${SUBCOMMAND} " >/dev/stderr + exit 1 + fi + + PAYLOAD=$(cat </dev/null + + echo "${DID} taken down" +elif [[ "${SUBCOMMAND}" == "untakedown" ]]; then + DID="${2:-}" + + if [[ "${DID}" == "" ]]; then + echo "ERROR: missing DID parameter." >/dev/stderr + echo "Usage: $0 ${SUBCOMMAND} " >/dev/stderr + exit 1 + fi + + if [[ "${DID}" != did:* ]]; then + echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr + echo "Usage: $0 ${SUBCOMMAND} " >/dev/stderr + exit 1 + fi + + PAYLOAD=$(cat </dev/null + + echo "${DID} untaken down" +else + echo "Unknown subcommand "$0 ${SUBCOMMAND}"." >/dev/stderr + exit 1 +fi diff --git a/pdsadmin/create-invite-code.sh b/pdsadmin/create-invite-code.sh new file mode 100644 index 0000000..79d49e4 --- /dev/null +++ b/pdsadmin/create-invite-code.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +PDS_ENV_FILE="/pds/pds.env" + +source "${PDS_ENV_FILE}" + +curl \ + --fail \ + --silent \ + --show-error \ + --request POST \ + --user "admin:${PDS_ADMIN_PASSWORD}" \ + --header "Content-Type: application/json" \ + --data '{"useCount": 1}' \ + https://${PDS_HOSTNAME}/xrpc/com.atproto.server.createInviteCode | jq --raw-output '.code' diff --git a/pdsadmin/help.sh b/pdsadmin/help.sh new file mode 100644 index 0000000..66f58c7 --- /dev/null +++ b/pdsadmin/help.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +# This script is used to display help information for the pdsadmin command. +cat < + Create a new account + e.g. pdsadmin account create alice@example.com alice.example.com + delete + Delete an account specified by DID. + e.g. pdsadmin account takedown did:plc:xyz123abc456 + takedown + Takedown an account specified by DID. + e.g. pdsadmin account takedown did:plc:xyz123abc456 + untakedown + Remove a takedown an account specified by DID. + e.g. pdsadmin account takedown did:plc:xyz123abc456 + +request-crawl [] + Request a crawl from a relay host. + e.g. pdsadmin request-crawl bsky.network + +create-invite-code + Create a new invite code. + e.g. pdsadmin create-invite-code + +help + Display this help information. + +HELP diff --git a/pdsadmin/request-crawl.sh b/pdsadmin/request-crawl.sh new file mode 100644 index 0000000..d7ee391 --- /dev/null +++ b/pdsadmin/request-crawl.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +PDS_ENV_FILE="/pds/pds.env" +source "${PDS_ENV_FILE}" + +RELAY_HOSTS="${1:-}" +if [[ "${RELAY_HOSTS}" == "" ]]; then + RELAY_HOSTS="${PDS_CRAWLERS}" +fi + +if [[ "${RELAY_HOSTS}" == "" ]]; then + echo "ERROR: missing RELAY HOST parameter." >/dev/stderr + echo "Usage: $0 [,,...]" >/dev/stderr + exit 1 +fi + +for host in ${RELAY_HOSTS//,/ }; do + echo "Requesting crawl from ${host}" + curl \ + --fail \ + --silent \ + --show-error \ + --request POST \ + --user "admin:${PDS_ADMIN_PASSWORD}" \ + --header "Content-Type: application/json" \ + --data "{\"hostname\": \"${PDS_HOSTNAME}\"}" \ + https://${host}/xrpc/com.atproto.sync.requestCrawl >/dev/null +done + +echo "done" diff --git a/pdsadmin/update.sh b/pdsadmin/update.sh new file mode 100644 index 0000000..44b6887 --- /dev/null +++ b/pdsadmin/update.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +PDS_DATADIR="/pds" +COMPOSE_FILE="${PDS_DATADIR}/compose.yaml" +COMPOSE_URL="https://raw.githubusercontent.com/bluesky-social/pds/main/compose.yaml" + +# TODO: allow the user to specify a version to update to. +TARGET_VERSION="${1:-}" + +COMPOSE_TEMP_FILE="${COMPOSE_FILE}.tmp" + +echo "* Downloading PDS compose file" +curl \ +--silent \ +--show-error \ +--fail \ +--output "${COMPOSE_TEMP_FILE}" \ +"${COMPOSE_URL}" + +if cmp -s "${COMPOSE_FILE}" "${COMPOSE_TEMP_FILE}"; then + echo "PDS is already up to date" + rm "${COMPOSE_TEMP_FILE}" + exit 0 +fi + +echo "* Updating PDS" +mv "${COMPOSE_TEMP_FILE}" "${COMPOSE_FILE}" + +echo "* Restarting PDS" +systemctl restart pds + +cat <