Skip to content

Sofin, the software installer

Daniel (dmilith) Dettlaff edited this page Mar 7, 2014 · 1 revision

If you ever tried building software for your server without getting mad and frustrated, without approaching endless problems with software requirements, user demands and that entire mess, you should probably know that there’s a solution available that addresses these problems. It’s called Sofin.

What you will learn

  • How is software built nowadays in the Open Source world.
  • Why you should avoid the way of building software that’s currently considered as the standard, and look something that’s designed better.
  • How Sofin cures your headaches—the details not mentioned in the project README file.

What you should know

  • You should know how to building software from source.
  • You should know what a shared library is and have some basic understanding how compiler and linker work.
  • If you want to make your own software definitions, you should have (at least) basic knowledge on shell scripting.

How things look like today

Basically, every *NIX compliant server system that’s currently used, has its own way of approaching software. I’ll explain it by example:

Let’s assume you have clean installation of favorite OS base. It’s FreeBSD 9.1 in my case—it’s my major production server platform of choice. There’s /usr/bin, /usr/sbin and /bin, where all base system software executables reside. In short—every piece of software is put into a “common bag” of binaries (bin, sbin) and libraries (lib, lib32, lib64). If you want to use software which isn’t provided by your base system, say Ruby 2.0, you’ll need to install it manually. I’ll skip the part about installing software from prebuilt binary packages, mostly because you won’t find my example software in binary builds, and you won’t find binary builds for “your software,” especially if you’re dealing with custom or old server configurations.

So you end up building it from source manually or through ports. Each additional software built from source will go into yet another “common bag” in /usr/local/ by default.

So, what’s wrong with this approach?

The thing is—when you’re creating a server, you usually want it to be used by your users, right? A user is an unprivileged entity which only wants to run some software.

Users demand that you build reliable software for them to use. But you can’t give them that with FHS approach to software. I’ll explain by another real life example:

Let’s assume that, after Ruby, you’ve installed also PostgreSQL, MySQL, Redis, Imagemagick, Cairo and a few other pieces of software in your /usr/local/ bag. You end up with tons of common libraries (that you usually know nothing about), all put in just one place.

It should be fine, right? Not even close. Try to uninstall some of them now. No make uninstall available. What now?

But the real fun begins when you want to do a security update for one of your libraries that’s commonly used (and shared) by some software. How many times did you do an upgrade of ports binaries/libraries and then ended up with part of software broken? (For *BSD there’s a UPDATING file in ports with information about how to do software updates, but usually it gives you nothing, and the problem remains, which, in short, depends on which ports you installed, in what order and so on.)

I had enough after a couple of times of reinstalling all my software because of one library change. My system became a mess and I lost control of my software and their dependencies. But this is only one side of the coin. There’s more: for example, what will you do if you have two applications that requires different version of the same library to be linked with?

The solution is to do some kind of a hack—usually by building prefixed library, prefixed binaries, or by giving options manually to a build script and so on. After a couple hacks of that kind, you still end up with a mess on your production machine which—I assume—is simply undesired.

Possibly the worst of all—you need to remember what did you do to make it work. There’s also the third side of the coin, user privileges.

Ruby is a great example of such issue. If your users want to install their gems (doesn’t matter if they use Bundler or not), they’ll require root priviledges to write their gems into default /usr/local/lib/directory. Those gems will be common for all users but not their own. This is where all the hacky solutions like rbenv and rvm are “shining?” Not at all. They’re just ugly hacks, created on top of bad software architecture. Believe it or not, these problems are just the tip of an iceberg.

How Sofin was born

Some say that the best software is born thanks to developer’s rage. Sofin was one of those projects that started spontaneously after I just gave up hacking one of my servers.

I wanted my software to be reliable, without shared dependencies, bundled, owned by user, yet fully customizable. I wanted it to work on all POSIX-compliant systems, and to be designed with simplicity in mind (KISS rule). I also wanted it to be BSD-licensed because I’ve had enough fighting with GPL/GNU stuff. And well… Sofin was born.

Sofin celebrated its second birthday in May 2013. Currently, there’re more than two hundred definitions of server software available. You may think that 200 is nothing when compared to 20,000 of ports. Yes, but how many of these ports definitions actually work? And how many of these are just X11 utilities? Which are just dead, obsolete or simply broken, and which aren’t maintained anymore?

