Latest Entries »

Amazon EC2 and NFS

There is a lack of updated information online about launching NFS in EC2 specifically, so I thought I would contribute to help those who might encounter this in the future; and it’s actually quite easy.

We’re running Fedora 8 (while we wait for Fedora 13 to be supported).

NFS-utils and rpcbind should already be installed, but if not, you need to run the following on the instance that will act as the server:

yum install nfs-utils rpcbind

*Note: you’ll find in a lot of tutorials portmap is used instead of rpcbind. Portmap was renamed to rpcbind.

Still on the server, we need to define what directory we want to share, with whom (which server), along with the permissions and options for that share.

# open up our definition file
nano /etc/exports

You can look at all the options in the exports man page.

For the purpose of this tutorial I’m just going to share a directory I created under /var/www

/var/www/test   ec2-180-71-131-129.compute-1.amazonaws.com(rw,async)

In the example above, I’m using the public DNS of one of my instances that has an elastic IP. Elastic IP DNS names inside EC2 get translated to internal IPs (you can verify it with ping). You can use hostnames, IPs, netmasks and even wildcards. rw specifies that it’s read/writable and async allows disk operations to happen asynchronously. Next we need to load the changes.

exportfs -ar

You can use that anytime you add another mount point.

And now we need to start the server and related services:

service rpcbind start
service nfs start
service nfslock start

Next, we need to open up some ports in Amazon’s security group. Through the EC2 Console or API, you want to allowing connections from your client to your server on the following ports:

TCP: 111, 2049
UDP: 111, 32806

On the client, you need to start nfslock and rpcbind, and optionally add them to startup:

service rpcbind start
service nfslock start
chkconfig --level 2345 rpcbind on
chkconfig --level 2345 nfslock on

We need to create a directory as a mount point and mount the NFS server:

mkdir /var/www/test
mount -t nfs ec2-180-71-131-132.compute-1.amazonaws.com:/var/www/test /var/www/test

*Make sure to change the host name to the hostname or IP of your NFS server.

That’s all there is to it.

Originally I had only opened up port TCP 111 and 2049. This only gave me this error:

mount to NFS server ’10.214.58.54′ failed: timed out, retrying

I then opened up UDP 111 and by running the mount command in verbose mode (-v) I saw that I needed to open UDP 32806. It looks like the mount command requires TCP and UDP 111, but only TCP 2049 and UDP 32806.

If you want these services to run on startup, you’ll want to do the following:

chkconfig --level 2345 rpcbind on
chkconfig --level 2345 nfs on
chkconfig --level 2345 nfslock on

And to have the mounts created at startup, edit /etc/fstab

ec2-180-71-131-132.compute-1.amazonaws.com:/var/www/test    /var/www/test       nfs rsize=8192,wsize=8192,timeo=14,intr 0 0

Django Custom Model Manager Chaining

Let’s say you have a custom model manager like this:

from datetime import datetime

from django.db import models

class PostManager(models.Manager):
    def by_author(self, user):
        return self.filter(user=user)

    def published(self):
        return self.filter(published__lte=datetime.now())

class Post(models.Model):
    user = models.ForeignKey(User)
    published = models.DateTimeField()

    objects = PostManager()

But let’s say you are want to filter those results that are both published and by a certain author. There is no built-in mechanism that will allow you to chain the two together.

So for example, the following will fail since by_author returns a QuerySet:

Post.objects.by_author(user=request.user).published()

There are a few discussions on the matter:

But using Python mixins I’ve developed what I think to be a better way:

from django.db.models.query import QuerySet

class PostMixin(object):
    def by_author(self, user):
        return self.filter(user=user)

    def published(self):
        return self.filter(published__lte=datetime.now())

class PostQuerySet(QuerySet, PostMixin):
    pass

class PostManager(models.Manager, PostMixin):
    def get_query_set(self):
        return PostQuerySet(self.model, using=self._db)

It keeps with DRY principles and works just like you expect Django’s ORM to work.

GoDaddy SSL Certificate and Chrome

On one of my recent projects, I was getting a message from Google Chrome saying: “The site’s security certificate is not trusted!”

It was was a valid certificate in all other major browsers. And a few different computers running Chrome had no issues. So what gives?

Through some Googling, I learned this:

