MACRO: An Update Alerter for Distributed Macros

If you post your macros (here or elsewhere) for others to use, one of the challenges is letting your users know when there's an updated version available. I update my web search via abbreviations macro on a regular basis, and it seemed silly to have to update the forum post here, tweet about it, and update my blog post every time I did so—and still hope that current users saw one of those sources.

I decided to write a simple update checker that will let users know when there's an updated version of the macro available. Here's how it looks in action:

It is not a software updater—that is, it won't handle the process of installing the updated version, copying customized bits over, etc. But it will alert users that there's a new version available, and let them download it from within the macro.

The following is going to be long and only possibly of interest to those who distribute their macros. And there's a gating prerequisite to make this work:

You must have access to a web server where you can host a very simple web page (that has one line of PHP code) and a few simple files. If you have shell access, it's even better, but not required.

Assuming you have that, here's a demo project for the updater:

DIsplay the Time.kmmacros (71.1 KB)

(Of course, if you have the project already, you can get the update within the macro itself.)

IMPORTANT NOTE! If you're going to use my updater, you should keep this demo macro in your collection. If I make changes in the future, they'll be reflected in updated versions of this demo, and you'd be notified of those changes.

Using the Demo

━ USING THE DEMO ━━━━━━━━━━

After you install the macro, run the Display the Time macro, and you'll see a first-run message, then a large text display of the date and time ... which will be wrong.

Now run the zp-Check for a new version macro, and you should see the screenshot posted above. Click OK and the new version will download.

Install it, then run the Display the Time macro in the new version. You'll see a new first run message, along with the correct date and time.

That's the updater in action, and here's how to set it up for your macros…

Using the updater in your macros

━ USING THE UPDATER IN YOUR MACROS ━━━━━

My updater uses three of the four files in the demo—all but the example macro itself. The first run macro is optional, and the center window macro centers the update windows—so really, only "Check for a new version" macro is absolutely required. I've tried to set it up (for my own benefit, but now yours, too) to be as easy as possible to update with each new release.

First time setup in Keyboard Maestro

  1. Set the version number. This is the number for the version you're working on, not the current version, despite the box title—it's the current version of the macro being developed, not the currently released version:
    Keyboard Maestro Export
    Warning: My updater relies on the version number to be numeric—it can use decimals, but if you want something like "2.3 Alpha 5, Build 112," I don't think it'll work right.

  2. Set the path on your server to the folder that holds the various update files:
    Keyboard Maestro Export
    Do not include the trailing slash.

  3. Set a display name for the macro. This is strictly for use in the notification window (and anywhere else you'd like to display it:
    Keyboard Maestro Export

  4. Set the update filename hint, which should match the first part of your download's filename:
    Keyboard Maestro Export
    This is used in the dialog after a user has downloaded, in some text that reads "...who's name starts with [filename hint]" or something like that.

Future use in Keyboard Maestro

After you've set up the updater for a given macro, the only value you need to change for each new update is the version number (assuming you keep your naming conventions the same and don't reorganize your server, etc.).

First time setup on your web server

