config management essentials
Find a file
2025-12-25 21:59:31 +11:00
bin fig: set -E and fix backtrace in subshells 2025-12-25 21:59:31 +11:00
lib fig: set -E and fix backtrace in subshells 2025-12-25 21:59:31 +11:00
fig.png add readme 2025-11-25 20:00:36 +11:00
README.md add readme 2025-11-25 20:00:36 +11:00

Fig icon Fig

Fig is configuration management for small infrastructure. It's written in Bash and aims to provide just enough structure to make it pleasant to configure a system with shell scripts.

The central concept in Fig is a module. Fig modules can specify a list of packages to install, and can provide an apply function which is called on every configured module during a fig apply. Fig provides several idempotent helper functions allowing placing of files, template generation, and more.

A module is a directory under modules/ which contains a module.sh file. Module names can contain slashes for namespacing, and in this case they appear as subdirectories somewhere under modules/. An example module.sh might look like:

packages=(
    nginx
)

apply() {
    ensure-file /etc/nginx/nginx.conf
    ensure-service nginx
}

When applied, this example module will install the nginx package, copy the file files/etc/nginx/nginx.conf relative to the module root to /etc/nginx/nginx.conf in the filesystem, and then enable and start the nginx systemd service.

The collection of modules to be applied to a system is defined in index.sh in the root of your configuration repository. index.sh defines a modules array, and can populate this array however makes sense. For example, you might use a system's hostname to determine which modules are applied:

# apply the base module to all nodes
modules=(
    base
)

# apply node-specific modules based on hostname
case "$(hostname -s)" in
web-server)
    modules+=(nginx)
    ;;
dns-server)
    modules+=(bind)
    ;;
esac

Getting started

  1. Create a new Git repository for your configuration and cd into it:

    $ git init my-infra
    $ cd my-infra
    
  2. Add the Fig repository as a submodule at fig in the root of the repository:

    $ git submodule add https://github.com/haileys/fig fig
    
  3. Create the necessary file structure:

    $ touch index.sh # the entrypoint to your fig configuration
    $ mkdir modules # fig modules go here
    

You're good to go! Use fig apply to apply your Fig config to the local machine, or fig apply-ssh to apply to a remote machine over SSH.

Lifecycle of fig apply

  1. Load index.sh to determine modules to apply on this system

  2. Collect all bootstrap_packages from each configured module and install using system package manager

  3. Call before-packages function for each configured module

  4. Collect all packages from each configured module and install using system package manager

  5. Call apply function for each configured module

Repository layout

/                               # repo root
    index.sh                    # fig entrypoint, defines `modules` array
    modules/
        foo/
            module.sh           # module definition for "foo"
            files/
                etc/
                    foo.txt     # placed in /etc/foo.txt by `ensure-file`
            gen/
                etc/
                    magic.txt   # executable script which generates /etc/magic.txt called by `ensure-gen`
            bar/
                module.sh       # nested module definition for "foo/bar"
                                # NOTE: has no implicit relationship to "foo" module

Full module structure

# Packages listed in the `packages` array are installed using the system package manager.
packages=()

# The `apply` function is called after installing packages.
#
# Example use case: place config files and enable systemd services.
apply() {
    true
}

# The `before-packages` function is called before installing packages.
#
# Example use case: setup third-party apt sources.
before-packages() {
    true
}

# Packages listed in `bootstrap_packages` are installed before calling `before-packages`.
# Use sparingly, because there's no hook that comes before this one.
#
# Example use case: install gnupg for verifying third-party apt signatures, or install
# `ca-certificates` for accessing apt repos over https.
bootstrap_packages=()

Ensure helpers

ensure-service

usage: ensure-service service

Enables and starts systemd unit service.

ensure-file

usage: ensure-file path [source=source] [chown=chown] [chmod=chmod]

Places a file at path in the filesystem, sourcing from files/path by default.

  • source - path to source file, relative to module root

  • chown - arguments to chown(1), eg. user, user:group, :group

  • chmod - arguments to chmod(1), eg. +w, u+wa+x, 0755

ensure-dir

usage: ensure-dir path [chown=chown] [chmod=chmod]

Ensures path in the filesystem is a directory.

  • chown - arguments to chown(1), eg. user, user:group, :group

  • chmod - arguments to chmod(1), eg. +w, u+wa+x, 0755

ensure-gen

usage: ensure-gen path [template=template] [chown=chown] [chmod=chmod]

Executes a script to generate the contents of path in the filesystem, executing gen/path by default.

  • template - path to executable template file, relative to module root

  • chown - arguments to chown(1), eg. user, user:group, :group

  • chmod - arguments to chmod(1), eg. +w, u+wa+x, 0755

ensure-url

usage: ensure-url path url sha256=sha256 [chown=chown] [chmod=chmod]

Downloads file from url, validates checksum according to sha256, and places in filesystem at path.

  • sha256 - sha256 checksum for validation. mandatory

  • chown - arguments to chown(1), eg. user, user:group, :group

  • chmod - arguments to chmod(1), eg. +w, u+wa+x, 0755

ensure-user

usage: ensure-user user [groups=groups] [usergroup=1] [homedir=homedir] [createhome=1] [shell=shell] [uid=uid] [gid=gid]

Creates user with name user if user does not already exist.

⚠️ Does not make any changes if user with name user already exists.

  • groups - comma separated list of supplementary groups for new user, eg. sudo, sudo,wheel

  • usergroup - create group with same name as user

  • homedir - home directory of the new user

  • createhome - create the user's home directory

  • shell - login shell for new user

  • uid - uid for new user

  • gid - gid for new user

ensure-group

usage: ensure-group group [gid=gid]

Creates group with name group if group does not already exist.

⚠️ Does not make any changes if group with name group already exists.

  • gid - gid for new group

ensure-ssh-keygen

usage: ensure-ssh-keygen user [type=type] [file=file]

Ensures SSH key exists for user at file.

  • type - -t parameter to ssh-keygen(1), defaults to ed25519

  • file - file path for SSH key, defaults to ~user/.ssh/id_type


Fig icon in fig.png designed by Freepik, sourced from https://www.freepik.com/icon/fig_13324385