grain.social is a photo sharing platform built on atproto.
1#!/bin/bash
2set -o errexit
3set -o nounset
4set -o pipefail
5
6PDS_ENV_FILE=${PDS_ENV_FILE:-".env"}
7source "${PDS_ENV_FILE}"
8
9# curl a URL and fail if the request fails.
10function curl_cmd_get {
11 curl --fail --silent --show-error "$@"
12}
13
14# curl a URL and fail if the request fails.
15function curl_cmd_post {
16 curl --fail --silent --show-error --request POST --header "Content-Type: application/json" "$@"
17}
18
19# curl a URL but do not fail if the request fails.
20function curl_cmd_post_nofail {
21 curl --silent --show-error --request POST --header "Content-Type: application/json" "$@"
22}
23
24# The subcommand to run.
25SUBCOMMAND="${1:-}"
26
27#
28# account list
29#
30if [[ "${SUBCOMMAND}" == "list" ]]; then
31 DIDS="$(curl_cmd_get \
32 "https://${PDS_HOSTNAME}/xrpc/com.atproto.sync.listRepos?limit=100" | jq --raw-output '.repos[].did'
33 )"
34 OUTPUT='[{"handle":"Handle","email":"Email","did":"DID"}'
35 for did in ${DIDS}; do
36 ITEM="$(curl_cmd_get \
37 --user "admin:${PDS_ADMIN_PASSWORD}" \
38 "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.getAccountInfo?did=${did}"
39 )"
40 OUTPUT="${OUTPUT},${ITEM}"
41 done
42 OUTPUT="${OUTPUT}]"
43 echo "${OUTPUT}" | jq --raw-output '.[] | [.handle, .email, .did] | @tsv' | column -t
44
45#
46# account create
47#
48elif [[ "${SUBCOMMAND}" == "create" ]]; then
49 EMAIL="${2:-}"
50 HANDLE="${3:-}"
51
52 if [[ "${EMAIL}" == "" ]]; then
53 read -p "Enter an email address (e.g. alice@${PDS_HOSTNAME}): " EMAIL
54 fi
55 if [[ "${HANDLE}" == "" ]]; then
56 read -p "Enter a handle (e.g. alice.${PDS_HOSTNAME}): " HANDLE
57 fi
58
59 if [[ "${EMAIL}" == "" || "${HANDLE}" == "" ]]; then
60 echo "ERROR: missing EMAIL and/or HANDLE parameters." >/dev/stderr
61 echo "Usage: $0 ${SUBCOMMAND} <EMAIL> <HANDLE>" >/dev/stderr
62 exit 1
63 fi
64
65 PASSWORD="$(openssl rand -base64 30 | tr -d "=+/" | cut -c1-24)"
66 INVITE_CODE="$(curl_cmd_post \
67 --user "admin:${PDS_ADMIN_PASSWORD}" \
68 --data '{"useCount": 1}' \
69 "https://${PDS_HOSTNAME}/xrpc/com.atproto.server.createInviteCode" | jq --raw-output '.code'
70 )"
71 RESULT="$(curl_cmd_post_nofail \
72 --data "{\"email\":\"${EMAIL}\", \"handle\":\"${HANDLE}\", \"password\":\"${PASSWORD}\", \"inviteCode\":\"${INVITE_CODE}\"}" \
73 "https://${PDS_HOSTNAME}/xrpc/com.atproto.server.createAccount"
74 )"
75
76 DID="$(echo $RESULT | jq --raw-output '.did')"
77 if [[ "${DID}" != did:* ]]; then
78 ERR="$(echo ${RESULT} | jq --raw-output '.message')"
79 echo "ERROR: ${ERR}" >/dev/stderr
80 echo "Usage: $0 ${SUBCOMMAND} <EMAIL> <HANDLE>" >/dev/stderr
81 exit 1
82 fi
83
84 echo
85 echo "Account created successfully!"
86 echo "-----------------------------"
87 echo "Handle : ${HANDLE}"
88 echo "DID : ${DID}"
89 echo "Password : ${PASSWORD}"
90 echo "-----------------------------"
91 echo "Save this password, it will not be displayed again."
92 echo
93
94#
95# account delete
96#
97elif [[ "${SUBCOMMAND}" == "delete" ]]; then
98 DID="${2:-}"
99
100 if [[ "${DID}" == "" ]]; then
101 echo "ERROR: missing DID parameter." >/dev/stderr
102 echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr
103 exit 1
104 fi
105
106 if [[ "${DID}" != did:* ]]; then
107 echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr
108 echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr
109 exit 1
110 fi
111
112 echo "This action is permanent."
113 read -r -p "Are you sure you'd like to delete ${DID}? [y/N] " response
114 if [[ ! "${response}" =~ ^([yY][eE][sS]|[yY])$ ]]; then
115 exit 0
116 fi
117
118 curl_cmd_post \
119 --user "admin:${PDS_ADMIN_PASSWORD}" \
120 --data "{\"did\": \"${DID}\"}" \
121 "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.deleteAccount" >/dev/null
122
123 echo "${DID} deleted"
124
125#
126# account takedown
127#
128elif [[ "${SUBCOMMAND}" == "takedown" ]]; then
129 DID="${2:-}"
130 TAKEDOWN_REF="$(date +%s)"
131
132 if [[ "${DID}" == "" ]]; then
133 echo "ERROR: missing DID parameter." >/dev/stderr
134 echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr
135 exit 1
136 fi
137
138 if [[ "${DID}" != did:* ]]; then
139 echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr
140 echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr
141 exit 1
142 fi
143
144 PAYLOAD="$(cat <<EOF
145 {
146 "subject": {
147 "\$type": "com.atproto.admin.defs#repoRef",
148 "did": "${DID}"
149 },
150 "takedown": {
151 "applied": true,
152 "ref": "${TAKEDOWN_REF}"
153 }
154 }
155EOF
156)"
157
158 curl_cmd_post \
159 --user "admin:${PDS_ADMIN_PASSWORD}" \
160 --data "${PAYLOAD}" \
161 "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.updateSubjectStatus" >/dev/null
162
163 echo "${DID} taken down"
164
165#
166# account untakedown
167#
168elif [[ "${SUBCOMMAND}" == "untakedown" ]]; then
169 DID="${2:-}"
170
171 if [[ "${DID}" == "" ]]; then
172 echo "ERROR: missing DID parameter." >/dev/stderr
173 echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr
174 exit 1
175 fi
176
177 if [[ "${DID}" != did:* ]]; then
178 echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr
179 echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr
180 exit 1
181 fi
182
183 PAYLOAD=$(cat <<EOF
184 {
185 "subject": {
186 "\$type": "com.atproto.admin.defs#repoRef",
187 "did": "${DID}"
188 },
189 "takedown": {
190 "applied": false
191 }
192 }
193EOF
194)
195
196 curl_cmd_post \
197 --user "admin:${PDS_ADMIN_PASSWORD}" \
198 --data "${PAYLOAD}" \
199 "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.updateSubjectStatus" >/dev/null
200
201 echo "${DID} untaken down"
202#
203# account reset-password
204#
205elif [[ "${SUBCOMMAND}" == "reset-password" ]]; then
206 DID="${2:-}"
207 PASSWORD="$(openssl rand -base64 30 | tr -d "=+/" | cut -c1-24)"
208
209 if [[ "${DID}" == "" ]]; then
210 echo "ERROR: missing DID parameter." >/dev/stderr
211 echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr
212 exit 1
213 fi
214
215 if [[ "${DID}" != did:* ]]; then
216 echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr
217 echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr
218 exit 1
219 fi
220
221 curl_cmd_post \
222 --user "admin:${PDS_ADMIN_PASSWORD}" \
223 --data "{ \"did\": \"${DID}\", \"password\": \"${PASSWORD}\" }" \
224 "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.updateAccountPassword" >/dev/null
225
226 echo
227 echo "Password reset for ${DID}"
228 echo "New password: ${PASSWORD}"
229 echo
230
231else
232 echo "Unknown subcommand: ${SUBCOMMAND}" >/dev/stderr
233 exit 1
234fi