This could be because the SSL provider is using a new Root certificate that isn’t included in the old browsers and devices. The error can usually be fixed by installing an Intermediate certificate that will link the new Root certificate to an old trusted certificate. –Robert [SSL Certificate Not Trusted Error]

If your curious to learn more about intermediate certificates, GoDaddy has a decent explanation: What is an intermediate certificate?

How to Fix

You need to download a GoDaddy Secure Server Certificate (Intermediate Certificate): gd_intermediate.crt [GoDaddy Repository]

For Apache, you need to add the following to your configuration:

SSLCertificateChainFile /etc/certs/gd_intermediate.crt

For Nginx, there’s a little more work to do the same thing. You need to concatenate your existing certificate file with the intermediate certificate to produce a new certificate file, which you will as your new certificate.

cat example.com.crt gd_intermediate.crt > example.com-combined.crt

Then in your nginx configuration file you can change

ssl_certificate      /etc/certs/example.com.crt;

to

ssl_certificate      /etc/certs/example.com-combined.crt;

Save the following as /System/Library/LaunchDaemons/org.mongo.mongod.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>org.mongo.mongod</string>
	<key>RunAtLoad</key>
	<true/>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/local/bin/mongod</string>
		<string>--dbpath</string>
		<string>/var/lib/mongodb/</string>
		<string>--logpath</string>
		<string>/var/log/mongodb.log</string>
	</array>
</dict>
</plist>

You will need to create a file for the log and a directory for the database.

sudo touch /var/log/mongodb.log
sudo mkdir /var/lib/mongodb

And in a bash session, run the following:

sudo chown root:wheel /System/Library/LaunchDaemons/org.mongo.mongod.plist
sudo launchctl load /System/Library/LaunchDaemons/org.mongo.mongod.plist
sudo launchctl start org.mongo.mongod

Django Messaging for AJAX Calls Using Jquery

The messaging contrib app for Django has always been been tied to a user, which has prevented me from using it in any of my apps. Now that the Django 1.2 Alpha has been released, I’ve been able to play with it, and feel good about using it. My sites usually have a mix of AJAX and traditional HTTP requests. So figuring out a good solution to handle messages for the AJAX requests, while maintaining consistency in interacting with the API was important to me. Here’s my solution for handling messages in AJAX requests.

First we need a middleware that will detect if the request is an AJAX request. I always use JSON, but if you mix the data types, you might have to add some additional logic here. The middleware adds to the JSON any messages that we might have added in the view.

import simplejson as json

from django.contrib import messages

class AjaxMessaging(object):
    def process_response(self, request, response):
        if request.is_ajax():
            if response['Content-Type'] in ["application/javascript", "application/json"]:
                try:
                    content = json.loads(response.content)
                except ValueError:
                    return response

                django_messages = []

                for message in messages.get_messages(request):
                    django_messages.append({
                        "level": message.level,
                        "message": message.message,
                        "extra_tags": message.tags,
                    })

                content['django_messages'] = django_messages

                response.content = json.dumps(content)
        return response

Make sure to add the new middleware to settings.py.

'apps.main.middleware.AjaxMessaging'

So if we have a view that responds to an AJAX request, we can add a message the same way we would for a traditional HTTP response:

if success:
    messages.success(request, "The object has been modified.")
else:
    messages.error(request, "The object was not modified.")

We need a place to put the messages in our template:

<ul id="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
</ul>

We’ll be using a global ajaxComplete handler, so we’ll have to use a JSON parser. Simply include it with the rest of our javascript. jquery.json.js

And finally the handler, which simply loops through all the Django messages, and fades them out after three seconds.

function addMessage(text, extra_tags) {
    var message = $('<li class="'+extra_tags+'">'+text+'</li>').hide();
    $("#messages").append(message);
    message.fadeIn(500);

    setTimeout(function() {
        message.fadeOut(500, function() {
            message.remove();
        });
    }, 3000);
}

