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
-
i. installation: how to install
mrbones
and get it working on your system -
ii. building a simple site: how to build a very
simple site using only the very basic features of
mrbones
-
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:
- GNU Bash, version 4.4 or newer
-
realpath
,sort
(from GNU coreutils) -
find
(from GNU findutils)
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: @
or \@
should do the
trick.
iii.a. the @permalink
directive
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
@permalink
s, 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
@include
s 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 @include
s 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
@use
s, 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 @use
s 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.