Posting to Hugo Static Site From a Facebook Profile

February 5, 2018

Hugo is a static site generator, it’s very fast generator built on Go, it can convert markdown and org files into a beautiful blog, I use it for my blog.

My current setup is a Hugo repository that is served from GitHub pages, linked it to my domain emadelsaid.com, whenever I need to post a new post I use spacemacs and a package called easyhugo to create a new post, then a function called easy-hugo-github-deploy that regenerates the site, commit and deploy it, it’s all open source, you can check it out from this repository.

I needed to create a similar website for my wife, a blog where she writes some articles about fitness and exercising, but we don’t want her to open spacemacs or any other editor of course, it would be great if she can write the article on her Facebook profile and it gets magically published to the Hugo website.

The theory

  1. a Hugo blog source directory
  2. a webhook server script
    • it exposes one secret endpoint that accepts POST request
    • When hit with a Facebook post text, writes it to a new post file, and regenerates the website
  3. A web server to serve the public directory generated by Hugo
  4. IFTTT.com applet to POST to the webhook when a Facebook post is published with a hashtag

The implementation

Services

I use docker compose to serve 2 containers, one of them will have the HTTP server to serve the actual website, the other will run the webhook script.

the docker-compose.yml file looks like so:

version: '2'
services:
  refresher:
    build: .
    volumes:
      - /root/data/website/public:/website/public
      - /root/data/website/post:/website/content/post
    restart: always
    ports:
      - '127.0.0.1:9002:8080'
    environment:
      RACK_ENV: production
  web:
    image: "nginx:latest"
    depends_on:
      - refresher
    volumes:
      - /root/data/website/public:/usr/share/nginx/html
    restart: always
    ports:
      - '127.0.0.1:9001:80'

Two services are there:

Refresher, builds a docker file as follows:

FROM ruby:latest

RUN wget -q https://github.com/gohugoio/hugo/releases/download/v0.30/hugo_0.30_Linux-64bit.deb
RUN dpkg -i hugo_0.30_Linux-64bit.deb

COPY . /website_source
WORKDIR /website_source

RUN bundle
RUN hugo
CMD ./server

it links 2 volumes one of them is the posts directory where posts will be written, the other is the public directory generated by Hugo, it should be served from the second service.

the web service is an out-of-the-box nginx image, links the public directory generated from the first service to the nginx default HTML.

The webhook server

A ruby script that listens on a port, will wait for a specific path (secret path segment), with the article text as the POST body in plain text.

It will get the first post line and assume it’s the post title, and will create a post with that title and the rest of the post as the post content text.

The script is very simple, it’s as follows:

#!/usr/bin/env ruby
require 'rack'

secret_path = '/<secret-string-here>'

def write_post(post)
  lines = post.split("\n")
  title = lines[0]
  body = lines[1..-1].join("\n")
  post_body = <<-END_OF_POST
---
title: "#{title}"
date: #{Time.now}
---
#{body}
END_OF_POST

  File.write("content/post/#{title}.md", post_body)
end

def generate_site
  `hugo`
end

app = proc do |env|
  request = Rack::Request.new(env)
  if request.path != secret_path || !request.post?
    [
      301,
      {
        'Location' => 'http://www.<main-domain-here>.com',
        'Content-Type' => 'text/html'
      },
      ['Moved Permanently']
    ]
  else
    write_post(request.body.read)
    generate_site
    [200, { 'Content-Type' => 'text/plain' }, ['OK']]
  end
end

Rack::Handler::WEBrick.run app

The secret string can be generated with uuid-gen command, or rails secret command.

IFTTT.com

IFTTT has a trigger for Facebook, one of the triggers listens if you posted a text post with hashtag, you can use it as your trigger.

then it should use a webhook, the URL should be your refresher domain/secret-path, the method should be POST, and the request body should be plain/text with the post body without hashtag, and you can use any hashtag you want for IFTTT to watch.

Conclusion

Now when you deploy that setup to your VPS server, linking your main HTTP proxy to both these services, I recommend a separate subdomain for your refresher service. and the www and naked domain to your web server.

The following is what will happen to trigger the whole setup:

  1. Post a text status with the hashtag on your profile, first line is the post title, the rest is your article.
  2. IFTTT.com will be triggered, sending the post to the webhook
  3. Your webhook script will get the request body and writing it to a file, then executing Hugo to regenerate the HTML.
  4. The HTML is served with the nginx service.