As I have already mentioned in the introductory post, my website and its blog is not dynamic webpage powered by a traditional blogging software or any other kind of backend software. Instead, I decided to use the static site generator called Middleman.
Static site generators
Before I continue, I briefly want to outline how static site generators work and why continuous deployment is necessary (or at least very convenient) for such webpages.
This also means that the site needs to be newly deployed every time the content changes (e.g. a new blog post has been added). Especially with frequent website changes, manual deployment quickly gets annoying. It also means a big convenience drawback compared to blogging software where post can be published with a single click.
I use git for versioning my website, the repository is hosted on GitHub while the real website run on my Uberspace. The idea was to automatically build and deploy the Middleman app every time I push changes to GitHub as it would allow me to seamlessly integrate the deployment into my development workflow.
There are many great (often free) CI and CD services that allow setups just like I described above. However, involving a third party service for such a simple task appears overly complex to me. Fortunately, GitHub allows to setup web-hooks which automatically get called on certain events (like pushes). This enables me to clone, build and deploy the website directly on my server. The result is a neat Ruby script that runs a Sinatra webserver implementing a single endpoint.
require 'sinatra' require 'thread' SECRET = 'secret!' TARGET = './deploy' BRANCH = 'new' set :port, 1234 mutex = Mutex.new post '/' do raw_body = request.body.read body = JSON.parse(raw_body) verify_signature(raw_body) if body['ref'] == 'refs/heads/' + BRANCH Thread.new do mutex.synchronize do system 'git', 'clone', '-b', BRANCH, body['repository']['url'], 'build' Bundler.with_clean_env do system 'cd build && bundle install && npm install && bundle exec middleman build' end system 'cp', '-r', 'build/build', TARGET system 'rm -rf build' end end end 'done' end def verify_signature(payload_body) signature = 'sha1=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), SECRET, payload_body) return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE']) end
The deployment process consists of cloning the repo, installing middleman and all other dependencies, building the app and deploying it to the document root of the webserver. As all those steps take some time and, due to HTTP request timeout, cannot be processed directly in the Sinatra handler function. Therefore, the actual deployment is handled in a separate thread, a common mutex ensures that no more than one thread is active at a time.
For security reasons, we want to make sure to handle only request coming directly from GitHub (an attacker could easily trigger a high number of deployments by simply sending a massive flood of HTTP requests). In order to do so, GitHub offers signing the requests with an HMAC. The
verify_signature function compares the server-generated and the request signatures and if they do not match, the request is being terminated.
With this script running on my webserver, my website (which includes this blog) is automatically deployed as soon as I push changes to the corresponding repository. Maybe this helps you with implementing your own deployment process. Feel free to used the script as you like, I dedicate it to the public domain.