Jayant Godse

Hosting My Own Sh*t Part 2

Aug 22, 2026

Getting Annoyed

Finally!

I ran into the reason why CI/CD actually exists. I finally had a service that I wanted to continually deploy! Until the events of today, I was manually pulling changes in my server then rebuilding my website container. I knew that there was a better way of doing this, but it would take a bit of time.

Today I had some, so I got to work. Reading up on GitHub’s docs, I realised that you can send a POST request to an endpoint anytime a push event happens on your repository. The question was then “How do I listen for this?”

Well, the answer was to make a web server that listens.

Since I’m here, I might as well use this chance to write the thing in Go. Since it’s known for being good at this kind of stuff anyways.

An Overview of What My Solution Is Doing

  • Listen for connections on jayantgodse.com/webhook (NGINX routes these to the internal go server) (Also please don’t DDOS me, that would be annoying.)
    • Had to expose localhost to the NGINX container <- I’m running the Docker service on the host machine. Ceebs containerising it.
    • Route /webhook to host.docker.internal:8090/webhook
  • The server does some auth:
    • Check that it’s a POST request
    • Check that it’s got the correct header (“X-Hub-Signature-256”).
    • Check that my secret matches with the one being sent.
  • Run a script to rebuild my site container.

Mandatory Praise to Golang

I’ve been trying to find an exclude to use Golang for a hot minute now. And this seemed to be a perfect use case.

It seems to do what it says it does. It was easy to write. I basically didn’t have to learn any new syntax. It writes (almost) just like Python, with some C concepts, mainly pointers, sprinkled in there.

The language came with everything I needed for this out of the box. There was a built-in web-server. HTTP handling, JSON decoding, IO handling, Logging, and cryptography algorithms all bundled.

Compilations were fast. Almost instantaneous. Sure the project was small, but there was genuinely no downtime between me typing go run and the server spinning up

Did I mention the concurrency was literally just typing the word go in front of my function? I understand it’s the whole selling point of the language, but it was still cool to see that being the only thing I had to do.

I like it :)

Okay I will confess the way that struct fields are made public by capitalising them is stupid. Should’ve just had a public keyword.

Where to from Here?

The one other thing missing from my setup is reporting to myself when the builds fail. GitHub would do this for me with nice emails. So I think I will have to do the same. Stay tuned for a part 3 where I learn to send emails from my server and hook them into this building service.


Bonus: How the Encryption Works for Dummies (Myself)

I have saved my secret to both this server and GitHub. Funnily enough I think it’s just a sha256 scramble of a no-no word.

Whenever GitHub pings me, it sends a header and a payload. To prove itself as GitHub, it takes the payload and the shared secret that it has and runs them through the hmac256 algorithm. This algorithm takes any 2 set of bytes and produces a single 256 character string. It is guaranteed to be deterministic and must have very little collisions.

On my end I do the same.

hashObj := hmac.New(sha256.New, []byte(secret))

This is telling Go to make a new [H]ash-based [M]essage [A]uthentication [C]ode object. I then add the bytes of the body

hashObj.Write(bodyBytes)

This I then convert back to a string. And compare with the secret that GitHub has sent me.

hmac.Equal([]byte(signature), []byte(expected))

It seems to be recommended to use the hmac.Equal() since it’s const time compare.

If they both match, that means whoever is sending me the request knows my key :)