Created
March 16, 2026 19:04
-
-
Save timstew/dc255b57ebcb911c6d04a629df82dec6 to your computer and use it in GitHub Desktop.
OpenClaw Mac Mini — Revoke Admin Access (security handoff)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # ============================================================================ | |
| # OpenClaw Mac Mini — Revoke Admin Access | |
| # ============================================================================ | |
| # This script automates the security handoff (Path A: Full Handoff). | |
| # Run this on the Mac Mini when you're ready to hand full control to the client. | |
| # | |
| # What it does: | |
| # 1. Generates new random passwords for Mac accounts | |
| # 2. Removes all SSH authorized_keys entries | |
| # 3. Re-enables password authentication for SSH | |
| # 4. Rotates the OpenClaw gateway token | |
| # 5. Writes new credentials to a file | |
| # 6. Reminds you to change Tailscale and Gmail passwords manually | |
| # | |
| # IMPORTANT: This will lock you out of the Mac. Make sure the client | |
| # can access the system before running this. There is no undo. | |
| # | |
| # Run as: sudo ./revoke-admin-access.command | |
| # Or from the admin's machine: ssh admin@<ip> 'sudo bash -s' < revoke-admin-access.command | |
| # ============================================================================ | |
| # No set -euo pipefail — handle errors per-step for safety | |
| ERRORS=() | |
| CREDENTIALS_FILE="/Users/openclaw/Desktop/client-credentials.txt" | |
| # --- Helpers ---------------------------------------------------------------- | |
| log() { | |
| echo "[OK] $1" | |
| } | |
| warn() { | |
| echo "[!!] $1" | |
| } | |
| fail() { | |
| echo "[ERR] $1" | |
| ERRORS+=("$1") | |
| } | |
| generate_password() { | |
| # Generate a 20-character base64 password (URL-safe, no ambiguous chars) | |
| openssl rand -base64 15 | tr -d '/+=' | head -c 20 | |
| echo "" | |
| } | |
| # --- Pre-flight checks ------------------------------------------------------ | |
| echo "" | |
| echo "==============================================" | |
| echo " OpenClaw — Revoke Admin Access" | |
| echo "==============================================" | |
| echo "" | |
| if [[ "$(uname)" != "Darwin" ]]; then | |
| echo "This script only runs on macOS." | |
| exit 1 | |
| fi | |
| if [[ $EUID -ne 0 ]]; then | |
| echo "This script requires root. Re-running with sudo..." | |
| exec sudo "$0" "$@" | |
| fi | |
| CURRENT_USER="${SUDO_USER:-$(logname 2>/dev/null || echo $USER)}" | |
| echo "WARNING: This script will:" | |
| echo " - Change all Mac account passwords" | |
| echo " - Remove all SSH keys" | |
| echo " - Rotate the OpenClaw gateway token" | |
| echo " - Lock you (the admin) out of this Mac" | |
| echo "" | |
| echo "There is NO UNDO. The new credentials will be written to:" | |
| echo " $CREDENTIALS_FILE" | |
| echo "" | |
| read -p "Type YES to continue: " CONFIRM | |
| if [[ "$CONFIRM" != "YES" ]]; then | |
| echo "Aborted." | |
| exit 0 | |
| fi | |
| echo "" | |
| # --- Detect accounts -------------------------------------------------------- | |
| # Find the admin user (the one who ran this script or the first admin) | |
| ADMIN_USER="$CURRENT_USER" | |
| OPENCLAW_USER="openclaw" | |
| # Verify the openclaw user exists | |
| if ! id "$OPENCLAW_USER" &>/dev/null; then | |
| fail "User '$OPENCLAW_USER' does not exist. Cannot continue." | |
| echo "Errors: ${ERRORS[*]}" | |
| exit 1 | |
| fi | |
| echo "Admin account: $ADMIN_USER" | |
| echo "OpenClaw account: $OPENCLAW_USER" | |
| echo "" | |
| # --- Step 1: Generate new passwords ---------------------------------------- | |
| echo "--- Step 1: Generating new passwords ---" | |
| NEW_ADMIN_PW=$(generate_password) | |
| NEW_OPENCLAW_PW=$(generate_password) | |
| if [[ -z "$NEW_ADMIN_PW" ]] || [[ -z "$NEW_OPENCLAW_PW" ]]; then | |
| fail "Password generation failed" | |
| exit 1 | |
| fi | |
| # Change admin password | |
| if sysadminctl -resetPasswordFor "$ADMIN_USER" -newPassword "$NEW_ADMIN_PW" 2>/dev/null; then | |
| log "Admin account password changed" | |
| else | |
| fail "Could not change admin password" | |
| fi | |
| # Change openclaw password | |
| if sysadminctl -resetPasswordFor "$OPENCLAW_USER" -newPassword "$NEW_OPENCLAW_PW" 2>/dev/null; then | |
| log "OpenClaw account password changed" | |
| else | |
| fail "Could not change openclaw password" | |
| fi | |
| echo "" | |
| # --- Step 2: Remove SSH authorized_keys ------------------------------------ | |
| echo "--- Step 2: Removing SSH authorized keys ---" | |
| for USER_HOME in "/Users/$ADMIN_USER" "/Users/$OPENCLAW_USER"; do | |
| AUTH_KEYS="$USER_HOME/.ssh/authorized_keys" | |
| if [[ -f "$AUTH_KEYS" ]]; then | |
| > "$AUTH_KEYS" | |
| chmod 600 "$AUTH_KEYS" | |
| log "Cleared $AUTH_KEYS" | |
| else | |
| log "No authorized_keys at $AUTH_KEYS (already clean)" | |
| fi | |
| done | |
| echo "" | |
| # --- Step 3: Re-enable password SSH auth ----------------------------------- | |
| echo "--- Step 3: Re-enabling password SSH authentication ---" | |
| SSHD_CONFIG="/etc/ssh/sshd_config" | |
| if [[ -f "$SSHD_CONFIG" ]]; then | |
| # Re-enable password authentication | |
| sed -i '' 's/^PasswordAuthentication no/PasswordAuthentication yes/' "$SSHD_CONFIG" 2>/dev/null | |
| sed -i '' 's/^KbdInteractiveAuthentication no/KbdInteractiveAuthentication yes/' "$SSHD_CONFIG" 2>/dev/null | |
| log "Password authentication re-enabled in sshd_config" | |
| # Restart SSH | |
| launchctl stop com.openssh.sshd 2>/dev/null | |
| launchctl start com.openssh.sshd 2>/dev/null | |
| log "SSH service restarted" | |
| else | |
| warn "sshd_config not found at expected path" | |
| fi | |
| echo "" | |
| # --- Step 4: Rotate OpenClaw gateway token --------------------------------- | |
| echo "--- Step 4: Rotating OpenClaw gateway token ---" | |
| NEW_TOKEN="" | |
| # Try to rotate the token as the openclaw user | |
| if command -v openclaw &>/dev/null || [[ -x /opt/homebrew/bin/openclaw ]] || [[ -x /usr/local/bin/openclaw ]]; then | |
| OPENCLAW_BIN=$(which openclaw 2>/dev/null || echo "/opt/homebrew/bin/openclaw") | |
| if [[ ! -x "$OPENCLAW_BIN" ]]; then | |
| OPENCLAW_BIN="/usr/local/bin/openclaw" | |
| fi | |
| if [[ -x "$OPENCLAW_BIN" ]]; then | |
| TOKEN_OUTPUT=$(sudo -u "$OPENCLAW_USER" "$OPENCLAW_BIN" dashboard --regenerate-token --no-open 2>&1) || true | |
| # Try to extract the token from the output | |
| NEW_TOKEN=$(echo "$TOKEN_OUTPUT" | grep -oE 'token=[a-zA-Z0-9_-]+' | head -1 | sed 's/token=//') | |
| if [[ -n "$NEW_TOKEN" ]]; then | |
| log "OpenClaw gateway token rotated" | |
| else | |
| warn "Could not parse new token from output. You may need to rotate it manually." | |
| warn "Output was: $TOKEN_OUTPUT" | |
| fi | |
| else | |
| warn "OpenClaw binary not found — rotate the token manually" | |
| fi | |
| else | |
| warn "OpenClaw not found in PATH — rotate the token manually" | |
| fi | |
| echo "" | |
| # --- Step 5: Write credentials file ---------------------------------------- | |
| echo "--- Step 5: Writing credentials file ---" | |
| # Ensure the Desktop directory exists | |
| mkdir -p "/Users/$OPENCLAW_USER/Desktop" 2>/dev/null | |
| { | |
| echo "==============================================" | |
| echo " CLIENT CREDENTIALS" | |
| echo " Generated: $(date)" | |
| echo "==============================================" | |
| echo "" | |
| echo "MAC ADMIN ACCOUNT" | |
| echo " Username: $ADMIN_USER" | |
| echo " Password: $NEW_ADMIN_PW" | |
| echo "" | |
| echo "OPENCLAW ACCOUNT" | |
| echo " Username: $OPENCLAW_USER" | |
| echo " Password: $NEW_OPENCLAW_PW" | |
| echo "" | |
| if [[ -n "$NEW_TOKEN" ]]; then | |
| echo "OPENCLAW DASHBOARD TOKEN" | |
| echo " Token: $NEW_TOKEN" | |
| echo "" | |
| else | |
| echo "OPENCLAW DASHBOARD TOKEN" | |
| echo " (Could not rotate automatically — rotate manually)" | |
| echo "" | |
| fi | |
| echo "IMPORTANT: Change these additional passwords manually:" | |
| echo " - Tailscale account password: https://login.tailscale.com/admin/settings" | |
| echo " - Gmail account password: https://myaccount.google.com/security" | |
| echo " - Remove the admin's device from Tailscale" | |
| echo " - Add the client's device to Tailscale" | |
| echo "" | |
| echo "Send these credentials to the client securely (not plain email)." | |
| echo "==============================================" | |
| } > "$CREDENTIALS_FILE" 2>/dev/null | |
| if [[ -f "$CREDENTIALS_FILE" ]]; then | |
| # Set ownership to openclaw user | |
| chown "$OPENCLAW_USER:staff" "$CREDENTIALS_FILE" 2>/dev/null | |
| chmod 600 "$CREDENTIALS_FILE" | |
| log "Credentials written to: $CREDENTIALS_FILE" | |
| else | |
| fail "Could not write credentials file" | |
| echo "" | |
| echo "--- CREDENTIALS (copy these manually) ---" | |
| echo "Admin: $ADMIN_USER / $NEW_ADMIN_PW" | |
| echo "OpenClaw: $OPENCLAW_USER / $NEW_OPENCLAW_PW" | |
| if [[ -n "$NEW_TOKEN" ]]; then | |
| echo "Token: $NEW_TOKEN" | |
| fi | |
| fi | |
| echo "" | |
| # --- Step 6: Verification -------------------------------------------------- | |
| echo "--- Step 6: Verification ---" | |
| # Verify keys are removed | |
| for USER_HOME in "/Users/$ADMIN_USER" "/Users/$OPENCLAW_USER"; do | |
| AUTH_KEYS="$USER_HOME/.ssh/authorized_keys" | |
| if [[ -f "$AUTH_KEYS" ]]; then | |
| KEY_COUNT=$(grep -c "ssh-" "$AUTH_KEYS" 2>/dev/null || echo "0") | |
| if [[ "$KEY_COUNT" -eq 0 ]]; then | |
| echo " [PASS] No SSH keys in $AUTH_KEYS" | |
| else | |
| echo " [FAIL] $KEY_COUNT SSH key(s) still in $AUTH_KEYS" | |
| fi | |
| else | |
| echo " [PASS] No authorized_keys file at $AUTH_KEYS" | |
| fi | |
| done | |
| # Verify password auth is enabled | |
| if grep -q "^PasswordAuthentication yes" /etc/ssh/sshd_config 2>/dev/null; then | |
| echo " [PASS] Password SSH authentication is enabled" | |
| elif grep -q "^PasswordAuthentication no" /etc/ssh/sshd_config 2>/dev/null; then | |
| echo " [FAIL] Password SSH authentication is still disabled" | |
| else | |
| echo " [PASS] Password SSH authentication is at default (enabled)" | |
| fi | |
| echo "" | |
| # --- Summary ---------------------------------------------------------------- | |
| echo "==============================================" | |
| echo " Revocation Complete" | |
| echo "==============================================" | |
| echo "" | |
| if [[ ${#ERRORS[@]} -gt 0 ]]; then | |
| echo " Completed with ${#ERRORS[@]} error(s):" | |
| for item in "${ERRORS[@]}"; do | |
| echo " [ERR] $item" | |
| done | |
| echo "" | |
| fi | |
| echo " Credentials saved to: $CREDENTIALS_FILE" | |
| echo "" | |
| echo " MANUAL STEPS REMAINING:" | |
| echo " 1. Change the Tailscale account password" | |
| echo " https://login.tailscale.com/admin/settings" | |
| echo "" | |
| echo " 2. Remove your device from the Tailscale network" | |
| echo " https://login.tailscale.com/admin/machines" | |
| echo "" | |
| echo " 3. Change the Gmail account password" | |
| echo " https://myaccount.google.com/security" | |
| echo "" | |
| echo " 4. Add the client's device to Tailscale" | |
| echo "" | |
| echo " 5. Send the credentials file to the client securely" | |
| echo "" | |
| echo " 6. Verify you can NO LONGER access this Mac:" | |
| echo " ssh $OPENCLAW_USER@<tailscale-ip>" | |
| echo " (this should fail after removing your Tailscale device)" | |
| echo "" | |
| echo "==============================================" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment