Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: actions

  • Hugo Deployment via GitHub Actions

    Hugo Deployment via GitHub Actions

    For a long time I’ve been managing deployment of a Hugo site via a few home-grown scripts and actions. All that has changed.

    The Setup

    So let’s get our setup explained:

    1. Hugo is used to manage a library of data
    2. The posts and the theme are in the same repo, but stored under ‘Content’ (which has data, posts, and static) and themes (which has … the theme)
    3. Most of the changes are in the post content

    Okay, now this is not a conversation about why (or why not) use Hugo. I like it for my pretty much text-only wiki type content, in that it lets me keep things organized and usable from everything including my iPad.

    But this use of Hugo comes with a cost. One of the reasons people love CMS tools like WordPress is that you can edit in your browser, and frankly that’s super easy. Using a static site builder, you have to run (somewhere) the static site build command. For a while I had a ‘deployme’ local command that did this:

    $ hugo -F -s /home/username/Development/site/hugo
    $ rsync -aCt --delete --exclude-from '/home/username/Development/rsync-exclude.txt' --force --omit-dir-times -e ssh /home/username/Development/site/hugo/ user@example.com:/home/username/domain/hugo
    

    Not super complicated, right? I have it on my laptop but it means I can’t always push code. Like if I edit directly on Github or on my iPad.

    Normally I’d look into something like Codeship (which I’ve talked about before) but … I thought it was high time I sat down and made things simpler.

    What’s Simpler?

    In this case, simpler means “fewer moving parts that could go wrong.”

    See I love Codeship, it lets me do a lot of cool things, but it’s also a little fragile and (honestly) sucky when it comes to Hugo. Creating a server and running hugo took longer than it did on my laptop. A lot longer. Many minutes longer.

    If I ran it locally it was a few seconds:

    Start building sites …
    hugo v0.87.0+extended darwin/arm64 BuildDate=unknown
    
                       |  EN
    -------------------+-------
      Pages            | 1762
      Paginator pages  |    0
      Non-page files   |    0
      Static files     |   92
      Processed images |    0
      Aliases          |    2
      Sitemaps         |    1
      Cleaned          |    0
    
    Built in 1562 ms
    

    When I did it on Codeship it would be 5-14 minutes! That’s crazy, right? Which is why I moved off Codeship and down to local. But that came with the cost of limiting when and where I could run anything. While my code is super simple, it’s also silo’d and that’s bad.

    In order to achieve simplicity, what I really needed is code that runs from Github. Or on the server where the site is. Back in ‘the day’ I installed hugo on the server, but also Git! That means I pushed to my git repo, which was on the same server, and I used post-commit hooks to deploy. I’ve toyed around with a few iterations, but then I moved to a new server where I didn’t install Go because … I didn’t need it.

    And that means here, simple is:

    • runnable from anywhere
    • automated
    • restricted when needed
    • not crossing multiple services

    Which led me to Github actions.

    Github Actions

    This is a service from Github.

    GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub. Make code reviews, branch management, and issue triaging work the way you want.

    In other words, Github saw us all using Travis and Codeship and thought “We could do that and keep people here, right?”

    But Actions goes beyond just automation. The Actions interface allows you to run tests, builds, checks, and, yes, deploys. It’s an order of magnitude faster than tools like Codeship because it’s a stripped down, basic interface. It’s also controlled by Github so you don’t need more access than committing code.

    There are some cons, though. One of the headaches with Codeship was that when Hugo updated, Codeship might just … stop working right. So you had to find the magic sauce to make it work. With Github Actions, you’re using ‘actions’ built by other people a lot of the time, and if you’re familiar with the drama that happened in npm a while ago, you may share my fear of “What if someone else deletes their action…?”

    Yeah, I have concerns/

    main.yml

    Here’s my code:

    name: 'Generate and deploy'
    
    on:
      push:
        branches: [ production ]
    
    jobs:
      deploy-website:
        runs-on: ubuntu-latest
        steps:
          - name: Do a git checkout including submodules
            uses: actions/checkout@v2
            with:
              submodules: true
    
          - name: Setup Hugo
            uses: peaceiris/actions-hugo@v2
            with:
              hugo-version: 'latest'
              # extended: true
    
          - name: Build Hugo
            run: hugo --minify
    
          - name: Deploy to Server
            uses: easingthemes/ssh-deploy@main
            env:
              SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
              ARGS: "-rlgoDzvc -i"
              SOURCE: "public/"
              REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
              REMOTE_USER: ${{ secrets.REMOTE_USER }}
              TARGET: "/home/username/domain/library/"
              #EXCLUDE: "/dist/, /node_modules/"
    

    There are a number of alternatives, but I picked peaceiris/actions-hugo because that developer is well known and respected. And while there are all-in-one Hugo build and deploy, I decided to separate them because I linked peaceiris’ code. This meant I needed an Rsync or ssh deployment. I settled on easingthemes/ssh-deploy because they strongly encouraged the use of secrets, and that’s a good sign to me. Also it’s heavily recommended by Flywheel, and a I cannot imagine them being reckless.

    The only ‘gotcha’ I had was the directions about how to setup SSH was not great.

    To make it work, you need to create a pem key on the server:

    ssh-keygen -m PEM -t rsa -b 4096
    

    Then you need to put that key in a secret (I named mine SERVER_SSH_KEY). But what they don’t mention quite as clearly is what this means:

    Private key part of an SSH key pair. The public key part should be added to the authorized_keys file on the server that receives the deployment.

    Yes, they’re saying “the public key for your own server has to be on the authorized_keys for the server.” And yes, that’s a weird thing to say, but there it is. That means you copy your own key from your server at ~/.ssh/id_rsa.pub (the .PUB is the part that explains this is a PUBLIC key) and you paste that in to the end of ~/.ssh/authorized_keys on the same server. Yes, it’s really funky.

    My ongoing concerns

    I mentioned that there are security issues. I spend a lot of time in WordPress plugin land, where people could commit nefarious code to a plugin any day. Some do. I’ve banned enough people from .org for stupid stuff like that. And some of Github’s advice matches my own: the only way to be safe is to do the damn research yourself.

    But that’s not really something most people can do. And it’s something for a longer post in general. My short list of concerns right now is:

    • the action I’m using is deleted
    • the action is edited and breaks my flow/injects malware
    • the action is used to steal my credentials

    There are ways to mitigate this

    • I can use actions made and managed and maintained by Github only (those are under the Actions org) — those are unlikely to be deleted and can be trusted as much as Github
    • I can make copies of the actions I want to use (and periodically remember to update them…)
    • I can make use of encrypted secrets to hide sensitive information

    But. It’s a risk. And I know it.

  • It’s Just Math

    It’s Just Math

    Recently I posted about how I added new features to a show scoring system, over on LezWatchTV. We added in what we call Intersectionality, which is rewarding shows for positive representation of diversity in the world. Naturally that begs the question of what is show scoring (answer: it’s calculative a qualitative value of how good a TV show on a scale of 0-100). And that makes people ask …

    How do you do that?

    It’s really math

    Look, the bare answer to all of this is that it’s math. I’m taking the meta data we assign to shows, like how much screen time characters get and how good the show is for them, and so on, and assigning numbers to those values. Then I add up the numbers, in various ways, and determine what the over all score is.

    It’s a little more complex than that, and if you’re super interested in how it all works, we wrote up an explanation as to the framework I created to value shows.

    But most people who ask me how I did this aren’t asking about the math, they want to know about the code. That is, how did I get WordPress to automate all this, because you know I don’t do it all by hand. No, I do it when the post saves.

    Magical Hooks

    Let’s take a moment. What’s a hook anyway?

    A hook is an action or a filter in WordPress, which allows you to write code that ‘hooks’ into the rest of WordPress code. You can use this to trigger your code to run at specific times.

    For example, if I had a hook called publish_post (which we do – WordPress has that built in), and I wanted to run my show calculations when I publish a post, I would do this:

    add_action ( 'publish_post', 'shows_calculations' );

    That would of course require me to have a function called show_calculations() that does the math, which is fine. But. I don’t want to do this on post publish. I want to do it on post save, and only if the post is of the shows post type. And that sounds like a lot of if/then statements until you learn about dynamic hooks.

    You see, I’m using the save_post_ hook, which runs when a post is saved, and it’s a little special. Unlike a hook like publish_post(), this hook lets me customize it how I want by being save_post_{$post->post_type} …

    Yeah those curly brackets are weird, right? That’s the dynamic part. That part changes.

    Dynamic Saving

    To understand how the hook name changes, you should know that in my case, I have a post type called post_type_shows — logical right? Well the subsequent hook is called save_post_post_type_shows — that is, I’ve replaced those weird brackets with the post type.

    Once you know your new hook name, it’s like every other hook:

    add_action( 'save_post_post_type_shows', 'shows_calculations', 10, 3 );

    What that does is call the function post_type_shows_calculations on every post save. And that function calls another one I called do_the_math() which passes the post ID to the myriad complications of calculations I perform.

    But the magic literally is in that post save hook.