I'm a full-stack web developer, and this is my blog. Please connect with me on LinkedIn or visit my Github for more! Also, you may be interested in learning more about me.

Projects

  • Automating Stuff the 'Easy' Way With anacron

    Dali's famous "melting clocks" painting, "The Persistence of Memory." Melty, soft clocks litter an otherwise apocalyptic landscape.

    cron is a wonderful tool for running tasks on a schedule. Set it once, and then forevermore on your schedule, cron will handle repeatedly doing the thing.

    Unless your computer is off! cron was originally designed for mainframes that never are turned off. If you have a task scheduled to run every day at midnight but your laptop is closed every night, cron will never run the task.1

    I recently learned about anacron , which solves this problem by running things at a “scheduled-ish” time. Instead of specifying a time, you specify a frequency (hourly, daily, weekly, etc). When anacron starts (when your computer wakes from sleep/etc) if it’s been more than the specified time since it last ran, it runs. Simple, right?

    Eh, not so much. Possibly because I had some complicated things I wanted to set up, getting everything aligned and working took almost a week (admittedly because I was waiting for anacron to run and wasn’t interested in force-scheduling it).

    My main problem was that I wanted anacron to run as me, rather than as root. By default, unlike cron, anacron only runs as root.

    I wanted to set up urlwatch to keep an eye on some websites, using headless Chrome. Automatically hitting a website as root seems like not a great idea? So running as a non-superuser seemed like the way to go.

    Here’s what I did:

    Setup

    First, get anacron. If you have a Red Hat system, you already have it as part of cronie. On Ubuntu-based distros, you may have to install it. On a Mac, there is no official support for anacron and you are recommended to use launchd. On the other hand, there’s this Macports option with all of 12 installs 😬 which I probably will not be trying.

    Then, I followed the steps in this Opensource.com article to configure anacron to run as my user. To summarize that article in my own words:

    • Create a test script to practice on. My example script looked like this:

        #!/bin/bash
        echo $(whoami) >> ~/dontyouknowwhoiam.txt
      

      This should prove that anacron is running as me (which I still wasn’t 100% confident of until testing it).

    • Create local directories that we’ll point anacron to instead of the system directories:

        mkdir -p ~/.local/etc/cron.daily ~/.var/spool/anacron
      
    • Create a local configuration file at /.local/etc/anacrontab:

        SHELL=/bin/sh
        PATH=/sbin:/bin:/usr/sbin:/usr/bin #replace with your full $PATH. anacron doesn't have access to system environment variables set in, say, ~/.profile.
        1  0  cron.mine    run-parts /home/{user}/.local/etc/cron.daily/
      

      This will run everything in the ~/.local/etc/cron.daily folder every 1 day, waiting 0 minutes after the computer starts up. The buffer can be helpful if you’re running heavy scripts, I assume. The mine is an arbitrary string to distinguish from cron.daily or cron.weekly etc.

      As an aside, anacrontab is a perfectly logical name for the scheduling file belonging to anacron. cron has a crontab (short for cron table), and so anacron has an anacrontab. But for an elder millennial like myself, this was inevitable:

      Sir Mix-a-lot overlaid with meme text saying "My anacrontab don't want none"

      I’m so sorry. (Not really)

    • Tell our computer to run anacron on a schedule. The article suggests adding this line to your ~/.bash_profile:

        anacron -t /home/{user}/.local/etc/anacrontab -S /home/{user}/.var/spool/anacron
      

      This manually triggers anacron with the local anacrontab, and use your local spool file (which is what anacron uses to keep track of the last time it ran). Putting it in your ~/.bash_profile means that anacron will check for new jobs every time you restart your computer.

      Putting it in ~/.bashrc (or sourcing the profile file from rc) will run it when you open a new terminal window. Finally, putting it in your crontab:

        0 0 * * * anacron -t /home/rachel/.local/etc/anacrontab -S /home/rachel/.var/spool/anacron
      

      means anacron will also check for new jobs at midnight, in case your computer hasn’t been off. I used all three options, because what’s the harm. Worst case, anacron decides that it hasn’t been long enough since it last ran and goes back to sleep.

    Those are the basic steps. That said, I ran into at least one bump on the road to better automation.

    It’s not stated anywhere but the man page, butrun-parts has fairly specific naming requirements for executables, for it to run them. In default mode, according to the man page,

    the names must consist entirely of ASCII upper- and lower-case letters, ASCII digits, ASCII underscores, and ASCII minus-hyphens.

    You can check that run-parts is happy with run-parts --test /home/{user}/.local/etc/cron.daily.

    Then I had to get urlwatch up and running. I’m going to save that for a separate post, because (as usual) I’ve gotten a little verbose. Up next (eventually): how to configure urlwatch to check for changes to websites on a schedule!

    1. Unless your laptop is like mine and “forgets” to sleep when the lid is closed, which is a whole other hilarious problem. 

  • Marking 4 Years at Attentive 🎉

    Today (okay, tomorrow if you’re being pedantic) marks my four-year anniversary at Attentive. I started thinking I’d learn a lot about the infrastructure that powers SMS sending over the internet at scale. Haha, wrong, that’s actually a completely different team and besides from a software perspective it’s basically just API calls.

    Instead, I learned a whole new programming language (nobody told me in my interview that Attentive was primarily a Java shop!) and made a whole bunch of new friends. Seriously, my coworkers are the greatest. Oh, and I got promoted. Three times.

    My main source of income prior to switching to software engineering was, except for a brief stint on staff at an online magazine, primarily through freelance writing and editing. And while I had many long-term clients that were happy with my work and that I worked with for more than four years, I still feel like four years in a job at the same company carries more weight.

    I’m celebrating this meaningful milestone by making a donation to Unidos MN, a grassroots organization focused on immigration, education and climate justice in Minnesota. Seemed like the right time.

  • Why I Switched to git switch

    Git is too hard.

    When I first learned1 git, I learned that to stop working in the current branch and to start working in a new branch, the command was git checkout branchname. Or to create a new branch, git checkout -b branchname.

    All the way back in 2019, though, the git team released git switch, which is supposed to replace git checkout. (Don’t worry, checkout isn’t going anywhere.) And finally, this year, I retrained my muscle memory to use git switch instead of checkout. Why is switch better?

    1. checkout tries to do too many things. git checkout <branchname> is valid, as is git checkout <hash>, as is git checkout -- path/to/file. These all do different things. Checking out a *branch means “start working on this branch.” Checking out a commit hash puts you in “detached HEAD” state, the source of many a developer’s footgun.2 Checking out a file reverts it to a previous state (usually to the state of the last commit).

      I’ve used all these use cases! Usually on purpose! But you have to admit it’s kinda confusing.

      Also, if you have a file and a branch that have the same name, git has to decide which one you meant when you use checkout. I’ve never come across this particular collision myself but I can imagine there are a non-zero number of git branches called readme out there, which could lead to really unexpected results if you just typed git checkout readme without looking closely at the output.

    2. The way to create a new branch with switch is to use the -c flag, which means “create.” The way to create a new branch with checkout is to use the -b flag, which means “branch,” which is a tautology.

    Basically, as I understand it, the git team felt that checkout did too many things and was confusing for new users. All good reasons to split the command into two.

    Most important to me, however, is git switch - which switches back to the previous branch, similar to other Unix-y commands, such as cd - , which takes you back to your previous directory.

    As always, saving like two keystrokes is the only thing I care about. Efficiency!

    1. Nobody has learned git. We all just type random things into our terminal until it works or we have to do a git push --force

    2. I still don’t fully understand what a detached HEAD is. 

  • TIL: Cleaner Log Output When Using 'Concurrently'

    People's legs and feet on a racetrack. They're about to begin running at the same time.

    If you’ve ever used the package concurrently to run more than one thing at the same time while developing, you probably have seen how the logs are a little jumbled together. All output is logged to the console, by default, with a number representing the process that created the output. As an example, here’s a Vite frontend and a toy websocket server in the backend that logs every message received from the FE:

    [0]   VITE v7.3.0  ready in 220 ms
    [0] 
    [0]   ➜  Local:   http://localhost:5174/
    [0]   ➜  Network: use --host to expose
    [1] received: { 'message': 'hi' }
    

    This is…fine? But it’s easy to miss the [0] or [1], it’s just one number and honestly if you’re not looking for it it just sort of fades into the background.

    But by passing the --names flag in you can call your processes anything you want.

    concurrently --names frontend,backend 'vite' 'node src/server.js' turns the above output into:

    [frontend]   VITE v7.3.0  ready in 220 ms
    [frontend] 
    [frontend]   ➜  Local:   http://localhost:5174/
    [frontend]   ➜  Network: use --host to expose
    [backend] received: { 'message': 'hi' }
    

    And of course you can use more descriptive names, although these are plenty for me.

    I wasn’t able to find the official documentation on this, although there is documentation on how to interact programmatically with concurrently and pass in the names options. I’m not sure how to incorporate that into a development pipeline but that’s a me problem. For now, the command-line flag is all I need.