Forget running ruby on a router
Turns out it's too difficult to keep a 128MB router up and running typo and rails. The biggest issue I had was the router kernel panicking causing reboot and loss of nvram settings. So we're switching back to a nice virtualized ubuntu box and my router can actually do what it does best - route packets and presumably get hax0red.
I finally got monit to keep this blog up and running
After a long struggle, I got monit to restart my webrick when it gets too big. I started by switching back to ruby 1.8.7, which doesn't use multiple pthreads, and it much easier for me to manage. It does use a little more memory, but doesn't spike memory temporarily to high values. I managed to get myself into several monit problems, such as the following image:

Here's the configuration that ended up working:
check process rails with pidfile /opt/home/blog/tmp/pids/server.pid
start program = "/opt/bin/bash -l -c '/opt/etc/init.d/S91rails start'" with timeout 240 seconds
stop program = "/opt/bin/bash -l -c '/opt/etc/init.d/S91rails stop'" with timeout 60 seconds
if cpu > 60% for 2 cycles then alert
if cpu > 80% for 5 cycles then restart
if totalmem > 95.0 MB for 2 cycles then restart
check system localhost
if swap usage > 15% then exec "/opt/bin/killall -9 /opt/local/bin/ruby"And my startup script S91rails looks like:
#!/opt/bin/bash
# Add the user to /etc/passwd since it disappears on reboot (flashed filesystem is readonly)
AS_USER=blog
grep -q ${AS_USER} /etc/group > /dev/null || echo "${AS_USER}:x:40:" >> /etc/group
grep -q ${AS_USER} /etc/passwd > /dev/null || echo "${AS_USER}:x:40:40:${AS_USER}:/opt/home/blog:/opt/bin/bash" >> /etc/passwd
# handle comand
case "$1" in
start)
su - $AS_USER -c "bash -l -c 'cd /opt/home/blog && nice rails s -d -e production'"
;;
stop)
su - $AS_USER -c "bash -l -c 'killall -s INT -q ruby; sleep 10; killall -s KILL -q -w ruby'"
;;
status)
su - $AS_USER -c "ps aux | grep 'rails server' >/dev/null || echo 'No rails server running'"
;;
restart)
stop
start
;;
esac
Running Ruby&Rails on a tiny MIPS machine
Here's a quick summary of which versions of ruby and rails app servers I've successfully ran on my router (see previous post):
| Ruby | Success | Comment |
|---|---|---|
| 1.8.7p334 | YES | Typo quickly takes more RAM than is comfortable (95-125MB) |
| 1.8.7 enterprise | NO | Segfault in GC in webrick |
| 1.9.1 from ipkg-opt | NO | Cross compiled paths to gcc, libs, and includes are hardcoded wrong |
| 1.9.1p431 | YES | Typo memory footprint is lower (75-95MB), get nice warning about rails test suites in 1.9.1 |
| 1.9.2p290 | NO | Webrick dies with NotImplementedError: ruby engine can initialize only in the main thread |
I've had a lot of trouble finding a compatible rack app server. Unfortunately, webrick is the only one so far.
| Rails App Server | Success | Comment |
|---|---|---|
| Webrick | YES | Can't get uploaded files to work so far |
| Thin | NO | Segfault in pthread |
| Unicorn | NO | Won't compile because lib_atomic_ops is not supported in MIPS |
| Passenger-Nginx | NO | ext/oxt/detail/../detail/spin_lock_pthreads.hpp can't find pthread functions |
I'm running this ruby Typo blog on a home router
Hardware

