☠️ mrbones ☠️

a bare-bones static site generator

introduction

mrbones is a bare-bones static site generator in the form of a single Bash script. Its goal is to be very fast and simple to use while allowing for some basic templating, to handle the needs of most basic static sites. This documentation is built using mrbones, and you can see its source code on the mrbones repo.

table of contents

  1. i. installation: how to install mrbones and get it working on your system
  2. ii. building a simple site: how to build a very simple site using only the very basic features of mrbones
  3. iii. using directives: how to use mrbones's directives to avoid repetition

i. installation

i.a. dependencies

The following dependencies must be installed on your system for mrbones to work:

If you wish to use the Makefile to install mrbones, then you will also need the following:

i.b. normal install

The suggested way of installing is via the Makefile. First, obtain a clone of the mrbones repo via Git:

$ git clone https://github.com/kokkonisd/mrbones.git
$ cd mrbones/

Then, you should probably check out a specific tag, associated to a stable version of mrbones. You can see all existing tags by running git tag. For instance, this is how you would check out the tag or version 0.3.0:

$ git checkout 0.3.0

Now you can install mrbones via the Makefile. This needs root privileges, so you will probably have to run it with sudo:

$ sudo make install  # you can optionally set DESTDIR=/path/to/install/dir

You can check to make sure that the installation succeeded by running the following command:

$ mrbones --version
mrbones 0.3.0 (build <ref>)

i.c. direct use from the repo

Since mrbones is a single Bash script, you can also simply use it by running it with Bash, either locally:

$ bash /path/to/mrbones/mrbones.sh --version

Or, you can even run it remotely from the repo URL (downloading it via curl for example):

