post

Posting to Google Spaces from Bash

I recently started using Google Spaces and wanted to have some automations send me messages when certain things happened on my systems, so I wrote a simple bash script to send a notification message.

If you’re the Space Manager of a Google Space you can create webhooks to send messages to that space. You can create a webhook for Google Spaces by going into Google Chat and selecting the Space you want to send messages to. Click on the Space name at the top of the screen then choose “Apps & integrations” from the drop-down menu. Then select “Webhooks” and create a webhook.

Each webhook has a name, an icon, and a URL. I wanted three types of messages: info, warning, and critical, so I created three webhooks named Info, Warning, and Critical, each with an appropriate icon.

Then I created the notify.sh script, copying the URLs for my space into an associative array. (Associative arrays were only introduced in Bash v4, so if you’re using an older version of bash this won’t work.)

#!/bin/bash

# List of webhooks from Google Spaces.
declare -A webhooks
webhooks=(
    ["info"]="https://chat.googleapis.com/v1/spaces/..."
    ["warning"]="https://chat.googleapis.com/v1/spaces/..."
    ["critical"]="https://chat.googleapis.com/v1/spaces/..."
)

# Use the first argument -- "info", "warning",
# or "critical" -- to pick a webhook URL.
GCHAT_WEBHOOK_URL="${webhooks[$1]}"

if [[ -z $GCHAT_WEBHOOK_URL ]]; then
    # If the first argument wasn't "info", "warning",
    # or "critical", use the "critical" webhook.
    GCHAT_WEBHOOK_URL="${webhooks["critical"]}"
else
    # Remove the first argument from the arg list.
    shift
fi

# Everything after the first argument becomes the message.
msg="$*"

# Escape the message text to make it "safe".
escapedText=$(echo "$msg" | sed 's/\n/\\n/g' \
    | sed 's/"/\\"/g' | sed "s/'/\\'/g" )

# Create a json object with the text.
json="{\"text\": \"$escapedText\"}"

# Use curl to send the JSON to Google.
curl -X POST -H 'Content-Type: application/json' \
    -d "$json" "${GCHAT_WEBHOOK_URL}" 

Now I can call the script like so:

notify.sh info "You should know about this."
notify.sh warning "There's a problem but it's not that bad."
notify.sh critical "There's a bad problem."

If I have another script that needs to get my attention I just have it call notify.sh.

Hope you found this useful.

post

Fixing VSCode when it keeps dropping ssh connections

I really like VSCode, and I use the ssh plugin to edit code on remote machines, but recently the ssh connection has been dropping all of the time, even when I’m editing code on another machine that’s on the same local network.

I’ve updated both my OS and VSCode multiple times recently, so I thought some bug had slipped into one of the updates and that was causing a problem. I was somewhat correct. It seems that VSCode keeps a cache of data and code on the remote machine, and something in a VSCode update was trying to do something using the bits in the old cache data that was no longer supported.

To fix the problem I just removed the cache as follows:

  • Exit completely out of VSCode so that no VSCode processes are running. Force quit if you have to.
  • ssh to the remote machine(s) and delete the ~/.vscode-server directory with rm -Rf ~/.vscode-server/
  • If you get any “cannot remove [file]: Device or resource busy” errors then look for stuck processes:
    lsof | grep $HOME/.vscode-server | awk '{ print $2 }' | sort -u
    … then kill those processes, then trying removing the directory again.
  • Restart VSCode.

Once I did this all of my connection problems disappeared.

Hope you find this useful.

Quickly get IP addresses of new VMs

I spin up a lot of VMs using VMware Fusion. I generally keep “clean” generic copies of a few different distros and versions of Linux servers ready to go with my login, an sshd server, ssh keys, and basic settings that I use already set up. When I need to quickly test something manually — usually some new, multi-VM distributed container orchestration or database system — I just make as many copies of the server’s *.vmwarevm file as I need, fire up the VM copies on my laptop, test whatever I need to test, then shut them down. Eventually I delete the copies and recover the disk space.

