Deploying an Astro site through SourceHut
Today I decided to finally try out Astro, a Javascript framework for building websites, by trialling it on my homepage. Suffice to say I liked it so much, and it was so easy to get to grips with, that this site is now built using Astro.[1]
I was previously less interested in Astro because I thought it was mostly a tool for building websites using an islands architecture, something we're extensively trialling at work. But having read the excellent Astro docs, I realised that Astro websites are "Zero JS, by default", and that islands are simply an optional, albeit useful feature. With Astro under the hood, this website ends up just as small and static as it was when I deployed it using my self-designed static site generator, Orogene.
With the move to a new framework and a new git repository, I also thought to make use of my SourceHut account and set up an automatic deployment script using SourceHut's superb build.sr.ht build system. With a deployment system in place, my workflow will look like the following:
- Write a new post, like this one, on my local machine
- Commit it to this site's SourceHut repository
- SourceHut will spin up a virtual machine, install Astro, build the static site from the latest commit in the repository, and copy the newly minted static site directly to my server - all without my lifting a finger.
In my research, I found a brilliant post which documented a similar process, but for the static site generator Hugo. I've borrowed extensively from that post - mostly for my own reference - the steps needed to create a secure path between SourceHut's build system and my server, which will prevent my server from being compromised if the private key is leaked. I strongly recommend you read the original post for the context.
Create SSH keypair
We'll be creating a user, homepage_deployer
, on the remote server, who will be allowed only to upload files to a specific directory. That user needs an SSH key to log in to the server from inside the build job. Create a keypair on your local machine:
ssh-keygen -t ed25519 -f ~/.ssh/homepage_deployer
Copy the contents of the public key, ~/.ssh/homepage_deployer.pub
, for the next step.
Server setup
This is all borrowed from sidhion.com:
# On the server:
useradd homepage_deployer
# Lock the password because we won't be using it.
passwd -l homepage_deployer
mkdir -p /home/homepage_deployer/.ssh
# Manually add the contents of homepage_deployer.pub to the authorized_keys file.
vim /home/homepage_deployer/.ssh/authorized_keys
chown -R homepage_deployer:homepage_deployer /home/homepage_deployer
Next, we make use of a script called rrsync, which is a restricted rsync
designed for scenarios where a user should only be permitted to rsync in a specific directory on a remote server. The script is inside rsync's directory and needs to be moved to a location on the PATH
:
cp /usr/share/doc/rsync/scripts/rrsync /usr/local/bin/rrsync
Reopen homepage_deployer
's authorized_keys
file and add the following text at the beginning of the line with the public key, separated from the key by a space:
restrict,pty,command="/usr/local/bin/rrsync -wo /var/www/raphaelkabo.com"
restrict
prevents all forwarding for this key and restricts tty allocation; pty
re-enables tty allocation (since we need one for the script to run); the final option, command=
, will simply execute the provided command whenever this key is used for authentication, ignoring any supplied commands. All these options are documented in the sshd manpages.
The rrsync
flags tell the script that it is only allowed to write to the directory /var/www/raphaelkabo.com
, which is where my homepage lives on the server.
The final step is to ensure that rsync
has access to the directory where it needs to work and its parent directory, if need be. I ran into some issues with this: non-fatal errors caused by rsync
failing to save modification times caused the SourceHut build script to register a failure. Simply assigning homepage_deployer
to the group which owned /var/www/raphaelkabo.com
was not enough; I had to make homepage_deployer
the owner of /var/www/raphaelkabo.com
and all the files inside it:
chown -R homepage_deployer /var/www/raphaelkabo.com
chmod 755 /var/www/raphaelkabo.com
SourceHut
Add the contents of the private key you created, ~/.ssh/homepage_deployer
, to SourceHut's secrets page. If this key is leaked, it will only allow the attacker to rsync
write into one directory on the server - hardly the end of the world. Make a note of the UUID which SourceHut generates for the secret.
Build manifest
SourceHut's build system is controlled by a build manifest, such as a file called .build.yml
located in the root directory of the repository. This is a pleasantly simple way to orchestrate a build task and I am a big fan. When the .build.yml
file is commited to the repository, SourceHut immediately starts its first build, and will then trigger a build using the latest contents of .build.yml
on every commit.
As easy as the system might be, I did have to run my builds about twelve times before I got this right, just to save you the trouble. Here is my final build manifest to build and deploy an Astro site using pnpm
:
image: alpine/edge
packages:
- rsync
- nodejs
- npm
sources:
- https://git.sr.ht/~lown/homepage
secrets:
- a5e2fe34-6fce-48d3-be7b-484400a4ab04
tasks:
- install-build-deps: |
wget -qO pnpm "https://github.com/pnpm/pnpm/releases/latest/download/pnpm-linuxstatic-x64"
chmod +x pnpm
sudo mv pnpm /usr/bin
- build: |
cd homepage
pnpm install
pnpm astro build
- deploy: |
cd homepage
rsync -rvz -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/a5e2fe34-6fce-48d3-be7b-484400a4ab04" dist/ homepage_deployer@raphaelkabo.com:/
This file instructs SourceHut to do the following things:
- Spin up an Alpine Linux virtual machine
- Install
rsync
,nodejs
, andnpm
- Clone the latest from my repository into a folder called
homepage
in the working directory - Fetch my secret SSH key and set it up
- Download the
pnpm
sources for Alpine Linux and move them into a location on thePATH
- Set up and build my website using Astro
- Deploy it via
rsync
to my server.
As the original blog post notes (with my alterations):
Using
-o StrictHostKeyChecking=no
is required, otherwise the build job will hang because it doesn’t recognize the server to connect to (and will ask for confirmation before proceeding). [...] Also important is the destination of the rsync command:homepage_deployer@raphaelkabo.com:/
. Notice how the path only specifies /. This is because back in the server, we told rrsync to use /var/www/raphaelkabo.com as the root path of any file synced with rsync.
Once I got my rsync
permissions issues ironed out, this system worked flawlessly for me. I found the SourceHut build system very pleasant and easy to use, and I can now publish to my website with a simple commit, which is lovely. It complements Astro's design philosophy nicely: 'get out of the developer's way as much as possible to let them do work without fiddling with tooling for weeks'.
I still think you should write a static site generator, though. ↩︎