All Sofin software definitions are tested and used on FreeBSD 9.1, Debian 6.0, and Mac OS X 10.8. There’s a policy that definition isn’t accepted in Sofin’s repository unless it builds and installs correctly on all supported systems.

Sofin in depth

Differences from FHS standard in real life

In late 90’s, we had small disks. I used to work on machine with 840 MiB of disk space. This was probably the major reason of FHS rule about keeping software in common prefixes: /usr and /usr/local—simply to save space. Each software depending on library X could just link to it in one common place. It was sufficient for simple software, simple solutions.

But the world is heading forward. Today, I have at least 1 TiB of disk space on each server that run software with tons of features and dependencies.

The main idea of software bundling in Sofin was to get rid of that system wide “shared nature” of binaries and libraries. I wanted to stop using /usr/local (Sofin will warn when this folder will exist on production server) and never touch base system files in /usr. Each Sofin package has own “root” directory (similar to that in /usr). By default, it’s ~/Apps/YourApp/ where all software, libraries and dependencies reside.

Here’s why I mentioned disk space in the first place: each piece of software has own copy of dependencies, hence they use more disk space. Usually it’s up to 3 times more space than standard software. Not an issue these days, right? For some people that are more familiar with BSD systems family, a software bundle might look similar to PBI packages from PC-BSD, but PBI bundles are far from simple. They’re too complicated to define something as simple as software bundle.

The second difference is that PBI aren’t designed to be server software at all, they’re just an imitation of an *.app bundle used by Apple.

One more difference from FHS approach is an additional exports directory in root of each Sofin bundle. It keeps closed dependency model and is designed to provide access only to binaries that a user requires. The very imporant thing to know is that Sofin’s shell setup, won’t ever set default $PATH access to ~/Apps/YourApp/bin/ nor ~/Apps/YourApp/sbin/, but only to ~/Apps/YourApp/exports/. If you want to have right away access to a command from YourApp bundle, you’ll need to add it to APP_EXPORTS in its definition, or manually run sofin export your-command yourapp after installation. That’s it.

Let’s take a close look to some more features.

Design assumptions

Sofin is written in probably most primitive of all shell languages, the legacy sh. The surprising fact is that, this simple language is probably one of most powerful utilities to write software like Sofin. All configuration and every definition is also written in sh, hence you have the built-in scripting language into definitions for free. It gives you something that’s very important: flexibility.

Here comes one of Sofin’s major features: system integration with minimum interaction. By default Sofin works in two modes: for user or for super user. When building software for regular users, it’s put into ~/Apps/YourApp/. When building it as super user, it goes into /Software/YourApp. For regular user, ~/.profile file is generated after each software installation/uninstallation. For super user, Sofin modifies /etc/zshenv, /etc/bashrc and /etc/profile files once (when installing Sofin) to support any sh-compliant shells, and then regenerates /etc/profile_sofin (the equivalent of user’s ~/.profile).

The idea behind /Software directory was to give ability of installing “base system extension software” without interacting with /usr/bin and /usr/lib. By default, software from /Software directory is common for all users (for example Ccache, Clang, Git and Zsh are recommended to be installed in /Software).

When building software as user, all software belongs to that particular user. This is very important that nobody in the system has access to your applications directory, and, after a build, you can freely copy the entire app bundle to another machine and it will just work. The requirements of each software bundle comes from it own bundle and/or base system. No external dependencies are allowed. (At the time of writing this article, I’m working on binary builds for Sofin which will give ability to skip software build process, with drastic time savings, with additional ability to move software between users.)

Next major feature is flat dependencies. Each software definition has own, optional list of dependencies that will be installed in given order before the very software. Sofin automatically detects library dependencies and builds destination software with them.

I won’t mention all Sofin features. If you’re interested in all of them in detail, I already mentioned them on the project page at GitHub.

Installation and deployment: how Sofin affects environment