To replace my Asus eeePC netbook/apple airport extreme/linksys smallbiz VPN router, I chose the Asus N16 router for $86. It's equipped with 128MB of RAM (the most important thing for this project), 32MB of flash (which allows me to run the largest firmware), and a 480mhz broadcom MIPS processor.
DD-WRT
The DD-WRT project is an open source firmware that runs on tons of home routers. It was easy to install on mine, just flash the build that was listed in their database for my router. Next I connected a USB 4GB flash drive that I partitioned into 3 partitions using a different machine (/opt, SWAP, and /mnt) and enabled all the necessary features in the web GUI to automount /opt and enable JFFS. I finally installed optware using these instructions which proved to much better than figuring out everything be hand. Then I installed the build tools (g++, gcc, make, etc) and nginx to host static content, sqlite as a DB, and probably tons of other packages I forgot as this point. At one point I got mysql server 5.0 working, but went back to sqlite.
The LD_LIBRARY_PATH variable that's preset with optware (maybe in /opt/etc/profile?) breaks the compiler like crazy. I found the only way to fix it was unset LIBRARY_PATH, then I can configure/make software with some success.
Building ruby
I had no success with the binary ruby 1.9.1 package that comes with optware, so I tried to build it myself. Ruby 1.8.7 was fairly easy to build (takes hours of course), though I did have to manually specify the build target as "mips". Ruby 1.9.2 was easy to build and install as well, and uses less memory, but I couldn't get webrick to run without segmentation faulting. RubyEE 1.8.7 seemed to work, but I had seg fault in the GC when I ran webrick.
Installing Typo
First I installed bundler. It takes about 20 minutes to install a gem, unless you specify --no-ri --no-rdoc --no-update-sources, but you have to download the sources at least once. Then I ran bundle install, which takes several hours. I failed to get any app servers working except webrick to work (unicorn is not supported on MIPS, and thin segfaults).
Memory Issues
The webrick process inflates to a pretty large size and I have to kill it frequently. 128MB of RAM is the bare minimum for a big rails app on ruby 1.8.7, I wish that I could get 1.9.x to work. I do some amount of swapping to flash drive, which may be bad for the life of the disk. I'm also planning on using monit to restart rails automatically sometimes. Nginx works like a dream and uses less than 1MB of RAM.
My Rails Performance talk at Pivotal Labs
I gave a talk in Singapore before the ruby conference there, and again in San Francisco for Pivotal Labs. I cover both when you should optimize in an agile world and the how-to to making your rails app faster.
http://pivotallabs.com/talks/133-performance-and-scalability-make-your-app-just-fast-enough-in-an-agile-wayI'm running this blog on a windows vista netbook running a VM
I have an asus eeepc netbook that recently had a screen-breaking incident (paw-shaped cracked area in the LCD). This seemed like a good fit for running a headless server of some sort. It had 2GB of RAM and 2x1.5ghz intel atom cores, so it's not that far away from an m1.small EC2 instance in terms of performance. This blog was previously run on a VMWare Server image on top of my Windows 7 media PC, which had plenty of extra CPU power, but only 3.25 GB of usable memory to begin with (and I like to play games that use lots of memory).
The natural solution was to put linux on the netbook and move the 2 linux VMs over. I need an OS that supported my netbook hardware and could run VMWare server (I didn't try to switch to hypervisor or anything newer, since it seemed unlikely to support my netbook hardware). I tried the follow OS combinations, in order:
- Ubuntu 10.04 - cannot install VMWare server
- Ubuntu 9.10 - cannot install VMWare server
- Ubuntu 8.04 - VMWare server works, cannot install network drivers easily
- Ubuntu 8.10 - VMWare server works, cannot install network drivers easily
- Windows XP - bluescreen during kernel load on setup
- Windows Vista - worked perfectly
This is rather embarrassing, since I'm no fan of Vista, and I'm sure there's a better solution with VirtualBox or another VM solution that isn't as old as VMWare server.
Introducing mechanized_session and acts_as_other_website
Have you ever wanted to make a website that's just a re-skin or re-layout of another website? I always find myself wanting to do this, especially when there's no mobile version of a site and no API.
Enter mechanzied_session
All the mechanized_session gem provides is a way for the programmer to easily define remote actions that require an authenticated session. By providing these actions and an implementation for how to login, mechanized_session makes your life very easy.
View mechanized_session on GitHub
Acts_as_other_website on rails
MechanizedSession requires work to integrate with a frontend, so if you're building a rails app that just a skin of another website, acts_as_other_website is for you. It provides a default login page, handles all the normal exceptions that MechanizedSession raises, and provides the glue that stores your remote session data in your local session hash.
Example using EngineYard's cloud website
class EySession < MechanizedSession action :login do |session, options| session.get("https://login.engineyard.com/login") do |page| next_page = page.form_with(:action =>"/login") do |form| form["email"] = options[:username] form["password"] = options[:password] end.click_button end true # tells MechanizedSession that login was successful. EY returns a 401 exception if not successful end action :list_environments do |session| envs = [] session.get("https://cloud.engineyard.com/dashboard") do |page| page.parser.css("div.environment").each do |env| envs << env.css('h3').first["title"] end end envs end end
Then invoke acts_as_other_website in ApplicationController
class ApplicationController acts_as_other_website :using => EySession end
Finally create actions that call the remote server using the @mechanized_session object like so:
class EnvironmentsController < ApplicationController def index @environments = @mechanized_session.list_environments end end
The plugin will take care of ensuring that there's always an authenticated session when you do actions that require it, and will redirect users to a provided login page to establish that session when necessary.
View this example online using your iPhone or Safari: http://eycloud.heroku.com
Ruby Sandboxing Resources
The sandbox itself
- Freaky Freaky Sandbox - A sandboxing gem based on the MRI ruby interpreter. Written in C, it hacks the VM to allow safe execution of untrusted code
- Why-the-lucky-stiff talking about the freaky freaky sandbox - He explains it as an alternative to $SAFE in a discussion with ruby team.
- How to set up the C-Ruby sandbox
- JavaSand - A sandboxing gem for JRuby. It provides the same API as the C-based-ruby sandbox.
- How to set up the JRuby sandbox
Sandbox Support
acts_as_wrapped_class- A gem that adds easy class-wrapping for safely exposing an API to the sandbox code.acts_as_runnable_code- A gem that makes creation of sandboxes and evaluation of uploaded code easier.- Safely Exposing your App to a ruby sandbox - My article on setting up a sandbox.
Sandbox Examples
How to set up the JRuby sandbox
The JRuby Sandbox is simply a rewrite of why's original sandbox gem in JRuby. It's much less of a hack than the C implementation, and generally considered to be more safe. Here's how I set it up:
- Download and install the latest JRuby binaries from CodeHaus (I tested with 1.1.5).
- Download the source of the javasand jruby gem from the JRuby addons project
svn checkout http://jruby-extras.rubyforge.org/svn/trunk/javasand - Compile the gem:
ant
BUILD SUCCESSFUL
If the build fails, it might be because it can't find the JRuby classes. You'll need to find jruby.jar and then add a line to build.xml inside the "build.classpath" path:
<fileset dir="/path/to/jruby/jars" includes="*.jar" /> - Package up the gem:
jgem build javasand.gemspec - Install the gem:
sudo jgem install javasand-0.0.2.gem - Test the sandbox with
jirb -rubygemsrequire "sandbox" Sandbox.safe.eval("2+2") # yields 4
As you can see above, I had to compile the gem from source. The binary gem of javasand
from rubyforge failed with the following exception:
irb(main):001:0> require "sandbox"
=> true
irb(main):002:0> Sandbox.safe
org.jruby.ext.sandbox.Sandkit:714:in `removeMethods': java.lang.NoSuchMethodError: org.jruby.RubyModule.removeMethod(Ljava/lang/String;)V
How to set up the ruby sandbox
There's very little recent work on the MRI ruby sandbox, so here's a quick guide to getting the sandbox installed and running. Unfortunately, the sandbox requires a patched ruby, but luckily it's not that hard to set up.
- Download the latest version of ruby 1.8.6 from ftp://ftp.ruby-lang.org/pub/ruby/1.8 (does not work with 1.8.7 or 1.9, sorry)
- Download the sandbox gem source from git://github.com/why/sandbox.git
-
Patch ruby:
patch -p1 < ../sandbox_gem/patch/ruby-1.8.6-sandbox_needs.patch
patching file error.c -
Compile and install the patched ruby:
./configure
make
sudo make install - Download and install rubygems from RubyForge
-
Install the sandbox gem:
cd sandbox_gem && sudo ruby setup.rb -
Test the sandbox:
require "sandbox" Sandbox.safe.eval("2+2") # yields 4
Now that you've got the sandbox running, read more about it in my article on Advanced Sandboxing, or my Sandbox Introduction.