Andrew Timberlake Andrew Timberlake

Hi, I’m Andrew, a programer and entrepreneur from South Africa, founder of Sitesure for monitoring websites, APIs, and background jobs.
Thanks for visiting and reading.


Using Dead Man's Snitch with Whenever

A quick tip to make it easier to use Dead Man's Snitch with the whenever gem

Whenever is a great gem for managing cron jobs. Dead Man’s Snitch is a fantastic and useful tool for making sure those cron jobs actually run when they should.

Whenever includes a number of predefined job types which can be overridden to include snitch support.

The job_type command allows you to register a job type. It takes a name and a string representing the command. Within the command string, anything that begins with : is replaced with the value from the jobs options hash. Sounds complicated but is in fact quite easy.

Include the whenever gem in your Gemfile and then run

$ bundle exec wheneverize

This will create a file, config/schedule.rb. Insert these lines at the top of your config file, I have mine just below set :output.

These lines add && curl https://nosnch.in/:snitch to each job type just before :output.

job_type :command,   "cd :path && :task && curl https://nosnch.in/:snitch :output"
job_type :rake,      "cd :path && :environment_variable=:environment bin/rake :task --silent && curl https://nosnch.in/:snitch :output"
job_type :runner,    "cd :path && bin/rails runner -e :environment ':task' && curl https://nosnch.in/:snitch :output"
job_type :script,    "cd :path && :environment_variable=:environment bundle exec script/:task && curl https://nosnch.in/:snitch :output"

Now add your job to the schedule. A simple rake task would like this:

every 1.day, roles: [:app] do
  rake "log:clear"
end

Now it’s time to create the snitch. You can grab a free account at deadmanssnitch.com and add a new snitch.

New Snitch

Then, once that’s saved, you’ll see a screen with your snitch URL. All you need to do is copy the hex code at the end.

Snitch URL

Use that hex code in your whenever job as follows:

every 1.day, roles: [:app] do
  rake "log:clear", snitch: "06ebef375f"
end

Now deploy and update your whenverized cron job. DMS will let you know as soon as your job runs for the first time so you know it has begun to work. After that, they’ll only let you know if it fails to check in.

Tip: For best tracking, you want your DMS job to check in just before the end of the period you’re monitoring (in the above example 1 day). To do that, I revert to cron syntax in whenever and set my job up as:

# Assuming your server time zone is set to UTC
every "59 23 * * *", roles: [:app] do
  rake "log:clear", snitch: "06ebef375f"
end

See Does it matter when I ping a snitch?. Remember to allow time for the job to run and complete. For more information, read through the full DMS FAQ

6 Sep 2015

How to protect downloads but still have nginx serve the files

I’ve just been working on a project where a number of downloads needed to be restricted to specific users. I needed to authenticate the user and then allow them access to the file. This is not too difficult in rails:

def download
  if authenticated?
    send_file #{RAILS_ROOT}/downloads/images/myfile.zip'
  end
end

The problem with this is that if the file is large, rails will spend a lot of time sending this file to the browser. The solution, hand it off to the webserver (in my case, nginx) to send the file once the authentication has succeeded. nginx supports a header named X-Accel-Redirect. Using this header, you send a full path to the file to be downloaded:

def download
  if authenticated?
    #Set the X-Accel-Redirect header with the path relative to the /downloads location in nginx
    response.headers['X-Accel-Redirect'] = '/downloads/myfile.zip'
    #Set the Content-Type header as nginx won't change it and Rails will send text/html
    response.headers['Content-Type'] = 'application/octet-stream'
    #If you want to force download, set the Content-Disposition header (which nginx won't change)
    response.headers['Content-Disposition'] = 'attachment; filename=myfile.zip'
    #Make sure we don't render anything
    render :nothing => true
  end
end

You will need to add a location directive in nginx marked as internal which nginx will use along with your path to get to the physical file.

location /downloads {
  root /rails_deploy/current/downloads;
  #Marked internal so that this location cannot be accessed directly.
  internal;
}

Notes:

You can also set additional control using the following headers:

X-Accel-Limit-Rate: 1024
X-Accel-Buffering: yes|no
X-Accel-Charset: utf-8

See the nginx documentation on X-Accel-Redirect for more information.

1 Dec 2010