Partial Under-the-Hood Migration With Netlify
⏱ 11 minutes read. 📅 Published . ✎ Last updated .For a while I have been the “proud” owner of the domain broken.engineer. As with many other of my domain ventures, this has thus far been yet another fruitless one. That has just, ever so slightly, imperceptibly changed.
My original plan looked sort of like this:
redirect broken.engineer to roht.no while we sort it out- create a new repository?
- or use the same repository?
- can we build two different sites from Netlify?
Evidently I had more questions than surefire approaches.
Fast-forward till the completion of my Master’s thesis, whereupon I was suddenly able to devote time to more than one thing in my life. My goal was as follows: I want to move my section of scribbles1 to broken.engineer, where I will divide my posts into a section for that which I think could be useful some day; that which is fragmented; and that which I spent a lot of time on, and I have no idea whether it’ll ever be useful but where it’s not going to stop me from putting it online anyway.
However, I’d like to link to this content in a dynamic and seemingly integrated way from this site roht.no. My current sketches have something akin to a special blob or call-to-action on my homepage that links to the latest and greatest. There is the potential for a “last three posts I Googled my way to a solution” sort of section instead, or a whole long list of “my other five”, perhaps “featured garbage” or “most viewed”2.
Regardless of which one I choose, I’d like to integrate in a manner which fits with a static site. The integration should exist at build time, thus no JavaScript or any other “quick” front-end solution. I should be able to do whatever I want with the files, so ideally I shouldn’t have to rely too much on an API as designed at some point: I’m the developer and the consumer. Ignore best practices, ignore design! Give me the raw stuff!
Here we are. I’d like the content for my other site accessible within my current site. Where do we go?
Considerations: They Were Made
After considering the pros and cons, it would not be feasible or clean to attempt to keep two sites in the same repo with Netlify. “That’s crazy!”, you say, “clearly the people over at Nx knows how to do it, why don’t you?” While a monorepo on its own is possible, that is different from keeping two different sites in the same repository. Keep in mind that the differences that come with a monorepo are not on the table here, and focus on a repository, that contains two distinct Hugo sites with different domains in the same language.
The following were my discarded ideas.
Use two different sets of configuration files. In essence, determine if we need to build each of the sites based on the files that have changed. My preferred alternative, although one I did not continue with. Requires additional configuration and exclusion in order to have one content root without spilling over in terms of content.
Use3 the multilingual support and create a matrix of site-to-language. This way, we can produce number of sites times number of languages sites across as many domains as need be. Unfortunately, this both feels filthy, and results in having to use
lang
to target which pages to publish. Don’t be like that. Don’t do it. Stop it.
There’s The Way
After considering the solution, the way forward seemed alright.
redirect broken.engineer to roht.no while we sort it out- move or copy content over
- create a scaffolding
- create a new theme
- publish site
- add redirects from the original site
- what now?
I was not quite happy with the approach, in part because of my drafts. I’m terrible at publishing the notes I make, but I enjoy dumping my notes into a folder without the slightest trace of concern. Plus, what do I do if I update a post? Should I make the change in two repositories?
How about Git Submodules, they’re perfect for the job! Well, unless you take into account that you pin a submodule to a given reference, and I’d like to make sure the content is fresh across the two.
The solution seemed as bright as the day long. Enter Hugo Modules. They’re really just Go modules with some added integration into Hugo, namely the easy ability to overlay contents of a Hugo Module over your own content.
You can use Hugo’s mod init
subcommand, or just create the Go module yourself. Whip out a go.mod
in your project and make sure you get your module
correctly specified. Chuck in your other module with the mystique specifier latest
— all the dependency managers will hate you and self-loathing is not unheard of.
module github.com/thor/roht.no
go 1.14
require github.com/thor/broken.engineer latest // indirect
Proceed to smile as you open your Hugo configuration in one of the many variants it exists, such as the config.yml
.
# config.yml
module:
imports:
- path: "github.com/thor/broken.engineer"
ignoreConfig: true
mounts:
- source: content/posts
target: content/posts
I — and you if you were looking for this too! — have now created parts of the solution to the problem.
Perhaps, if you’re unlike me, it will be the entirety of the solution.
Go with go get
will get the modules, whether you do it or hugo mod get -u
does it, and Hugo will overlay the files in content/posts
over content/posts
in our current project.
redirect broken.engineer to roht.no while we sort it outmove (not copy) content to new repo4add the Hugo moduleadmit that I’m not sure what I want to do before I deploy itremove other tasks- continue working in the meanwhile
- figure out the next step out of many paths
- publish site
- add redirects from the original site
The Taste of Progress
Ah, I feel good. I test it locally, and it wo– no. It fails.
hugo: downloading modules …
go: errors parsing go.mod:
/home/thor/dev/roht.no/go.mod:5: require github.com/thor/broken.engineer: git ls-remote -q origin in /tmp/thor/hugo_cache/modules/filecache/modules/pkg/mod/cache/vcs/00692885174d6dcf453ccebebafa216e1f15d87b99b90e43028d562572ed5e26: exit status 128:
remote: Repository not found.
fatal: Authentication failed for 'https://github.com/thor/broken.engineer/'
hugo: collected modules in 11461 ms
go: errors parsing go.mod:
/home/thor/dev/roht.no/go.mod:5: require github.com/thor/broken.engineer: git ls-remote -q origin in /tmp/thor/hugo_cache/modules/filecache/modules/pkg/mod/cache/vcs/00692885174d6dcf453ccebebafa216e1f15d87b99b90e43028d562572ed5e26: exit status 128:
remote: Repository not found.
fatal: Authentication failed for 'https://github.com/thor/broken.engineer/'
Go can’t get my repository because it tries to check it out over HTTPS, and it’s private and I do not use any authentication for Git besides SSH keys. Ugh. What now? Should I, where the Git host supports it, add a token for my account into the repository path? That feels filthy. How about getting Go to check out the modules using SSH instead? Shirley, that must be easy.
As with most things in life, our hindsight is 20/20. Here is a quick list of things I recommend you do not try or do:
Don’t think that the
GOPRIVATE
environment variable will solve your SSH issueIt has its place, but it’s not here.
Don’t think that it is sufficient to add
git config --global url."[email protected]:".insteadOf https://github.com/
aloneIt’s not. You still need an SSH key to fetch it with.
Don’t think that the deploy key in Netlify works like other build pipelines
It doesn’t. You have no SSH keys available after the initial checkout, even if you add the deploy key in Netlify’s interface. I’m not sure if this should be surprising, but it caught me unawares. Routine and expectations are dangerous.
Don’t think there’s proper secret management with Netlify
There isn’t. Environment variables are treated equally, and you cannot use newlines in the environment variables.
The Way in the End
There are two parts to the solution, the first of which pertains in particular to Netlify. I believe it is still generally applicable, but I haven’t come across other build pipelines where it’s been a hassle as such.
Loading SSH Keys into Netlify
It’s not ideal, but if you have a one-person team it may be considered acceptable.
Generate a new SSH key.
$ ssh-keygen -t ed25519 id_actual_deploy_key_ed25519
Encode it in base64 in one line.
$ cat id_actual_deploy_key_ed25519 | base64 --wrap 0
Add it as an environment variable to your Netlify site, e.g.
SSH_KEY
5.Add the following script to “load” your SSH key into your session.
# load-ssh-keys.sh #!/bin/sh # Check if we're running in a Netlify environment # See https://www.netlify.com/docs/continuous-deployment/#environment-variables if [ ! -z "${DEPLOY_PRIME_URL}" ]; then # Init .ssh dir and expand $SSH_KEY mkdir -p ~/.ssh echo "${SSH_KEY}" | base64 -d > ~/.ssh/id_actual_deploy_key_ed25519 chmod og-rwx ~/.ssh/id_actual_deploy_key_ed25519 # Add host keys as an alternative to specifying -o UserKnownHostsFile=/dev/null ssh-keyscan -H github.com >> ~/.ssh/known_hosts fi
The snippet is inspired by a comment on a support guide I haphazardly stumbled over on Netlify’s forum. Note that there’s no point in starting an SSH agent. In fact, it’s more hassle: if you have multiple keys and need to start an SSH agent, you will also need to manually terminate it once the build is done in order to avoid your build failing due to a lingering
ssh-agent
.Execute
load-ssh-keys.sh
somewhere in your build process.Two primary approaches: either prepend your build command with
load-ssh-keys.sh
(and a&&
) or add it to your equivalent of apackage.json
.// package.json { ..., 'scripts': { 'preinstall': 'load-ssh-keys.sh' }, ... }
You’re now good to go. If you went with the package.json
route you already know that Netlify will automatically install your package when building, which will execute load-ssh-keys.sh
before building your site.
Rewriting HTTPS to SSH
We’ve already alluded to it: it’s time to change the Git configuration.
If you want to go the route of the ever-increasing build command you can—yet again—prepend something to it.
Add git config --global url."[email protected]:".insteadOf https://github.com/
and then &&
to the build command and be on your merry way.
There is another way, and while I rate it 0/5 in terms of code maturity or robustness, it current does the trick. I wanted to do this in a way that didn’t involve editing the build command, and I found the far-too-integrated solution.
Enter the newly created Netlify plugin netlify-plugin-hugo-modules
.
It does the following two-and-a-half things:
Configures Git to use
[email protected]:
instead ofhttps://github.com/
, resulting in Go checking out your Go modules over SSH.Executes
hugo mod get -u
before building your project.Half points: removes the Git configuration when done.
The last point may seem useless, but remember that you can use the Netlify command line client to build and test locally (without the build image), in which case you want the plugin to not overwrite any preexisting setting and to clean up after itself.
$ yarn add netlify-plugin-hugo-modules
$ cat >> netlify.toml <<EOF
[[plugins]]
package = "netlify-plugin-hugo-modules"
EOF
$ git add . # oo, risque of you!
That’s it. Hit deploy. Regret the lack of a Terraform module for Netlify. Curse manual steps. You’re set.
My Path Forward
Now I am here.
redirect broken.engineer to roht.no while we sort it outmove (not copy) content to new repoadd our own Hugo modulerewrite Go modules (or everything) to load over SSH instead of HTTPSfix loading of SSH keys inside of Netlifyadd build hook tobroken.engineer
that buildsroht.no
when I push changesadmit that I’m not sure what I want to do before I deploy itremove other tasks- ✅ continue working in the meanwhile
- figure out the next step out of many paths
- publish site
- add redirects from roht.no
I think, when considering the amount of time one can dedicate to something so seemingly simple, that the amount of time it’ll take to check the last three points of this list at least fits with something: gradual migrations may take more work, but they’re more likely to get you where you want in the end.
You need your own critical mass.
Summary
In this post we’ve seen elements of my own foolery, as well as how to load Go modules over SSH in Netlify in order to allow us to perform a partial migration of the underlying structure of my site without sacrifying flexibility and the ability to update content as I see fit without duplicating my work. This enabled us to utilise Hugo modules to add content from another Hugo repo without requiring us to use submodules or manually checkout a repository, which could’ve been even easier.
We’ve also seen some minor frustrations with Netlify, and how we can work around those provided the security circumstances are acceptable, by utilising some good ol’ scripting and—in my own case—yarn
.
Possibly known as a blog. ↩︎
Hah! Who are we kidding?! Nonetheless we are in this for the long run. For ourselves. And maybe you. Maybe. Actually, do I even know you? ↩︎
I consider this to be more akin to abusing the functionality than using it. ↩︎
Clone the repository. Filter out all the junk that’s not important. Whoops, is
git filter-branch
meant to remove everything? ↩︎I am devoid of imagination when it comes to naming variables. All that matters is semantics and understanding. ↩︎