Last active
July 17, 2025 15:52
-
-
Save DV8FromTheWorld/adbe2eb3b5ba50620706a702989dd413 to your computer and use it in GitHub Desktop.
Notify vscode when remote server script is complete
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
Spawns a notification & sound on mac when a remote command executed via VS Code's ssh based Remote Host system has completed. | |
Only triggers when VS Code is not focused. | |
Follow ths instructions in `.zshrc` and the header of each file. |
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
# This goes in your ~/.zshrc file on your Coder box | |
function with_ssh_err_detection() { | |
# Color definitions | |
local RED=$'\033[0;31m' | |
local BLUE=$'\033[0;34m' | |
local YELLOW=$'\033[0;33m' | |
local BOLD=$'\033[1m' | |
local RESET=$'\033[0m' | |
local age_of_shell_in_seconds=$(ps -p $$ -o etimes=) | |
local logfile="$(mktemp)" | |
# Run the command (assumed to be an ssh commands), capture the output to check for connection errors. | |
{ | |
"$@" > /dev/null 2>"$logfile" < /dev/null | |
} &! | |
{ | |
# Let the log file populate a bit | |
sleep 0.5 | |
log_contains_connection_error=false | |
if grep -qE "Could not resolve|Connection refused|Permission denied|No route to host|timed out" "$logfile"; then | |
log_contains_connection_error=true | |
fi | |
# We grant ourselves a 20 second reprieve to let the reverse ssh server get setup | |
if $log_contains_connection_error && ((age_of_shell_in_seconds > 20 )); then | |
echo "" | |
echo "${BOLD}${YELLOW}[VSCode SSH command notification]${RESET} ${RED}Failed to show notification across reverse SSH tunnel:${RESET}" | |
while IFS= read -r line; do | |
echo "${RED} >${RESET} $line" | |
done < "$logfile" | |
echo "" | |
echo "${BOLD}${BLUE}💡 Did you remember to turn on the Reverse SSH Tunnel from your Mac?${RESET}" | |
echo "" | |
fi | |
rm -f "$logfile" | |
} &! | |
} | |
# Connects to the mac that is remoted into this coder box and triggers a notification any time a command completes. | |
# The notification script determines if the notification should go out or not based on if VSCode is foregrounded or not. | |
# This relies on having ssh setup correctly | |
# 1. Enable Remote Login on Mac | |
# | |
# 2. You need to make sure that you have the reverse SSH server setup on port 2222 on your mac | |
# On your Mac: | |
# > ssh -R 2222:localhost:22 <coder-ssh-host> | |
# My coder-ssh-host is coder.austin-keener-workspace2 | |
# | |
# THIS MUST ALWAYS BE ON IN THE FUTURE FOR THIS TO WORK. | |
# This will be maintained by launchd and ~/bin/coder/manage-coder-terminal.sh | |
# | |
# 3. You need to generate a key on the coder box and send it to the mac so coder box can use passwordless ssh | |
# On your Coder | |
# > ssh-keygen -t ed25519 -C "remote-to-mac" | |
# > ssh-copy-id -p 2222 -i ~/.ssh/id_ed25519.pub <user.name>@localhost | |
# My username was austin.keener@localhost | |
# | |
# 4. You need the correct ssh config in ~/.ssh/config on your Coder | |
# | |
# Host mac-notify | |
# HostName localhost | |
# Port 2222 | |
# User austin.keener | |
# ControlMaster auto | |
# ControlPath ~/.ssh/cm-%r@%h:%p | |
# ControlPersist 10m | |
# | |
# 5. You need the shell file on your mac at ~/bin/coder/notify-if-vscode-is-not-focused.sh with chmod +x | |
# File available at https://gist.github.com/DV8FromTheWorld/adbe2eb3b5ba50620706a702989dd413 | |
if [[ "$TERM_PROGRAM" == "vscode" ]]; then | |
precmd() { | |
with_ssh_err_detection ssh -o ConnectTimeout=2 mac-notify '~/bin/coder/notify-if-vscode-is-not-focused.sh' | |
} | |
fi |
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
<!-- | |
Goes in ~/Library/LaunchAgents/com.discord.coder-tunnel-watcher.plist on your mac | |
!!!Make sure to update the REPLACE_THIS_HERE with the correct path. | |
PList requires that all paths be absolute, so I cannot pre-fill it for you. | |
Mine is: | |
<string>/Users/austin.keener/bin/coder/manage-coder-tunnel.sh</string> | |
You'll need yours to have _your_ username. | |
Add to launchd via: | |
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.discord.coder-tunnel-watcher.plist | |
--> | |
<?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>com.discord.coder-tunnel-watcher</string> | |
<key>ProgramArguments</key> | |
<array> | |
<string>REPLACE_THIS_HERE</string> | |
</array> | |
<key>KeepAlive</key> | |
<true /> | |
<key>ThrottleInterval</key> | |
<integer>15</integer> | |
<key>RunAtLoad</key> | |
<true/> | |
<key>StandardOutPath</key> | |
<string>/tmp/coder-tunnel-watcher.log</string> | |
<key>StandardErrorPath</key> | |
<string>/tmp/coder-tunnel-watcher.err</string> | |
</dict> | |
</plist> |
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/zsh | |
# This goes in ~/bin/coder/manage-coder-tunnel.sh on your mac | |
# Make sure to run set execution permissions | |
# chmod +x ~/bin/coder/manage-coder-tunnel.sh | |
# | |
# Also, you must set the CODER_HOST below to the ssh host where your coderbox is setup | |
# You can likely find this information in your ~/.ssh/config or VSCode | |
# Austin's is "coder.austin-keener-workspace2" | |
CODER_HOST="" | |
TUNNEL_PORT="2222" | |
GROUP_ID="reverse-tunnel-notifier" | |
# Safely add Homebrew paths without removing existing system paths. We rely on executables installed via brew | |
if [[ -x /opt/homebrew/bin/brew ]]; then | |
export PATH="/opt/homebrew/bin:$PATH" | |
elif [[ -x /usr/local/bin/brew ]]; then | |
export PATH="/usr/local/bin:$PATH" | |
fi | |
# --- Helper Functions ------------------------------------ | |
is_vscode_ssh_active() { | |
ps -eo ppid,pid,command | grep '[s]sh' | grep "$CODER_HOST" | while read -r ppid pid cmd; do | |
parent_cmd=$(ps -p "$ppid" -o command=) | |
if echo "$parent_cmd" | grep -qE 'coder2|code|code-insiders'; then | |
return 0 | |
fi | |
done | |
return 1 | |
} | |
get_active_reverse_tunnel_pid() { | |
ps -eo pid,command | grep '[s]sh' | grep "$CODER_HOST" | grep "$TUNNEL_PORT" | while read -r pid command; do | |
echo "$pid" | |
return 0 | |
done | |
return 1 | |
} | |
wait_for_pid() { | |
local pid="$1" | |
while kill -0 "$pid" 2>/dev/null; do | |
sleep 1 | |
done | |
} | |
# --- Notification Wrapper ------------------------------------ | |
function notify() { | |
local title="Reverse SSH Tunnel" | |
local message="$1" | |
local group="${2:-$GROUP_ID}" # default group for notification deduplication | |
if command -v terminal-notifier >/dev/null 2>&1; then | |
terminal-notifier \ | |
-title "$title" \ | |
-message "$message" \ | |
-group "$group" | |
elif command -v osascript >/dev/null 2>&1; then | |
osascript -e "display notification \"${message}\" with title \"${title}\"" | |
else | |
echo "[notify fallback] $title: $message" | |
fi | |
} | |
# --- Main Tunnel Logic ------------------------------------ | |
if [[ -z "$CODER_HOST" ]]; then | |
notify "❌ CODER_HOST is not set in ~/bin/manage-coder-terminal.sh. Checking again in 60 seconds" | |
sleep 60 | |
exit 1 | |
fi | |
# Tunnel is already running. launchd got out of sync a bit, but we'll wait for this tunnel to be done | |
existing_tunnel_pid=$(get_active_reverse_tunnel_pid) | |
if [[ -n "$existing_tunnel_pid" ]]; then | |
wait_for_pid "$existing_tunnel_pid" | |
fi | |
# Check if forward SSH session is active | |
if is_vscode_ssh_active; then | |
notify "Starting reverse SSH tunnel to $CODER_HOST..." | |
ssh -N \ | |
-o ExitOnForwardFailure=yes \ | |
-o ServerAliveInterval=15 \ | |
-o ServerAliveCountMax=2 \ | |
-R ${TUNNEL_PORT}:localhost:22 ${CODER_HOST} & | |
ssh_pid=$! | |
# Wait a bit to let the SSH connection go through the handshake and either succeed or fail | |
# If it fails, the child ssh process will die, and then we can detect that | |
sleep 4 | |
if [[ -n "$ssh_pid" ]] && kill -0 "$ssh_pid" 2>/dev/null; then | |
notify "Tunnel is up ✅" | |
wait "$ssh_pid" | |
notify "Tunnel has disconnected ❌" | |
else | |
notify "Tunnel failed to establish ❌" | |
fi | |
fi |
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/zsh | |
# This goes in ~/bin/coder/notify-if-vscode-is-not-focused.sh on your mac | |
# Make sure to run set execution permissions | |
# chmod +x ~/bin/coder/notify-if-vscode-is-not-focused.sh | |
GROUP_ID="reverse-tunnel-notifier" | |
# Safely add Homebrew paths without removing existing system paths | |
if [[ -x /opt/homebrew/bin/brew ]]; then | |
export PATH="/opt/homebrew/bin:$PATH" | |
elif [[ -x /usr/local/bin/brew ]]; then | |
export PATH="/usr/local/bin:$PATH" | |
fi | |
# Notify + install terminal-notifier if missing | |
if ! command -v terminal-notifier >/dev/null 2>&1; then | |
osascript -e 'display notification "Installing terminal-notifier..." with title "VSCode SSH"' | |
if command -v brew >/dev/null 2>&1; then | |
if brew install terminal-notifier >/dev/null 2>&1; then | |
osascript -e 'display notification "terminal-notifier installed successfully." with title "VSCode SSH"' | |
else | |
osascript -e 'display notification "Failed to install terminal-notifier." with title "VSCode SSH"' | |
exit 1 | |
fi | |
else | |
osascript -e 'display notification "Homebrew not found. Cannot install terminal-notifier." with title "VSCode SSH"' | |
exit 1 | |
fi | |
fi | |
# Get bundle ID of the frontmost app | |
frontmost_bundle_id=$(osascript -e ' | |
tell application "System Events" | |
set frontApp to first application process whose frontmost is true | |
return bundle identifier of frontApp | |
end tell | |
') | |
# Only notify if VSCode is NOT frontmost | |
if [[ "$frontmost_bundle_id" != "com.microsoft.VSCode" ]]; then | |
terminal-notifier \ | |
-title "VSCode SSH" \ | |
-message "Remote command finished" \ | |
-activate "com.microsoft.VSCode" | |
-group "$GROUP_ID" \ | |
-sound "Glass.aiff" # sounds come from /System/Library/Sounds/ | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment