How to use Issues on a Private Github repo as a Public Microblog

GitHub Issues on a private repository is the perfect microblogging platform. No one can comment or react to your posts but you still share your fleeting thoughts with the public world through your website.

The set up –

  • A Hugo-powered website
  • A remote virtual private server (DigitalOcean Droplet)
  • A private GitHub repository for tracking the website’s source code
  • A GitHub Issue thread on the same repository

The result –

Here’s how I connect the wires to make a private Issues thread publicly available on my VPS.

Giving the VPS access to the private GitHub repository

First, we need to give the VPS access to the GitHub repository so that it can –

  1. Pull the changes on a new commit
  2. Run hugo to build the site afresh
  3. Serve the freshly brewed website to the Internet (I am assuming this is already happening for you using NGINX/Apache)

Create a new SSH key pair on the VPS

1
2
3
4
5
# 1. replace the email with your GitHub email address
ssh-keygen -t ed25519 -C "your_email@example.com"
# 2. When asked for the file name, give a recognisable name like "hugo_website_git_deploy_bot"
> Enter a file in which to save the key (/Users/YOU/.ssh/id_ed25519): [Press enter]
# 3. Do NOT use a passphrase when prompted; or if you know what you're doing, make sure to persistently cache it in your session

Add the public key to the private GitHub repo

Your public key will be stored at /home/YOU/.ssh/hugo_website_git_deploy_bot.pub. It will look something like this:

ssh-ed25519 AAAA-S0M3OTH3RL3TT3R$ANDNUMB3RS <the_email_you_gave_when_generating_the_key>

Copy this key.

  1. Go to Settings on your GitHub repo and click on Deploy keys
  2. Add a new key. Give it a recognisable title like “VPS Site Deploy key”.
  3. Paste the public key.

Tell your SSH agent to default to this key when connecting to GitHub

Add these lines to the config file in ~/.ssh:

1
2
3
4
5
Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/hugo_website_git_deploy_bot
    IdentitiesOnly yes

Run nano ~/.ssh/config if the file doesn’t already exist. Make sure to save it.

Test that the connection works

Run ssh -T git@github.com and you’ll get a positive response from GitHub acknowledging your GitHub username and repo.

Using webhooks to let the VPS know of new commits

Next, we need to create a webhook in the GitHub repository settings. This webhook will ping a URL which will tell our server that a new commit has occurred in the repository.

Get an API up and running

I have an existing API set up using FastAPI. I added the following function in my API code to speak to the webhook:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Webhook: Receive event when GitHub repo has a new commit
@app.post("/api/path/new_commit")
async def deploy(request: Request):
    try:
        # Trigger deployment script
        subprocess.call(["./deploy.sh"])
        return {"message": "Deployment triggered successfully"}
    except Exception as e:
        # Handle any errors during subprocess execution
        raise HTTPException(status_code=500, detail=f"Error during deployment: {str(e)}")

Set up the webhook config on GitHub

Go to Settings > Webhooks in your GitHub repository.

