Technical Generalism

Work-in-progress projects, ideas, how-tos, and rants from a guy in the InfoSec industry

Adventures in Ruby

| Comments

After reading a a blog post on exactly what I needed for students to remotely access the virtual machines in the Nova Labs classes, I decided I needed to give back to the community just a bit more. Because I like github, and because I’ve used github-pages before, I decided on Octopress. I’d soon find out this wouldn’t be as simple as setting up github-pages for my thin-provisioning site, which oddly enough appears to be returning a 404 as I compose this post. Sigh.

This is the story of me getting Octopress operating “correctly” at home.

Note: I went through an iterative process of breaking shit, uninstalling, re-installing, troubleshooting, researching, uninstalling, re-installing things in a different way until things started working. This lack of fear of breaking something that’s already broken is special: It’s the SysAdmin way. I’ve attempted to document below what went wrong and what I did to fix it, but as I attempt to re-trace my steps, things aren’t adding up correctly.

This was quite possibly my first real venture into the Ruby programming language, so I’m about as uninitiated as you can be. I skimmed through the Octopress documentation, and started installing Ruby packages on my Fedora 18 home desktop. A move I’d soon regret, in about 12 hours time. Spoiler: I didn’t know that the Ruby community has a thing for requiring specific versions of Ruby and associated Ruby gems (extensions, modules, or libraries by any other name) together with an application. So Octopress had these Gemfile and Gemfile.lock files in the repository that described a specific Ruby environment that the Octopress developers had blessed as “good”.

In good inquisitive hacker learning fashion, I completely and quite intentionally disregarded it all. I learn by doing. :)

You’re supposed to run bundle install to install Octopress’s pre-requisites (including a specific version of the Ruby interpreter itself), which will end up in ~/.gem. Bundle’s job is to make setting up an application repeatable, but I was unwittingly playing mix-master with multiple versions of gems and multiple locations Ruby gems could be installed on the file-system.

  • /usr/share/gems <-- where RPM packages put gems
  • /usr/local/share/gems <-- where gem(1) puts gems
  • ~/.gem/ruby/1.9.1/gems <-- where bundle puts gems

The first thing that went wrong was running Ruby’s version of make, rake as part of installing the default Octopress theme. Rake informed me that I was running too new of a version of rake:

Installing the Octopress theme
1
2
3
4
$ rake install
rake aborted!
You have already activated rake 0.9.6, but your Gemfile requires rake 0.9.2.2.
Using bundle exec may solve this.

This was because I had a copy of rake from my system in /bin/rake, and a copy from bundle in ~/.gem somewhere. I wasn’t using the version of rake specified by the Gemfile.lock that bundle downloaded. From the error message I learned about the bundle exec command. This tries to run the specific versions of programs that are part of your application’s bundle.

So, bundle exec rake install? Yep, that worked. I thought I was golden until I tried to run bundle exec rake generate to “compile” the website. I thought to myself “Am I going to need to run bundle exec every time?”. Something was silently failing to run. The “My Octopress Page is coming soon” index.html made by the Rakefile was still in the _deploy directory, and none of the templates had executed and produced any output files. So I went digging on the Rakefile:

Octopress’s Rakefile generate task
1
2
3
  puts "## Generating Site with Jekyll"
  system "compass compile --css-dir #{source_dir}/stylesheets"
  system "jekyll"

It turns out, this system() call wasn’t locating the compass script somewhere buried under ~/.gem/. When I tried to run compass on the command line straight up, I got -bash: compass: command not found. bundle exec compass worked for me on the command line, and that’s essentially what system() does. It runs a shell to run the program. I tried to hack the Rakefile to use bundle exec in the system() call, but [ my memory is fuzzy here ] things didn’t work. I ultimately resorted to uninstalling Ruby entirely, from RPM packages, and the Gems installed in the 3 locations above. Then I went through a process of installing the core of Ruby from RPM, installing Gems as a user to get a list of Gem + version Octopress wanted, and then installed those explicitly as root with gem system-wide.

Installing version 0.12.1 of the json Gem
1
% gem install jekyll -v 0.12.1

At that point I got compass and jekyll in standard system paths (/bin, and /usr/local/bin). So I tried to run rake generate again:

Jekyll can’t load json? dafuq?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ bundle exec rake generate
## Generating Site with Jekyll
identical source/stylesheets/screen.css
Configuration from /home/warewolf/git/octopress/_config.yml
/home/warewolf/git/octopress/plugins/config_tag.rb:1:in `require': cannot load such file -- json (LoadError)
        from /home/warewolf/git/octopress/plugins/config_tag.rb:1:in `<top (required)>'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:78:in `require'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:78:in `block (2 levels) in setup'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:77:in `each'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:77:in `block in setup'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:76:in `each'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:76:in `setup'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:31:in `initialize'
        from /usr/local/share/gems/gems/jekyll-0.12.1/bin/jekyll:238:in `new'
        from /usr/local/share/gems/gems/jekyll-0.12.1/bin/jekyll:238:in `<top (required)>'
        from /usr/local/bin/jekyll:23:in `load'
        from /usr/local/bin/jekyll:23:in `<main>'

