Genesis is a Go package for building self-contained, self-extracting installers. It is intended as a configuration management utility for offline systems and devices, but it can be used for pretty much any system. Genesis can install, roll back, and report status. You can construct groups of tasks and define conditional dependencies.
Genesis is designed to configure a system from scratch. No initial setup is required. Go's ability to cross-compile static binaries makes it easy to generate installers for other platforms.
Most configuration management systems (chef, ansible, etc.) don't work well for devices because they make some assumptions that don't hold for these systems. These assumptions are:
- disposability
- preconfiguration
These assumptions hold true for cloud servers and virtual machines, which is the typical target of configuration management. For hardware devices and similar, these assumptions often fail.
Genesis is built with a few key design goals in mind.
Genesis tries to make as few assumptions about the target system as we can. This allows it to target more systems. It also means less manual a priori setup. Here are some assumptions Genesis tries to avoid:
- Network connectivity
- Specific OS
- Preinstalled software (python, ruby, bash, etc)
- Availability of command line utilities (grep, awk, etc).
A configuration management system should do more than configure a system. It should also describe the current status of the system and show how the system's current state differs from the desired state. This allows the user to make informed decisions about what to do next, or how to modify the installer.
The Genesis software should be able to undo changes it makes to the system. Obviously there are limits to this, and Genesis will never be perfect at this. However, it should make reasonable attempts to restore the system to its previous state.
Hardware systems are often delivered to a customer, and the customer gains complete control of the system. It is critical that non-expert users be able to update a system.
Gensis consists of two parts: a set of modules, and an installer package.
Each module performs a specific configuration task, such as: creating a directory,
inserting a line in a file, or creating a user. The official Genesis modules
are contained in the modules
directory. However, a module can live anywhere.
To create a custom module, it must only satisfy the Module
interface.
The second component is the installer. The installer handles all the complicated stuff of figuring out which tasks should run, and storing information about previous states.
Here is a very simple example showing how to build an installer.
package main
import (
"github.com/wx13/genesis/installer"
"github.com/wx13/genesis/modules"
)
func main() {
inst := installer.New()
defer inst.Done()
inst.AddTask(modules.Mkdir{Path: "/tmp/genesis_example"})
aptSection := installer.NewSection("Install some debian packages.")
pkgs := []string{"git", "gitk", "tig", "screen"}
for _, pkg := range pkgs {
aptSection.AddTask(modules.Apt{Name: pkg})
}
inst.Add(aptSection)
}
which produces this:
See the example
directory for more examples.
There are a few pieces here. First off, we import the installer package
and the modules package, and create a new installer instance. Notice that
we defer the inst.Done()
command. Here's why: when we add tasks to the
installer, those tasks don't get run right away. Which order they run in
could depend on circumstances, so they get stored up by the installer.
The inst.Done()
command actually runs the installation.
To add a task, we can simply run AddTask
with its argument being a module instance.
As you can see above, we can also create named sections, and add tasks to those sections.
There are two ways to build the installer: manually or with genesis's assistance. Both begin by building the exectable with:
go build [FILE]
or
GOOS=linux GOARCH=arm go build [FILE]
if you are cross-compiling.
The installer extracts zip data from the end of itself. So you can create the full installer by appending the zip data.
zip -r files.zip files
cat files.zip >> installer
zip -A installer
The last command fixes the zip file indexing to account for the executable prepended to the zip data.
There are a couple of issues with building manually. First off, you must make sure you zip up all the correct files with correct relative paths. If you have different versions of your installer (e.g. installer versus updater), you have to manually manage which files to zip for each. Finally, you have to remember to correct the zip file index or else your installer will fail.
Thankfully, genesis has a solution to this. To build the self-extracting installer from a binary, just run:
./installer build [list of dirs]
This will figure out which files are needed, look for them in the current directory, and create a self-contained installer at 'installer.x'. You can optionally specify a list of directories to look for files in (instead of the current directory).
This will fail for cross-compiled binaries, because you won't be able to execute the binary on the build system. Instead run:
go run installer.go -x installer build [list of dirs]
To run the installer, place it on the target system and execute it with one of the standard commands: "status", "install", or "remove". Use the "-h" flag to see the help screen. Each of the above commands has its own help screen as well.