Welcome to the lab.

Just your average project update and wellness check-in

I’ve done a lot of coding in the last two weeks. Playing with projects, adding features, fixing bugs, writing tests. I’ve done a lot of learning Vim (I’m writing this post in Vim, I think I like it). Some day I’ll feel like I’ve learned something in Vim that would actually surprise anybody and I’ll write it up. For now, I’m just playing catch up.

Wellness check

We’ll do this Overtired style, mental health corner at the top of the show…

I had a few manic days last week and am riding out this lull before depression hits. I’ve had depression that wasn’t preceded by mania, but I’ve never had mania that wasn’t followed by depression. Oh, in case you missed it, I’m bipolar. And I’m trying to talk about it more because, well, it’s scary to talk about.

I’m also ADHD, which can be a horrible combination. One symptom of ADHD is hyperfocus, where you can focus on one thing to the exclusion of all others. The problem is, that one thing is almost never the thing that most needs focus at the time. And when I’m manic, hyperfocus goes into overdrive and I have even less control than usual of where it goes.

I actually managed to focus pretty well on day job stuff. Set some impressive precedents that will definitely bite me when the inevitable downturn happens. Hopefully it will be manageable. I gotta get stabled out.

In the meantime, and in addition to doing a great job at work, I updated a bunch of projects. Here’s a rundown.

A Keyboard Maestro macro for cross-linking Markdown docs

Ok, this is one of my other projects from last weekend. In its present state it has a more limited scope of appeal than my Howzit updates, but the idea could easily1 be modified to work with any project that consists of a bunch of Markdown documents.

I designed this for use when working on my Jekyll-based documentation sites. Jekyll actually isn’t the important part, it just requires the files to have YAML headers with a title key, and any manually-specified header IDs to be in Kramdown’s format:

## My Title {#id}

If you were to change the handling of both of those requirements, any folder containing a bunch of Markdown files would work. The rest of the macro is all based on easily-configured templates.