Installing Sofin in new system might be considered a non straight forward task. You start with installing sofin itself (using detailed information obtained from the project page). Remember that the Sofin installation process is slightly different for each system family. The thing I didn’t mention in installation process on the project page, is the deployment process of Sofin itself. After the installation, it’s very important to do sofin install base as super user. It will install base software like Clang and Ccache which is widely used by Sofin to built software later. If Sofin won't find /Software/Clang/exports/clang (and clang++) it will require gcc (and g++) installed in the system. Please consider gcc/ g++ as slow and faulty - try avoiding these! Depending on your system, GNU compiler might fail to build certain software, and it’s not widely tested. (Note that there are definitions, with defined requirement on gcc, hence it’s still required to be installed). By default, Sofin will try to use Clang to build each software. If it also finds Ccache, it will give you a speed boost, when you’re compiling similar software dependencies between installed software. It also supports parallel builds by default (amount of parallel tasks equals to the amount of CPU cores available on your server). If the given software doesn’t support one of the default approaches, they might be turned off explicitly in the definition file.

The next important issue is a shell implementation itself.

Currently, the best supported shell is Zsh (it’s installed with “base” by default), so it’s recommended to use a shell that reads standard shell initialization scripts on launch (/etc/zshenv for Zsh). If something is not working, it’s usually caused by inproper shell configuration, or conflicts caused by some third party software. Sofin demands (yes, it’s not a policy but a requirement) from sysadmin to have clean system/ shell/ software configuration.

Next thing, which is very important for new users is to invoke sofin reload (once every first user login).

It will (re)generate ~/.profile file and cause current shell to reload it automatically. After that, you’re free to install any defined software. After each installation, a shell reload is done automatically for current shell, and software is available to run immediately. If you’re running multiple shell environments at once, you may require a sofin reload after the installation of new software.

Available definition features

Software definitions are based on a default definition in “defaults.def”. This file contains all available settings that might be set for each definition. I'll mention only some of them, that might not be as self explainable as the rest:

  • APP_NAME - name of the software. It’s a special value, used to name the software bundle directory.
  • APP_HTTP_PATH - the address of the definition source archive
  • FORCE_GNU_COMPILER - an option to tell Sofin that the given definition can’t be built using Clang compiler.
  • APP_NO_CCACHE - set to anything but “”, if your software doesn’t support building with Ccache.
  • DISABLE_ON - an option that’s required for software that isn’t working on some systems. It’s a space separated list of system names (uname) on which the definition build will be skipped.
  • APP_EXPORTS - a space separated executables list taken from bundle bin/, sbin/ and libexec/ directories. Defines binaries to be exported for the given software bundle.
  • APP_REQUIREMENTS - a space separated list of definition names (without .def extension) to be installed, before the given definition itself (defines software dependencies)
  • APP_AFTER_*_CALLBACK - callbacks invoked after given stages of the build, where ‘*’ is a stage name. Current stage names (in order of execution): UNPACK, CONFIGURE, MAKE, INSTALL, PATCH, EXPORT. Callback might contain shell commands or sh function name, (this function must be defined in definition itself), which will be called by name. For an example, look on sbt.def in available definitions.
  • APP_SHA - SHA1 checksum of the source archive of the given definition. If the check will fail, Sofin will assume truncated/ broken file, and retry a download from software source server.
  • APP_CURRENT_VERSION - used to determine the availablity of new software version (usually from software home page). Look into ruby.def definition for an example.
  • APP_CONFLICTS_WITH - a space separated list of Bundle names (capitalized), that will export the same binaries as given definition. (under the hood - it just renames exports/ to exports-disabled/ for each conflicting definition).
  • REQUIRE_ROOT_ACCESS - set for definitions that must be built as root (for example Openafs which includes kernel module)

Sofin in action