You will need to set up the following on your server:

  1. One folder for each macro that will be using the updater.

  2. A folder to hold all of your downloads. (You could put them in the macro folder, but I like having them in one spot. If you want them in the macro folder, you'll have to do some file editing).

Within each macro folder, as seen here, are four files:

image

Here's what's in each file…

filename: This file simply holds the full name (no path info!) of the updated macro to be downloaded. For the updated demo in this post, that's demo_11.zip.

index.php: This is the page that loads when the URL loads by default, and it simply redirects to the download file. Here are the entire contents:

index.php
$thefile = file_get_contents('filename');
header('Location: https://www.robservatory.com/macro_versions/_downloads/' . $thefile);
exit;
?>

<!DOCTYPE html>

<html>
    <head>
        <title>Latest macro update demo macro</title>
    </head>

    <body>
        <p>Redirecting to download…</p>
    </body>
</html>

Nothing there will show up, because the redirect in the header starts the download. I do this via $thefile variable so that I don't have to edit this file with each release.

releasenotes.html: This is the longest file here; it's a "real" HTML page with CSS, etc. This page is loaded in the floating window that the user sees when an update is available. You really should change this page so you're not stuck with my ugly design skills. Because this page is loaded by Keyboard Maestro, I have some Keyboard Maestro-specific bits in the body tag. Here's the code:

releasenotes.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
        <META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
        <title>Release Notes</title>

        <style type="text/css">
            body {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Helvetica Neue", Arial, sans-ser;
                font-size: 1em;
                line-height: 1.3;
                margin: 10px;
                background-color: #fff5ce;
                width:97%;
            }
            body * {
                box-sizing: border-box;
            }

            h3 {
                padding-bottom: 6px;
                border-bottom: 4px double #ccc;
                margin-bottom: -10px;
            }

            a {
                color: black;
                text-decoration: none; 
            }

            tt {
                font-size: 120%;
                color: #b3502c;
            }

            ul {
                margin-bottom: 30px;
            }

            .explain {
                color:gray;
                font-size:90%;
                margin-top:-20px;
            }

            .shortcut {
                color: blue;
                font-weight: semi-bold;
            }
        </style>
    </head>

    <body data-kmwindowid="releasenoteswindow" data-kmwindow="ROUND(SCREENVISIBLE(Front,MidX)-400),ROUND(SCREENVISIBLE(Front,MidY)-200)+8,MIN(ROUND(SCREENVISIBLE(Front,Width)*.5),800),MIN(ROUND(SCREENVISIBLE(Front,Height)*.58),600)">
        <h2>Macro: Updater for Macros Demo</h2>

        <h3>1.1 - Jan 6 2022</h3>
        <h4>New or updated features</h4>
        <ul>
            <li>The time is actually correct now. Sorry about that!</li>
        </ul>

        <h3>1.0 - Dec 29 2021</h3>
        <ul>
            <li>Initial release.</li>
        </ul>
    </body>

</html>

version: This file holds exactly one thing, the version number of the macro you will be releasing. For this sample project, that's 1.1. This is the file that's grabbed by the macro, to compare to the current version as stored in the macro.

As soon as you modify this file, your users will see that there's an update available

With those files in place, the web server is ready to go. So how do you actually update a macro?

Releasing an update

━ RELEASING AN UPDATE ━━━━━━━━━━

Here's the process I go through when releasing an updated version of the macro.

  1. In Keyboard Maestro, make sure that the version number is set correctly on my newly-updated macro. Nothing else should have to be edited.

  2. Disable the macro and export it.

  3. Zip and rename the macro as needed to match the naming scheme I chose, i.e. demo_11.zip.

  4. Upload the new macro to the downloads folder.

  5. On the server, I do this: echo demo_11.zip > filename to store the new filename. If you don't have shell access, use Transmit (or whatever) to edit via FTP.

  6. Update the release notes, but do not save them just yet.

  7. Triple-check that everything is ready to go—all values were correct, macro is uploaded, release notes look good.

  8. On the server, I then do this: echo 1.1 > version, which puts the new version number online. At the same time, in my remote editor, I save the updated release notes.

Althought that seems like a lot of steps, there's nothing complex about any of them after the initial setup has been done. It only takes a few minutes to push an update out.

The potential danger

━ THE POTENTIAL DANGER ━━━━━━━━━━

You are hosting files that users will download when they see a software update dialog. If someone gets into your server, they could replace the download file with some evil payload file, update the version number, and your users would see an alert to download an update.

As of version 1.2, the updater tries to protect users in such situations: It will compare the shasum value for the file they downloaded with values stored on another server; if they differ, the download has been tampered with, and the user will be alerted. Note that this will require accounts on two servers (one to host your downloads, and one to host the shasum values). I'm using Github for the latter, as it's well-respected, and importantly for this hobby of mine, free.

Do not use this macro without the verification process in place if you are uncomfortable with the possibility that a hacked server could lead to your users downloading evil payloads!

━ WRAP UP ━━━━━━━━━━
This is a very simple updater, but it addresses my main need: Users of my macro will now know when there's a new version available, without having to take any action on their part. From a security perspective, nothing is sent from the user's computer to my server (beyond whatever cookies, etc. might be sent when accessing any web page).

If you do decide to use it, I'd love to hear how it works for you, and how it could be improved.

-rob.

3 Likes

Version 1.2 is out now, and will be soon linked above, and it solves my main worry: There's now a download verification step that should catch any macros that were replaced by other files on your server. Note that you can install 1.2 now, but to really test the verification step, you'll need 1.3, which I'll release with some meaningless change over the weekend.

This verification step ideally requires two servers, as it stores shasum values on the second server. It downloads those values (values for the .zip and .kmmacros versions) and compares them to one it calculates for the just-downloaded file. If they differ, a dialog appears that hopefully makes it clear to the user what's happened…

This isn't a 100% foolproof system, but it does make it very unlikely that someone would be able to replace your downloadable macros with something that an unsuspecting user would install—the bad actors would have to gain access to two separate servers in order to do so.

I'm using Github to host my shasum values, but any server you have access to that can serve a web page you can curl would work. There are more instructions in the verification macro.

-rob.

3 Likes

Version 1.3 is out, and I changed the way I'm storing the SHA-1 hashes. As a result of this change, if you update from within the demo, the verification step will fail. That's because 1.2 has the old method, which fails with the new storage method. So download the macro from here instead, or just ignore the failed verification.

It's a bit of a pain now, but it was the right thing to do for future versions.

-rob.

1 Like