1 - Hugo
Hugo is a Static Site Generator (SSG) that turns Markdown files into static web pages that can be deployed anywhere.
Like WordPress, you apply a ’theme’ to style your content. But rather than use a web-inteface to create content, you directly edit the content in markdown files. This lends itself well tomanaging the content as code and appeals to those who prefer editing text.
However, unlike other SSGs, you don’t have to be a front-end developer to get great results and you can jump in with a minimal investment of time.
1.1 - Hugo Install
Requirements
I use Debian in this example, but any apt-based distro will be similar.
Preparation
Enable and pin the Debian Backports and Testing repos so you can get recent versions of Hugo and needed tools.
–> Enable and Pin
Installation
Hugo requires git and go
# Assuming you have enable backports as per above
sudo apt install -t bullseye-backports git
sudo apt install -t bullseye-backports golang-go
For a recent version of Hugo you’ll need to go to the testing repo. The extended version is recommended by Hugo and it’s chosen by default.
# This pulls in a number of other required packages, so take a close look at the messages for any conflicts. It's normally fine, though.
sudo apt install -t testing hugo
In some cases, you can just install from the Debian package with a lot less effort. Take a look at latest and copy the URL into a wget.
https://github.com/gohugoio/hugo/releases/latest
wget https://github.com/gohugoio/hugo/releases/download/v0.124.1/hugo_extended_0.124.1_linux-amd64.deb
Configuration
A quick test right from the quickstart page to make sure everything works
hugo new site quickstart
cd quickstart
git init
git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke themes/ananke
echo "theme = 'ananke'" >> config.toml
hugo server
Open up a browser to http://localhost:1313/ and you you’ll see the default ananke-themed site.
Next Steps
The ananke theme you just deployed is nice, but a much better theme is Docsy. Go give that a try.
–> Deploy Docsy on Hugo
Links
1.2 - Docsy Install
Docsy is a good-looking Hugo theme that provides a landing page, blog, and a documentation sub-sites using bootstrap CSS.
The documentation site in particular let’s you turn a directory of text files into a documentation tree with relative ease. It even has a collapsible left nav bar. That is harder to find than you’d think.
Preparation
Docsy requires Hugo. Install that if you haven’t already. It also needs a few other things; postcss, postcss-cli, and autoprefixer from the Node.JS ecosystem. These should be installed in the project directory as version requirements change per theme.
mkdir some.site.org
cd some.site.org
sudo apt install -t testing nodejs npm
npm install -D autoprefixer
npm install -D postcss
npm install -D postcss-cli
Installation
Deploy Docsy as a Hugo module and pull in the example site so we have a skeleton to work with. We’re using git, but we’ll keep it local for now.
git clone https://github.com/google/docsy-example.git .
hugo server
Browse to http://localhost:1313 and you should see the demo “Goldydocs” site.
Now you can proceed to configure Docsy!
Updating
The Docsy theme gets regular updates. To incorporate those you only have to run this command. Do this now, actually, to get any theme updates the example site hasn’t incoporated yet.
cd /path/to/my-existing-site
hugo mod get -u github.com/google/docsy
Troubleshooting
hugo
Error: Error building site: POSTCSS: failed to transform “scss/main.css” (text/css)>: Error: Loading PostCSS Plugin failed: Cannot find module ‘autoprefixer’
And then when you try to install the missing module
The following packages have unmet dependencies:
nodejs : Conflicts: npm
npm : Depends: node-cacache but it is not going to be installed
You may have already have installed Node.JS. Skip trying to install it from the OS’s repo and see if npm works. Then proceed with postcss install and such.
1.3 - Docsy Config
Let’s change the basics of the site in the config.toml file. I put some quick sed commands here, but you can edit by hand as well. Of note is the Github integration. We prepoulate it here for future use, as it allows quick edits in your browser down the road.
SITE=some.site.org
GITHUBID=someUserID
sed -i "s/Goldydocs/$SITE/" config.toml
sed -i "s/The Docsy Authors/$SITE/" config.toml
sed -i "s/example.com/$SITE/" config.toml
sed -i "s/example.org/$SITE/" config.toml
sed -i "s/google\/docsy-example/$GITHUBID\/$SITE/" config.toml
sed -i "s/USERNAME\/REPOSITORY/$GITHUBID\/$SITE/" config.toml
sed -i "s/https:\/\/policies.google.com//" config.toml
sed -i "s/https:\/\/github.com\/google\/docsy/https:\/\/github.com\/$GITHUBID/" config.toml
sed -i "s/github_branch/#github_branch/" config.toml
If you don’t plan to translate your site into different languages, you can dispense with some of the extra languages as well.
# Delete the 20 or so lines starting at "lLanguage] and stopping at the "[markup]" section,
# including the english section.
vi config.tml
# Delete the folders from 'content/' as well, leaving 'en'
rm -rf content/fa content/no
You should also set a default meta description or the engine will put in in the bootstrap default and google will summarize all your pages with that
[params]
copyright = "some.site.org"
privacy_policy = "/privacy"
description = "My personal website to document what I know and how I did it"
Keep and eye on the site in your browser as you make changes. When you’re ready to start with the main part of adding content, take a look at the next section.
Docsy Operation
Notes
You can’t dispense with the en folder yet, as it breaks some github linking functionality you may want to take advantage of later
1.4 - Docsy Operate
This is a quick excerpt from the Docsy Content and Customization docs. Definitely spend time with those after reading the overview here.
Directory Layout
Content is, appropriately enough, in the content directory, and it’s subdirectories line up with the top-level navigation bar of the web site. About, Documentation, etc corresponds to content/about, content/docs and so on.
The directories and files you create will be the URL that you get with one important exception, filenames are converted to a ‘slug’, mimicking how index files work. For example, If you create the file docs/tech/mastadon.md the URL will be /docs/tech/mastadon/. This is for SEO (Search Engine Optimization).
The other thing you’ll see are _index.html files. In the example above, the URL /docs/tech/ has no content, as it’s a folder. But you can add a _index.md or .html to give it some. Avoid creating index.md or tech.md (a file that matches the name of a subdirectory). Either of those will block Hugo from generating content for any subdirectories.
The Landing Page and Top Nav Pages
The landing page itself is the content/_index.html file and the background is featured-background.jpg. The other top-nav pages are in the content folders with _index files. You may notice the special header variable “menu: main: weight: " and that is what flags that specific page as worth of being in the top menu. Removing that, or adding that (and a linkTitle) will change the top nav.
The Documentation Page and Left Nav Bar
One of the most important features of the Docsy template is the well designed documentation section that features a Section menu, or left nav bar. This menu is built automatically from the files you put in the docs folder, as long as you give them a title. (See Front Matter, below). They are ordered by date but you can add a weight to change that.
It doesn’t collapse by default and if you have a lot of files, you’ll want to enable that.
# Search and set in your config.toml
sidebar_menu_compact = true
Front Matter
The example files have a section at the top like this. It’s not strictly required, but you must have at least the title or they won’t show up in the left nav tree.
---
title: "Examples"
---
Page Content and Short Codes
In addition to normal markdown or html, you’ll see frequent use of ‘shortcodes’ that do things that normal markdown can’t. These are built in to Hugo and can be added by themes, and look like this;
{{% blocks/lead color="dark" %}}
Some Important Text
{{% /blocks/lead %}}
Diagrams
Docsy supports mermaid and a few other tools for creating illustrations from code, such as KaTeX, Mermaid, Diagrams.net, PlantUML, and MarkMap. Simply use a codeblock.
```mermaid
graph LR
one --> two
```
Generate the Website
Once you’re satisfied with what you’ve got, tell hugo to generate the static files and it will populate the folder we configured earlier
Publish the Web Site
Everything you need is in the public folder and all you need do is copy it to a web server. You can even use git, which I advise since we’re already using it to pull in and update the module.
–> Local Git Deployment
Bonus Points
If you have a large directory structure full of markdown files already, you can kick-start the process of adding frontmatter like this;
find . -type f | \
while read X
do
TITLE=$(basename ${X%.*})
FRONTMATTER=$(printf -- "---\ntitle = ${TITLE}\n---")
sed -i "1s/^/$FRONTMATTER\n/" "$X"
done
1.5 - Docsy Github
You may have noticed the links on the right like “Edit this page” that takes one to Github. Let’s set those up.
On Github
Go to github and create a new repository. Use the name of your side for the repo name, such as “some.site.org”. If you want to use something else, you can edit your config.toml file to adjust.
Locally
You man have noticed that Github suggested some next steps with a remote add using the name “origin”. Docsy is already using that, however, from when you cloned it. So we’ll have to pick a new name.
cd /path/to/my-existing-site
git remote add github https://github.com/yourID/some.site.org
Let’s change our default banch to “main” to match Github’s defaults.
Now we can add, commit and push it up to Github
git add --all
git commit -m "first commit of new site"
git push github
You’ll notice something interesting when you go back to look at Github; all the contributers on the right. That’s because you’re dealing with a clone of Docsy and you can still pull in updates and changes from original project.
It may have been better to clone it via github
2 - Grav
Grav is considered a CMS and is my first choice for anything that should alow direct page edits to update content. It also makes a pretty good wiki if combining that with flat files for your content is important.
Note: This content is circa 2018 so take it with a gain of salt for config details.
Prerequisites
- DNS Name
- Nginx
- PHP
- Certbot
DNS Name
This has mostly to do with your registrar and hosting. Suffice to say, if you don’t have a domain name you won’t be able to complete the certificate steps and have meaningful SSL. Even if you don’t care about SSL, a DNS name is pretty useful.
Nginx
We’re installing just the light version as we need only the minimal set of modules.
sudo apt install nginx-light
PHP
A minimal install of Debian 9 does not include PHP, nor would it be the right version if it did. You must install php 7.1 and the grav requirements. The docs there mention php-openssl and php-session, but these seem to be compiled in and enabled by default in the Debian packages. You also need to install php-fpm as there’s no php module like apache.
Note: not sure about php-zip. It may have been included by one of the other packs because I went back to add and it was already installed
sudo apt install php-common php-curl php-ctype php-dom php-gd php-json php-mbstring php-simplexml php-xml php-fpm
# possibly sudo apt install php-zip
Certbot
In this example we’re on Debian stable so we’re installing the stretch backports enabling us to install and use the current certbot, per their instruction.
sudo echo "deb http://deb.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/stretch-backports.list
sudo apt update;sudo apt upgrade
sudo apt-get install certbot python-certbot-nginx -t stretch-backports
Configuration
Let’s configure the firewall, nginx, certbot and file permissions.
Firewall Config
We only need two ports; 80 and 443. On ubuntu you can use ufw. On debian you can issue iptables commands such as:
sudo iptables -A INPUT -p tcp -m tcp --dport 80 -m conntract --ctstate NEW,ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p tcp -m tcp --dport 443 -m conntract --ctstate NEW,ESTABLISHED -j ACCEPT
Nginx config
Nginx comes with a somewhat verbose default config. Let’s clean that up, arrange for a virtual host specific site, and block any random scans. We can do that on the SSL site as well thanks to nginx’s support of SNI. We also need to add a rewrite so that short-pathing works and you don’t get 404s for all sub pages.
# Remove the default config files and content
sudo rm /etc/nginx/sites-available/*
sudo rm /etc/nginx/sites-enabled/*
sudo rm /var/www/html/*
# Make a directory for your grav deployment and the default site
sudo mkdir /var/www/html/default
sudo mkdir /var/www/html/cms.yourdomain.com
# Create a generic SSL key so we can inspect all requests[^7] and do some basic SSL testing
sudo mkdir /etc/nginx/ssl
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt
# Define a default site simply drops requests (return code 444) if they didn't request a specific hostname
sudo vi /etc/nginx/sites-available/default
server {
listen 80 default_server;
server_name _;
return 444;
}
server {
listen 443 ssl default_server;
server_name _;
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
return 444;
}
# Define the site that will host grav
sudo vi /etc/nginx/sites-available/cms.yourdomain.com
#
# Redirect requests for the site that are unencrypted
#
server {
listen 80;
server_name cms.yourdomain.com;
return 301 https://$host$request_uri;
}
#
# Site cms.yourdomain.com
#
server {
listen 443 ssl;
server_name cms.yourdomain.com;
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
root /var/www/html/cms.yourdomain.com;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html index.php;
location / {
# if file doesn't exist (and it shouldn't unless you
# put a file there), pass to the index.php for routing
try_files $uri $uri/ /index.php?$query_string;
}
# Some additional security settings from the grav example
location ~* /(\.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
location ~* /user/.*\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
location ~ /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess) { return 403; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
}
}
Now let’s do some basic testing to make sure it’s working
# Enable the sites, test for syntax errors then load the config in nginx
sudo ln -rs /etc/nginx/sites-available/default /etc/nginx/sites-enabled
sudo ln -rs /etc/nginx/sites-available/cms.yourdomain.com /etc/nginx/sites-enabled
sudo nginx -t
sudo systemctl reload nginx
# This tests that requests without the right server name are dropped
curl http://localhost
curl -k https://localhost
# This tests that the right server name does work and that non-ssl is redirected.
curl --header 'Host: cms.yourdomain.com' http://localhost
curl --header 'Host: cms.yourdomain.com' -k https://localhost
PHP Config
One enables php by including a ’location’ snippit in the nginx config file and allowing for index php files. We snuck that in above when we configured nginx. So let’s just create a php info file and make sure it’s working. As an added bonus the contents of that web page will tell you where your php.ini files is, if you do need to make any php config changes.
sudo vi /var/www/html/cms.yourdomain.com/info.php
<?php
phpinfo();
?>
curl --header 'Host: cms.yourdomain.com' -k https://localhost/info.php
sudo rm /var/www/html/cms.yourdomain.com/info.php
PHP Optimization
The Grav docs recommend some optional modules and state You should run a PHP opcache and usercache….
Opcache has replaced apcu and should be installed and enabled by default. Recreate the info.php from above and search for OPcache to verify
PECL-yaml is the simply the php native yaml. You can install it and test that’s it’s enabled by
php -i | grep yaml
sudo apt install php-yaml
php -i | grep yaml
In the advanced section they mention memcached and redis, but these are for distributed systems and are slower than OPcache
Cerbot Config
We’re using certbot to automate the installation and maintenance of free certificates from Let’s Encrypt. It will scan your config and ask which site you want to use. After it does it’s thing, it will ask you if you want to redirect. Choose no on that as you’ve already set that up.
When it’s done it will insert the appropriate stanza in your nginx config file and a cron job to renew the cert in your /etc/cron.d/ folder
sudo certbot --nginx
...
...
Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: cms.yourdomain.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
...
...
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 1
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://cms.yourdomain.com
You can verify this with curl by asking for the site without the insecure option
curl https://cms.yourdomain.com
File Permissions
Editing content means modifying files under the web-root. This is done by the web server itself (for on-line editing), syncing files via sftp and direct edits from the shell.
Managing this type of access is best done with group permissions and ACLs. Using simple file group ownership and umask breaks down when you have multiple ways in.
Luckily, there is already a group for this in debian:www-data. So lets add ourself to the group, own the directory to the group and set the existing and future permissions so the group can edit files.
sudo usermod -aG www-data allen
sudo chown www-data:www-data /var/www/html/cms.yourdomain.com
sudo apt install acl
sudo setfacl --recursive --modify "g:www-data:rwx,d:g:www-data:rwx" /var/www/html/cms.yourdomain.com
Grav Installation
Grav strongly recommends moving the extracted directory and not just copying the contents. I suspect this is so hidden .htaccess files and file permissions are kept, neither of which we need. But let’s go ahead and do as they ask, then change to be like we want.
sudo apt install unzip
cd ~
wget -O grav-admin.zip https://getgrav.org/download/core/grav-admin/latest
unzip grav-admin.zip
mv grav-admin /var/www/html/cms.yourdomain.com
sudo chown -R www-data:www-data /var/www/html/cms.yourdomain.com
sudo setfacl --recursive --modify "g:www-data:rwx,d:g:www-data:rwx" /var/www/html/cms.yourdomain.com
rm -rf grav-admin
Test it
You should be able to access the default page now at cms.yourdomain.com, and the admin panel at /admin. The admin panel will ask you set set a password and from there you’re free to move on to picking themes and adding content
Resources