$ bash <(curl https://raw.githubusercontent.com/kokkonisd/mrbones/main/mrbones.sh) --version

This last method is not recommended for stable use, though, as the API or behavior might change without you noticing, and because running Bash scripts downloaded from an endpoint you don't control is generally a bad idea.

ii. building a simple site

Let's get started by building a very simple site first, with only a simple index file:

$ mkdir my-site/
$ cd my-site/
$ echo "hello, world" > index.html

In order to build it, we simply invoke mrbones:

$ mrbones
[mrbones]  Setting up output directory '/home/plumtrie/my-site/_site'...
[mrbones]  Copying site content...
[mrbones]  Generating pages...
[mrbones]  Done! Site is ready at '/home/plumtrie/my-site/_site'.

mrbones takes one argument: the path to the source directory of the site. If no argument is provided, then the current directory is used by default.

If we look at _site, not much seems to have happened; mrbones simply copies our index.html file over.

$ ls _site/
index.html
$ cat _site/index.html
hello, world

Let's add some more files. For example, maybe our site has various assets, such as CSS files, or even static files like text or images:

$ mkdir assets/ css/
$ echo "hello" > assets/message.txt
$ echo "body { font-size: 16pt; }" > css/main.css

If we build the site again, we will observe that mrbones has simply moved all of our files to the _site directory, in their corresponding directories. This is because the site does not make use of any special features of mrbones for now. Let's change that by adding another page:

$ echo "today I made a site" > news.html
$ mrbones
[mrbones]  Setting up output directory '/home/plumtrie/my-site/_site'...
[mrbones]  Copying site content...
[mrbones]  Generating pages...
[mrbones]  Done! Site is ready at '/home/plumtrie/my-site/_site'.
$ ls _site
assets css index.html news news.html
$ ls _site/news
index.html

It seems that mrbones did not only copy over news.html, but also created a copy of it which it placed at news/index.html. In fact, we can see this information if we build the site in verbose mode:

$ mrbones --verbose
[mrbones]  Setting up output directory '/home/plumtrie/my-site/_site'...
[mrbones]    Removing '/home/plumtrie/my-site/_site/'...
[mrbones]    Creating '/home/plumtrie/my-site/_site/'...
[mrbones]  Copying site content...
[mrbones]  Generating pages...
[mrbones]    Generating page 'index.html'...
[mrbones]      Putting page in '_site/index.html'...
[mrbones]    Generating page 'news.html'...
[mrbones]      Putting page in '_site/news.html'...
[mrbones]      Creating page copy in '_site/news/index.html'...
[mrbones]  Done! Site is ready at '/home/plumtrie/my-site/_site'.

This is done automatically to allow endpoints to be more lenient when serving the site. That way, when a user visits /news (as opposed to /news.html, they will be served _site/news/index.html instead of getting a 404 error.

mrbones treats files ending in .html or .htm differently, as we will see in the directives section. It copies everything else over as-is, and completely ignores everything in the _templates/ directory.

iii. using directives

Sites often have quite a bit of repetition, which we would like to avoid (re)coding by hand. To this end, mrbones offers a variety of directives, which we'll explore below.

All mrbones directives start with a '@' character. If you wish to escape them—as we have to do in this very documentation!—then use whatever means the language of the file provides: &#64; or \@ should do the trick.

Sometimes we might want to move pages to different locations in the final site, without bothering to create a complex directory structure in the source code. One such example is blog posts:

$ echo "this is my first post" > 2025-08-27-first-post.html

Let's say we want this to end up at /2025/08/27/first-post.html. One way would be to create those directories manually, but we could also just tell mrbones where to place the page using a @permalink directive:

$ echo "@permalink /2025/08/27/first-post.html" > 2025-08-27-first-post.html
$ echo "this is my first post" >> 2025-08-27-first-post.html
$ mrbones --verbose
...
[mrbones]    Generating page '2025-08-27-first-post.html'...
[mrbones]      Resolving destination (permalink)...
[mrbones]      Putting page in '_site/2025/08/27/first-post.html'...
[mrbones]      Creating page copy in '_site/2025/08/27/first-post/index.html'...
...

The @permalink target must be an absolute path (i.e., starting with '/') and obviously is not allowed to escape the generated site directory.

The @permalink directive must be the only element on its line (no HTML code after the @permalink target but before the newline). If there are multiple @permalinks, only the first one will be taken into account.

If use of the @permalink directive makes two (or more) pages point to the same location, then the last page (alphabetically speaking) will overwrite all previous pages pointing to that location, since mrbones processes pages after sorting them in alphabetic order.

iii.b. the @include directive

Many sites have at least one repeating element, for example the footer. In order to avoid copying and pasting the same footer (plus needing to change multiple files if we ever modify it), we can use the @include directive.

Targets (i.e., files) of the @include directive should be placed in the _templates directory, which has special meaning to mrbones: all files inside this directory are not processed directly, but may be targets of directives.

There are no further restrictions regarding _templates/: you can create whatever hierarchy you want inside of it, so long as you provide the relative path from the root of _templates/ as a target to directives.

The @include directive must be the only element on its line (no HTML code after the @include target but before the newline). Multiple @includes per file are, of course, allowed.

$ mkdir _templates/
$ echo "this is the footer" > _templates/footer.html
$ echo "@include footer.html" >> index.html
$ mrbones --verbose
...
[mrbones]    Generating page 'index.html'...
[mrbones]      Handling `@include footer.html`...
[mrbones]      Putting page in '_site/index.html'...
...

You can also nest @includes arbitrarily, so long as you don't create a loop:

$ echo -e "this is include one\n@include two.html" > _templates/one.html
$ echo -e "this is include two\n@include three.html" > _templates/two.html
$ echo "this is include three" > _templates/three.html
$ echo "@include one.html" >> index.html
$ mrbones --verbose
...
[mrbones]    Generating page 'index.html'...
[mrbones]      Handling `@include footer.html`...
[mrbones]      Handling `@include one.html`...
[mrbones]      Handling `@include two.html`...
[mrbones]      Handling `@include three.html`...
[mrbones]      Putting page in '_site/index.html'...
...

Besides other @include directives, include target pages will be copied verbatim. For example, a @permalink directive inside an @include target will not work: it will be treated as normal text.

iii.c. the @use directive

The ability to include snippets is very useful, but sometimes what we need is more akin to a template. For example, essentially all pages on most sites look like this:

<!DOCTYPE html>
<html>
    <head>
        <!-- Some stuff here... -->
    </head>
    <body>
        <!-- Some stuff here... -->
    </body>
</html>

We can achieve such templating via the @use directive. First, let's define the template, in _templates/page.html:

<!DOCTYPE html>
<html>
    <head>
        @content.head
    </head>
    <body>
        @content.body
    </body>
</html>

The @content.head and @content.body instruct the template to fill in these spots with whatever the page using the template defines as head and body. For example, let's rewrite our index.html page to use the template:

@use page.html

@begin head
<title>hello, world</title>
@end head

@begin body
<p>this is my site</p>
@end body

If we build with mrbones, we can observe how the template is filled in:

$ mrbones
$ cat _site/index.html
<!DOCTYPE html>
<html>
    <head>
        <title>hello, world</title>
    </head>
    <body>
        <p>this is my site</p>
    </body>
</html>

The @use directive must be the only element on its line (no HTML code after the @use target but before the newline). If there are multiple @uses, only the first one will be taken into account.

You can name the various sections (i.e., what comes after @content.) whatever you like, but only using characters 'a-z', 'A-Z', '0-9', '_' and '-'.

You don't have to provide a matching @begin <SECTION> ... @end <SECTION> for every @content.<SECTION> if you don't want to. If you don't provide it, @content.<SECTION> will simply be removed in the final page.

Symmetrically, you may define @begin <SECTION> ... @end <SECTION> without a @content.<SECTION> in the template. It will simply be ignored.

Do not provide multiple implementations for a given section. For example, consider the following:

@begin main
one
@end main
@begin main
two
@end main

That code will produce the following main section:

one
@end main
@begin main
two

If you want an "empty implementation" for a given section, you can simply do:

@begin <SECTION>
@end <SECTION>
There needs to be at least one newline between @begin <SECTION> and @end <SECTION>.

You can also nest @uses arbitrarily, so long as you don't create a loop. Let's make a new template, _templates/basic.html:

@use page.html

@begin head
<title>@content.title | my site</title>
@end head

@begin body
<main>
    @content.main
</main>
@end body

And then rewrite index.html to use it:

@use basic.html

@begin title
home
@end title

@begin main
<p>this is my site</p>
@end main

Again, if we now build with mrbones we should see both templates being filled in:

$ mrbones
$ cat _site/index.html
<!DOCTYPE html>
<html>
    <head>
        <title>home | my site</title>
    </head>
    <body>
        <main>
    <p>this is my site</p>
</main>
    </body>
</html>

If you want to "propagate" content up the @use chain, you have to redefine sections. Consider the following example:

$ cat _templates/one.html
@use two.html
$ cat _templates/two.html
@content.main
$ cat index.html
@use one.html
@begin main
this is the main
@end main
$ mrbones
$ cat _site/index.html

If we want to propagate "this is the main", we have to redefine main in _templates/one.html:

$ cat _templates/one.html
@use two.html
@begin main
@content.main
@end main
$ cat _templates/two.html
@content.main
$ cat index.html
@use one.html
@begin main
this is the main
@end main
$ mrbones
$ cat _site/index.html
this is the main

Finally, we can of course add back our footer via the @include directive. We will modify _templates/page.html:

<!DOCTYPE html>
<html>
    <head>
        @content.head
    </head>
    <body>
        @content.body
        @include footer.html
    </body>
</html>

Again, we can see that it works:

$ mrbones
$ cat _site/index.html
<!DOCTYPE html>
<html>
    <head>
        <title>home | my site</title>
    </head>
    <body>
        <main>
    <p>this is my site</p>
</main>
        this is the footer
    </body>
</html>

You cannot use @permalink directives inside of @use targets.