|
| 1 | +#! /bin/bash |
| 2 | +# |
| 3 | +# This script configures a Durable Object namespace on your Cloudflare Workers account. |
| 4 | +# |
| 5 | +# This is a temporary hack needed until we add Durable Objects support to Wrangler. Once Wrangler |
| 6 | +# support exists, this script can probably go away. |
| 7 | +# |
| 8 | +# On first run, this script will ask for configuration, create the Durable Object namespace bindings, |
| 9 | +# and generate metadata.json. On subsequent runs it will just update the script from source code. |
| 10 | + |
| 11 | +set -euo pipefail |
| 12 | + |
| 13 | +if ! which curl >/dev/null; then |
| 14 | + echo "$0: please install curl" >&2 |
| 15 | + exit 1 |
| 16 | +fi |
| 17 | + |
| 18 | +if ! which jq >/dev/null; then |
| 19 | + echo "$0: please install jq" >&2 |
| 20 | + exit 1 |
| 21 | +fi |
| 22 | + |
| 23 | +# If credentials.conf doesn't exist, prompt for the values and generate it. |
| 24 | +if [ -e credentials.conf ]; then |
| 25 | + source credentials.conf |
| 26 | +else |
| 27 | + echo -n "Cloudflare account ID (32 hex digits): " |
| 28 | + read ACCOUNT_ID |
| 29 | + echo "Please create a Cloudflare API Token with Workers Scripts Edit permission on your account (can be created using the Edit Cloudflare Workers API Token template)." |
| 30 | + echo -n "API Token: " |
| 31 | + read API_TOKEN |
| 32 | + echo -n "JavaScript module file (e.g. counter.mjs): " |
| 33 | + read SCRIPT_FILE |
| 34 | + echo -n "script name: (e.g counter-worker): " |
| 35 | + read SCRIPT_NAME |
| 36 | + echo -n "class name: (e.g. Counter): " |
| 37 | + read CLASS_NAME |
| 38 | + |
| 39 | + cat > credentials.conf << __EOF__ |
| 40 | +ACCOUNT_ID=$ACCOUNT_ID |
| 41 | +API_TOKEN=$API_TOKEN |
| 42 | +SCRIPT_FILE=$SCRIPT_FILE |
| 43 | +SCRIPT_NAME=$SCRIPT_NAME |
| 44 | +CLASS_NAME=$CLASS_NAME |
| 45 | +__EOF__ |
| 46 | + |
| 47 | + chmod 600 credentials.conf |
| 48 | + |
| 49 | + echo "Wrote credentials.conf with these values." |
| 50 | +fi |
| 51 | + |
| 52 | +# curl_api performs a curl command passing the appropriate authorization headers, and parses the |
| 53 | +# JSON response for errors. In case of errors, exit. Otherwise, write just the result part to |
| 54 | +# stdout. |
| 55 | +curl_api() { |
| 56 | + RESULT=$(curl -s -H "Authorization: Bearer $API_TOKEN" "$@") |
| 57 | + if [ $(echo "$RESULT" | jq .success) = true ]; then |
| 58 | + echo "$RESULT" | jq .result |
| 59 | + return 0 |
| 60 | + else |
| 61 | + echo "API ERROR:" >&2 |
| 62 | + echo "$RESULT" >&2 |
| 63 | + return 1 |
| 64 | + fi |
| 65 | +} |
| 66 | + |
| 67 | +# Let's verify the credentials work by listing Workers scripts and Durable Object namespaces. If |
| 68 | +# either of these requests error then we're certainly not going to be able to continue. |
| 69 | +echo "Checking credentials..." |
| 70 | +curl_api https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts >/dev/null |
| 71 | +curl_api https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/durable_objects/namespaces >/dev/null |
| 72 | + |
| 73 | +# upload_script uploads our Worker code with the appropriate metadata. |
| 74 | +upload_script() { |
| 75 | + curl_api https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/$SCRIPT_NAME \ |
| 76 | + -X PUT \ |
| 77 | + -F "[email protected];type=application/json" \ |
| 78 | + -F "script=@$SCRIPT_FILE;type=application/javascript+module" \ |
| 79 | + > /dev/null |
| 80 | +} |
| 81 | + |
| 82 | +# upload_bootstrap_script is a temporary hack to work around a chicken-and-egg problem: in order |
| 83 | +# to define a Durable Object namespace, we must tell it a script and class name. But when we upload our |
| 84 | +# script, we need to configure the environment to bind to our durable object namespaces. This function |
| 85 | +# uploads a version of our script with an empty environment (no bindings). The script won't be able |
| 86 | +# to run correctly, but this gets us far enough to define the namespaces, and then we can upload the |
| 87 | +# script with full environment later. |
| 88 | +# |
| 89 | +# This is obviously dumb and we (Cloudflare) will come up with something better soon. |
| 90 | +upload_bootstrap_script() { |
| 91 | + echo '{"main_module": "'$SCRIPT_FILE'"}' > bootstrap-metadata.json |
| 92 | + curl_api https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/$SCRIPT_NAME \ |
| 93 | + -X PUT \ |
| 94 | + -F "[email protected];type=application/json" \ |
| 95 | + -F "script=@$SCRIPT_FILE;type=application/javascript+module" \ |
| 96 | + > /dev/null |
| 97 | + rm bootstrap-metadata.json |
| 98 | +} |
| 99 | + |
| 100 | +# upsert_namespace configures a Durable Object namespace so that instances of it can be created and called |
| 101 | +# from other scripts (or from the same script). This function checks if the namespace already exists, |
| 102 | +# creates it if it doesn't, and either way writes the namespace ID to stdout. |
| 103 | +# |
| 104 | +# The namespace ID can be used to configure environment bindings in other scripts (or even the same |
| 105 | +# script) such that they can send messages to instances of this namespace. |
| 106 | +upsert_namespace() { |
| 107 | + # Check if the namespace exists already. |
| 108 | + EXISTING_ID=$(\ |
| 109 | + curl_api https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/durable_objects/namespaces | \ |
| 110 | + jq -r ".[] | select(.script == \"$SCRIPT_NAME\" and .class == \"$1\") | .id") |
| 111 | + |
| 112 | + if [ "$EXISTING_ID" != "" ]; then |
| 113 | + echo $EXISTING_ID |
| 114 | + return |
| 115 | + fi |
| 116 | + |
| 117 | + # No. Create it. |
| 118 | + curl_api https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/durable_objects/namespaces \ |
| 119 | + -X POST --data "{\"name\": \"$SCRIPT_NAME-$1\", \"script\": \"$SCRIPT_NAME\", \"class\": \"$1\"}" | \ |
| 120 | + jq -r .id |
| 121 | +} |
| 122 | + |
| 123 | +if [ ! -e metadata.json ]; then |
| 124 | + # If metadata.json doesn't exist we assume this is first-time setup and we need to create the |
| 125 | + # namespaces. |
| 126 | + |
| 127 | + upload_bootstrap_script |
| 128 | + NAMESPACE_ID=$(upsert_namespace $CLASS_NAME) |
| 129 | + |
| 130 | + cat > metadata.json << __EOF__ |
| 131 | +{ |
| 132 | + "main_module": "$SCRIPT_FILE", |
| 133 | + "bindings": [ |
| 134 | + { |
| 135 | + "type": "durable_object_namespace", |
| 136 | + "name": "$CLASS_NAME", |
| 137 | + "namespace_id": "$NAMESPACE_ID" |
| 138 | + } |
| 139 | + ] |
| 140 | +} |
| 141 | +__EOF__ |
| 142 | +fi |
| 143 | + |
| 144 | +upload_script |
| 145 | + |
| 146 | +echo "App uploaded to your account under the name: $SCRIPT_NAME" |
| 147 | +echo "You may deploy it to a specific host in the Cloudflare Dashboard." |
0 commit comments