Fabula

Introduction

At its core, Fabula is a simple Bash script preprocessor and runner. It lets you run scripts locally and on remote servers. Fabula (latin for story) is inspired by Python's Fabric.

local echo "This runs on the local machine"
echo "This runs on the server"

If you place the above snippet in a file named echo.fab and configure a remote server in Fabula's configuration file (fabula.js):

export default {
  ssh: {
  	server: {
      hostname: '1.2.3.4',
      username: 'user',
      privateKey: '/path/to/key'
    }
  }
}

Executing fabula server echo will run the script on server (as specified under ssh in fabula.js), but every command preceded by local will run on the local machine.

Conversely, if you omit the server argument like below:

fabula echo

It'll run the script strictly in local mode, in which case it will fail if it finds any command that is not preceded by local. The point is to allow both context-hybrid scripts and strictly local ones.

To run on all available servers, use fabula all <task>.

Context

If you have a Fabula task that is bound to run on multiple servers and parts of the commands rely on information specific to each server, you can reference the current server settings via $server:

In fabula.js:

export default {
  ssh: {
    server1: {
      hostname: '1.2.3.4',
      customSetting: 'foo'
    },
    server2: {
      hostname: '1.2.3.4',
      customSetting: 'bar'
    }
  }
}

In task.fab:

echo <%= quote($server.customSetting) %>

Running fab all task will cause the correct command to run for each server. Note that quote() is a special function that quotes strings for Bash, and is provided automatically by Fabula.

Preprocessor

Fabula's compiler will respect Bash's semantics for most cases, but allows you to embed interpolated JavaScript code (<% %> and <%= %>) using lodash.template internally. Take for instance a fabula.js configuration file listing a series of files and contents:

export default {
  files: {
  	file1: 'Contents of file1',
  	file2: 'Contents of file2'
  }
}

You could write a Fabula script as follows:

<% for (const file in files) { %>
local echo <%= quote(files[file]) %> > <%= file %>
<% } %>

Fabula will first process all interpolated JavaScript and then run the resulting script.

Components

Concentrating options in a single file (fabula.js) makes sense sometimes, but might also create a mess if you have a lot of specific options pertaining to one specific task. Fabula lets you combine settings and commands in a single-file component, inspired by Vue. Here's what it looks like:

<fabula>
export default {
  files: {
  	file1: 'Contents of file1',
  	file2: 'Contents of file2'
  }
}
</fabula>

<commands>
<% for (const file in files) { %>
local echo <%= quote(files[file]) %> > <%= file %>
<% } %>
</commands>

See more about Fabula components in its dedicated section.

Motivation

Please refer to this introductory blog post.