Swift Calcs on an Air-gapped Machine

A few weeks back, the Swift Calcs dev team received an unusual request from a customer: Could we deliver a version of Swift Calcs that they could run locally, without internet access, on an 'air-gapped' machine?


In organizations that have high needs for secrecy and security, air-gapping a computer is an effective strategy to keep files safe. An air-gapped machine has no internet connection, no network connection, and no USB ports. The machine exists in its own bubble, with no live connections of any kind to any other machine, anywhere.

In fact, the only way to bring data on, or off, an air-gapped machine is with a writeable CD (remember CD-R technology?).

And so we were asked: could we deliver a version of Swift Calcs that could be delivered on a CD-R and then run on an air-gapped machine?

A journey back in time

This request presented our development team with a number of challenges. Swift Calcs is a web-based application designed to be used remotely, through a web-browser, by users worldwide. Our back-end, which relies on Ruby and Rails, is not designed for easy install or packaging for distribution. And we rely on a number of third party code that, generally, requires an internet connection to download when setting up a new Swift Calcs server. We were being asked to essentially put Swift Calcs into a time-machine, and send it back to a day before the cloud and networked applications.

Enter Phusion

Thanks to the fine folks at Phusion, who are behind a number of wonderful libraries (Passenger anyone?), we had a potential solution: Traveling Ruby. Traveling Ruby packages applications for MacOS, Linux, and Windows into a single compressed archive that can then be run directly, without any need for other installs. All the required libraries are downloaded and included, and special builds of native gems are included for each distribution (Although the readme states windows is not supported for native gems, this is not entirely true...see this discussion for some help, and this folder for some pre-built binaries for common gems. It was all we needed.)

Dumbing it down

To make this work locally, we had to do a few things:

Bringing it all together

To make things work, we started with the Traveling Ruby tutorials and then updated them to work with Rails 4. The tutorial is set up around providing access to a single ruby file. Rails is a whole application. We made a few key changes to the Rakefile example from Tutorial 3 (with the Tutorial 4 win32 additions). First, instead of the single line to copy 'hello.rb' into the package, we replace it with the following:

  sh "cp -rf ./app #{package_dir}/lib/app/app"
  sh "cp -rf ./bin #{package_dir}/lib/app/bin"
  sh "cp -rf ./config #{package_dir}/lib/app/config"
  sh "cp -rf ./db #{package_dir}/lib/app/db"
  sh "cp -rf ./lib #{package_dir}/lib/app/lib"
  sh "mkdir -p #{package_dir}/lib/app/log"
  sh "mkdir -p #{package_dir}/lib/app/tmp"
  sh "cp -rf ./public #{package_dir}/lib/app/public"
  sh "cp -rf ./test #{package_dir}/lib/app/test"
  sh "cp -rf ./vendor #{package_dir}/lib/app/vendor"
  sh "cp .env #{package_dir}/lib/app"
  sh "cp local.db #{package_dir}/lib/app"
  sh "cp config.ru #{package_dir}/lib/app"
  sh "cp Rakefile #{package_dir}/lib/app"
  sh "cp README.rdoc #{package_dir}/lib/app"

We use both the json and sqlite3 native gems, so we also had to update the build tasks to pull these gems as well. For example, the osx build task became:

  desc "Package your app for OS X"
  task :osx => [:bundle_install,
  ] do

Which requires the addition of:

file "packaging/traveling-ruby-#{TRAVELING_RUBY_VERSION}-osx-json-#{JSON_VERSION}.tar.gz" do  
  download_native_extension("osx", "json-#{JSON_VERSION}")

We also used the json and sqlite build for win32 provided at the link shown above, and added them into the win32 build tasks using the same syntax. We had to create a special download_native_extension method for these files as they were in a different location that those provided by Phusion.

Finally, we updated the command in wrapper.sh to launch the WEBrick server directly:

# Run the actual app using the bundled Ruby interpreter, with Bundler activated.
exec "$SELFDIR/lib/ruby/bin/ruby" -rbundler/setup "$SELFDIR/lib/app/bin/rails" s  

We can't recommend running Swift Calcs this way, it really defeats the purpose of a cloud based application to take it out of the cloud! But for those users with extraordinary requirements, we want to make sure the power and flexibility of Swift Calcs works for them as well.

comments powered by Disqus