Uhh. Excuse me? Why can’t you find json?

But json is installed…
1
2
3
4
5
6
7
8
9
$ gem list | grep json
json (1.7.7)
$ bundle check
The Gemfile's dependencies are satisfied
$ ruby
require 'json'
puts "I guess I loaded json." <Ctrl-D>
I guess I loaded json.
$ 

No warnings. Normal Ruby loads json. So why can’t jekyll when it’s run by rake? From the Rakefile above, we know that rake simply runs jekyll via system(). What happens when we run it standalone?

Jekyll running like it should be. The hell?
1
2
3
4
5
# warewolf@xasf:~/git/octopress$ jekyll
Configuration from /home/warewolf/git/octopress/_config.yml
Building site: source -> public
Successfully generated site: source -> public
# warewolf@xasf:~/git/octopress$ 

Ok, this shit is getting real. SYSADMIN INVESTIGATION MODE ACTIVATE

I’ve got a few tricks up my sleeve, one of which I use for helping me secure automated batch processing through ssh pubkeys. Note to self: write this up later!

Lets look at the execution environment of compass and jekyll when run via rake generate. I do this by running the command through bash, but as a series of commands instead of just one. I compared this to my normal shell environment and saw some things that were added.

bash -c ftw
1
  system "bash -c 'set; jekyll'"

And the output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## Generating Site with Jekyll
identical source/stylesheets/screen.css
[.. cut for brevity..]
BUNDLE_BIN_PATH=/usr/local/share/gems/gems/bundler-1.3.5/bin/bundle
BUNDLE_GEMFILE=/home/warewolf/git/octopress/Gemfile
GEM_HOME=/home/warewolf/.gem/ruby/1.9.1
GEM_PATH=/home/warewolf/.gem/ruby/1.9.1:/usr/share/gems:/usr/local/share/gems
RUBYOPT='-I/usr/local/share/gems/gems/bundler-1.3.5/lib -rbundler/setup'
_ORIGINAL_GEM_PATH=/home/warewolf/.gem/ruby/1.9.1:/usr/share/gems:/usr/local/share/gems
[..cut for brevity..]
Configuration from /home/warewolf/git/octopress/_config.yml
/home/warewolf/git/octopress/plugins/config_tag.rb:1:in `require': cannot load such file -- json (LoadError)
        from /home/warewolf/git/octopress/plugins/config_tag.rb:1:in `<top (required)>'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:78:in `require'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:78:in `block (2 levels) in setup'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:77:in `each'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:77:in `block in setup'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:76:in `each'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:76:in `setup'
        from /usr/local/share/gems/gems/jekyll-0.12.1/lib/jekyll/site.rb:31:in `initialize'
        from /usr/local/share/gems/gems/jekyll-0.12.1/bin/jekyll:238:in `new'
        from /usr/local/share/gems/gems/jekyll-0.12.1/bin/jekyll:238:in `<top (required)>'
        from /usr/local/bin/jekyll:23:in `load'
        from /usr/local/bin/jekyll:23:in `<main>'

So what stands out here? There’s some environment variables that look like they’re explicitly related to the path for Gems, GEM_HOME, GEM_PATH, and RUBYOPT. Exactly the same way I printed out these environment variables, I can unset them. So now in the Rakefile we have this:

Octopress Rakefile generate task un-setting environment variables
1
2
3
  puts "## Generating Site with Jekyll"
  system "compass compile --css-dir #{source_dir}/stylesheets"
  system "bash -c 'unset GEM_HOME GEM_PATH RUBYOPT; jekyll'"

And now we have this:

Jekyll works like a champ, just like under a regular shell
1
2
3
4
5
6
$ bundle exec rake generate
## Generating Site with Jekyll
identical source/stylesheets/screen.css
Configuration from /home/warewolf/git/octopress/_config.yml
Building site: source -> public
Successfully generated site: source -> public

So something that is setting those environment variables that is supposed to be helping is ending up hurting us. I bet it’s the bundle stuff. I found this at the top of the Rakefile:

Rakefile
1
2
3
require "rubygems"
require "bundler/setup"
require "stringex"

So I commented out the require "bundler/setup" line and re-ran rake generate. Now:

Lets run rake generate again
1
2
3
4
5
6
$ rake generate
## Generating Site with Jekyll
identical source/stylesheets/screen.css
Configuration from /home/warewolf/git/octopress/_config.yml
Building site: source -> public
Successfully generated site: source -> public

Viola! And now I have a blog.

Comments