Anyway, what it does is create a shortcut (lnk//) that, when typed, offers a spotlight-esque search through all of the other documents in the project (and the headlined sections they comprise), and when you choose one, it inserts a templated link to it relative to the project root, including a #fragment to link a specific headline.

The first time you run it, it will ask for your root directory and link template. The directory is a POSIX path, e.g. /Users/me/Sites/dev/bunch, and the link template allows placeholders in the format <path>. Available placeholders are:

  • <path>: Full path from the DocsDir root (e.g. “/subdir/file”)
  • <dir>: Directory portion of path (e.g. “/subdir”)
  • <file>: Filename portion of path (e.g. “/file”)
  • <frag>: Headline ID, empty if none (e.g. “#fragment”)

All extensions are removed from the paths, and all placeholders except for <ext> and <frag> have leading slashes (<frag> gets a leading #). So my template for the Bunch docs is:

<path>/<frag>

To change configuration later, type lnk\\ (with backslashes) and you’ll be able to enter a new directory and optionally change the template.

Now, just type lnk// in your Markdown document, type a few characters of the page you want to link to, hit return, and you have a correct link to the topic you wanted to reference.

That’s it. It’s pretty simple. It actually started off as a TextExpander snippet, but that would take a search string and insert the first matching document. The matching was a little more flexible, but I really liked Keyboard Maestro’s “Prompt with List” interface and the ability to refine a search on the fly. You have to put a space between search terms to represent path breaks, e.g. to match “docs/bunch-files/commands/awake” you would need a space where the slash goes: “comm awa” would find it.

There’s also a version included that works on selected text using a keyboard shortcut (defaults to ⌃⌥⇧L). It uses the selected text as the initial search, which can be hit and miss but is great for wiki-style linking. I mostly use the text-triggered one, though.

Markdown Document Search for Keyboard Maestro v1.0.0

Keyboard Maestro macros for cross-linking in Markdown document collections (e.g. Jekyll pages)

Published 08/30/21.

Updated 08/30/21. Changelog

DonateMore info…

  1. If you know a little Ruby and regex… 

Howzit gets templates

I had a bit of a manic coding week this last week. Which probably means I’m headed for some depression soon, so I wanted to write about at least one of the things I’ve worked on recently before I lose motivation. So let’s start with Howzit, my little CLI for tracking all of the build settings and other notes for your coding projects.

I added the ability to run tasks from within a notes file a while back, and I use it a lot. I quickly found that there were some tasks that were common to most projects of the same type, be it ruby gems, Xcode projects, or just Markdown repositories. Projects within a type tend to use some common commands and scripts with only the target paths or build flags changing (if anything does). So to make it easy to replicate tasks between build files, I added “templates” to Howzit.

I wrote it up for the docs, so I’ll just give you a quick description and then point you there.

All you do is create a Markdown file in ~/.config/howzit/templates/ and, just like a Howzit file, add level 2 or higher headers for each topic/task followed by the notes and directives. You can use MultiMarkdown-style placeholders for variables, e.g. [%variable] inside the notes. The name of the file is the key with which you’ll reference it in your build notes.

Then, in buildnotes.md in some Markdown-based project, you would just add MMD-style metadata:

template: markdown
spelling_dirs: . docs

# My Markdown Project
[...]

spelling_dirs becomes a variable I can reference in my template, which in this case includes a Spellcheck task, and spelling_dirs tells it which directories to find Markdown files in. Variables can even have default values after a colon, e.g. [%spelling_dirs:.].

A file can reference multiple templates, and you can include only specific tasks with wildcard support in the format:

template: markdown[spellcheck,markdown lint], ruby[build*]

If you get into the template thing and have a bunch, it’ll be easy to forget what titles you gave them all and what tasks they contain, so I added a --templates flag that will show you all of your installed templates and what topics will be added when you include them. Just run howzit --templates to get the list.

Be sure to update to the latest version of the script to get all the new goodies. Someday I’ll probably package it as a gem and make updating easy, but for now you need to re-download the script and overwrite your current version.

Or you can do what I do and clone the repository from GitHub, then symlink the howzit file into a directory in your path. Then you can just do a git pull when you want to get the latest version… {.tip}

Anyway, that was my Sunday morning project. If you’ve found howzit useful before, I hope this adds some extra usability for you.

Oh yeah, and a week or two ago I also added “upstream searches,” which checks for build notes in parent directories and compiles any topics found into the notes for the current folder. It travels up to root (/), so you can have a universal build note in an ancestor directory and all topics in it will be included in descendant notes. It was the predecessor to templates (which are definitely a superior solution). Upstream searches have to be enabled in ~/.config/howzit/howzit.yaml with :include_upstream: true.

Also, if you run howzit in a subdirectory of a git repo and it doesn’t find a build notes file in the current directory, it now checks the top level of the repo for build notes and executes tasks from there. Fun stuff.

All the details on the Howzit project page.

Web Excursions for August 30, 2021

Web excursions brought to you in partnership with MindMeister, the best collaborative mind mapping software out there.

rtomayko/ronn: the opposite of roff
It’s an old project (last saw an update 8 years ago), but I’ve had great results turning (basically) Markdown files into man pages with this tool. Can output to HTML and roff, generate a TOC, and easily generate a man ready file for your command line tool.
kotfu/ksc: Generate properly formatted keyboard shortcuts
Inspired by my Jekyll plugin for the same purpose, a CLI to generate properly formatted keyboard shortcuts, including a KeyboardMaestro macro.
msanders/pam-watchid: PAM plugin module that allows the Apple Watch to be used for authentication
A PAM plugin module that allows the Apple Watch to be used for sudo authentication. This particular fork has modifications to make it work with macOS 11 on an M1 Mac.
Huemint - AI color palette generator
This is an excellent random color palette generator. I promise that if you care about colors at all, you’ll have fun playing with it.
agarrharr/awesome-macos-screensavers: 🍎 🖥 🎆 A curated list of screensavers for Mac OS X
A curated list of screensavers for macOS. Some real gems in here.

Check out MindMeister and start brainstorming, collaborating, and boosting productivity.

Custom URLs for your Synology with Namecheap

The Synology DSM has a handy built-in updater for dynamic DNS (DDNS). It makes a lot of sense, given the Synology is always on and connected to the internet, keeping a custom domain pointed to the right IP at all times.

Most people who followed this headline already know these definitions, but just to recap: dynamic DNS allows a fully qualified domain name like “example.com” to point to an IP address that changes regularly, as most home ISPs do. Unless you’re paying for a static IP, your IP is changing now and then, defeating any custom domains you point to it.

Synology’s DDNS integration comes with presets for quite a few services, but most of the free ones don’t allow you to use custom domain names, just subdomains of domains like “synology.me” or “zapto.org”. I wanted to use something short and personalized (because I’m lazy and vain, I guess).

I already had some unused domains registered with Namecheap, which offers DDNS for your domains, but Synology didn’t have a preset for it. In a lot of cases you can use the DSM to create a custom DDNS, using a URL with __PLACEHOLDERS__ in it. Namecheap doesn’t offer a URL you can curl, though, and using dynamicdns.park-your-domain.com doesn’t work with that (I’m not sure why). The good news is that it’s pretty easy to add your own Namecheap service provider to your Synology.

I found a few existing solutions for this but each of them had some failing. This solution is what I distilled from multiple sources, simplified, and currently have working.

  1. First, register the domain you want to use with Namecheap and go to the Advanced DNS configuration for the domain. Ensure that there’s a an A record for the “@” wildcard.

    Namecheap Advanced DNS

    If you want to use a subdomain as your dynamic host (e.g. “home.example.com”), add a record for it by clicking “Add New Record”, selecting A Record, and entering the subdomain (just “home” in the previous example). The IP address here doesn’t matter, the script we’ll set up will be updating that.

  2. Scroll down to Dynamic DNS, toggle the switch to enable it, and note/copy the password it shows you, we’ll use that in step 6.

  3. For this next part, you’ll need SSH access to your Synology, which you can enable with Control Panel->Terminal & SNMP.

    As an aside, I highly recommend changing the default SSH port, setting up keys, and disabling password login. Also set your Control Panel->Security setting to the highest level. Especially once you have a public domain associated with your IP, you’ll get your ports scanned frequently, and there will be regular brute force login attempts.

  4. Now, add a little script that will handle pinging an update URL with your credentials and IP. Save this script to /usr/local/bin/namecheap_ddns.sh on your Synology:

     #!/bin/bash
    
     ## Namecheap DDNS updater for Synology
     ## Brett Terpstra <https://brettterpstra.com>
    
     PASSWORD="$2"
     DOMAIN="$3"
     IP="$4"
    
     PARTS=$(echo $DOMAIN | awk 'BEGIN{FS="."} {print NF?NF-1:0}')
     # If $DOMAIN has two parts (domain + tld), use wildcard for host
     if [[ $PARTS == 1 ]]; then
         HOST='@'
         DOMAIN=$DOMAIN
     # If $DOMAIN has a subdomain, separate for HOST and DOMAIN variables
     elif [[ $PARTS == 2 ]]; then
         HOST=${DOMAIN%%.*}
         DOMAIN=${DOMAIN#*.}
     fi
    
     RES=$(curl -s "https://dynamicdns.park-your-domain.com/update?host=$HOST&domain=$DOMAIN&password=$PASSWORD&ip=$IP")
     ERR=$(echo $RES | sed -n "s/.*<ErrCount>\(.*\)<\/ErrCount>.*/\1/p")
    
     if [[ $ERR -gt 0 ]]; then
         echo "badauth"
     else
         echo "good"
     fi
    

    Make it executable with chmod a+x /usr/local/bin/namecheap_ddns.sh.

  5. Next, we need to add the provider to the DSM configuration. You’ll need to be root to do this, so run sudo -i and enter your admin account’s password. Now edit the file at /etc.defaults/ddns_provider.conf. I have Vim installed on my Synology, but I can’t remember if it’s included by default or I added it. Use whatever you have handy, or use cat redirection to do it (copy and paste the whole block below at once):

     cat >> /etc.defaults/ddns_provider.conf << 'EOF'
     [Namecheap]
             modulepath=/usr/local/bin/namecheap.sh
             queryurl=https://namecheap.com
             website=https://namecheap.com
     EOF
    
  6. Go back to your Synology DSM and open Control Panel->External Access->DDNS. Click Add and you should see Namecheap in the Service Provider dropdown. Select it and enter your custom domain (including subdomain if you set that up) in the Hostname field. Username isn’t needed in our script, just paste your password from step 2 in the Password/Key field. Click “Automatic Setup” next to External Address to enter your current public IP.

    Click the Test Connection button to see if everything is working. Click OK to save.

Assuming you see Normal under the Status column, you’re now updating your custom domain with your public IP. It may take a bit for it to propagate initially, but once it does, you can access Synology DSM, Synology Drive, Filestation, and all of your packages using your custom domain and the appropriate ports. If you head to Control Panel->Security->Certificate, there’s even a wizard for adding a Let’s Encrypt SSL certificate for your new domain, allowing you to use https connections for everything.

Hope that’s useful to some Synology users out there, it was definitely a fun little hack for me.

A new Jekyll plugin for handling beta documentation

I’ve added a new Jekyll plugin to my repository called Availability. It lets you mark sections of documentation based on their availability in different software builds. This post will be short as I’ve already documented it pretty well in the README, plus it will likely be of use to literally nobody but me, as it would only be useful to people running Jekyll-based documentation sites about a single Mac app that has a public beta program, which is a pretty limited audience.

But it is, in my opinion, an elegant solution to a particular problem, so I thought I’d share it anyway.

The Problem

When I add new features to Bunch, the first thing I do after testing is write the documentation. Nothing puts a feature through its paces like trying to write thorough documentation for it.

But new features are always released to the beta feed before they make it into the stable feed, and I need to have the documentation of the new features available to beta users, and I don’t want to publish a whole subsite with the beta docs. So I add the new feature documentation to the main site with a note that the feature is currently only in the beta version, which works well, but when the features in the beta move to stable, I have to go through and delete all of those beta alerts.

So this plugin lets me tie sections of text to specific build numbers. I just add a tag like:

{% available 119 %}
# My New Feature

Some documentation...
{% endavailable %}

At build time, the plugin checks my Sparkle appcast for the latest public and beta build numbers. If the marked feature has a build number less than or equal to the live release, it just gets processed as if the tag wasn’t there. If it’s greater than the public release and less than or equal to the live beta build, it gets markup to indicate it’s in the beta only. If it’s higher than the beta build, it gets marked up as an “Upcoming” feature, allowing me to document new features before they even make it into the beta without worrying that I’ll confusingly publish documentation for features nobody has yet.

You can also easily use the plugin to remove unreleased features from the output entirely just by setting the template strings to empty quotes. This allows you to write documentation ahead of time, and have it automatically appear only when the appropriate build is added to the appcast.

If you’re interested, the code is up on GitHub and you’re welcome to customize it and make it work for you.

BetterTouchTool Touch Bar followup: Zoom buttons

Last week I posted my craziest Mac experiment in a while — a script that handles adding and updating myriad BetterTouchTool Touch Bar (and menu bar) widgets. A couple of days after I posted it, I ended up adding some widgets for Zoom that may actually be the most useful of the bunch.

My day job requires a lot of Zoom meetings. A lot for me, at least, coming from an indie dev world where Zoom meetings were mostly for occasional pandemic gatherings of friends and family. Now I’m in Zoom every day, multiple times a day.

I wanted easy buttons for controlling mic, camera, and sharing without having to focus the Zoom app. Keyboard shortcuts are ok, but they don’t provide visual feedback. So I found a plugin for my Stream Deck that did the trick nicely, letting me control Zoom functions outside the app with indicators for mute, camera, sharing, and even a Leave button for getting out of a meeting in one tap.

Having more to do with my Stream Deck than with the plugin, though, I kept getting communication errors that rendered the buttons useless at inopportune times. It always worked perfectly in testing, but then in an actual Zoom meeting I’d frequently be met with the yellow triangle that indicates plugin communication failure. So I decided to replicate the plugin in my Touch Bar.

Borrowing heavily from the code by Martijn Smit, I created mic, camera, sharing, and leave widgets for BetterTouchTool. These can all be installed and operated by the btt_stats.rb script I posted about last week. And all of the installation and usage information is in the README.

The widgets all show current status of their respective feature, toggle its state, and disappear when no meeting is active. You can even set them up to refresh their display within 1 second, instead of the 4-5 seconds the Stream Deck plugin takes to catch up with changes (owing to BetterTouchTool’s scriptability, not any shortcoming in the original code).

The scripts use Accessibility scripting to click the menu items, so you don’t have to assign shortcut keys. That also means that menu titles are hardcoded, and English-specific. However, I added a whole section to the config file where you can enter the menu titles for a different language and get the buttons to work with just a little translation.

Anyway, just a brief addendum to the craziness. See the original post for more details, and find the whole shebang on GitHub.