Depending on where my laptop is running I’ll get a completely random IP address for the VM from the local DHCP server. I would log into the consoles, get the IPs, then log into the various VMs from a terminal. (Cut and paste just works a whole lot better on a terminal than on the VMware console.)

However, since the console screens are up, and I repeat this pattern several times a week, I figured why not save a step and make the ephemeral VMs just show me their IP address on their consoles without having to login, so I added an “on reboot” file called /etc/cron.d/welcome on the master image which updates the /etc/issue file.

/etc/cron.d/welcome looks like this:

@reboot root (/bin/hostname; /bin/uname -a; echo; if [ -x /sbin/ip ]; then /sbin/ip addr; else /sbin/ifconfig; fi) > /etc/issue

When a new VM boots, it writes the hostname, kernel info, and the ethernet config to the /etc/issue file. /etc/issue is displayed on the screen before the login prompt, so now I can just glance at the console, see the IP address, and ssh to the new VM.

Ephemeral VM

Although you’d never want to do this on a production system, it works great for ephemeral, throw-away test VMs.

Hope you find this useful.

Synchronizing Thunderbird e-mail filters using Dropbox

Two words: Use symlinks.

If you already know what a symlink is then you don’t need to read the rest of this article. If you want a better explanation, read on…

I use the Thunderbird e-mail client to read mail stored on my company’s IMAP mail server. I have a lot of filters set up that sort the mail into different folders, and Thunderbird stores the filter definitions in a file called msgFilterRules.dat. I read mail on different machines, some running various Linux distros and some running Mac OS X. I wanted all of the different machines to use the same rules for filtering e-mail into different folders, and if I make changes to the filters on one host I want those changes to take effect on all of the other hosts as well.

To do this I first set up a Dropbox account and installed the Dropbox software on my different machines, so now there’s a directory called “Dropbox” in my home directory that is synchronized between all of my different machines. I moved my filter file into the Dropbox directory and symlinked that to the location where Thunderbird expects to find the filter rules.

The step-by-step explanation if you want to do this:

Set up Dropbox on all of your machines.

Shut down Thunderbird if it’s running.

Start up a terminal window.

Find the msgFilterRules.dat file that you want to use as your “master” copy. On both my Mac laptop and Linux hosts the file is stored in ~/.thunderbird/[profile name]/ImapMail/[imap server name]/, where [profile name] is your Thunderbird profile name on that host, usually some random characters followed by ‘.default’. (Type cat ~/.thunderbird/profiles.ini if you want to see all of your profile names.)

Make a backup copy of the msgFilterRules.dat file:

cd ~/.thunderbird/[profile name]/ImapMail/[imap server name]/

cp msgFilterRules.dat msgFilterRules.dat.backup

Move the filter file to Dropbox:

mv msgFilterRules.dat ~/Dropbox/

Symlink the Dropbox copy of the file to the current directory, where Thunderbird expects to find it:

ln -s ~/Dropbox/msgFilterRules.dat .

Verify that the symlink was created correctly:

ls -al

You should see a line that looks like:

lrwxrwxrwx  1 earl users        37 Oct 25 21:12 msgFilterRules.dat -> /home/earl/Dropbox/msgFilterRules.dat

Now the machine you’re on is using the Dropbox copy of the filter file. To set this up on your other machines:

Verify that the file exists in the ~/Dropbox directory:

ls -al ~/Dropbox

Get to the directory where the filter file lives, remove the local copy, then create the symlink:

cd ~/.thunderbird/[profile name]/ImapMail/[imap server name]/

rm msgFilterRules.dat

ln -s ~/Dropbox/msgFilterRules.dat .

One word of warning: Thunderbird reads the filters into memory when it starts, and writes them back to disk when it exits. That means that if you have two hosts and Thunderbird is running on both of them, the last host that exits will write it’s version of the filters to disk. So if you make a change to the filters on one host and exit from Thunderbird, then exit from Thunderbird on the second host, the older filters on second host will overwrite the filter you just added. Because of this, I recommend exiting from Thunderbird whenever you leave your computer. I added a “killall thunderbird-bin” that runs from cron at 2am just to make sure that my copy at work isn’t running if I check mail from home in the morning.