$(document).ready(function() {
    $('#messages').ajaxComplete(function(e, xhr, settings) {
        var contentType = xhr.getResponseHeader("Content-Type");

        if (contentType == "application/javascript" || contentType == "application/json") {
            var json = $.evalJSON(xhr.responseText);

            $.each(json.django_messages, function (i, item) {
                addMessage(item.message, item.extra_tags);
            });
        }
    }).ajaxError(function(e, xhr, settings, exception) {
        addMessage("There was an error processing your request, please try again.", "error");
    });

How to Handle HTTP Redirects with Jquery and Django

Jquery has a funny way of handling 302 redirects during AJAX calls. These frequently come up when I need to redirect a user to a login page. Here’s how I’m handling it with Django.

The underlying problem we experience with redirects in Jquery is that the redirects are followed. So instead of getting a status code of HTTP/1.1 302 Found, we get HTTP/1.1 200 OK. So one way to get around this is to return a made up status code. This is easily accomplished with a middleware in django.

from django.http import HttpResponseRedirect

class AjaxRedirect(object):
    def process_response(self, request, response):
        if request.is_ajax():
            if type(response) == HttpResponseRedirect:
                response.status_code = 278
        return response

Make sure to add the new middleware to settings.py.

'apps.main.middleware.AjaxRedirect'

And the final piece of the puzzle is to add a global AJAX complete handler, that checks for the new status code, redirects the page to the Location header in the HTTP response, and replace the next query variable. The last part is important because the login_required decorator automatically sets the next query variable to the HTTP Referrer, which in this case would be the URL of the AJAX call. So we want to replace it with the page we’re currently on.

$(document).ready(function() {
    $('body').ajaxComplete(function(e, xhr, settings) {
        if (xhr.status == 278) {
            window.location.href = xhr.getResponseHeader("Location").replace(/\?.*$/, "?next="+window.location.pathname);
        }
    });
});
  1. Download and install MySQL Package file [MySQL 5.1 for 10.5 (x86_64)]
  2. Install MySQL Startup Item [Howto]
  3. Turn on Web Sharing in System Preferences -> Sharing
    Screen shot 2009-11-29 at 5.19.43 PM
  4. Copy /etc/php.ini.default to /etc/php.ini
    sudo cp /etc/php.ini.default /etc/php.ini
  5. Add timezone information to php.ini (PHP will error without this)
    date.timezone = 'America/New_York'
  6. Install php-mcrypt
    1. Download libmcrypt 2.5.8
    2. Build and install .
      ./configure --disable-posix-threads --enable-dynamic-loading
      make
      sudo make install
    3. Download PHP 5.3.1
    4. Navigate to php-5.3.1/ext/mcrypt/
    5. Build and install .
      phpize
      ./configure
      make
      sudo make install
    6. Add extension to php.ini
      extension=mcrypt.so
  7. Install APC
    1. Download PCRE 8.0
    2. Build and install .
      ./configure
      make
      sudo make install
    3. Download APC 3.1.3p1
    4. Navigate to APC-3.1.3p1/APC-3.1.3p1/
    5. Build and install .
      phpize
      ./configure
      make
      sudo make install
    6. Add extension to php.ini
      extension=apc.so
  8. Download and install MySQL-python 1.2.3c1
    sudo python setup.py install
  9. Download and install libjpeg.v7
    ./configure --enable-shared
    make
    sudo make install
  10. Download and install Python Imaging Library 1.1.6
    sudo python setup.py install

MySQL Startup Item for Mac OS 10.6 Snow Leopard

Save the following as /System/Library/LaunchDaemons/org.mysql.mysqld.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>RunAtLoad</key>
	<true/>
	<key>Umask</key>
	<integer>7</integer>
	<key>UserName</key>
	<string>_mysql</string>
	<key>Disabled</key>
	<false/>
	<key>WorkingDirectory</key>
	<string>/usr/local/mysql</string>
	<key>GroupName</key>
	<string>_mysql</string>
	<key>KeepAlive</key>
	<true/>
	<key>Program</key>
	<string>/usr/local/mysql/bin/mysqld</string>
	<key>Label</key>
	<string>org.mysql.mysqld</string>
	<key>ProgramArguments</key>
	<array>
		<string>--user=_mysql</string>
	</array>
</dict>
</plist>

And in a bash session, run the following:

sudo chown root:wheel /System/Library/LaunchDaemons/org.mysql.mysqld.plist
sudo launchctl load /System/Library/LaunchDaemons/org.mysql.mysqld.plist
sudo launchctl start org.mysql.mysqld

Nginx Startup Script for Mac OS 10.6 Snow Leopard

If you don’t use MacPorts, and have a hard time finding a startup plist file for Nginx, and had trouble getting Nginx to startup with your custom plist file, this might be your solution:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>nginx</string>
    <key>Program</key>
    <string>/usr/local/nginx/sbin/nginx</string>
    <key>KeepAlive</key>
    <true/>
    <key>NetworkState</key>
    <true/>
    <key>LaunchOnlyOnce</key>
    <true/>
</dict>
</plist>

Just save this as /System/Library/LaunchDaemons/org.nginx.nginx.plist and perform the following in a bash session:

sudo chown root:wheel /System/Library/LaunchDaemons/org.nginx.nginx.plist
sudo launchctl load /System/Library/LaunchDaemons/org.nginx.nginx.plist
sudo launchctl start org.nginx.nginx

UPDATE: You can grab a beta version of 64-bit Growl 1.2 from the Growl beta site.

GrowlMail is broken on Mac OS X Snow Leopard (10.6). The dev team for Growl has a fix in the works, but I use Prowl and like to have notifications go to my iPhone when I’m not at my computer. So in the meantime, here is my solution.

Screen shot 2009-09-02 at 9.51.22 AM

I was originally inspired by James Higgs.

You first want to create a new AppleScript using AppleScript Editor and save it somewhere logical. I used /Library/Scripts/Mail Scripts/Rule Actions/Growl.scpt Below is the code:

-- Growl Alerts in Mail
-- Hunter Ford [http://www.cupcakewithsprinkles.com]
-- This script arises from the lack of any Growl Support in Mac OS X Snow Leopard (10.6)
-- Code inspired by and adapted from James Higgs [http://blog.jameshiggs.com/2009/08/28/growlmail-on-snow-leopard-a-temporary-fix/] as well as those mentioned.

tell application "GrowlHelperApp"
	-- Make a list of all the notification types
	-- that this script will ever send:
	set the allNotificationsList to {"New Email"}

	-- Make a list of the notifications
	-- that will be enabled by default.
	-- Those not enabled by default can be enabled later
	-- in the 'Applications' tab of the growl prefpane.
	set the enabledNotificationsList to {"New Email"}

	-- Register our script with growl.
	-- You can optionally (as here) set a default icon
	-- for this script's notifications.
	register as application "Mail" all notifications allNotificationsList default notifications enabledNotificationsList icon of application "Mail"
end tell

-- Mail Rule Trigger
--
-- Source: Benjamin S. Waldie [http://www.mactech.com/articles/mactech/Vol.21/21.09/ScriptingMail/index.html]
using terms from application "Mail"
	on perform mail action with messages theSelectedMessages for rule theRule
		repeat with thisMessage in theSelectedMessages
			-- Process the current message

			-- Grab the subject and sender of the message
			set growlSubject to subject of thisMessage
			set growlSender to my ExtractName(sender of thisMessage)

			-- Use the first 100 characters of a message
			set growlMessage to (content of thisMessage)
			set growlLength to (length of growlMessage)

			if growlLength > 100 then
				set growlMessage to (characters 1 through 100 of growlMessage) & "…"
			end if

			set growlMessage to growlSubject & "

" & growlMessage

			-- Send a Notification
			tell application "GrowlHelperApp"
				notify with name "New Email" title growlSender description growlMessage application name "Mail"
			end tell
		end repeat
	end perform mail action with messages
end using terms from

-- *ExtractName*
--
-- gathers the name portion from the "From: " line
--
-- Source: robJ [http://forums.macosxhints.com/archive/index.php/t-19954.html]
to ExtractName(sender_)
	if sender_ begins with "<" then
		return text 2 thru -2 of sender_
	else
		set oldTIDs to text item delimiters
		try
			set text item delimiters to "<"
			set name_ to first text item of sender_
			set text item delimiters to oldTIDs
		on error
			set text item delimiters to oldTIDs
		end try
		return name_
	end if
end ExtractName

Run this first in the AppleScript Editor. As this is what will “register” the notification with Growl. Click the green “play” button that says “Run”.

Screen shot 2009-09-01 at 10.46.18 PM

Then you want to create a new rule in mail that will run this script on every message that comes in.

Screen shot 2009-09-01 at 4.45.20 PM

Make sure to select “Don’t Apply” that way you don’t flood your screen with tons of Growl notifications.

Screen shot 2009-09-01 at 4.53.15 PM

Powered by WordPress | Theme: Motion by 85ideas.