DEV Community

Henrik Sommerfeld
Henrik Sommerfeld

Posted on • Originally published at henriksommerfeld.se

Asuswrt-Merlin Firmware Update Checker

I have an Asus RT-AC68U router at home. I've previously used the build-in update checker together with a notification script that ran on the router itself. Recently I noticed that I hadn't got any update notifications in a long time, a downside of silent failures.

When I ran a manual check using the router's web interface, it just said: "Temporarily unable to get the latest firmware information. Please try again later." It doesn't seem to be that temporary though.

TLDR

The code is in this GitHub repo and the scheduling piece with cron is described a the end of this post.

Building my own update checker

Since the project website of Asuswrt-Merlin presents the latest version in an easily parsable way, I decided to write my own checker using screen scraping in NodeJS.

Version checker

To find the latest version, I just looked at the website, inspected the HTML, installed the packages request-promise and cheerio, and finally extracted the version number of interest.

import rp from 'request-promise';
import $ from 'cheerio';

export async function getLatestStableVersion() {
  try {
    const html = await rp('https://www.asuswrt-merlin.net/');
    const text = $('#block-currentrelease', html).text();
    const rows = text.split('\n');
    const stableOthers = rows.find(r => r.startsWith('Others:'));
    const stableVersion = stableOthers.split('Others:')[1].trim();
    return stableVersion;
  }
  catch (error) {
    Promise.reject(error);
  }
}

Saving last checked version

In order to know if there is a new version since my last check, I of course need to keep track of what the version was the last time I checked. I did this with a simple text file on disk.

import { existsSync, readFileSync, writeFileSync } from 'fs';
import * as path from 'path';

const savedVersionFilePath = path.resolve(path.resolve(''), './last-checked-version.txt');

export function getLastCheckedVersion() {
  if (!existsSync(savedVersionFilePath)) {
    return "0.0"
  }

  const lastCheckedVersion = readFileSync(savedVersionFilePath, 'utf8');
  return lastCheckedVersion;
}

export function saveLastCheckedVersion(version) {
  writeFileSync(savedVersionFilePath, version);
}

Notifier

I already had a working notification script using the service Pushover that I ported from Bash to NodeJS.

import dotenv from 'dotenv';
import rp from 'request-promise';

export function sendPushoverNotification(message) {
  dotenv.config();
  var options = {
    method: 'POST',
    uri: 'https://api.pushover.net/1/messages.json',
    body: {
      token: `${process.env.PUSHOVER_TOKEN}`,
      user: `${process.env.PUSHOVER_USER}`,
      message: message
    },
    json: true
  };

  rp(options)
    .then(function (parsedBody) {
      console.log(`Pushover notification sent: ${message}`);
    })
    .catch(function (err) {
      console.error(err);
    });
  }

Gluing it together

By sending a notification both when there is no update and when an error occurs, I won't have any silent failures unless I made a mistake here somewhere.

import { getLatestStableVersion } from './latest-version-checker.js';
import { sendPushoverNotification } from './notify.js';
import { getLastCheckedVersion, saveLastCheckedVersion } from './localFile.js';

async function main() {
  try {
    const lastCheckedVersion = getLastCheckedVersion();
    console.log("main -> lastCheckedVersion", lastCheckedVersion)
    const latestVersion = await getLatestStableVersion();

    if (latestVersion !== lastCheckedVersion) {
      const message = `🔔 New firmware version ${latestVersion} is now available at 

      https://www.asuswrt-merlin.net/`;
      sendPushoverNotification(message);
      saveLastCheckedVersion(latestVersion);
    }
    else {
      const message = `🤷‍♂️ No firmware released. ${latestVersion} is the latest.`;
      sendPushoverNotification(message);
    }
  } catch (error) {
    console.log("main -> error", error)
    const message = `⚠️ Router firmware update check failed`;
    sendPushoverNotification(message);
  }
}

main();

Scheduling the update checker

I'm running this on a RaspberryPi and it's scheduled to run once a week, 18:10 on Wednesdays. I found https://crontab.guru to be helpful for not mixing up the time settings.

crontab -e

10 18 * * 3 /home/pi/router-update-check.sh >> /home/pi/router-update-check.log

The trickiest thing for me as a terrible Linux admin, was to get the cron scheduling working. Adding the output of echo $PATH at the top of the script did the trick. Logging the output (to router-update-check.log in this case), also helped.

router-update-check.sh script contains the following:

#!/bin/bash
PATH=/home/pi/.nvm/versions/node/v13.12.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games:/snap/bin

cd /home/pi/Code/asuswrt-merlin-update-check/
node ./main.js

The result of running the script once every minute (while troubleshooting) showed up in my phone like this. I now have an update checker that I can only blame myself if it doesn't work. Great success!

Pushover notifications on iOS

Top comments (0)