Sofin supports two “kinds” of definitions. The first is regular definition (*.def files - more details in README file in the git repository) which has all the information required to build given software. The second is a definitions list, which is just a simple text file with newline separated names (again without extensions) of definitions to install. In the sofin install base example above, the “base” part is just a name of list, that includes mentioned software definitions. Sofin will automatically recognize which one is given. Most common usage of Sofin is: sofin install softwarename and sofin remove softwarename. Most of the time, it’s the only thing you want to do. “You want to type one command and get your software” - this was one of my major thoughts when I was starting to write Sofin. But there’s one more important thing to mention. By default, software lists, and definitions (with software patches) are just plain bz2 archive put on a http server. Sofin isn’t updating those definitions on each run. To get fresh definitions from a server, you need to run sofin update. The thing is, that they’re only user side definitions. Each user may have different local versions of them, and these definitions do not affect other users. One more common feature of Sofin, is partial software upgrade. For example: to upgrade libffi in Ruby bundle, you need to call sofin upgrade libffi ruby. It might be confusing, but the command works as if it would be written in natural language: “use sofin to upgrade libffi dependency in Ruby bundle”. Sofin will automatically detect rebuilt dependency, and invoke rebuild of ruby itself. It’s important to tell, that the upgrade process isn’t perfect yet - it’s one of the features that might fail with certain definitions, so please have that in mind. It tries to look for patterns of updated definition name, and remove matching files from bundle before doing the upgrade, but it’s just very tough to test all the possibilities. In case of a failure, just rebuild the whole software from scratch.

Implemented utility commands and hidden features

Sofin comes as a shell command. The launcher itself is written in C++ to fully support lock file functionality which is used by Sofin under the hood to avoid some issue (mostly to solve launching two conflicting commands at once). If one software build is already in progress, second instance of Sofin will wait until first is finished.

Here’s a list of additional Sofin commands with short explanation:

  • sofin list - Lists all installed bundles owned by the current user. Use sofin fulllist to get list with dependencies included.
  • sofin available - Shows the list of all definitions available to install.
  • sofin vars - Generates the sh-compliant dump of ENV values, based on the currently installed software.
  • sofin log - Probably the most useful command if you want to see what’s going on under Sofin’s hood. It will show the software installation progress log, including all commands invoked by during the process.
  • sofin ver - Shows Sofin’s version.
  • sofin outdated - Shows software bundles installed by Sofin that are outdated.
  • sofin clean - Removes source packages cache. It also clears the installation log.
  • sofin dependencies - One of the rarely used features that might be used for dedicated software. It reads $(cwd)/.dependencies file as software list, and installs software from it.
  • DEBUG=true sofin anycommand - Turns on debug logger if you’re really interested about details (prints in magenta to the standard output).

Some words about development process and Sofin’s issues

Sofin isn’t ideal software, but it has already proven to be very useful on several production environments. Here’s the thing: I don’t want to hide any of it pitfalls, because I don’t have to.

Sofin is built to be always production-ready software. It means that, if your software doesn’t build, then in vast majority of cases it will be caused either by an error in definition or by an issue with software itself.

Sofin’s core is almost untouched since the beginning of project, only extended with new features from time to time (usually on request).

But of course there are a few limitations. First of all, the default definition source is placed on my private HTTP server. If you want to host your own definitions, you’d need to reconfigure Sofin for your needs. The second major pitfal is that it requires XQuartz on OS X hosts to build some definitions. I couldn’t find better solution for Mac OS X, without Darwin-specific hacks on X11.

TheSS, Sofin-based software managment tool

Parallely with Sofin, the second project called TheSS is under heavy development. It was part of closed source, but this year I made it public (BSD-licensed as well).

In short, it uses software built by Sofin to easily perform software deployment (includes the support for web applications of any kind).

Summary

Sofin is free software.

The content of this article covers only a part of design patterns used internally. Feel free to support this project in any way. I’m easy to find, there’s only one dmilith.

I’m open for suggestions and improvement ideas. Please feel invited to contribute. If you want to get more details, just meet me online, and I’ll try to explain every detail you might want to know about Sofin and TheSS.

On the Web

Glossary

  • KISS — the acronym of Keep It Simple Stupid. Methodology to software development with simplicity in mind.
  • FHSFilesystem Hierarchy Standard.
  • POSIX — compatibility standard between operating systems (used by BSD, OS X and Linux).

About the author

Daniel (dmilith) Dettlaff

Sysadmin of several medium companies in Poland. Enterprise and cloud hater. Currently working at Monterail.com, LLC. Working on building self-healing, self-managable, distributed, AI-driven systems.

He makes new ideas real, while systems are automatically doing his work. Constantly learning how to become a good software architect. Musician, lyricist, writer in his free time.

(Thanks to Dominik Porada and Michał Hewelt for help with my horrible English grammar)