How this site was made
This site was created with a few main goals in mind:
- Being easy to update from any device which I own
- Cheap to host, without reliance on ‘free’ services
- Stable, with ease of updating and upgrading
- Storing my writing in a domain which I own and control
- Allowing me to learn more about linux server management through practice
- Easy, simple backups
Some sites which inspired the design choices here are:
- danluu.com
- A great blog full of interesting technical writing, usually containing thorough investigation and research
- stallman.org
- An interesting point of view from a much more interesting person, along with a wonderfully simple template
- sheldonbrown.com
- Bicycle website with in depth analysis of all kinds of bicycle technology
- Hacker News
- Forum for Silicon valley types, great online discussion here on technical subjects with a simple and clean UI
Based on my goals and inspiration, I found two possible paths.
- Using Hugo, a simple framework for generating static websites
- Using Netlify, a complete product for generating and maintaining static websites, with a very permissive ‘free’ tier.
I chose to utilize Hugo, instead of a free option like Netlify, Github pages, or other free solutions. I desired to have more control over my content than I would have with those services. I also felt that I would be missing out on an opprutunity to learn more about linux server administration if I went with a pre-made service. I did end up using a few cloud services, like Github and Digital Ocean but I am working on plans to replace them with self-hosted options.
Negatives of going the self-hosted route are:
- Lack of a security team, using a VPS or my own server requires me to be in charge of hardening
- Increased administration workload compared to premade options
Positives of self-hosting are:
- Less reliance on centralized services
- I pay for usage, and not for access to support or additional features
- I learn and demonstrate subject area knowledge all in one project
How to create your own similiar site using a VPS, Hugo, Nginx, and Github
Required before starting
- A domain you control. I used namecheap.
- A server. I chose to use a VPS, and went with Digital Ocean with a $5/mo droplet running ubuntu server.
- You could use your own server if you own a physical machine. If you don’t have a static IP address, use a DDNS service like noip (however this is an additonal external dependancy)
- A github account
Configuring VPS
The first step is to create an account with a VPS provider. The goal is to get an IP address and a root password to a Linux machine. Once you have these things, record them, and attempt to use an ssh client to connect and login to the root account.
Configuring DNS
Before you begin these instructions, you should go to your DNS provider, and create an A record to point your domain name, to the IP address given to your by your VPS provider. Instructions for Namecheap are here
Note on these instructions
If a command is preceded by #
, it is to be run as the root user. If it is preceded by $
it is to be run as the non-root user.
Once signed in as root, do the following security tasks:
-
Use
adduser
command to create a new non-root, non-sudoers account on the machine. e.g.adduser james
-
Run
# apt update -y && apt upgrade -y && apt install nginx git fail2ban
- This will update, upgrade, and install the required software for this project, except for Hugo which will be installed through it’s package binary
-
Now use your favorite text editor to edit and then save
/etc/ssh/sshd_config
The lines being changed are:PermitRootLogin
should be commented out, this will disable ssh login as rootPort
should be uncommmented and the number after changed from 22 to any number of your choosing except for 80 or 443, which nginx will use for HTTP or HTTPS. Record what port you change it to so you know how to connect to the VPS later
-
# service sshd restart
-
Log in as your newly created non-root user, on your newly chosen port.
-
Now visit Hugo’s Github Release Page and find the one which matches the OS and architecture of your VPS. In Digital Ocean’s Ubuntu case, I used:
hugo_0.74.3_Linux-64bit.deb
Copy the link address of the file to your clipboard -
$ wget https://github.com/gohugoio/hugo/releases/download/v0.74.3/hugo_0.74.3_Linux-64bit.deb
(making sure to replace the URL after wget with the correct URL for your VPS system) -
Use
su
to become the root user -
Locate the downloaded file and install it. In my case it was a .deb file so the correct installation procedure is:
dpkg -i hugo_0.74.3_Linux-64bit.deb
. Once installed, userm
to delete the .deb binary as it is no longer needed
Now we will be following instructions also written [here on Hugo’s docs] (https://gohugo.io/getting-started/quick-start/) (skipping the install step)
10. # exit
to become the non-root user
-
$ hugo new site site_name
wheresite_name
is what you want to call your site folder and repository. -
$ cd site_name
to enter the directory -
$ hugo new posts/hello-world.md
to create a new Markdown (Hugo uses Goldmark) -
$ cd content
then$ cd posts
-
Use your favorite editor to set ‘draft’ to false in the header, and add some words below the header. Save and exit.
-
$ cd ../..
to get back to the hugo root directory -
$ git init
-
Locate a theme you like for Hugo from Hugo Themes This page uses the ‘smol’ theme. Once chosen, find the download link of the theme. It should look something like
https://github.com/budparr/gohugo-theme-ananke.git
-
$ git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke
making sure to replace the URL and theme path with your desired theme -
Use your favorite text editor to edit config.toml which is located in Hugo’s root directory. Add the line
theme = 'smol'
to specify to Hugo which theme to use as you can have multiple submodules at a time. -
$ hugo
should be executed in the hugo root directory. This will convert your hello-world.md file to HTML as well as create any additional nessecary linking files.
Hugo is nearly all set up now! HTML files have been created from .md files! What is left is the ‘CMS’ process through Github, and the webhosting process through Nginx.
-
$ su
then# cd /etc/nginx/site-available
-
# rm default
then# touch site_name
to create an empty file with your blogs chosen name -
Using your editor of choice, use this nginx conf file:
server {
listen 80 default_server;
listen [::]:80 default_server;
root /home/james/site_name;
index index.html;
server_name jamesdesmond.org wwww.jamesdesmond.org;
location / {
try_files $uri $uri/ =404;
}
}
Make sure to edit the root value and server_name value
-
# ln -s /etc/nginx/sites-available/site_name /etc/nginx/sites-enabled/site_name
to create a symbolic link for nginx -
# rm /etc/nginx/sites-enabled/default
to clean up -
# service nginx restart
to reload nginx and have it read your newly created configuration file -
# exit
to get back to non-root user
This conclude the Nginx portion! If your DNS records have propogated (Takes somewhere between an hour and a day) you should be able to visit your domain name through a web browser, and see your Hugo page! Here are some basic troubleshooting steps if you do not see it:
- In command prompt of any computer, execute
ping www.jamesdesmond.org
, replacing my domain with yours. If you see your IP address resolved through the domain name, then DNS has propogated. If not, wait and try pinging in a little bit. - If DNS is working, then the issue is either with Hugo or Nginx.
- Check in your Hugo root folder for a
public
folder. Go into it and look forindex.html
files. If you seexml
files and nothtml
files, that is a sign that Hugo was unable to generate your website for you. This could be an issue with your theme possibly requiring configuration, or it could be an issue with the version of Hugo you used. - Check your nginx logs, especially if you are getting HTTP response code errors when trying to access the site in a browser. A good command to do this is
# tail -f /var/log/nginx/error.log
- Check in your Hugo root folder for a
Now onto the Github section! Right now, you could go without Github entirely, but you would need to use ssh or scp to transfer markdown files to your VPS, then manually rerun $ hugo
to generate new HTML. That is tedious, and not very user friendly so lets fix that.
-
$ cd
to your Hugo root folder, as the non-root user. -
Using your favorite text editor, create a file called
.gitignore
with a single linepublic/
. This will stop git from tracking the public folder which contains Hugo’s output of HTML files. There is no need to track this folder as it would be analogous to storing .class files in git alongside .java files. -
$ git add -A
then$ git commit -m "initial commit"
-
$ ssh-keygen
to generate a keypair, and then$ cat ~/.ssh/id_rsa.pub
Copy the public key to your clipboard. -
Now this step is done on a webbrowser on github.com Create a new repo, making sure not to create a README or a gitignore file.
-
In your Github Account Settings, go to “SSH and GPG keys” Guide here Paste your newly created ssh public key here, and name it something like Blog VPS. This will allow your VPS to access git without a user/pass combination, instead just using your key to authenticate.
-
Go to your newly created repository and copy the SSH clone URL (NOT the HTTP one, which will require user/pass even with keys)
-
Back on the VPS (inside hugo root dir), execute
$ git remote add origin git@github.com:jamesdesmond/site_name.git
to connect your local hugo folder to your new repo. -
$ git push -u origin master
will push all your local changes to GitHub. Check the GitHub website to make sure it went successfully. -
$ mkdir .github
then$ cd .github
then$ mkdir workflows
then$ cd workflows
to create a github specific folder which stores information about Github Actions. -
Use your favorite editor to create a file called
main.yml
Here is an example file:
name: CI
on: [push]
jobs:
deploy:
if: github.ref == 'refs/heads/master'
runs-on: [ubuntu-latest]
steps:
- uses: actions/checkout@v1
- name: Push to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USERNAME }}
password: ${{ secrets.SERVER_PASSWORD }}
port: ${{ secrets.PORT }}
script: cd ${{ secrets.PROJECT_PATH }} && rm -rf public && git pull && hugo
This CI workflow will SSH into your VPS, navigate to your Hugo root directory, clear the existing HTML, pull new changes, and re-generate HTML for nginx to display. The script above does not use SSH keys, which is a better, safer option than passworded login. It is more simple to use passwords, but less secure. The ssh-action used here does support keys so if you know how to do it, you should. I will update this tutorial at a later date to cover how I implemented ssh keys.
-
Go to Github to your new repo settings page. On that page is a tab “Secrets” Here is where you will input the values that the script uses above. Make sure you name the secrets the same as used in the script, case sensitive.
-
On the VPS, in the hugo directory:
$ git add -A
then$ git commit -m "Added CI path"
then$ git push
Now, on Github, in your repo, on the Actions tab you should see this new commit. You can watch the deployment process, and read any errors that may occur.
That is it! Now the pipeline from Github to the VPS to the web is complete! To test: Use Github web editor to change the hello-world.md file you created earlier. Add a line to it, commit and push. Then visit your website from a web browser, you should see the change once the CI process completes.
My next post will go over how to set up client computers to update your blog without needing to either SSH into your VPS or the Github Web file editor.
If you see any issues or have suggestions for this guide please contact me at james@jamesdesmond.org and I appreciate you taking the time to visit my site!
Update:
I realized while deploying new content that the above steps would result in the website 404’ing until hugo has finished running. This results in a few seconds of downtime, that for my use case should not be a big deal. But it is easily avoided, so we will avoid it. Here are the modifications that must be made to reduce the chance of 404’s while hugo is building.
- Create a build.sh in your hugo root folder. I use the following buildscript (replacing my name with your username):
#!/bin/bash
# delete hugo public folder, best practice according to hugo docs
rm -rf public
# fetch and merge latest changes
git pull
# rebuild public folder in hugo folder
hugo
# delete current folder that nginx is serving, website will 404 until cp completes.
rm -rf /home/james/www
# copy newly generated public folder to the nginx server
cp -r public /home/james/www
- Modify the github action file from your hugo root folder: ./github/workflows/main.yml . Edit the
script
line to becd ${{ secrets.PROJECT_PATH }} && ./build.sh
- git add, commit, and push these changes. This should not break your site as nginx will still point to the hugo/public folder. The www folder will also be created in your home directory, this is what nginx will be made to point to.
- Modify
/etc/nginx/sites-available/site_name
as the root user so that theroot
line is:root /home/james/www
(replacing my name with your username) - Now refresh nginx with
# service nginx restart