Roman's avatar

Hi, my name is Roman. I run a job board and make tutorials for BSD users. I'm fan of OpenBSD, POSIX-shell, and vi.

Twitter - RSS - Patreon - PayPal

"...Thanks for providing ssg! It is helping me to (slowly) put my #OpenBSD musings into words..."
fd0 (@fd0_nl)

"...thanx again for ssg! Starting to move gists to my site and it couldn't have been more simple."
(((Mischa 🕶 🐡 RCX))) (@mischapeters)

"I am loving @romanzolotarev's"
Doppelvizsla (@doppelvizsla)

"Thanks to @romanzolotarev's static site generator, I'm back on my "static site sausage machine for some extra pocket peanuts" kick again."
pr1ntf (@pr1ntf)

"It's really inspiring to see you give back much to the community. I appreciate your work - ssg, your how-to's for less familiar users, etc. I felt I should mention that to you 😀"
H3artbl33d (@h3artbl33d)

Static site generator with rsync and lowdown

ssg is a tiny POSIX-compliant shell script with few dependencies:

It generates Markdown articles to a static website.

  1. It copies the current directory to a temporary on in /tmp skipping .* and _*,

  2. renders all Markdown articles to HTML,

  3. generates RSS feed based on links from index.html,

  4. extracts the first <h1> tag from every article to generate a sitemap and use it as a page title,

  5. then wraps articles with a single HTML template,

  6. copies everything from the temporary directory to $DOCS/.

ssg 299 LoC in Vim with Terminus 8px. Enlarge, enhance, zoom!

Why not Jekyll or "$X"?

ssg is one hundred times smaller than Jekyll.

ssg and its dependencies are about 800KB combined. Compare that to 78MB of ruby with Jekyll and all the gems. So ssg can be installed in just few seconds on almost any Unix-like operating system.

Obviously, ssg is tailored for my needs, it has all features I need and only those I use.

Keeping ssg helps you to master your Unix-shell skills: awk, grep, sed, sh, cut, tr. As a web developer you work with lots of text: code and data. So you better master these wonderful tools.


100 pps. On modern computers ssg generates a hundred pages per second. Half of a time for markdown rendering and another half for wrapping articles into the template. I heard good static site generators work---twice as fast---at 200 pps, so there's lots of performance that can be gained. ;)


If you agree with the license, feel free to use this script, its HTML and CSS or/and re-write them for your needs.

Install dependencies and download ssg. For example, on OpenBSD: as root install entr, rsync, and lowdown.

# pkg_add entr rsync-3.1.3-iconv lowdown
quirks-2.414 signed on 2018-03-28T14:24:37Z
entr-4.0: ok
rsync-3.1.3-iconv: ok
lowdown-0.3.1: ok
The following new rcscripts were installed: /etc/rc.d/rsyncd
See rcctl(8) for details.

Then as a regular user change into ~/.bin directory.

$ cd ~/.bin
$ ftp
100% |****************************************|  7257       00:00
7257 bytes received in 0.00 seconds (2.99 MB/s)
$ chmod +x ssg

Let's customize your ssg setup.


To configure ssg you need to set two variables:

There are three more variables, but these are optional:

You can set all those variables in enviornment with export or env, but I recommend to create _ssg.conf file. For example, here is mine:

: "${DOCS:=/var/www/htdocs/}"
WEBSITE_TITLE='Roman Zolotarev'
RSS_AUTHOR=' (Roman Zolotarev)'
RSS_DESCRIPTION='Personal website'

Note: in this example if $DOCS is set, then ssg uses the original value, not the value from _ssg.conf.

Required files

There is only one file required:

  1. index.html or - home page

Example of

# Jack

- [About](/about.html "01 Aug 2016")

ssg renders to index.html and then generates the RSS feed based on first 20 links, if they have the following syntax (it only uses page URL and date from <a> tag):

<li><a href="/about.html" title="01 Aug 2016">About</a></li>

Optional files

  1. _header.html - header of every page
  2. _footer.html - and its footer
  3. _styles.css - styles, take mine and customize

If you use my CSS, don't forget to wrap the content of _header.html into <div class="header>...</div> and the content of _footer.html into <div class="footer>...</div>.

Reserved file names

There are also reserved filenames, these files are generated when you run ssg build. Don't use these names.

  1. rss.xml - reserved for RSS feed
  2. sitemap.xml - for the sitemap

Your first page

Let's create about.html with one header and some text about your site.

# About this site


ssg converts all .md article into .html and then uses content of the first <h1> tag as a page title.

Nota bene: Don't use ===== in titles.


Now we are ready to build. If your current source directory looks like this:

|-- .git/
|-- _footer.html
|-- _header.html
|-- _styles.css

After you run ssg (don't forget to set $DOCS):

$ ssg build
building /var/www/htdocs/www  2018-04-10T10:56:52+0000 4pp

You have your static website ready in /var/www/htdocs/www.

|-- about.html
|-- index.html
|-- rss.xml
`-- sitemap.xml


For OpenBSD I suggest to run httpd locally.

For macOS and Linux you can run:

$ cd /var/www/htdocs/www
$ python -m SimpleHTTPServer
Serving HTTP on port 8000...


To re-build pages on change run:

$ ssg watch
watching /home/jack/src/www
building /var/www/htdocs/www  2018-04-10T11:04:11+0000 4pp

entr(1) watches changes in *.html, *.md, *.css, *.txt files and runs ssg build on every file change.


If you'd like to delete all files in the destination directory during build, then run:

$ ssg build --clean
building /home/jack/src/www/docs --clean
2018-04-16T09:03:32+0000 4pp

The same option works for watching.

$ ssg watch --clean
watching /home/jack/src/www
building /home/jack/src/www/docs --clean
2018-04-16T09:04:25+0000 4pp

Deploy with rsync

If you don't have a public server yet, try Vultr. To deploy to remote server you can use rsync(1) like this:

$  rsync -avPc     /var/www/htdocs/www \

Or if you want to clean up the target directory on the remote server use:

$ rsync -avPc --delete-excluded \
                /var/www/htdocs/www \

Deploy with Git post-receive hook

Set up Git on your server.

As root install rsync and lowdown packages on that server.

# pkg_add rsync-3.1.3-iconv lowdown
quirks-2.414 signed on 2018-03-28T14:24:37Z
rsync-3.1.3-iconv: ok
lowdown-0.3.1: ok
The following new rcscripts were installed: /etc/rc.d/rsyncd
See rcctl(8) for details.

Then as git user download ssg on the server:

# cd /home/git
# su git
$ mkdir -p /home/git/bin
$ cd /home/git/bin
$ ftp
100% |****************************************|  7257       00:00
7257 bytes received in 0.00 seconds (2.99 MB/s)
$ chmod +x ssg

Then add these lines to /home/git/REPOSITORY.git/hooks/post-receive:

TMPDIR="$(mktemp -d)"
git archive --format=tar HEAD | (cd "$TMPDIR" && tar xf -)
cd "$TMPDIR"
DOCS='/var/www/htdocs/' \
/home/git/ssg build --clean

As root make sure git user owns $DOCS directory:

# chown -R git:git /var/www/htdocs/

Tested on OpenBSD 6.3

Thanks to Denis Borovikov for reading the draft of this, h3artbl33d, and Mischa Peters, and Tom Atkinson for testing ssg, Kristaps Dzonsons for lowdown(1) and Eric Radman for entr(1).