Skip to content

Instantly share code, notes, and snippets.

@msrivastav13
Last active April 9, 2026 02:16
Show Gist options
  • Select an option

  • Save msrivastav13/2a7d237daf2c649a06cc9c1badbf3d62 to your computer and use it in GitHub Desktop.

Select an option

Save msrivastav13/2a7d237daf2c649a06cc9c1badbf3d62 to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# setup_tdx_av.sh
# Creates TDX_AV/ with two SFDX projects (Todo: standard, Pronto: reactexternalapp),
# logs in via `sf org login web`, sets the org as default for both projects,
# and runs `npm install` in each.
#
# Surfaces OS permission errors immediately with actionable hints.
#
# Requires: sf CLI, node/npm.
set -eo pipefail
ROOT_DIR="TDX_AV"
TARGET_PARENT="${TDX_AV_DIR:-$HOME/Desktop}" # override with TDX_AV_DIR=/some/path
TARGET_DIR="$TARGET_PARENT/$ROOT_DIR"
STANDARD_PROJ="Todo"
B2C_PROJ="Pronto"
ORG_ALIAS="TDX26"
# Heroku sandbox broker. Override SANDBOX_API at runtime if needed.
SANDBOX_API="${SANDBOX_API:-https://tdx-av-zone-broker-663ef89ce24a.herokuapp.com}"
LOGIN_URL="https://test.salesforce.com"
CLIENT_UA="tdx-setup/1.0"
step() { printf "\n\033[1;34m==> %s\033[0m\n" "$1"; }
err() { printf "\n\033[1;31m!! %s\033[0m\n" "$1" 1>&2; }
sudo_hint() {
{
err "Permission denied while: $1"
echo " The OS rejected the operation. To proceed, re-run with sudo:"
echo " sudo $0"
echo " Or fix ownership of the parent directory first:"
echo " sudo chown -R \"\$(whoami)\" \"$(pwd)\""
} 1>&2
}
node_perm_hint() {
{
err "npm hit a permission error (EACCES)."
echo " npm can't write to its cache, prefix, or this project folder."
echo " Run ONE of the following, then re-run this script:"
echo
echo " 1) Fix ownership of the npm cache (recommended):"
echo " sudo chown -R \"\$(whoami)\" \"\$HOME/.npm\""
echo
echo " 2) Fix ownership of this project directory:"
echo " sudo chown -R \"\$(whoami)\" \"$(pwd)\""
echo
echo " 3) Switch to a user-space Node via nvm (no sudo needed afterwards):"
echo " brew install nvm"
echo " mkdir -p \"\$HOME/.nvm\""
echo " export NVM_DIR=\"\$HOME/.nvm\""
echo " . \"\$(brew --prefix nvm)/nvm.sh\""
echo " nvm install --lts && nvm use --lts"
} 1>&2
}
# run_check <label> <cmd> [args...]
# Streams output live and, on failure, classifies the error.
run_check() {
local label="$1"; shift
local logfile
logfile=$(mktemp)
if ! "$@" 2>&1 | tee "$logfile"; then
if grep -qiE "permission denied|eacces|operation not permitted" "$logfile"; then
case "$1" in
npm|npx|node) node_perm_hint ;;
*) sudo_hint "$label" ;;
esac
fi
rm -f "$logfile"
exit 1
fi
rm -f "$logfile"
}
# ---- Sandbox broker helpers ------------------------------------------------
# All JSON parsing uses Node (already required by this script — see README).
# No python anywhere.
require_node() {
command -v node >/dev/null 2>&1 || { err "node is required on PATH"; exit 1; }
}
# json_get <key> — read JSON on stdin, print value at top-level key.
json_get() {
node -e '
let s = "";
process.stdin.on("data", d => s += d).on("end", () => {
try {
const v = JSON.parse(s)[process.argv[1]];
if (v == null) process.exit(2);
process.stdout.write(String(v));
} catch (e) { process.exit(1); }
});
' "$1"
}
# json_get_path <dot.path> — read JSON on stdin, print nested value.
json_get_path() {
node -e '
let s = "";
process.stdin.on("data", d => s += d).on("end", () => {
try {
let v = JSON.parse(s);
for (const k of process.argv[1].split(".")) v = v == null ? v : v[k];
if (v == null) process.exit(2);
process.stdout.write(String(v));
} catch (e) { process.exit(1); }
});
' "$1"
}
# claim_sandbox <pin> — POST /claim, populates CLAIMED_USER.
# The broker NEVER returns a password — the attendee types their own
# sandbox password in the Salesforce browser login screen.
claim_sandbox() {
local pin="$1" resp http
resp=$(mktemp)
chmod 600 "$resp" 2>/dev/null || true
http=$(curl -sS --max-time 10 -A "$CLIENT_UA" \
-o "$resp" -w '%{http_code}' \
-X POST "$SANDBOX_API/claim" \
-H "Content-Type: application/json" \
-H "X-Pin: $pin" \
--data '{}') \
|| { err "Network error contacting sandbox service. Check Wi-Fi and try again."; rm -f "$resp"; exit 1; }
if [ "$http" != "200" ]; then
# Never echo the body — it could be misleading or attacker-controlled.
case "$http" in
401) err "Wrong PIN. Double-check the PIN you were given for the event." ;;
409) err "Sandbox pool is empty. Please see a TDX staffer." ;;
429) err "This laptop is temporarily rate-limited. Wait a minute and retry." ;;
503) err "Sandbox service is in safe mode. Please see a TDX staffer." ;;
*) err "Sandbox claim failed (HTTP $http). Please see a TDX staffer." ;;
esac
rm -f "$resp"; exit 1
fi
CLAIMED_USER=$(json_get username < "$resp") \
|| { err "Bad response from sandbox service."; rm -f "$resp"; exit 1; }
rm -f "$resp"
}
# release_sandbox — POST /release on EXIT if we never confirmed.
release_sandbox() {
[ -n "${CLAIMED_USER:-}" ] && [ -z "${CLAIMED_CONFIRMED:-}" ] || return 0
[ -n "${STAFF_PIN:-}" ] || return 0
curl -sS --max-time 10 -A "$CLIENT_UA" \
-X POST "$SANDBOX_API/release" \
-H "Content-Type: application/json" \
-H "X-Pin: $STAFF_PIN" \
--data "{\"username\":\"$CLAIMED_USER\"}" >/dev/null 2>&1 || true
}
# confirm_sandbox — POST /confirm after successful OAuth + username verify.
confirm_sandbox() {
curl -sS --max-time 10 -A "$CLIENT_UA" \
-X POST "$SANDBOX_API/confirm" \
-H "Content-Type: application/json" \
-H "X-Pin: $STAFF_PIN" \
--data "{\"username\":\"$CLAIMED_USER\"}" >/dev/null 2>&1 \
&& CLAIMED_CONFIRMED=1
}
# scrub_secrets — wipe PIN from this shell's memory on EXIT.
scrub_secrets() {
unset STAFF_PIN
}
if [ ! -d "$TARGET_PARENT" ]; then
err "Target parent does not exist: $TARGET_PARENT"
{
echo " Set TDX_AV_DIR to an existing directory and re-run, e.g.:"
echo " TDX_AV_DIR=\"\$HOME\" $0"
} 1>&2
exit 1
fi
if [ -d "$TARGET_DIR" ]; then
step "Wiping existing $TARGET_DIR for a clean setup"
run_check "rm -rf $TARGET_DIR" rm -rf "$TARGET_DIR"
fi
require_node
# Always release on exit if we never confirmed, then wipe secrets from memory.
trap 'release_sandbox; scrub_secrets' EXIT
step "Enter the 4-digit TDX access PIN"
# -s hides the PIN from the terminal; </dev/tty makes this work under
# `bash <(curl ...)` where stdin would otherwise be the script body.
read -r -s -p "PIN: " STAFF_PIN </dev/tty
echo
if ! printf '%s' "$STAFF_PIN" | grep -Eq '^[0-9]{4}$'; then
err "PIN must be exactly 4 digits."
exit 1
fi
step "Allocating sandbox..."
claim_sandbox "$STAFF_PIN"
cat <<EOF
┌──────────────────────────────────────────────────────────────┐
│ Your sandbox username (use this in the browser login): │
│ │
│ Username : $CLAIMED_USER
│ │
│ You will type your sandbox password directly in the browser │
│ login screen — this script does not see it. │
└──────────────────────────────────────────────────────────────┘
Keep this username visible — you'll need it in the next step.
EOF
step "Removing existing Salesforce auth for alias $ORG_ALIAS (if any)"
sf org logout --target-org "$ORG_ALIAS" --no-prompt >/dev/null 2>&1 || true
step "Creating root directory: $TARGET_DIR"
run_check "mkdir $TARGET_DIR" mkdir -p "$TARGET_DIR"
cd "$TARGET_DIR"
step "Creating standard SFDX project: $STANDARD_PROJ"
run_check "sf template generate project $STANDARD_PROJ" \
sf template generate project --name "$STANDARD_PROJ" --template standard
step "Creating React (B2C / external) SFDX project: $B2C_PROJ"
run_check "sf template generate project $B2C_PROJ" \
sf template generate project --name "$B2C_PROJ" --template reactexternalapp
step "Salesforce sandbox login (alias: $ORG_ALIAS)"
echo " A browser will open. Sign in as: $CLAIMED_USER"
read -r -p "Press Enter to launch the browser login..." _ </dev/tty
run_check "sf org login web" \
sf org login web --alias "$ORG_ALIAS" --instance-url "$LOGIN_URL" --set-default
# Verify the user actually logged in with the issued sandbox username.
# If they typed personal credentials by mistake, abort and let the EXIT trap
# release the row back to the pool.
ACTUAL=$(sf org display --target-org "$ORG_ALIAS" --json 2>/dev/null | json_get_path result.username) \
|| { err "Could not read sf org display output."; exit 1; }
if [ "$ACTUAL" != "$CLAIMED_USER" ]; then
err "Logged-in username ($ACTUAL) does not match the issued sandbox ($CLAIMED_USER)."
err "Releasing the sandbox and aborting. Re-run the script and sign in with $CLAIMED_USER."
exit 1
fi
confirm_sandbox || { err "Could not confirm sandbox claim with the broker."; exit 1; }
step "Sandbox $CLAIMED_USER is yours."
for proj in "$STANDARD_PROJ" "$B2C_PROJ"; do
step "Configuring $proj (default org + npm install)"
(
cd "$proj"
run_check "set default org ($proj)" sf config set target-org="$ORG_ALIAS"
echo " Running npm install for $proj — this can take several minutes; you'll see each package as it downloads."
run_check "npm install ($proj)" npm install --loglevel=http --foreground-scripts
)
done
step "Done. Projects are under $(pwd)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment