Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: github

  • Hugo and a Lot of Images

    Hugo and a Lot of Images

    One issue with Hugo is that the way I’m deploying it is via Github actions, which means every time I want to update, the site has to be totally rebuilt. Now the primary flaw with that process is that when Hugo builds a lot of images, it takes a lot of time. About 8 minutes.

    The reason Hugo takes this long is that every time it runs its builds, it regenerates all the images and resizes them. This is not a bad thing, since Hugo smartly caches everything in the /resources/_gen/ folder, which is not sync’d to Github, and when you run builds locally it doesn’t take half as long.

    Now, this speed is about the same whether the images are locally (as in, stored in the repository) or remote (which is where mine are located – assets.example.com), because regardless it has to build the resized images. This only runs on a build, since it’s only needed for a build. Once the content is on the server, it’s unnecessary.

    The obvious solution to solve my speed issues would be to include the folder in Github, only I don’t want to store any images on Github if I can help it (legal reasons, if there’s a DMCA its easier to nuke them from my own storage). The less obvious solution is how we got here.

    The Basic Solution

    Here’s your overview:

    1. Checkout the repo
    2. Install Hugo
    3. Run the repo installer (all the dependancies etc)
    4. Copy the files from ‘wherever’ to the Git resource
    5. Run the build (which will use what’s in the resource folder to speed it up)
    6. Copy the resources folder content back down to the server

    This would allow me to have a ‘source of truth’ and update it as I push code.

    The Setup

    To start with, I had to decide where to upload the content. The folder is (right now) about 500 megs, and that’s only going to get bigger. Thankfully I have a big VPS and I was previous hosting around 30 gigs there, so I’m pretty sure this will be okay.

    But the ‘where’ specifics needed a little more than that. I went with a subdomain like secretplace.example.com and in there is a folder called /resources/_gen/

    Next, how do I want to upload to for starters? I went with only uploading the static CSS files because my plan involves pushing things back down after I re-run the build.

    Then comes the downloading. Did you know that there’s nearly no documentation about how to rsync from a remote source to your Github Action instance? It doesn’t help that the words are all pretty generic, and search engines think “Oh you want to know about rsync and a Github Action? You must want to sync from your action to your server!” No, thank you, I wanted the opposite.

    While there’s a nifty wrapper for syncing over SSH for Github, it only works one way. In order to do it the other way, you have to understand the actual issue that action is solving. The SSH-sync isn’t solving rsync at all, that’s baked in to the action image (assuming you’re using ubuntu…). No, what the action solves is the mishegas of adding in your SSH details (the key, the known hosts, etc).

    I could use that action to copy back down to the server, but if you’re going to have to solve the issue once, you may as well use it all the time. Once that’s solved, the easy part begins.

    Your Actions

    Once we’ve understood where we’re going, we can start to get there.

    I’ve set this up in my ci.yml, which runs on everything except production, and it’s a requirement for a PR to pass it before it can be merged into production. I could skip it (as admin) but I try very hard not to, so I can always confirm my code will actually push and not error when I run it.

    name: 'Preflight Checks'
    
    on:
      push:
        branches:
          - '!production'   # excludes production.
    
    concurrency:
         group: ${{ github.ref }}-ci
         cancel-in-progress: true
    
    jobs:
      preflight-checks:
        runs-on: ubuntu-latest
    
        steps:
          - name: Do a git checkout including submodules
            uses: actions/checkout@v4
            with:
              submodules: true
    
          - name: Install SSH Key
            uses: shimataro/ssh-key-action@v2
            with:
              key: ${{ secrets.SERVER_SSH_KEY }}
              known_hosts: unnecessary
    
          - name: Adding Known Hosts
            run: ssh-keyscan -H ${{ secrets.REMOTE_HOST }} >> ~/.ssh/known_hosts
    
          - name: Setup Hugo
            uses: peaceiris/actions-hugo@v3
            with:
              hugo-version: 'latest'
              extended: true
    
          - name: Setup Node and Install
            uses: actions/setup-node@v4
            with:
              node-version-file: '.nvmrc'
              cache: 'npm'
    
          - name: Install Dependencies
            run: npm install && npm run mod:update
    
          - name: Lint
            run: npm run lint
    
          - name: Make Resources Folder locally
            run: mkdir resources
    
          - name: Download resources from server
            run: rsync -rlgoDzvc -i ${{ secrets.REMOTE_USER }}@${{ secrets.REMOTE_HOST }}:/home/${{ secrets.REMOTE_USER }}/${{ secrets.HUGO_RESOURCES_URL }}/ resources/
    
          - name: Test site
            run: npm run tests
    
          - name: Copy back down all the regenerated resources
            run: rsync -rlgoDzvc -i --delete resources/ ${{ secrets.REMOTE_USER }}@${{ secrets.REMOTE_HOST }}:/home/${{ secrets.REMOTE_USER }}/${{ secrets.HUGO_RESOURCES_URL }}/
    

    Obviously this is geared towards Hugo. My command npm run tests is a home-grown command that runs a build and then some tests on said build. It’s separate from the linting, which comes with my theme. Because it’s running a build, this is where I can make use of my pre-built resources.

    You may notice I set known_hosts to ‘unnecessary’ — this is a lie. They’re totally needed but I had a devil of a time making it work at all, so I followed the advice from Zell, who had a similar headache, and put in the ssh-keyscan command.

    When I run my deploy action, it only runs the build (no tests), but it also copies down the resources folder to speed it up. I only copy it back up on testing for the teeny speed boost.

    Results

    Before all this, my builds took 8 to 9 minutes.

    After they took 1 to 2, which is way better. Originally I only had it down to 4 minutes, but I was using wget to test things (and that’s generally not a great idea — it’s slow). Once I switched to rsync, it’s incredibly fast. The build of Hugo is still the slowest part, but it’s around 90 seconds.

  • 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.

  • AutoDeploy: Github to DreamObjects

    AutoDeploy: Github to DreamObjects

    There are a lot of things that Github makes easy. One of them is a great way to keep versions of data publicly accessible, trackable, and deployable.

    Well. Not so much that last one.

    Automated deployment with Github can be incredibly convoluted, twisted, and perplexing. If you don’t speak high geek, it’s worse. Thankfully there are services like Codeship that let you ship code, run tests, and more importantly, run some hefty commands.

    When the day came that I wanted to keep some JSON files on Github for version control, but deploy them to the cloud, I knew what to do. I was already doing a version of the process, in reverse, for my Remoteless SVG images, after all.

    Create Your Repository

    Now I’m using Github, but Codeship supports Bitbucket or Gitlab, so that’s also an option. Once you’ve made your repository, commit your code but don’t push anything yet! We have to make a couple decisions first.

    First: Branches.

    I have two philosophies of this. If I’m working on something like a plugin or a theme or an app that needs versioned releases, I’m going to name my branches REL_1.2.3 and so on. If, instead, I’m maintaining a theme or plugin for personal use, I have a development branch that I use for all normal dev work. I may spin up a special branch for some new feature, but it always goes into that dev branch first.

    Second: Deployments.

    The reason I use that branch system is that when I push to the dev branch, it pushes code to the dev server.

    Schnieder from One Day at a Time (2017) going 'Mind blown'
    Mind? Blown.

    Once you decide how you want your set up to deploy, and when, you’re ready to Ship That Code.

    Gettit? Right…

    Setup Codeship

    If you’ve never setup Codeship before, I explain how you do that in my post about Deploying from Github with Codeship. The special sauce here is your Custom Script.

    Before you write the script, add in Environment Variables for AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY with those names specifically. This way you won’t have to write a mess of custom code.

    pip install 
    awscli aws s3 --endpoint-url https://objects-us-east-1.dream.io sync ~/clone/ s3://bucket-name/

    The important bits:

    • Endpoint URL is my S3-esque host
    • bucket-name is my bucket name…

    Push Your Code

    Once that’s saved, you can push code and it’ll automatically kick the code to the cloud.

    A lot of this is obviously a backwards implementation of code I already described, but sometimes we need something explained in the straightforward way to help us move forward.

    Enjoy your deploys!

  • Deploying from Github via TravisCI

    Deploying from Github via TravisCI

    When you develop your code on Git, you can automagically (and easily) deploy to places all you want, if the repository is on the same server. If it's not, you can use something like Codeship to automate pushing for you.

    Free Services Have Limits

    Codeship, which I like a lot, has a limit of 100 pushes a month. In October, when Tracy and I finally deployed the newest version of our website, we actually hit that. In part, this is because we don't have a great 'test' environment where we can internally develop and share the results with the other. We both have our own versions of local environments, but you can't show your cohort 3000 miles away what you've done without pushing the code to the private development site and letting her see it.

    After 100 pushes, it's $490 a year for unlimited. While the product claims to be 'free forever' for open source, there's actually no documentation that I could find on how one gets added to that list, or what the qualifications are. Does my personal open source project  qualify? I'll have to email and find out.

    TravisCI Is ‘Free’

    All 'free' services have a paid component. Travis, like Codeship, is free for public, open source, projects. And like Codeship, it doesn't require you to host (and thus) update anything yourself. Which is nice. In fact, it only has a few pre-requsits:

    Sounds like a match made in heaven, except for the part about documentation on GitHub being out of date. I don't begrudge them, as keeping up docs is a pain and if it's with another service it's nigh impossible.

    Awesome. Sign up for Travis, activate your repositories, add a .travis.yml file with the programing language, and you're ready to do… what?

    Writing The Build Script

    This is the weird part. You have to invent a way to push the code. Unlike DeployHQ or Codeship, there's no place to type in the code on their servers. You have to make a file and write the script.

    The scripts look like this (cribbed from Florian Brinkkman):

    language: php
    
    addons:
      ssh_known_hosts:
      - $DEVELOPMENT_SERVER
      - $PRODUCTION_SERVER
    
    before_script:
      - echo -e "Host $DEVELOPMENT_SERVERntStrictHostKeyChecking non" >> ~/.ssh/config
      - echo -e "Host $PRODUCTION_SERVERntStrictHostKeyChecking non" >> ~/.ssh/config
    
    script:
      -
    before_deploy:
      - openssl aes-256-cbc -K $ENCRYPTED_KEY -iv $ENCRYPTED_IV -in deploy_rsa.enc -out /tmp/deploy_rsa -d
      - eval "$(ssh-agent -s)"
      - chmod 600 /tmp/deploy_rsa
      - ssh-add /tmp/deploy_rsa
    
    deploy:
      - provider: script
        skip_cleanup: true
        script: ssh -p22 $DEVELOPMENT_SERVER_USER@$DEVELOPMENT_SERVER "mkdir -p $DEVELOPMENT_PATH_STABLE" && ssh -p22 $DEVELOPMENT_SERVER_USER@$DEVELOPMENT_SERVER "mkdir -p $DEVELOPMENT_PATH_TRUNK" && rsync -rav -e ssh --exclude='.git/' --exclude=scripts/ --exclude='.travis.yml' --delete-excluded ./ $DEVELOPMENT_SERVER_USER@$DEVELOPMENT_SERVER:$DEVELOPMENT_PATH_TRUNK && rsync -rav -e ssh --exclude='.git/' --exclude=scripts/ --exclude='.travis.yml' --delete-excluded ./ $DEVELOPMENT_SERVER_USER@$DEVELOPMENT_SERVER:$DEVELOPMENT_PATH_STABLE
        on:
          branch: DEVELOPMENT
      - provider: script
        skip_cleanup: true
        script: ssh -p22 $PRODUCTION_SERVER_USER@$PRODUCTION_SERVER "mkdir -p $PRODUCTION_PATH_STABLE" && rsync -rav -e ssh --exclude='.git/' --exclude=scripts/ --exclude='.travis.yml' --delete-excluded ./  $PRODUCTION_SERVER_USER@$PRODUCTION_SERVER:$PRODUCTION_PATH_STABLE
        on:
          branch: master

    And to be honest, it's really not that explanatory. I read it a few times and sighed. While I'm (obviously) not opposed to learning new code to do things, I am opposed to all these services making it needlessly complicated.

    One Big Problem…

    You can't (easily) set up SSH keys on Travis for free. That's because they're restricted to the pro version. Now you totally can set it up, but it's incredibly insane and not something I was willing to do in the long term. And since the cost of TravisPro is $69 a month compared to Codeship's $49 or so a month, it was a no brainer.

    I emailed Codeship to ask if I qualified for 'open source.' Most likely they'll tell me no, because I deploy to a closed system, but it doesn't hurt to ask.

  • Github Introduces Archiving

    Github Introduces Archiving

    One of my laments has been that I can't (easily) flag a repository in Github as archived.

    https://twitter.com/ipstenu/status/926529534580555777

    As of today, you can!

    Github just announced the ability to archive repositories, and yes, it can be undone.

    To archive a repository, go to your Repository Settings Page, scroll down to the Danger Zone and click Archive this repository. It will pop up with a screen to warn you that this is a serious change, and while it can be undone, it will ask you to type in the repository slug to confirm.

    Once you've closed the repository, you'll see it has a new banner at the top, announcing "This repository has been archived by the owner. It is now read-only."

    This repository has been archived by the owner. It is now read-only.

    The repository will also have a little 'Archived" badge in the regular view.

    So. YAY!

  • Deploying from GitHub with Codeship

    Deploying from GitHub with Codeship

    For the longest time I self-hosted all my git repositories not so much because I enjoyed doing so but because there were limited options for easily pushing code from git to my servers. Invariably you will end up using an intermediary because GitHub and their peers have no real reason nor inclination to make those things easy for you. After all, if they can keep you in their systems, more money to ’em.

    And while that’s perfectly understandable and logical, it’s annoying. And if you’re on a budget and not a Git Expert, it’s extremely frustrating. The deployment API was written in High Level Geek, and I found it a headache to decipher and test.

    Thankfully there are tools for that, and one of them in Codeship. Codeship is free for 100 builds a month, and unlike DeployHQ, it uses rsync, which meshes with my preferred way of moving data.

    The Plan

    My plan is simple. I want to push code to a Github Repository and, when it’s a push to master, it would rsync the files over to my server. This will allow multiple people to work on code, among other things.

    The Setup

    First make an account with Codeship. You can log in as Github which is useful since you’ll want to connect your Github account with Codeship anyway. When you create a project, you’ll be prompted which SCM you want to use:

    Create a Project in Codeship

    I’m using Github, but it’s nice to see GitLab in there as well. Once you pick your SCM, paste in the clone URL.

    Examples:

    • git@github.com:<username>/<repository_name>.git
    • https://github.com/<username>/<repository_name>.git
    • https://github.com/codeship/<repository_name>

    Finally you can pick Codeship Pro or Basic – I picked Basic because it required the least amount of know-how. Not that it’s easy, but I don’t have to configure the server or anything annoying. In fact, Basic is so basic, than you can just accept the default setup commands and go. Which is what I did.

    The Settings

    Once you’ve done all that, you’re on a new screen that tells you to push code. Of course, you can’t do that until you set up a couple more things. Like tell it where to push the code.

    Click on “Project Settings” and go to the General tab. You’ll need to get that SSH key and add it to your server’s ~/.ssh/authorized_keys to allow passwordless deployment. I strongly recommend that.

    Now you can click Deploy and add a Deployment Pipeline. Pick ‘master’ unless you’re using something else.

    Now you have to add a deployment to your pipeline. There are a lot of options here, but for my plan of an rsync, the choice is “Custom Script”. Since I’m pushing code to my DreamPress server, the rsync looks like this:

    # DreamPress
    rsync -aCz -e "ssh" ~/clone/ wp_e413xh@mysite.dream.press:/home/wp_e414xh/mysite.dream.press/wp-content/themes/my-theme-name/ --delete
    

    You can customize your rsync commands however you want. I like mine to delete files I’ve removed.

    Deploy the Ships

    Everything is set up so go back to your project page and push some code to Github

    Magic happens.

    The code deploys.

    Huzzah.

    On the free version you only get 100 private builds and five private projects. Mine are public (so’s the Github repo for that matter) so it doesn’t matter. The only downside is only getting one push at a time, but since they take less than five minutes, it’s perfectly acceptable for a free solution.