Posting to Hugo Static Site From a Facebook Profile
Tuesday 1 November 2022

This page is outdated. I moved to Xlog from Jekyll after trying Hugo

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
  1. A web server to serve the public directory generated by Hugo
  2. 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:

 1version: '2'
 2services:
 3  refresher:
 4    build: .
 5    volumes:
 6      - /root/data/website/public:/website/public
 7      - /root/data/website/post:/website/content/post
 8    restart: always
 9    ports:
10      - '127.0.0.1:9002:8080'
11    environment:
12      RACK_ENV: production
13  web:
14    image: "nginx:latest"
15    depends_on:
16      - refresher
17    volumes:
18      - /root/data/website/public:/usr/share/nginx/html
19    restart: always
20    ports:
21      - '127.0.0.1:9001:80'
22

Two services are there:

Refresher, builds a docker file as follows:

 1FROM ruby:latest
 2
 3RUN wget -q https://github.com/gohugoio/hugo/releases/download/v0.30/hugo_0.30_Linux-64bit.deb
 4RUN dpkg -i hugo_0.30_Linux-64bit.deb
 5
 6COPY . /website_source
 7WORKDIR /website_source
 8
 9RUN bundle
10RUN hugo
11CMD ./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:

 1#!/usr/bin/env ruby
 2require 'rack'
 3
 4secret_path = '/<secret-string-here>'
 5
 6def write_post(post)
 7  lines = post.split("\n")
 8  title = lines[0]
 9  body = lines[1..-1].join("\n")
10  post_body = <<-END_OF_POST
11---
12title: "#{title}"
13date: #{Time.now}
14---
15#{body}
16END_OF_POST
17
18  File.write("content/post/#{title}.md", post_body)
19end
20
21def generate_site
22  `hugo`
23end
24
25app = proc do |env|
26  request = Rack::Request.new(env)
27  if request.path != secret_path || !request.post?
28    [
29      301,
30      {
31        'Location' => 'http://www.<main-domain-here>.com',
32        'Content-Type' => 'text/html'
33      },
34      ['Moved Permanently']
35    ]
36  else
37    write_post(request.body.read)
38    generate_site
39    [200, { 'Content-Type' => 'text/plain' }, ['OK']]
40  end
41end
42
43Rack::Handler::WEBrick.run app
44

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.

Backlinks