Recently I found myself wanting to rewrite The Irish Fighting Game Community’s homepage. Originally I had put it together primarily as a place to display our large number of discord servers but I found myself wishing for a solution with a blog posting element.
So I set out to create a Hugo site with the functionality of the original react project but the functionality of a wordpress page (without all the bloat). Hugo obviously came straight to mind, with it’s lovely themes and easy posting abilities. But how was I supposed to let other people contributing without having to teach normies how to use git?
Decap
Enter Decap CMS! Decap is an open source CMS solution that was spun out from Netlify (a paid service). This is great, but as with a lot of services like this, they have very little interest in telling you how to use their open source solution. Their documentation for creating your own auth handshake server essentially says “lol, dunno, you figure it out”.
Thankfully many kind souls have written open source servers. I ended up using this node based one, which works great for me as I already have pm2 running some processes on my server. So here’s the full run down on adding Decap to your Hugo project without Netlify.
Some notes
There’s some real “chicken and egg” circular headaches with this setup, where one part wants the other part to be set up first and vice versa. This was one of the most frustrating things about figuring out how to do this. So bear with me til the end!
You’ll probably want (or already have) a git based pipeline to build and deploy your site to something like github pages. I wont be covering that part. This is sort of the main reason you might want to use your own solution rather than Netlify. If that sounds like a headache, Netlify might be a good solution for you.
You’ll also need a server to host the handshake server on.
Finally, in case it isn’t clear, replace example.com with your domain throughout!
Github App
We’re going to start by creating a Github App which will authenticate our users and create PRs on the repo for them. Go to github’s settings > developer settings and create a New Github App.
- Give it a name
- Type
example.com
in Homepage URL - Add your callback URL as
https://auth.example.com/callback
- Tick the box that says
Request user authorization (OAuth) during installation
- Untick
Webhook active
, we wont need it - Under Permissions, add
Contents: Read and Write
andPull Requests: Read and Write
. - Generate a new Client Secret and take note of it
- Create the app and take note of both the App ID and the Client ID
Now we can install the app. Go to the install page and install it only to the your site repo. This is the advantage of using a Github app over Github OAuth, as we can choose a specific repo rather than giving it access to our whole account.
Note the installation ID in the URL after you install, you’ll need this! (the installation may 404 because it tries to call the callback server which we haven’t created yet. Don’t worry about that.)
Handshake Server
Next, we’re going to sort out our auth handshake server. This server will facilitate communication between Decap on your site and the Github App we just made, which will handle authentication and create Pull Requests for each post to merge into main.
We’ll be using this node based one. Follow the instructions to install it to a folder on your server. The .env file you’ll be using is very similar to the one in the readme
NODE_ENV=production
ORIGINS=example.com,auth.example.com,*.example.com
OAUTH_CLIENT_ID=f432a9casdff1e4b79c57 # The Client ID listed on the github app page
OAUTH_CLIENT_SECRET=pampadympapampadympapampadympa # The Client Secret you generated on the github app page
REDIRECT_URL=https://auth.example.com/callback
GIT_HOSTNAME=https://github.com # the actual site where the repo is hosted, not your site!
PORT=3000 # can change this
Run the server with npm start
which simply runs node app.js
(in case you need it for pm2).
Set up an A Record in your host’s DNS settings which points to auth.example.com. This is important because your DNS records are already pointing to github for the site itself.
Finally, we’re going to edit our nginx config to route traffic from auth.example.com to our handshake server.
server {
listen 80;
server_name auth.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Don’t forget to setup let’s encrypt for the new subdomain! After all that, all traffic to auth.example.com should be redirected to port 3000, where our handshake server will deal with everything.
Decap Setup
We’re getting there! Once all that’s sorted, we’ll create /static/admin
in our hugo project and place two files there. First, a simple index.html
file which hosts the frontend at example.com/admin
<!-- /static/admin/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex" />
<title>Content Manager</title>
</head>
<body>
<!-- Include the script that builds the page and powers Decap CMS -->
<script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
</body>
</html>
And then a config.yml
file for Decap. This file contains all the settings it wants to know, including what branch to push to, where your repo lives and where to send auth requests. Don’t worry too much about collections for now.
# /static/admin/config.yml
backend:
name: github
branch: main # or master, depending on your default branch
repo: D4RKONION/example.com # Replace with your actual repo
base_url: https://auth.example.com # The address where your server will listen for requests from github
squash_merges: true
# GitHub App authentication
app_id: "1636334" # Replace with your App ID
installation_id: "76740093" # Replace with your Installation ID
# Enable editorial workflow for content review
cms_label_prefix: "decap-cms/"
# Enable editorial workflow (creates PRs instead of direct commits)
publish_mode: editorial_workflow
# Media files configuration
media_folder: "static/images/uploads"
public_folder: "/images/uploads"
# Collections define the structure of your content
collections:
- name: "posts"
label: "Posts"
folder: "content/posts"
path: "{{slug}}/index"
create: true
slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
editor:
preview: false # Disable preview pane for better performance
fields:
- {label: "Title", name: "title", widget: "string"}
- {label: "Date", name: "date", widget: "datetime", default: "{{now}}"}
- {label: "Draft", name: "draft", widget: "boolean", default: false}
- label: "Author"
name: "authors"
widget: "select"
multiple: true
required: true
options:
- {label: "D4RK ONION", value: "d4rk-onion"}
- {label: "SHIN-A", value: "shin-a"}
- label: "Featured Image"
name: "featured_image"
widget: "image"
min: 1
media_library:
config:
max_file_size: 1024000 # in bytes
- label: "Tags"
name: "tags"
widget: "select"
multiple: true
min: 1
options: ["News", "Accouncement", "Tournament", "Round Up", "Belfast", "Cork", "Dublin", "Galway", "Kerry", "Travel Blog", "Website Updates"]
- {label: "Summary/Lead line (Shown in links & at top of article)", name: "description", widget: "text", required: true}
- label: "Image Gallery"
name: "galleryImages"
widget: "list"
max: 10
fields:
- label: "Image"
name: "image"
widget: "image"
media_folder: "/static/images/uploads/posts/{{slug}}"
public_folder: "/images/uploads/posts/{{slug}}"
media_library:
allow_multiple: true
- {label: "Body", name: "body", widget: "markdown"}
Now when you navigate to example.com/admin you’ll be greeted with a Decap login and a “login via github” button. If you’ve set it up correctly, clicking that button will send a request to the github app, which will in turn callback your server and initiate a handshake between the two, logging you into Decap! Congrats! The hard part’s done.
Finally, you can setup your collections (in the above config file) which determines what widgets your users will see when they are creating a post. This part is well documented at least.
Pipeline/Workflow change
Only a small change required here, you’ll need to set up your workflow to build on pull requests, as this is how Decap creates posts.
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
types: [ closed ] # Only when PR is merged
Allowing others to use Decap
This is probably why you went through all this in the first place! Simply add them as a collaborator on github, then have them install your Github authentication app. That’s all there is to it, they should be able to login to decap just like you now. If you want to restrict them to only be able to create drafts (Pull Requests) which you approve, you can set up a ruleset in your github repo. I’d also suggest setting up a CODEOWENERS file to protect your project, just in case.
Summary
We’ve created a Decap setup using Github apps and our own auth server. It’s a bit of a faff, but hopefully this writeup eases the pain a bit. Good luck!