Create a new webhook. Tell it to ping your API URL (https://yourapi.com/api/path/new_commit). Check the Just the push event. option because you don’t need any other details for this to work.

Now whenever there is a new commit on the GitHub repository, this API path receives a POST request from the GitHub servers which in turn triggers the deploy.sh script on your VPS.

Running a deploy script on the VPS

Before you create the deploy script, create a new folder on your VPS where you will clone the Git repo:

1
2
cd YOUR_GIT_REPO_FOLDER_PATH
git clone git@github.com:<YOUR_GITHUB_USERNAME>/<YOUR_GITHUB_REPO>.git

Create a new script in your API folder and call it deploy.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/bash
# Define the log file for errors and output
ERROR_LOG="error-deploy.log"
STD_LOG="logger.log"

# Print the environment for debugging
# echo "Environment: $(env)" >> $ERROR_LOG
# echo "PATH: $PATH" >> $ERROR_LOG

# Redirect stderr to the error log file
exec 2>>$ERROR_LOG

# Set the PATH explicitly
export PATH="<PATH>"

# Define the Git URL and Deploy path
GIT_REPO="git@github.com:<YOUR_GITHUB_USERNAME>/<YOUR_GITHUB_REPO>.git"
DEPLOY_DIR="<YOUR_GIT_REPO_FOLDER_PATH>"

# Navigate to the deploy directory
cd $DEPLOY_DIR

# Pull the latest changes and log the output
git pull $GIT_REPO >> $STD_LOG

# Build the Hugo site and log the output
hugo >> $STD_LOG

When interacting with your VPS in the Terminal, run echo $PATH to get your current path. Paste this in the script above.

After creating the script run sudo chmod +x deploy.sh to make the script executable.

Test the script by running it (./deploy.sh) and check the log files.

Test the deploy script using the webhook

Make a new commit to your GitHub repo. Go to Settings > Webhooks and check whether the webhook was successfully delivered or not.

If it was successful, the deploy script should have run on your VPS and the log files would have updated. And most importantly, the latest changes from your repo would have been pulled to your VPS and the site built afresh by hugo.

Celebrate

You’ve done well. Now you can simply update your website using the GitHub mobile app or a web browser. And your site will automatically be redeployed to the public Internet thanks to your server.

Using Issues as a Microblog

Create a dedicated microblog page on your Hugo website

I use stream.md on my site:

1
2
3
4
5
{{% stream id="2490897302" timestamp="2024-11-21T11:42:54Z" %}}
New deployment workflow. We've got webhooks, APIs, and deploy keys playing together to build the site on each commit.

Reusing the old GitHub Actions workflow to update the site when a new thote is added here.
{{% /stream %}}

The stream shortcode looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{{ $id := .Get "id" }}
{{ $timestamp := .Get "timestamp"  }}
{{ $unixtime := (time $timestamp).Unix }}
{{ $localtime := time $unixtime "Asia/Kolkata" }}

<div class="stream-card" style="margin-bottom: 2em" id="{{ $id }}" unix-timestamp="{{ $unixtime }}" timestamp="{{ $timestamp }}">
  <div class="stream-timestamp">
    <div class="stream-timestamp-date" title="Date and time is in your local timezone">{{ time.Format "Jan 2, 2006" $localtime }}</div>
    <div class="stream-timestamp-time" title="Date and time is in your local timezone">{{ time.Format "3:04 pm" $localtime }}</div>
  </div>
  <div class="stream-body">
    {{ .Inner }}
  </div>
</div>

If you want your microblog to show your posts in reverse chronological order, you need to follow an extra step. Reverse chronological is the better way because it allows you to paginate the posts in future.

Create a static file that contains your stream.md file’s frontmatter and any other opening notes. Mine is kept at static/stream-head:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
title: "Gurjot's Stream of Consciousness"
date: 2023-08-04T18:05:41+05:30
draft: false
keywords: []
description: "Thoughts the way they come."
tags: []
categories: []
author: "Gurjot."
hiddenFromHomePage: false
layout: "page"
---
Welcome to my scrapbook of thotes. It contains less-filtered thoughts and near real-time notes from things I am reading/doing.

<div id="stream">

Create a GitHub Action

Anytime you post a new comment on the GitHub Issue thread, this action will run a workflow that will update the stream.md file in your source code and make a new commit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# This GitHub workflow is triggered when a new comment is made on an issue.
# It pulls the comment body, timestamp and ID and updates the stream.md file in the content directory with it.
# The site is automatically rebuilt and deployed on the new commit using webhooks.

name: Update Stream

on:
  issue_comment:
    types: [created]

env:
  BODY: ${{ github.event.comment.body }}
  ID: ${{ github.event.comment.id }}
  TIMESTAMP: ${{ github.event.comment.created_at }}
  MATCHING_STRING: "<div id=\"stream\">"
  FILE_LOCATION: "./content/stream.md"
  STREAM_TEXT: '{{% stream id=\"${{ github.event.comment.id  }}\" timestamp=\"${{ github.event.comment.created_at }}\" %}} ${{ toJson(github.event.comment.body) }} {{% /stream %}}'
jobs:
  update-stream:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Update Stream
        run: |
          echo "Updating ./content/stream.md"
          cp ./static/file-template/stream-head.md ./content/temp.md
          echo -e "\n" >> ./content/temp.md
          echo "{{% stream id=\"$ID\" timestamp=\"$TIMESTAMP\" %}}" >> ./content/temp.md
          echo "$BODY" >> ./content/temp.md
          echo "{{% /stream %}}" >> ./content/temp.md
          echo -e "\n" >> ./content/temp.md
          sed -n '/<div id="stream">/{n; :a; N; $!ba; p;}' "./content/stream.md" >> "./content/temp.md"
          mv ./content/temp.md ./content/stream.md
          echo "Updated ./content/stream.md"          
      - name: Commit and Push changes
        run: |
          git config --global user.name <YOUR_NAME>
          git config --global user.email <YOUR_EMAIL>
          git add ./content/stream.md
          git commit -m "Update stream.md with comment $ID"
          git push origin master          

Test that it works

Create a new GitHub Issue in the repo and write the first fleeting thought in your microblog. Then go to the Actions tab and see that workflow runs successfully. Your repo should have a new commit and the stream.md file should have your comment’s body appended.

Make a new comment in the Issue thread and see that the there’s a new commit and the comment’s body is added to stream.md but this time it is above the previous comment.

If the Actions workflow is working correctly and the commits are happening, your server’s deploy script should be automatically updating the website.

Celebrate again

You’ve got a working microblog on your blog! How cool is that?!

Keep in mind:

  • Images probably won’t work in this setup. I dunno, I haven’t tested it.
  • The size of your stream page is directly proportional to the number of posts/comments you make in the Issue thread. If it’s getting heavy, consider implementing a pagination technique or creating a new issue and reconfiguring the Actions workflow.
  • Every single commit on your repo is going to run the deploy script on your server. If that is undesirable, consider updating the webhook to trigger only when commits are made to a certain branch (like deployment).

Cheers and happy hacking!