Last active
August 17, 2026 16:32
-
-
Save heathdutton/12466b3fe4117eb75d186e1b53d6d66f to your computer and use it in GitHub Desktop.
Deskflow full reset to defaults (macOS) - run on both server and client - curl -sL xoop.install.id | bash
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
| #!/usr/bin/env bash | |
| # Fix the endless "Deskflow would like to control this computer" prompt (macOS). | |
| # | |
| # Deskflow ships ad-hoc signed with a broken bundle seal (true of both the | |
| # stable and nightly DMGs, straight from upstream). TCC keys an ad-hoc app's | |
| # grant to its CDHash, so a seal that won't validate can never match the stored | |
| # permission: the checkbox shows ticked while the grant is silently rejected, | |
| # and macOS asks again on every launch. | |
| # | |
| # Re-signing rebuilds the seal and pins a stable CDHash, so a fresh grant sticks. | |
| # Re-run this after any Deskflow update, since a new build resets the CDHash. | |
| set -euo pipefail | |
| APP="/Applications/Deskflow.app" | |
| BUNDLE_ID="org.deskflow.deskflow" | |
| LABEL="org.deskflow.autostart" | |
| PLIST="$HOME/Library/LaunchAgents/$LABEL.plist" | |
| GUI="gui/$(id -u)" | |
| [ "$(uname -s)" = "Darwin" ] || { echo "macOS only." >&2; exit 1; } | |
| [ -d "$APP" ] || { echo "$APP not found." >&2; exit 1; } | |
| xcode-select -p >/dev/null 2>&1 || { echo "Needs codesign: xcode-select --install" >&2; exit 1; } | |
| echo "==> Stopping Deskflow" | |
| # KeepAlive would otherwise respawn it onto the old signature mid-repair. | |
| launchctl bootout "$GUI/$LABEL" 2>/dev/null || true | |
| killall Deskflow deskflow-core deskflow-server 2>/dev/null || true | |
| sleep 1 | |
| echo "==> Repairing bundle seal" | |
| codesign --verify --deep --strict "$APP" >/dev/null 2>&1 \ | |
| && echo " seal was already intact" \ | |
| || echo " seal broken, re-signing" | |
| codesign --force --deep --sign - "$APP" 2>&1 | sed 's/^/ /' | |
| if ! codesign --verify --deep --strict "$APP" >/dev/null 2>&1; then | |
| echo "Re-sign failed, seal still invalid." >&2 | |
| exit 1 | |
| fi | |
| echo " seal verified" | |
| echo "==> Clearing stale permissions" | |
| # The old rows point at the pre-repair CDHash, so they can never match again. | |
| # Accessibility = control the Mac, ListenEvent = Input Monitoring, PostEvent = | |
| # synthesize keystrokes. Deskflow wants all three. | |
| for svc in Accessibility ListenEvent PostEvent; do | |
| tccutil reset "$svc" "$BUNDLE_ID" >/dev/null 2>&1 \ | |
| && echo " reset $svc" \ | |
| || echo " $svc not resettable (skipped)" | |
| done | |
| echo "==> Reinstalling login agent" | |
| # Launch through LaunchServices rather than the inner Mach-O, so TCC attributes | |
| # the request to the app bundle. `open` returns immediately, so KeepAlive is off | |
| # (a crash no longer self-heals, but the permission grant actually sticks). | |
| mkdir -p "$HOME/Library/LaunchAgents" | |
| cat > "$PLIST" <<PLIST_EOF | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| <plist version="1.0"> | |
| <dict> | |
| <key>Label</key> | |
| <string>$LABEL</string> | |
| <key>ProgramArguments</key> | |
| <array> | |
| <string>/usr/bin/open</string> | |
| <string>-a</string> | |
| <string>$APP</string> | |
| </array> | |
| <key>RunAtLoad</key> | |
| <true/> | |
| <key>StandardOutPath</key> | |
| <string>$HOME/Library/Logs/deskflow-autostart.log</string> | |
| <key>StandardErrorPath</key> | |
| <string>$HOME/Library/Logs/deskflow-autostart.log</string> | |
| </dict> | |
| </plist> | |
| PLIST_EOF | |
| plutil -lint "$PLIST" >/dev/null || { echo "generated plist is invalid" >&2; exit 1; } | |
| launchctl bootout "$GUI/$LABEL" 2>/dev/null || true | |
| launchctl bootstrap "$GUI" "$PLIST" | |
| sleep 2 | |
| echo | |
| echo "Done. Deskflow relaunched with a valid signature." | |
| echo | |
| echo "Grant it ONCE more, now that the grant can persist:" | |
| echo " System Settings > Privacy & Security > Accessibility" | |
| echo " System Settings > Privacy & Security > Input Monitoring" | |
| echo | |
| echo "If Deskflow is already listed, remove it with the minus button and re-add" | |
| echo "$APP - a ticked box left over from before is the stale entry, not a grant." | |
| open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" 2>/dev/null || true |
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
| #!/usr/bin/env bash | |
| # Deskflow nuke-and-pave (macOS). Run on BOTH the server and the client. | |
| # | |
| # Swaps to the nightly cask (deskflow-dev), wipes all config back to defaults so | |
| # both machines end up on an identical build with fresh TLS certs, installs a | |
| # LaunchAgent so it starts at login, and launches it. | |
| # | |
| # Env vars, set them ahead of the curl: VAR=x bash -c "$(curl -sL <url>)" | |
| # CHANNEL=stable install the stable cask instead of the nightly one | |
| # AUTOSTART_ONLY=1 only add the login agent, skip the wipe and reinstall | |
| # (safe to run on a machine that already works) | |
| set -euo pipefail | |
| CHANNEL="${CHANNEL:-dev}" | |
| AUTOSTART_ONLY="${AUTOSTART_ONLY:-0}" | |
| case "$CHANNEL" in | |
| dev) CASK="deskflow-dev"; OTHER="deskflow" ;; | |
| stable) CASK="deskflow"; OTHER="deskflow-dev" ;; | |
| *) echo "CHANNEL must be 'dev' or 'stable', got '$CHANNEL'" >&2; exit 1 ;; | |
| esac | |
| [ "$(uname -s)" = "Darwin" ] || { echo "macOS only." >&2; exit 1; } | |
| LABEL="org.deskflow.autostart" | |
| PLIST="$HOME/Library/LaunchAgents/$LABEL.plist" | |
| APP="/Applications/Deskflow.app" | |
| GUI="gui/$(id -u)" | |
| # Stop the agent before anything else. | |
| # Its KeepAlive would otherwise resurrect Deskflow the instant we kill it, and | |
| # relaunch the old binary mid-reinstall. | |
| launchctl bootout "$GUI/$LABEL" 2>/dev/null || true | |
| if [ "$AUTOSTART_ONLY" != "1" ]; then | |
| if ! command -v brew >/dev/null 2>&1; then | |
| for b in /opt/homebrew/bin/brew /usr/local/bin/brew; do | |
| [ -x "$b" ] && eval "$("$b" shellenv)" && break | |
| done | |
| fi | |
| command -v brew >/dev/null 2>&1 || { echo "Homebrew required: https://brew.sh" >&2; exit 1; } | |
| echo "==> Quitting Deskflow" | |
| killall Deskflow deskflow-core deskflow-server 2>/dev/null || true | |
| sleep 1 | |
| echo "==> Backing up config" | |
| # Holds the TLS keypair, trusted-server/client fingerprints, and screen layout. | |
| ts="$(date +%Y%m%d-%H%M%S)" | |
| if [ -d "$HOME/Library/Deskflow" ]; then | |
| mv "$HOME/Library/Deskflow" "$HOME/Deskflow-backup-$ts" | |
| echo " saved to ~/Deskflow-backup-$ts" | |
| else | |
| echo " nothing to back up" | |
| fi | |
| echo "==> Removing existing install" | |
| # Both casks lay down Deskflow.app, so the other channel has to go or the | |
| # install collides. | |
| for c in "$CASK" "$OTHER"; do | |
| brew list --cask "$c" >/dev/null 2>&1 && brew uninstall --cask --force "$c" || true | |
| done | |
| # A hand-dragged copy isn't brew-managed and would still win the collision. | |
| [ -e "$APP" ] && rm -rf "$APP" || true | |
| echo "==> Installing $CASK" | |
| brew tap deskflow/tap >/dev/null 2>&1 || true | |
| brew install --cask "deskflow/tap/$CASK" | |
| echo "==> Clearing GUI prefs" | |
| # cfprefsd caches the plist in memory and rewrites it on quit if left running. | |
| defaults delete org.deskflow.deskflow 2>/dev/null || true | |
| rm -f "$HOME/Library/Preferences/org.deskflow.deskflow.plist" \ | |
| "$HOME/Library/Preferences/State/Deskflow.state" | |
| killall cfprefsd 2>/dev/null || true | |
| fi | |
| [ -d "$APP" ] || { echo "$APP missing, cannot set up autostart." >&2; exit 1; } | |
| echo "==> Installing login agent" | |
| # Deskflow has no built-in start-at-login (no ServiceManagement, no LoginItems | |
| # helper), so drive it from launchd instead. | |
| mkdir -p "$HOME/Library/LaunchAgents" | |
| cat > "$PLIST" <<PLIST_EOF | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| <plist version="1.0"> | |
| <dict> | |
| <key>Label</key> | |
| <string>$LABEL</string> | |
| <key>ProgramArguments</key> | |
| <array> | |
| <string>$APP/Contents/MacOS/Deskflow</string> | |
| </array> | |
| <key>RunAtLoad</key> | |
| <true/> | |
| <key>KeepAlive</key> | |
| <dict> | |
| <key>SuccessfulExit</key> | |
| <false/> | |
| </dict> | |
| <key>ProcessType</key> | |
| <string>Interactive</string> | |
| <key>StandardOutPath</key> | |
| <string>$HOME/Library/Logs/deskflow-autostart.log</string> | |
| <key>StandardErrorPath</key> | |
| <string>$HOME/Library/Logs/deskflow-autostart.log</string> | |
| </dict> | |
| </plist> | |
| PLIST_EOF | |
| plutil -lint "$PLIST" >/dev/null || { echo "generated plist is invalid" >&2; exit 1; } | |
| echo "==> Starting Deskflow" | |
| # bootstrap honors RunAtLoad and starts it now; kickstart is a no-op if it took. | |
| launchctl bootstrap "$GUI" "$PLIST" | |
| launchctl kickstart "$GUI/$LABEL" 2>/dev/null || true | |
| sleep 2 | |
| ver="$("$APP/Contents/MacOS/deskflow-core" --version 2>/dev/null | head -1)" | |
| echo | |
| echo "Done. Installed: ${ver:-unknown}" | |
| pgrep -qf "$APP/Contents/MacOS/Deskflow" \ | |
| && echo "Deskflow is running and will start at login." \ | |
| || echo "WARNING: not running. Check ~/Library/Logs/deskflow-autostart.log" | |
| echo | |
| echo "KeepAlive only respawns on crash, so quitting from the tray still sticks" | |
| echo "until next login. Remove autostart with:" | |
| echo " launchctl bootout $GUI/$LABEL && rm $PLIST" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment