Documentation:

Packaging the Ruby-on-Rails blog example application

The Ruby-on-Rails site rubyonrails.org uses a simple blog application to teach developers how to get started with RoR. We have taken this blog application, packaged it for UBOS, and documented the process in this section. As it follows the standard RoR conventions, packaging other RoR apps for UBOS should be very similar.

In this example, we have chosen MySQL as the production database, and Passenger as the application server that connects Apache to our application. We package all required gems into the package, so the App only depends on UBOS to provide ruby and ruby-bundler and no other gems.

To obtain the source code:

% git clone https://github.com/uboslinux/ubos-toyapps

Go to subdirectory ruby-rails-blog.

Following the tutorial

On our development machine (see also here), we first installed the required tools from Arch:

% sudo pacman -S ruby ruby-bundler
% gem install rails

Then, we followed the tutorial on the rubyonrails.org site as written, with only one difference: we used ruby-rails-blog as the name of the application, not just blog.

This produced the following top-level files and directories:

.gitignore
config.ru
Gemfile
Gemfile.lock
package.json
Rakefile
README.me
app/
bin/
config/
db/
lib/
log/
public/
test/
tmp/
vendor/

Most Ruby-on-Rails applications should start out with a very similar structure, and so the additions to be made for UBOS should be very similar to the ones described below.

Modifications to the existing project files

There are only two:

Gemfile modifications

In production, we want to use a database other than the default sqlite. We choose MySQL. So we add the following section to the Gemfile:

group :production do
  gem 'mysql2'
end

.gitignore modifications

We don’t want to check in things that will be regenerated by the build, so we add these lines to the .gitignore file:

public/assets/
ruby-rails-blog-*.pkg*

The latter is the name of the package being generated for UBOS, with wildcards for the version numbers and package compression that may easily change.

New file additions

UBOS needs the following additions:

  • PKGBUILD: defines how the UBOS package is being put together;
  • ubos-manifest.json: metadata that allows UBOS to provision databases, configure web servers, directories and the like when the App is deployed to a device;
  • appicons/: a directory with two image files that will be used when an icon for the App needs to be shown to the user;
  • tmpl/database.yml.tmpl: the template for the database.yml file generated during deployment of the App that will contain database information specifically for this installation of the App;
  • tmpl/htaccess.tmpl: the template for the Apache config file fragment that configures Apache and Passenger for our application.

The organization in these files and directories is by UBOS convention; however, with suitable modifications to the PKGBUILD file, any other organization is possible.

While you read the discussion below, you may want to open the content of these files and follow along.

About PKGBUILD

PKGBUILD is a bash script, invoked by makepkg when building the UBOS package.

It defines some variables to identify the developer of the App, the name and version of the package, the license of the App and the like. It also identifies package dependencies. The only dependency here is on UBOS package ubos-rails-support, which bundles a few useful scripts for deploying RoR apps on UBOS.

The build() function is invoked by makepkg to build what needs building before the package is assembled. This invokes the familiar rails commands:

RAILS_ENV=production bin/bundle install --deployment --without development test
RAILS_ENV=production bin/rails assets:precompile db:migrate

${startdir} refers to the project’s top-level directory.

In the package() function, we copy the files we want to package up into a directory hierarchy (starting at ${pkgdir}) which makepkg will tar up for us.

  • You can see that the UBOS manifest and the icons need to be in particular places, so UBOS can find them.
  • We will use a subdirectory of the UBOS “data” directory (${pkgdir}/ubos/lib/${pkgname}, which will expand to /ubos/lib/ruby-rails-blog at installation time) to put together the run-time directory structure that Passenger will work on. Here, we only need to create the directory; it will be populated not upon installation of the package, but every time an App is deployed at a unique Site and context path based on the information in the UBOS manifest.
  • Then we carefully pick and choose which of the files on our development machine we actually want to have in the package. There is no need to ship more files than needed. This is performed by the list of files and directories, which then is copied recursively to below ${pkgdir}/ubos/share/${pkgname}/ (expanded to /ubos/share/ruby-rails-blog on the target device).
  • Finally, we create a directory below /var/log/ruby-rails-blog that will be the parent directory for the Rails log files.

About ubos-manifest.json

The UBOS manifest for this App captures the essence of the way the App is deployed on UBOS. Let’s go through it step by step:

  • The type of this package is app (not accessory).
  • This App can only be deployed to the root of a Site (fixedcontext is empty). As it seems, most RoR apps make this assumption and so we go with it in order to avoid having to make substantive changes to the App we package. Your Apps, if at all possible, should allow user-picked context paths, so the user can run your App in addition to other Apps on the same Site.
  • When run under Apache, the package passenger must be installed, and the Apache module passenger must have been activated. UBOS will make sure of both before deploying the App.

There are a number of AppConfigItems, i.e. items that need to provisioned for each instance of this App deployed to a device:

  • The file tmpl/htaccess.tmpl (discussed below) needs to be copied to the place where Apache expect it (refered to by symbolic name), after contained variables have been replaced with the values for this deployment.
  • This AppConfiguration‘s data directory must have been created. The symbolic name ${appconfig.datadir} will expand to /ubos/lib/ruby-rails-blog/aXXXX where aXXXX is a unique identifier for this particular AppConfiguration. This enables multiple deployments of the same App to coexist on the same device.
  • Below that data directory, we create another directory called approot, to which we will direct Passenger through the Apache config file (discussed below). This directory is strictly not necessary, but good practice, in case we have a need for other, AppConfiguration-specific data in the future (say uploaded files).
  • Into this approot directory, we now recursively copy, preserving file and directory permissions, the files and directories that Passenger needs to run.
  • After that, we invoke utility script setup-logging from the ubos-ruby-support package. This will simply make sure that directory /var/log/ruby-rails-blog/aXXXX exists and contains a writable file production.log. This is a script, rather than a file or directory AppConfigItem, because we don’t want to delete the directory once the App is undeployed.
  • We create a symbolic link to that log directory from the approot directory, so RoR can find its log file.
  • Finally, we copy the database.yml file into its place after replacing contained variables (see also below).

Now to SQL:

  • We need a database for this App, whose symbolic name is maindb (this is the name by which we refer to it in the template files below). This database contains valuable data – the App‘s blog posts and comments – and thus we specify a retentionpolicy and a retentionbucket. UBOS backup and restore will thus save and restore that data. Because Rails likes to make schema changes itself when db::migrate is run, the database user automatically provisioned for this database will have all privileges.
  • When the App is first deployed, and every time it is updated after initial deployment, we need to run the Rails db::migrate task. To make this easy, ubos-rails-support provides a script called db-migrate that does this. It is specified in both the installers (when first deployed) and updaters (susequently) sections.

And finally, there are two customization points. They are marked as private, so they won’t be shown to any user other than root on the device, and they are being automatically initialized to a random 128-character hex number upon first deployment. These are used as the secrets for environment variables RAILS_MASTER_KEY and SECRET_KEY_BASE passed to the application (see Apache config file below). We declare those as customization points, so it is possible for the user to specify their own values. This will also cause them to be backed up and restored. No point in database backups that contain encrypted information whose keys have not been backed up ...

About appicons/

This directory contains two PNG files, at 72 and 144 pixels square, respectively, containing the icon of the App. We made one up for this purpose.

About database.yml.tmpl

This file is a typical RoR database.yml file, but instead of hardcoding database names, credentials and connection information, it contains variables that will be replaced by UBOS at deployment time.

For example, ${appconfig.mysql.dbname.maindb} will be replaced by the name of the MySQL database that UBOS picked for a particular deployment of this App. This makes it easily possible to run multiple instances of the same web App on the same device, for example at different virtual hostnames.

Here, we use variables for database name, database user, database user password and the hostname for the database.

About htaccess.tmpl

Similarly to database.yml.tmpl, htaccess.tmpl is a template file for an Apache web server configuration fragment whose contained variables will be replaced by UBOS at deployment time.

Variable ${appconfig.apache2.dir} refers to the top-level Apache directory that maps to the hostname and context path picked by the user when deploying this App (think of it as the Apache Docroot for this virtual host, or a subdirectory if not installed at the root of the Site).

However, we point Passenger to ${appconfig.datadir}/approot as the place where it finds the Rails App, as discussed above. We run Passenger as user and group http, like the Apache web server itself, to make it easier for Sites that use a mix of RoR and other types of applications.

Finally, we pass the values of the customizationpoints railsmasterkey and secretkeybase to Passenger by means of environment variables. These environmnent variables can then be picked up by the application, and we don’t need to change the Rails defaults (obviously this could be done differently).

Building and running the App on UBOS

On the development machine, change to the project’s root directory (that contains the PKGBUILD) and execute:

% makepkg

This will create the package file ruby-rails-blog-0.1-1-any.pkg.tar.xz.

Transfer this package file to your UBOS device using any way you like. Then, install the package there:

% sudo pacman -U ruby-rails-blog-0.1-1-any.pkg.tar.xz

Now you are ready to create a website that runs the application. Execute:

% sudo ubos-admin createsite

and answer the questions. When asked for the name of the application to install, enter ruby-rails-blog. Make sure that your DNS setup is consistent with the name of the Site (or use * as the name of the Site). Once the command is finished, your Site is accessible with a web browser at the hostname specified.