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?
Air-gapping
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:
- Change from PostgreSQL to SQLite3 for our database engine. Also required a few code changes to remove features not present in SQLite3, such as TRUE/FALSE in SQL statements and JSON data type.
- Remove a whole bunch of other gems and supporting code. Lots of gems were in our gemfile to help with accessing third party websites through omniauth. Or for loading data to AWS S3. Or sending email with a third-party provider. None of these gems were needed locally, and we removed them all and produced something that looked a lot more like a development environment than a production one
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,
"packaging/traveling-ruby-#{TRAVELING_RUBY_VERSION}-osx.tar.gz",
"packaging/traveling-ruby-#{TRAVELING_RUBY_VERSION}-osx-sqlite3-#{SQLITE3_VERSION}.tar.gz",
"packaging/traveling-ruby-#{TRAVELING_RUBY_VERSION}-osx-json-#{JSON_VERSION}.tar.gz"
] do
create_package("osx")
end
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}")
end
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