Blooming Cacti

Bring life to a barren, technological wasteland

Migrating "Timetrack" from Joyent to Linode

I decided to start my migration by migrating my Rails application, timetrack. This migration would have been a lot easier except that this particular application is using 2-year old tools and still needs a lot of tweaking.

The main dependencies are MySQL and Ruby 1.8.7.

User Account and Ruby

I started by creating a new user account, just for the timetrack application.

useradd timetrack

I installed rvm, then tried to install ruby 1.8.7, from the ‘timetrack’ user account.

rvm install 1.8.7

The rvm script gave me a list of Debian dependencies to install first.

/usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion pkg-config

After that, ruby 1.8.7 installed cleanly. Although I did get a warning that just compiling it on modern Linux required two extra patches and that I should strongly consider installing ruby 1.9, for better security.

Install MySQL

Installing MySQL was easy.

apt-get install mysql-server libmysqlclient-dev

I tuned it, using the parameters from Linode.

I created a new database, specifically for this application, then created a new database specific MySQL user account.

mysqladmin create timetrack

CREATE USER 'timetrack'@'localhost' IDENTIFIED BY 'xxxx';
GRANT ALL ON timetrack.* TO 'timetrack'@'localhost';

Finally, I could load the database backup, from Litho.

mysql -u timetrack -p timetrack < jmartin_desertflood_timetrack_2012-08-19_01h03m.Sunday.sql

Deploy the Application

Capistrano is quite powerful and should have made this the easiest part of the setup. But I never did build a really good deployment recipe, so it was only a partial help. I started by updating the config/deploy.rb file, to point to my new server and user account. Then I had to setup my 'timetrack' user account, to be able to access GitHub. Without it, I was getting errors on the cap deploy step.

  1. Create a new SSH key

    ssh-keygen
    
  2. Copy the contents of '.ssh/id_rsa.pub' to my Github keys. Then do an initial checkout, to make sure that SSH is okay with Github’s server.

    git clone git@github.com:jmartindf/timetrack.git
    
  3. Setup the required directories.

    mkdir -p /home/timetrack/apps/timetrack/releases
    mkdir shared/pids
    mkdir shared/log
    
  4. Deploy the app

    cap deploy
    
  5. Update local configurations

    vim config/database.yml
    vim config/unicorn.conf.rb
    
  6. Install required gem dependencies

    bundle install
    
  7. Start the app, to see if it works.

    unicorn -E production -D -o 127.0.0.1 -c /home/timetrack/apps/timetrack/current/config/unicorn.conf.rb
    

And, it works. Amazing.

Start at boot

It’s all well and good to start it manually, but I really need it to start at boot. After some Googling, I found an init.d script for unicorn, that I could use as a basis for my own init.d script.

After tweaking it a bit, this is what it looks like. The key points are that it uses rvm and bundler, to get the dependencies right. (As mentioned before, I’m using some old gems and an old version of ruby.) It also uses the chdir and chuid flags, to start-stop-daemon, to get everything running with the right permissions in the right folder. I’ve tested it and it does all work.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#! /bin/sh

PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Timetrack server"
NAME=timetrack
RVM=/home/timetrack/.rvm/bin/rvm
RVM_EXEC_ARGS="1.8.7 exec"
RAILS_DIR=/home/timetrack/apps/timetrack/current
DAEMON=$RVM
DAEMON_ARGS="$RVM_EXEC_ARGS bundle exec unicorn -c $RAILS_DIR/config/unicorn.conf.rb -E production -D"
PIDFILE=$RAILS_DIR/tmp/pids/timetrack.pid
USER=timetrack
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

# unicorn needs some environment vars
. /etc/environment

# Create /var/run dir if missing
# if [ ! -d /var/run/unicorn ]; then
#   mkdir -m 0755 /var/run/unicorn
#   chown $USER /var/run/unicorn
# fi

symlink_resque() {
    ln -sfT `$RVM $RVM_EXEC_ARGS which resque | tail -1`/server/public $RAILS_DIR/public/resque
}

#
# Function that starts the daemon/service
#
do_start()
{
    # Return
    #   0 if daemon has been started
    #   1 if daemon was already running
    #   2 if daemon could not be started
    symlink_resque
    start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER --exec $DAEMON --test $RAILS_DIR --chdir $RAILS_DIR > /dev/null \
        || return 1
    start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER --exec $DAEMON --chdir $RAILS_DIR -- \
        $DAEMON_ARGS \
        || return 2
    # Add code here, if necessary, that waits for the process to be ready
    # to handle requests from services started subsequently which depend
    # on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
    # Return
    #   0 if daemon has been stopped
    #   1 if daemon was already stopped
    #   2 if daemon could not be stopped
    #   other if a failure occurred
    start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
    RETVAL="$?"
    [ "$RETVAL" = 2 ] && return 2
    # Ensure the pidfile is cleared
    rm -f $PIDFILE
    return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
    symlink_resque
    start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE
    return 0
}

#
# Function that sends a SIGUSR2 to the daemon/service
#
do_graceful_reload() {
    symlink_resque
    start-stop-daemon --stop --signal USR2 --quiet --pidfile $PIDFILE
    return 0
}

case "$1" in
  start)
    [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
    do_start
    case "$?" in
        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    esac
    ;;
  stop)
    [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
    do_stop
    case "$?" in
        0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
        2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
    esac
    ;;
  status)
       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
       ;;
  reload)
    log_daemon_msg "Reloading $DESC" "$NAME"
    do_reload
    log_end_msg $?
    ;;
  graceful-reload)
    log_daemon_msg "Gracefully reloading $DESC" "$NAME"
    do_graceful_reload
    log_end_msg $?
    ;;
  restart)
    log_daemon_msg "Restarting $DESC" "$NAME"
    do_stop
    case "$?" in
      0|1)
        do_start
        case "$?" in
            0) log_end_msg 0 ;;
            1) log_end_msg 1 ;; # Old process is still running
            *) log_end_msg 1 ;; # Failed to start
        esac
        ;;
      *)
        # Failed to stop
        log_end_msg 1
        ;;
    esac
    ;;
  *)
    echo "Usage: $SCRIPTNAME {start|stop|status|restart|graceful-reload|reload}" >&2
    exit 3
    ;;
esac

:

I activated the timetrack init.d script, for the default run levels.

update-rc.d timetrack defaults

Nginx

My app is running, but it’s not on a port that the outside world can access. That’s where nginx comes in. I like nginx as a lightweight web server and in this case, I can easily use it to proxy traffic back and forth between my app. I just needed to create a very simple configuration file, for nginix.

vim /etc/nginx/sites-available/timetrack.desertflood.com
cd /etc/nginx/sites-enabled
ln -s /etc/nginx/sites-available/timetrack.desertflood.com
/etc/init.d/nginx start

Here’s what the file looks like.

upstream timetrack {
  server 127.0.0.1:10100;
}

server {
  listen       80;
  server_name  timetrack2.desertflood.com;
  location / {
  proxy_set_header  X-Real-IP        $remote_addr;
  proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
  proxy_set_header  Host             $http_host;
  proxy_redirect    off;
  proxy_pass        http://timetrack;
  }
}

And, just like that, I’ve migrated my first site from Joyent to Linode.