<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Bennyland &#187; Uncategorized</title>
	<atom:link href="http://blog.bennyland.com/category/uncategorized/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.bennyland.com</link>
	<description>this server is running in my bedroom - benny’s learning how to run a linux server</description>
	<lastBuildDate>Thu, 06 Jan 2011 16:41:43 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>testing facebook integration</title>
		<link>http://blog.bennyland.com/2011/01/06/testing-facebook-integration/</link>
		<comments>http://blog.bennyland.com/2011/01/06/testing-facebook-integration/#comments</comments>
		<pubDate>Thu, 06 Jan 2011 16:41:43 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=135</guid>
		<description><![CDATA[]]></description>
			<content:encoded><![CDATA[<p> <img src='http://blog.bennyland.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2011/01/06/testing-facebook-integration/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>GAK! DDClient hack not working</title>
		<link>http://blog.bennyland.com/2011/01/06/gak-ddclient-hack-not-working/</link>
		<comments>http://blog.bennyland.com/2011/01/06/gak-ddclient-hack-not-working/#comments</comments>
		<pubDate>Thu, 06 Jan 2011 15:55:07 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=133</guid>
		<description><![CDATA[so my "dynamic-dns-support-for-whm-using-ddclient" hack isn't totally working... i only hope i have time soon to come up with a better solution... sigh.]]></description>
			<content:encoded><![CDATA[<p>so my "dynamic-dns-support-for-whm-using-ddclient" hack isn't totally working... i only hope i have time soon to come up with a better solution... sigh.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2011/01/06/gak-ddclient-hack-not-working/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Extending Piston&#8217;s BaseHandler for ForeignKey support</title>
		<link>http://blog.bennyland.com/2010/03/17/extending-pistons-basehandler-for-foreignkey-support/</link>
		<comments>http://blog.bennyland.com/2010/03/17/extending-pistons-basehandler-for-foreignkey-support/#comments</comments>
		<pubDate>Wed, 17 Mar 2010 20:33:36 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[django]]></category>
		<category><![CDATA[guide]]></category>
		<category><![CDATA[piston]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=116</guid>
		<description><![CDATA[Recently I've started working on an API using Django and Piston for an update to our internal timekeeping stuff (something I've been in charge of for most of the time I've been at VV). I quickly ran into a problem with Piston when trying to create or update models that have ForeignKeys in them. As [...]]]></description>
			<content:encoded><![CDATA[<p>Recently I've started working on an API using <a href="http://www.djangoproject.com">Django</a> and <a href="http://bitbucket.org/jespern/django-piston/wiki/Home">Piston</a> for an update to our internal timekeeping stuff (something I've been in charge of for most of the time I've been at VV).  I quickly ran into a problem with Piston when trying to create or update models that have ForeignKeys in them.  As it turns out, Piston does not support ForeignKey.  What follows is my attempt at adding this support as well as support for better REST urls that allow for searching and such.  I went this route after reading through <a href="http://groups.google.com/group/django-piston/browse_thread/thread/f8b13031eb4daa5">this post's</a> responses about this very problem.</p>
<p>A note of caution: I've only very recently started learning Python, Django, and Piston - so it's likely that this isn't the best approach... It's also pretty dirty as I'm currently using a combination of rc and {'status':0, 'statusmsg':'message here'} whenever errors happen... what would be better would be to always use rc.SOMETHING and throw errors that get caught and formatted correctly - I just haven't had time to clean this all up just yet...<br />
<span id="more-116"></span></p>
<p>For now, I'm just going to paste heavily commented code - if you have any questions just post in the comments.  Later I'll add some more detailed explanation on what I'm doing and why I went this direction.</p>
<p>handlers.py snippit</p>
<pre class="brush: python; title: ;">
# extend the BaseHandler class so we can add some additional
# features to it (mostly to support ForeignKey and fancy urls
class BaseApiHandler(BaseHandler):
    # post_name will serve as the name of the field in our output
    # data.  My output is formatted to contain a status
    # int, status message, and the actual data
    # default names this key 'data'
    post_name = 'data'

    # this method should be overridden in derived classes to process kwargs when reading data
    # This allows for urls to contain all sorts of search stuff rather than just /pkname/number
    def process_kwarg_read(self, key, value, d_post, b_exact):
        pass

    # override 'read' so we can better handle our api's searching capabilities
    def read(self, request, *args, **kwargs):
        # d_post holds the return data along with status and statusmsg
        # i default it here just for the sake of having something to look at
        d_post = {'status':0,'statusmsg':'Nothing Happened'}
        try:
            # setup the named response object
            # select all employees then filter - querysets are lazy in django
            # the actual query is only done once data is needed, so this may
            # seem like some memory hog slow beast, but it's actually not.
            d_post[self.post_name] = self.queryset(request)
            # s_query is used to hold debug information to be shown in the statusmsg
            s_query = ''

            # b_exact allows me to search with __exact or __contains depending on what the
            # person accessing the api wants
            b_exact = False
            if 'exact' in kwargs and kwargs['exact'] &lt;&gt; None:
                b_exact = True
                s_query = '\'exact\':True,'

            # iterate over the kwargs passed in through urls to process the search
            for key,value in kwargs.iteritems():
                # the regex url possibilities will push None into the kwargs dictionary
                # if not specified, so just continue looping through if that's the case
                if value == None or key == 'exact':
                    continue

                # write to the s_query string so we have a nice debug message
                s_query = '%s\'%s\':\'%s\',' % (s_query, key, value)

                # now process this key/value kwarg in the derived class
                self.process_kwarg_read(key=key, value=value, d_post=d_post, b_exact=b_exact)

            # when we're done looping, this runs:
            else:
                if d_post[self.post_name].count() == 0:
                    d_post['status'] = 0
                    d_post['statusmsg'] = '%s not found with query: {%s}' % (self.post_name, s_query)
                else:
                    d_post['status'] = 1
                    d_post['statusmsg'] = '%s %s found with query: {%s}' % (d_post[self.post_name].count(), self.post_name, s_query)
        except:
            e = sys.exc_info()[1]
            d_post['status'] = 0
            d_post['statusmsg'] = 'error: %s' % e
            d_post[self.post_name] = []

        return d_post

    # this method is used in derived classes to handle modal specific data
    def process_attr_item_create(self, inst, k, v):
        pass

    def flatten_data_request(self, request):
        # note: also have to support user matching for this method as well as the update method
        attrs = self.flatten_dict(request.data)
        # support posting json data inside a 'data' post variable
        if 'data' in attrs:
            ext_posted_data = simplejson.loads(request.data.get('data'))
            attrs = self.flatten_dict(ext_posted_data)
        return attrs

    # override 'create' to allow for the 'data' POST variable, as well as do some
    # fancy ForeignKey stuff
    def create(self, request, *args, **kwargs):
        if not self.has_model():
            return rc.NOT_IMPLEMENTED

        # get the actual data that wants to be created from the api request
        attrs = self.flatten_data_request(request)

        # we need to fix ForeignKeys because self.model.objects.get(**attrs) breaks
        # when one of the fields is a dictionary.  what happens when you call 'get(...)'
        # is Django creates a MySQL query with key=value.  In cases where the value is
        # a dict, you end up with '`key`={...}' which breaks the MySQL query
        # so here we convert ForeignKeys into their corresponding pk:
        for field in self.model._meta.fields:
            if field.rel:
                # this is a ForeignKey (as far as I can tell... _meta isn't documented)
                # so we need to convert it to it's pk equivilent... there's most likely
                # a better way of doing this than hardcoding stuff...
                if field.name in attrs:
                    pkfield = field.rel.to._meta.pk.name
                    if pkfield not in attrs[field.name]:
                        return {'status':0,'statusmsg':'pk field %s missing from foreignkey field %s' % (pkfield, field.name)}
                    else:
                        attrs[field.name] = attrs[field.name][pkfield]

        try:
            inst = self.model.objects.get(**attrs)
            return rc.DUPLICATE_ENTRY
        except self.model.DoesNotExist:
            inst = self.model()
            #inst = self.model(**attrs)
            # piston does not support ForeignKey, so we need to do this by hand <img src='http://blog.bennyland.com/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' />
            for k,v in attrs.iteritems():
                self.process_attr_item_create(inst, k, v)
            inst.save()
            return inst
        except self.model.MultipleObjectsReturned:
            return rc.DUPLICATE_ENTRY

    # override 'update' to allow for the 'data' POST variable, as well as do some
    # fancy ForeignKey stuff
    def update(self, request, *args, **kwargs):
        if not self.has_model():
            return rc.NOT_IMPLEMENTED

        pkfield = self.model._meta.pk.name

        if pkfield not in kwargs:
            # No pk was specified
            response = rc.BAD_REQUEST
            response.write(&quot;, &quot; + &quot;pkfield not in kwargs&quot;)
            return response

        try:
            inst = self.queryset(request).get(pk=kwargs[pkfield])
        except ObjectDoesNotExist:
            return rc.NOT_FOUND
        except MultipleObjectsReturned: # should never happen, since we're using a PK
            response = rc.BAD_REQUEST
            response.write(&quot;, &quot; + &quot;MultipleObjectsReturned&quot;)
            return response

        # now grab the data from the request and update the row accordingly
        attrs = self.flatten_data_request(request)
        for k,v in attrs.iteritems():
            # fix to ForeignKey
            field = self.get_field(k)
            if field.rel:
                # this is a foreignkey, set _id attr instead...
                # note: not sure if it's correct to set _id, or if there's a way to
                # get the real name of the id like there is to get the pkname
                pkfield = field.rel.to._meta.pk.name
                if pkfield not in v:
                    return {'status':0,'statusmsg':'pk field %s missing from foreignkey field %s' % (pkfield, field.name)}
                else:
                    setattr(inst, '%s_id'%k, v[pkfield])
            else:
                setattr( inst, k, v )

        inst.save()
        return rc.ALL_OK

    # delete has to be overridden to remove 'None' url params... then run the base delete
    def delete(self, request, *args, **kwargs):
        # store the keys... you can't remove things while itterating
        del_keys = []
        for key,value in kwargs.iteritems():
            # the regex url possibilities will push None into the kwargs dictionary
            # if not specified, so just continue looping through if that's the case
            if value == None:
                del_keys.append(key)
        # now remove the bad keys from kwargs
        for key in del_keys:
            del kwargs[key]
        # now run the base delete
        return BaseHandler.delete(self, request, *args, **kwargs)

    def get_field(self, f_name):
        return [f for f in self.model._meta.fields if f.name == f_name][0]

# so here's an example handler that uses BaseApiHandler's new functionality to deal with foreign keys and better urls:
class HoursDetailHandler(BaseApiHandler):
    #allowed_methods = ('GET', 'PUT', 'POST', )
    model = HoursDetail
    exclude = ()
    post_name = 'hours_detail'

    def process_kwarg_read(self, key, value, d_post, b_exact):
        # each query is handled slightly differently... when keys are added
        # handle them in here.  python doesn't have switch statements, this
        # could theoretically be performed using a dictionary with lambda
        # expressions, however I was affraid it would mess with the way the
        # filters on the queryset work so I went for the less exciting
        # if/elif block instead

        # querying on a specific row
        if key == 'id':
            d_post[self.post_name] = d_post[self.post_name].filter(pk=value)

        # filter based on employee id - this is guaranteed to happen once
        # per query (see read(...))
        elif key == 'empid':
            d_post[self.post_name] = d_post[self.post_name].filter(emp__id__exact=value)

        # look for a specific project by id
        elif key == 'projid':
            d_post[self.post_name] = d_post[self.post_name].filter(proj__id__exact=value)

        elif key == 'datestamp' or key == 'daterange':
            d_from = None
            d_to = None
            # first, regex out the times in the case of range vs stamp
            if key == 'daterange':
                m = re.match('(?P&lt;daterangefrom&gt;\d{4,}[-/\.]\d{2,}[-/\.]\d{2,})(?:to|/-)(?P&lt;daterangeto&gt;\d{4,}[-/\.]\d{2,}[-/\.]\d{2,})', \
                             value)
                d_from = datetime.strptime(m.group('daterangefrom'), '%Y-%m-%d')
                d_to = datetime.strptime(m.group('daterangeto'), '%Y-%m-%d')
            else:
                d_from = datetime.strptime(value, '%Y-%m-%d')
                d_to = datetime.strptime(value, '%Y-%m-%d')

            # now min/max to get midnight on day1 through just before midnight on day2
            # note: this is a hack because as of the writing of this app,
            # __date doesn't yet exist as a queryable field thus any
            # timestamps not at midnight were incorrectly left out
            d_from = datetime.combine(d_from, time.min)
            d_to = datetime.combine(d_to, time.max)

            d_post[self.post_name] = d_post[self.post_name].filter(clock_time__gte=d_from)
            d_post[self.post_name] = d_post[self.post_name].filter(clock_time__lte=d_to)

        else:
            raise NameError

    def read(self, request, *args, **kwargs):
        # empid is required, so make sure it exists before running BaseApiHandler's read method
        if not('empid' in kwargs and kwargs['empid'] &lt;&gt; None and kwargs['empid'] &gt;= 0):
            return {'status':0,'statusmsg':'empid cannot be empty'}
        else:
            return BaseApiHandler.read(self, request, *args, **kwargs)

    # handle foreign keys in the create process by looking them up by id
    # the 'create' method truncates the dictionary of a foreignkey down to it's
    # pk, so we need to expand that again by searching for the id in the table
    def process_attr_item_create(self, inst, k, v):
        if k == 'emp':
            setattr(inst, k, Employee.objects.get(pk=v))
        elif k == 'proj':
            setattr(inst, k, Project.objects.get(pk=v))
        else:
            setattr(inst, k, v)
</pre>
<p>urls.py</p>
<pre class="brush: python; title: ;">
    # formatting your url like this might seem crazy, but it allows your end api user to format their api call
    # in any order they want to and you can have a single url handler here and easily add to it as you need more
    # things - note: always add before the /exact line.
    #hours_detail/id/{id}/empid/{empid}/projid/{projid}/datestamp/{datestamp}/daterange/{fromdate}to{todate}/exact
    #empid is required
    url(r'^api/hours_detail/(?:' + \
        '(?:[/]?id/(?P&lt;id&gt;\d+))?' + \
        '(?:[/]?empid/(?P&lt;empid&gt;\d+))?' + \
        '(?:[/]?projid/(?P&lt;projid&gt;\d+))?' + \
        '(?:[/]?datestamp/(?P&lt;datestamp&gt;\d{4,}[-/\.]\d{2,}[-/\.]\d{2,}))?' + \
        '(?:[/]?daterange/(?P&lt;daterange&gt;(?:\d{4,}[-/\.]\d{2,}[-/\.]\d{2,})(?:to|/-)(?:\d{4,}[-/\.]\d{2,}[-/\.]\d{2,})))?' + \
        ')+(?:/(?P&lt;exact&gt;exact))?$', hours_detail_resource),
</pre>
<p>models.py (for reference)</p>
<pre class="brush: python; title: ;">

# here's what the HoursDetail model looks like, you can see it has several foreign keys
class HoursDetail(models.Model):
    #id = models.IntegerField(primary_key=True)
    emp = models.ForeignKey(Employee)
    proj = models.ForeignKey(Project)
    datestamp = models.DateTimeField(null=True, blank=True)
    entry_type = models.CharField(max_length=192)
    hours_per_day = models.FloatField(default=0, blank=True)
    clock_time = models.DateTimeField(null=True, blank=True)
    is_clockin = models.BooleanField(default=False)
    status = models.CharField(max_length=255, null=True, blank=True)
    def __unicode__(self):
        return '%s, %s on %s worked on %s' % (self.emp.lname, self.emp.fname, self.datestamp, self.proj.name)
    class Meta:
        db_table = u'hours_detail'
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2010/03/17/extending-pistons-basehandler-for-foreignkey-support/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Update: Creating a TrashCan for shell undelete</title>
		<link>http://blog.bennyland.com/2010/02/13/creating-a-trashcan-for-rm-mistakes/</link>
		<comments>http://blog.bennyland.com/2010/02/13/creating-a-trashcan-for-rm-mistakes/#comments</comments>
		<pubDate>Sun, 14 Feb 2010 00:26:33 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[guide]]></category>
		<category><![CDATA[shell]]></category>
		<category><![CDATA[trash]]></category>
		<category><![CDATA[undelete]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=83</guid>
		<description><![CDATA[update: I updated this script so that it would put a number at the end of the file name if it already exists in the trash... it's a slow process (increment from 0 and test until you don't find an existing file) but it works Original post follows: so a couple of weeks ago I [...]]]></description>
			<content:encoded><![CDATA[<p>update: I updated this script so that it would put a number at the end of the file name if it already exists in the trash... it's a slow process (increment from 0 and test until you don't find an existing file) but it works <img src='http://blog.bennyland.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Original post follows:<br />
so a couple of weeks ago I was up far later than I should have been and managed to run the following, as root, in the root directory /</p>
<pre class="brush: bash; title: ;">rm -rf *</pre>
<p>What I was trying to do is delete a bunch of backup files that my backup script had been sticking in the wrong location (the smart thing would have been to copy the wrongly placed backup files into the correct place, and also not use -r<strong>f</strong>, but i'm a noob)</p>
<p>So, as you can imagine, after about 2 seconds (the time it took me to realize what had just happened and instantly wake up) the server was foobared.  Now that it's been a couple weeks I can actually marvel at how fast rm is, it literally deleted almost everything on the server (everything that wasn't running at least) in a matter of seconds, that's pretty impressive when you are use to deleting things in windows (and I almost always shift delete, a dangerous pattern emerges...)</p>
<p>Anyway, so I had to re-install the OS and everything else. Luckily I had a 5 day old backup of the web and svn contents, and some chrome caches of my blog (which had all of my instructions for getting the server back up and going) so I wasn't fully up a creek.  After restoring everything I did some searching around and <a href="http://www.gentoo-wiki.info/HOWTO_Move_Files_to_a_Trash">found a way</a> to replace rm with a move to trash bash script which is complemented by a cron that runs to clean out old trash.</p>
<p>Here's how to get the same thing<br />
<span id="more-83"></span></p>
<p>edit your .bashrc file and remove your rm alias (if there is one) and add the following two lines:</p>
<pre class="brush: bash; title: ;">vim ~/.bashrc</pre>
<pre class="brush: bash; title: ;">alias rm='/usr/local/bin/del'
alias rm.old='/bin/rm -i'
alias emptytrash='/usr/local/bin/emptytrash'</pre>
<p>rm.old will now work as rm did, however I try to forget it exists lest I do the same thing again!</p>
<p>Now, create the /usr/local/bin/del bash script which will look like the following:</p>
<pre class="brush: bash; title: ;">vim /usr/local/bin/del</pre>
<pre class="brush: bash; title: ;">#!/bin/bash
# Description:  Replacement for rm. It moves files to trashbin directories
#               which are stored on each mount point. trashbins can be different
#               for each mountpoint. All trashbins are logged in TRASHBIN_LOG.

TRASH=.Trash-$USER
TRASHBIN_LOG=~/.trashbins

## this is needed to reset TRASH variable for each argument
TRASH_DIR=$TRASH

## loop through all variables that are not options, and mv each to a trashbin.
while [ ! -z $1 ]; do
    if [ ${1:0:1} = &quot;-&quot; ]; then
            shift
    else

    ## reset trash variable.
    ## this is important for deleteing multiple files
    TRASH=$TRASH_DIR

    ## Get the Current mount point
    MNT=`df &quot;$1&quot; | grep / | awk '{ for (i=6;i&lt;=NF;i++) { printf &quot; &quot; $i } printf &quot;\n&quot;  }'`
    MNT=${MNT:1}

    ## Trash bins may be relocated depending on mount point.
    ## you may change these to your liking.
    ## TODO: this should be easier to edit, using variables
    if [ &quot;$MNT&quot; = &quot;/&quot; ]; then
        TRASH=/tmp/&quot;$TRASH&quot;
    elif [ &quot;$MNT&quot; = &quot;/home&quot; ]; then
        TRASH=/home/$USER/&quot;$TRASH&quot;
    else
        TRASH=&quot;$MNT&quot;/&quot;$TRASH&quot;
    fi

    ## Test for the TRASH folder
    if [ ! -d &quot;$TRASH&quot; ]; then
        if mkdir &quot;$TRASH&quot; ; then
            chmod u+rw &quot;$TRASH&quot;
            echo creating $TRASH
            echo writing info to  ~/.trashbins
            echo $TRASH &gt;&gt; ~/.trashbins
        else
            echo ERROR: cannot create $TRASH
            exit 1;
        fi
    fi

    ## Move each file to the trash, after updating time-stamp.
         touch &quot;$1&quot;
         MOVETO=&quot;$TRASH/$1&quot;
         MOVETOTWO=&quot;$TRASH/$1&quot;
         n=0
         while [ -e $MOVETOTWO ]; do
             let &quot;n++&quot;
             MOVETOTWO=&quot;$TRASH/$1.$n&quot;
         done
         mv -v &quot;$1&quot; &quot;$MOVETOTWO&quot;
    shift
    fi
done
</pre>
<pre class="brush: bash; title: ;">chmod 755 /usr/local/bin/del</pre>
<p>I also created an emptytrash script</p>
<pre class="brush: bash; title: ;">vim /usr/local/bin/emptytrash</pre>
<pre class="brush: bash; title: ;">#!/bin/bash
#
#   Empty the contents in directories listed in ~/.trashbins
#
for i in `cat ~/.trashbins`
    do
    if  [ `ls -a1  $i | wc -l ` -gt 2 ] ; then
            find &quot;$i&quot; -print -delete
    fi
done
</pre>
<pre class="brush: bash; title: ;">chmod 755 /usr/local/bin/emptytrash
source ~/.bashrc</pre>
<p>Now that you've got everything set up, you'll want to make a crontab that deletes old trash.  I picked 7 days as the "old enough to delete" timeframe</p>
<pre class="brush: bash; title: ;">/etc/cron.daily/clean_trash.sh</pre>
<pre class="brush: bash; title: ;">#!/bin/bash
#
#   Empty the contents in directories listed in ~/.trashbins
#
for i in `cat ~/.trashbins`
    do
    if  [ `ls -a1  $i | wc -l ` -gt 2 ] ; then
            find &quot;$i&quot; -mtime +7 -delete
    fi
done </pre>
<pre class="brush: bash; title: ;">chmod 755 /etc/cron.daily/clean_trash.sh</pre>
<p>and there you have it... whenever you rm something it'll go into trash.  you can recover it from there or you can empty the trash whenever you want.  every day things that are 7 days or older in your trash folder will be deleted. and you can always run emptytrash whenever you want and it'll wipe the trash clean <img src='http://blog.bennyland.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2010/02/13/creating-a-trashcan-for-rm-mistakes/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>New HD</title>
		<link>http://blog.bennyland.com/2010/02/12/new-hd/</link>
		<comments>http://blog.bennyland.com/2010/02/12/new-hd/#comments</comments>
		<pubDate>Fri, 12 Feb 2010 23:31:34 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=110</guid>
		<description><![CDATA[The hard-drive on my desktop started having S.M.A.R.T. errors yesterday so I went and picked up a WD Caviar Black and threw 64 bit Win 7 on it. Turns out the HD I had was still under warranty, so there will be another 500gigs coming my way sometime in the future... I'll have to figure [...]]]></description>
			<content:encoded><![CDATA[<p>The hard-drive on my desktop started having S.M.A.R.T. errors yesterday so I went and picked up a WD Caviar Black and threw 64 bit Win 7 on it.  Turns out the HD I had was still under warranty, so there will be another 500gigs coming my way sometime in the future... I'll have to figure out what to do with it!</p>
<p>I have another tutorial on how to get the latest python running in CentOS 5.4 - hopefully I'll finish writing that this weekend.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2010/02/12/new-hd/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>I&#8217;m learning Python</title>
		<link>http://blog.bennyland.com/2010/02/11/im-learning-python/</link>
		<comments>http://blog.bennyland.com/2010/02/11/im-learning-python/#comments</comments>
		<pubDate>Thu, 11 Feb 2010 18:59:20 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[books]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=106</guid>
		<description><![CDATA[As the subject of this post suggests, I'm learning python. I was going to go out to the local bookstore today to buy a book on python (I normally just try to learn things on my own, but I'm unfortunately in a bit of a hurry and am finding it difficult to find useful tutorials [...]]]></description>
			<content:encoded><![CDATA[<p>As the subject of this post suggests, I'm learning python.  I was going to go out to the local bookstore today to buy a book on python (I normally just try to learn things on my own, but I'm unfortunately in a bit of a hurry and am finding it difficult to find useful tutorials online) but it turned out I didn't need to.  <a href="http://diveintopython.org">Dive Into Python</a> is available free online, or in book form, as well as <a href="http://www.greenteapress.com/thinkpython/">Python for Software Design - How to Think Like a Computer Scientist</a>   I'm midway through chapter 3 in Dive... and my inability to stay focused has caused me to write this post.</p>
<p>I love free <img src='http://blog.bennyland.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Yesterday I hooked up a post-commit script in my svn repository that automatically publishes the python code I'm currently trying to write.  It's pretty cool as it allows you to source control your web(site/apps)  I'll post later on how to do such a thing.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2010/02/11/im-learning-python/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Serving ASP.NET pages in Apache on CentOS 5</title>
		<link>http://blog.bennyland.com/2010/02/06/serving-asp-net-pages-in-apache-on-centos-5/</link>
		<comments>http://blog.bennyland.com/2010/02/06/serving-asp-net-pages-in-apache-on-centos-5/#comments</comments>
		<pubDate>Sat, 06 Feb 2010 18:59:32 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[asp]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[guide]]></category>
		<category><![CDATA[mono]]></category>
		<category><![CDATA[server]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=78</guid>
		<description><![CDATA[I'm starting to love ASP.NET and the ability to do everything in C# (I'm currently working on an internal silverlight app at work and creating a RESTful API is more fun in C#). Anyway, you can do a yum install mod_mono, but it's a pretty old version so I did some research and found a [...]]]></description>
			<content:encoded><![CDATA[<p>I'm starting to love ASP.NET and the ability to do everything in C# (I'm currently working on an internal silverlight app at work and creating a RESTful API is more fun in C#).  Anyway, you can do a yum install mod_mono, but it's a pretty old version so I did some research and found a <a href="http://poormanstech.blogspot.com/2007/05/installing-mono.html">guide on building it yourself</a> along with two other guides that were roughly the same, but not as good... (<a href="http://blog.rubypdf.com/2009/10/23/how-to-install-mono-2-4-2-3-on-centos-5/">1</a>, <a href="http://init.sh/?p=74">2</a>) and somehow ended up with a working server.  This is a companion to that guide with more up to date versions of things. (it took me SEVERAL trial and error attempts to get things working :/ so hopefully i didn't miss any steps)</p>
<p>Let me preface this with the fact that if you're running one of the already supported OSs (debian or unbuntu for instance) you shouldn't follow this guide - instead you should just look on the mono website for information.<br />
<span id="more-78"></span><br />
First thing I did was grab the most up-to-date mono and xst sources (i matched versions)</p>
<pre class="brush: bash; title: ;">cd /tmp
wget http://ftp.novell.com/pub/mono/sources/mono/mono-2.6.tar.bz2
wget http://ftp.novell.com/pub/mono/sources/mod_mono/mod_mono-2.6.tar.bz2
wget http://ftp.novell.com/pub/mono/sources/xsp/xsp-2.6.tar.bz2</pre>
<p>Then unpack everything</p>
<pre class="brush: bash; title: ;">tar -jxvf mono-2.6.tar.bz2
 tar -jxvf xsp-2.6.tar.bz2
 tar -jxvf mod_mono-2.6.tar.bz2</pre>
<p>then build mono first (change anything on the ./configure line to your liking, I do suggest picking a place to put mono, letting it install wherever it wants to makes the rest of the process very painful!):</p>
<pre class="brush: bash; title: ;">cd /tmp/mono-2.6
./configure --prefix=/opt/mono
make ; make install
echo export PKG_CONFIG_PATH=/opt/mono/lib/pkgconfig:$PKG_CONFIG_PATH&gt;&gt;~/.bash_profile
echo export PATH=/opt/mono/bin:$PATH&gt;&gt;~/.bash_profile
source ~/.bash_profile</pre>
<p>after a bit you'll have mono installed <img src='http://blog.bennyland.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Now build xsp (i had to add pkgconfig to an environment variable to get it to work)</p>
<pre class="brush: bash; title: ;">cd /tmp/xsp-2.6
./configure --prefix=/opt/mono
make ; make install</pre>
<p>Finally build mod_mono, you'll probably need to tell it where apache's apr-config is... it took a while for me to find it.. heh</p>
<pre class="brush: bash; title: ;">find / -iname apr*config
cd /tmp/mod_mono-2.6
./configure --prefix=/opt/mono --with-mono-prefix=/opt/mono --with-apr-config=/usr/bin/apr-1-config
make ; make install</pre>
<p>you should now find mod_mono in your apache modules dir (in my case /usr/lib64/httpd/modules/)</p>
<pre class="brush: bash; title: ;">ls /usr/lib64/httpd/modules/mod_mono*
17 Feb  5 22:54 /usr/lib64/httpd/modules/mod_mono.so -&gt; mod_mono.so.0.0.0
145511 Feb  5 22:54 /usr/lib64/httpd/modules/mod_mono.so.0.0.0
</pre>
<p>the mod_mono install seems to have automatically created mod_mono.conf in my httpd/conf folder, but you may want to make sure it's there</p>
<pre class="brush: bash; title: ;">vim /etc/httpd/conf/mod_mono.conf</pre>
<pre class="brush: plain; title: ;"># mod_mono.conf

# Achtung! This file may be overwritten
# Use 'include mod_mono.conf' from other configuration file
# to load mod_mono module.

&lt;IfModule !mod_mono.c&gt;
    LoadModule mono_module /usr/lib64/httpd/modules/mod_mono.so
&lt;/IfModule&gt;

&lt;IfModule mod_headers.c&gt;
    Header set X-Powered-By &quot;Mono&quot;
&lt;/IfModule&gt;

AddType application/x-asp-net .aspx
AddType application/x-asp-net .asmx
AddType application/x-asp-net .ashx
AddType application/x-asp-net .asax
AddType application/x-asp-net .ascx
AddType application/x-asp-net .soap
AddType application/x-asp-net .rem
AddType application/x-asp-net .axd
AddType application/x-asp-net .cs
AddType application/x-asp-net .vb
AddType application/x-asp-net .master
AddType application/x-asp-net .sitemap
AddType application/x-asp-net .resources
AddType application/x-asp-net .skin
AddType application/x-asp-net .browser
AddType application/x-asp-net .webinfo
AddType application/x-asp-net .resx
AddType application/x-asp-net .licx
AddType application/x-asp-net .csproj
AddType application/x-asp-net .vbproj
AddType application/x-asp-net .config
AddType application/x-asp-net .Config
AddType application/x-asp-net .dll
DirectoryIndex index.aspx
DirectoryIndex Default.aspx
DirectoryIndex default.aspx
</pre>
<p>Now, you need to get this to work with ISPConfig3, since if you've followed my other tutorials, that's what you have running things on the back end.</p>
<p>Modify your httpd.conf file to include mod_mono.conf.  You want to do this at the very bottom, just before the NameVirtualHost directives (ISPConfig puts 3 directives at the very bottom of httpd.conf, you want to place this line just before them:</p>
<pre class="brush: bash; title: ;">vim /etc/httpd/conf/httpd.conf</pre>
<pre class="brush: plain; title: ;">Include /etc/httpd/conf/mod_mono.conf
# this is where ISPConfig's stuff is, don't modify it, this comment is so you know where to put the line above the comment <img src='http://blog.bennyland.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />
NameVirtualHost *:80
NameVirtualHost *:443
Include /etc/httpd/conf/sites-enabled/
</pre>
<p>now, restart apache</p>
<pre class="brush: bash; title: ;">service httpd restart</pre>
<p>and open up ISPConfig.  go to sites > website > Select the website you want to add a mono enabled directory to, then click on the "Options" tab for that site.  Enter the following into the Apache Directives (<strong>replace /var/www/path/to/the/web/folder with the path to that site's web folder</strong>)</p>
<pre class="brush: plain; title: ;">MonoPath default &quot;/opt/mono/lib/mono/2.0&quot;
MonoServerPath default /opt/mono/bin/mod-mono-server
AddMonoApplications default &quot;/test:/var/www/path/to/the/web/folder/test&quot;
&lt;location /test&gt;
	MonoSetServerAlias default
	SetHandler mono
&lt;/location&gt;
</pre>
<p>and finally restart httpd</p>
<pre class="brush: bash; title: ;">service httpd restart</pre>
<p>here's a test page, create it in your /var/www/path/to/the/web/folder/test directory and set the permissions to whatever the other site's permissions are, or apache.apache</p>
<pre class="brush: xml; title: ;">&lt;html&gt;
&lt;body&gt;
&lt;% Response.Write(&quot;Hello World!&quot;); %&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>now, once ISPConfig restarts apache (sometimes this is instant but i've seen it take up to 5 minutes) you'll see Hello World! displayed</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2010/02/06/serving-asp-net-pages-in-apache-on-centos-5/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Dynamic DNS support for WHM using ddclient</title>
		<link>http://blog.bennyland.com/2010/01/20/dynamic-dns-support-for-whm-using-ddclient/</link>
		<comments>http://blog.bennyland.com/2010/01/20/dynamic-dns-support-for-whm-using-ddclient/#comments</comments>
		<pubDate>Wed, 20 Jan 2010 23:11:01 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[dynamic dns]]></category>
		<category><![CDATA[guide]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[whm]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=36</guid>
		<description><![CDATA[The server which I use to use to host my websites is now primarily used as my DNS and also a backup ftp space for this server to store backups (that way I don't have to buy some sort of tape backup thing). This is great except for the fact that my IP changes a [...]]]></description>
			<content:encoded><![CDATA[<p>The server which I use to use to host my websites is now primarily used as my DNS and also a backup ftp space for this server to store backups (that way I don't have to buy some sort of tape backup thing).  This is great except for the fact that my IP changes a couple of times a year.  Up until now I was pushing requests from the webhost's DNS to <a title="dyndns" href="http://dyndns.org" target="_blank">dyndns</a> and then to my home server running <a title="ddclient" href="http://ddclient.sourceforge.net/" target="_blank">ddclient</a> to update dyndns.  Of course I could have paid around $30 a year per domain but I'm cheap.</p>
<p>So I modified ddclient so that it would update my host's DNS server.  My host uses <a title="cpanel and whm" href="http://www.cpanel.net/" target="_blank">cpanel/WHM</a> which luckily has a JSON API!</p>
<p>Here's how you can make the same modifications:<span id="more-36"></span></p>
<p>Install ddclient on your server - this is easy</p>
<pre class="brush: bash; title: ;">yum install ddclient</pre>
<p>You're also going to need some perl mods:</p>
<pre class="brush: bash; title: ;">cpan</pre>
<p>note: don't just copy and paste these 4 lines at once, some of the installs may have you type "yes" at some point.  In the case of WWW::Mechanize, it forced me to type "yes" at least 4 times.</p>
<pre class="brush: plain; title: ;">
install JSON
install JSON::XS
install WWW::Mechanize
exit
</pre>
<p>Now, make sure you have version 3.7.3 of ddclient and then patch it with this <a href='http://blog.bennyland.com/wp-content/uploads/2010/01/ddclient.tar.gz'>ddclient 3.7.3 patch file</a></p>
<pre class="brush: bash; title: ;">
cd /tmp
wget http://blog.bennyland.com/wp-content/uploads/2010/01/ddclient.tar.gz
tar -zxvf ddclient.tar.gz
cd ddclient patch -p0 /usr/sbin/ddclient &lt; ddclient.patch
</pre>
<p>Alternatively you can follow these directions:</p>
<pre class="brush: bash; title: ;">vim /usr/sbin/ddclient</pre>
<div style="border-top: black 2px dotted">
replace this:</p>
<pre class="brush: perl; first-line: 14; title: ;">
require 5.004;
use strict;
use Getopt::Long;
use Sys::Hostname;
use IO::Socket;
</pre>
<p>with:</p>
<pre class="brush: perl; first-line: 14; highlight: [19]; title: ;">
require 5.004;
use strict;
use Getopt::Long;
use Sys::Hostname;
use IO::Socket;
use WWW::Mechanize;
use JSON -support_by_pp;
</pre>
</div>
<div style="border-top: black 2px dotted">
replace this:</p>
<pre class="brush: perl; first-line: 474; title: ;">
    'sitelutions' =&gt; {
        'updateable' =&gt; undef,
        'update'     =&gt; \&amp;nic_sitelutions_update,
        'examples'   =&gt; \&amp;nic_sitelutions_examples,
        'variables'  =&gt; merge(
                          { 'server'       =&gt; setv(T_FQDNP,  1, 0, 1, 'www.sitelutions.com',   undef)    },
                          { 'min-interval' =&gt; setv(T_DELAY,  0, 0, 1, 0, interval('5m')),},
                          $variables{'service-common-defaults'},
                        ),
    },
</pre>
<p>with:</p>
<pre class="brush: perl; first-line: 474; highlight: [484]; title: ;">
    'sitelutions' =&gt; {
        'updateable' =&gt; undef,
        'update'     =&gt; \&amp;nic_sitelutions_update,
        'examples'   =&gt; \&amp;nic_sitelutions_examples,
        'variables'  =&gt; merge(
                          { 'server'       =&gt; setv(T_FQDNP,  1, 0, 1, 'www.sitelutions.com',   undef)    },
                          { 'min-interval' =&gt; setv(T_DELAY,  0, 0, 1, 0, interval('5m')),},
                          $variables{'service-common-defaults'},
                        ),
    },
    'whm' =&gt; {
        'updateable' =&gt; undef,
        'update'     =&gt; \&amp;nic_whm_update,
        'examples'   =&gt; \&amp;nic_whm_examples,
        'variables'  =&gt; merge(
                          { 'min-interval' =&gt; setv(T_DELAY,  0, 0, 1, interval('5m'), 0),},
                          $variables{'service-common-defaults'},
                        ),
    },
</pre>
</div>
<div style="border-top: black 2px dotted">
replace this:</p>
<pre class="brush: perl; first-line: 1410; title: ;">
sub split_by_comma {
    my $string = shift;

    return split /\s*[, ]\s*/, $string if defined $string;
    return ();
}
</pre>
<p>with:</p>
<pre class="brush: perl; first-line: 1410; highlight: [1416]; title: ;">
sub split_by_comma {
    my $string = shift;

    return split /\s*[, ]\s*/, $string if defined $string;
    return ();
}
sub split_by_semi {
    my $string = shift;

    return split /\s*[; ]\s*/, $string if defined $string;
    return ();
}
</pre>
</div>
<div style="border-top: black 2px dotted">
and at the very bottom of the file replace this:</p>
<pre class="brush: perl; first-line: 3185; title: ;">
######################################################################
# vim: ai ts=4 sw=4 tw=78 :
</pre>
<p>with:</p>
<pre class="brush: perl; first-line: 3185; highlight: [3185,3354]; title: ;">
######################################################################

######################################################################
## nic_whm_examples
######################################################################
sub nic_whm_examples {
    return &lt;&lt;EoEXAMPLE;

o 'whm'

The 'whm' protocol is an API provided by the cpanel/WHM system.

Configuration variables applicable to the 'whm' protocol are:
  protocol=whm                                   ##
  server=ip.of.your.whm.login  ## defaults to sitelutions.com
  login=service-login          ## login name and password  registered with the service
  password=service-password    ## whm hash used for API (not your whm password)
  host.to.update;host.to.update. #

Example ${program}.conf file entries:
  ## single host update
  protocol=whm,                                                                                                                                 \\
  login=my-whm-login,                                                                                                           \\
  password=a-really-long-hash-string                                            \\
  myhost.com;myhost.com.;subdomain1.myhost.com.;subdomain2.myhost.com.,myotherhost.com;subdomain2.myotherhost.com.

EoEXAMPLE
}
######################################################################
## nic_whm_update
##
## written by Benny Raymond
##
######################################################################
sub nic_whm_update {
    debug(&quot;\nnic_whm_update -------------------&quot;);
                my $has_failed = 0;
    ## update each configured host
    foreach my $h (@_) {
        my @host_and_names = split_by_semi($h);
        my $host_to_update = $host_and_names[0];
        my %hosts_names;
        undef %hosts_names;
        my $i = 0;
                for (@host_and_names) {
                        if ($i==0) {
                                $i++;
                        } else {
                                $hosts_names{$_} = 1;
                        }
                }
        info(&quot;setting IP address to %s for %s&quot;, $ip, $host_to_update);
        verbose(&quot;UPDATE:&quot;,&quot;updating %s&quot;, $host_to_update);

        my $url_lookup;
        $url_lookup     = &quot;http://$config{$h}{'server'}/json-api/dumpzone&quot;;
        $url_lookup .= &quot;?domain=$host_to_update&quot;;

        my $api_output = json_whm_fetch($config{$h}{'login'},$config{$h}{'password'},$url_lookup);
        if (!defined($api_output) || !$api_output) {
            failed(&quot;updating %s: Could not connect to %s.&quot;, $h, $url_lookup);
            last;
        }
        if (json_whm_status($api_output) != 1)
        {
                failed(&quot;updating %s: %s - %s&quot;, $h, json_whm_status_msg($api_output),  $url_lookup);
        }

        # loop through names and update
        # get the A records
        my(@a_records) = @{(json_whm_a_records($api_output))};
        # and update the DNS table
        foreach (@a_records)
        {
                my(%record) = %{($_)};
                if ($hosts_names{$record{name}})
                {
                    if ($record{address} eq $ip)
                    {
                        success(&quot;updating '%s' - '%s': good: IP already set to %s&quot;, $host_to_update, $record{name}, $ip);
                    }
                    else
                    {
                                # update this line
                    #    print $record{Line} . &quot;&gt;&quot; . $record{name};
                        $url_lookup     = &quot;http://$config{$h}{'server'}/json-api/editzonerecord&quot;;
                        $url_lookup .= &quot;?domain=$host_to_update&quot;;
                        $url_lookup .= &quot;&amp;Line=$record{Line}&quot;;
                        $url_lookup .= &quot;&amp;address=$ip&quot;;
                        my $api_update_output = json_whm_fetch($config{$h}{'login'},$config{$h}{'password'},$url_lookup);

                        if (json_whm_status($api_update_output) != 1)
                        {
                                failed(&quot;updating '%s' - '%s': %s %s&quot;, $host_to_update, $record{name}, json_whm_status_msg($api_update_output), $url_lookup);
                                $has_failed = 1;
                                $config{$h}{'status'} = 'failed';
                        } else {
                                success(&quot;updating '%s' - '%s': good: IP address set to %s&quot;, $host_to_update, $record{name}, $ip);
                        }
                     }
                } else {
                        # warning(&quot;name '%s' not found for host '%s'&quot;, $record{name}, $host_to_update);
                }
        }

        if ($has_failed == 0)
        {
          $config{$h}{'ip'}     = $ip;
          $config{$h}{'mtime'}  = $now;
          $config{$h}{'status'} = 'good';
        }
    }
}
sub json_whm_a_records
{
        my(%json) = %{(shift)};
        my(@records) = @{($json{result}[0]{record})};
        my(@a_records);
        foreach (@records)
        {
                my(%record) = %{($_)};
                if ($record{type} eq &quot;A&quot;)
                {
                        push(@a_records, \%record);
                }
        }
        return \@a_records;
}
sub json_whm_status
{
        my(%json) = %{(shift)};
        my $status = $json{result}[0]{status};
        return $status;
}
sub json_whm_status_msg
{
        my(%json) = %{(shift)};
        my $status = $json{result}[0]{statusmsg};
        return $status;
}
sub json_whm_fetch
{
        my ($whm_api_login) = $_[0];
        my ($whm_api_key) = $_[1];
        my ($json_url) = $_[2];
        my $browser = WWW::Mechanize-&gt;new();
        my @args = (
                Authorization =&gt; &quot;WHM &quot; . $whm_api_login . &quot;:&quot; . $whm_api_key
        );
        my $json_text = &quot;&quot;;
        eval{
                # download the json page:
                # print &quot;Getting json $json_url\n&quot;;
                $browser-&gt;get( $json_url, @args );
                my $content = $browser-&gt;content();
                my $json = new JSON;

                # these are some nice json options to relax restrictions a bit:
                $json_text = $json-&gt;allow_nonref-&gt;utf8-&gt;relaxed-&gt;escape_slash-&gt;loose-&gt;allow_singlequote-&gt;allow_barekey-&gt;decode($content);
        };
        # catch crashes:
        if($@){
                print STDERR &quot;[[JSON ERROR]] JSON parser crashed! $@\n&quot;;
                return &quot;&quot;;
        } else {
                return $json_text;
        }
}

######################################################################
# vim: ai ts=4 sw=4 tw=78 :
</pre>
</div>
<p>Save and close ddclient</p>
<pre class="brush: plain; title: ;">:wq</pre>
<p>Now, edit ddclient.conf.</p>
<pre class="brush: bash; title: ;">vim /etc/ddclient/ddclient.conf</pre>
<p>Here's a simple version (note: you need to log into WHM and get your API key or none of this is going to work.  You'll be entering your API key as one line - in WHM it is copied as several lines, just remove the carriage returns and enter it in one huge line as your password).  Notice how my domain looks, it's my domain name, followed by a colon, followed by colon seperated hostnames to update - they all have periods after them!</p>
<pre class="brush: perl; title: ;">
daemon=300                              # check every 300 seconds
syslog=yes                              # log update msgs to syslog
mail=root                               # mail all msgs to root
mail-failure=root                       # mail failed update msgs to root
pid=/var/run/ddclient.pid               # record PID in file.
ssl=yes                                 # use ssl-support.  Works with
                                        # ssl-library
## To obtain an IP address from Web status page (using the proxy if defined)
use=web, web=checkip.dyndns.org/, web-skip='IP Address' # found after IP Address

##
## WHM
##
server=host.or.ip.of.your.whm.maybe.with.port,       \
protocol=whm,                   \
login=your-whm-username,                 \
password=crazy-long-api-key-as-one-long-line               \
hostname.com;hostname.com.;subdomain.hostname.com.
</pre>
<p>Save and close ddclient.conf</p>
<pre class="brush: plain; title: ;">:wq</pre>
<p>And finally run it and tell it to run every time the server reboots:</p>
<pre class="brush: bash; title: ;">
/sbin/chkconfig ddclient on
/sbin/service ddclient start
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2010/01/20/dynamic-dns-support-for-whm-using-ddclient/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Installing Trac and Subversion on CentOS5</title>
		<link>http://blog.bennyland.com/2010/01/18/installing-trac-and-subversion-on-centos5/</link>
		<comments>http://blog.bennyland.com/2010/01/18/installing-trac-and-subversion-on-centos5/#comments</comments>
		<pubDate>Tue, 19 Jan 2010 01:30:31 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[guide]]></category>
		<category><![CDATA[subversion]]></category>
		<category><![CDATA[trac]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=19</guid>
		<description><![CDATA[Installing Trac and Subversion on CentOS is pretty simple. First, you need to install subversion - it was automatically installed for me already when I installed the OS, however I wanted to update it and found out that I couldn't do so until I deleted the i386 version. Don't do this step if you're on [...]]]></description>
			<content:encoded><![CDATA[<p>Installing Trac and Subversion on CentOS is pretty simple.</p>
<p>First, you need to install subversion - it was automatically installed for me already when I installed the OS, however I wanted to update it and found out that I couldn't do so until I deleted the i386 version.<span id="more-19"></span></p>
<p>Don't do this step if you're on a 32-bit system!</p>
<pre class="brush: bash; title: ;">
yum erase subversion.i386
</pre>
<p>Next, you can install or upgrade subversion and add mod_dav_svn</p>
<pre class="brush: bash; title: ;">
yum install subversion mod_dav_svn
</pre>
<p>And Trac...</p>
<pre class="brush: bash; title: ;">
yum install python
cd /tmp
wget http://peak.telecommunity.com/dist/ez_setup.py
python ez_setup.py
yum install mod_python
</pre>
<p>MySQL-python is needed for python to interact with mysql.  Download the latest MySQL-python from http://sourceforge.net/projects/mysql-python, then:</p>
<pre class="brush: bash; title: ;">
tar -zxvf mysql-python-tarball.tar.gz
cd mysql-python-source-directory
python setup.py build &amp;&amp; python.setup.py install
</pre>
<p>Some developer packages will also be needed:</p>
<pre class="brush: bash; title: ;">
yum install neon neon-devel python-devel swig
</pre>
<p>Next, install clearsilver.  You should look for the version that works for your OS. For CentOS 5, it's the latest el5.rf, and either i386 or x86 depending on your setup.  You can find the list of packages here: <a href="http://dag.wieers.com/rpm/packages/clearsilver/">http://dag.wieers.com/rpm/packages/clearsilver/</a>. Make sure that you get the same version number of clearsilver and python-clearsilver.</p>
<pre class="brush: bash; title: ;">
wget http://dag.wieers.com/rpm/packages/clearsilver/clearsilver-VERSION.el5.rf.ENVIRONMENT.rpm
rpm -i clearsilver-VERSION.el5.rf.ENVIRONMENT.rpm
wget http://dag.wieers.com/rpm/packages/clearsilver/python-clearsilver-VERSION.el5.rf.ENVIRONMENT.rpm
rpm -i python-clearsilver-VERSION.el5.rf.ENVIRONMENT.rpm
</pre>
<p>Now that you have all of these, you can install Trac.  Download the latest tar from <a href="http://trac.edgewall.org/wiki/TracDownload">http://trac.edgewall.org/wiki/TracDownload</a></p>
<pre class="brush: bash; title: ;">
cd /tmp
wget http://path.to/TracDownloadTarball.tar.gz
tar -zxvf TracDownloadTarball.tar.gz
cd TracDownloadTarball
python ./setup.py install
</pre>
<p>You're done. <img src='http://blog.bennyland.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Create SVN Repositories and link new Trac projects to them.  You'll first make directories to place your svn and trac projects (i'm using /var/www/svn|trac)</p>
<pre class="brush: bash; title: ;">
mkdir /var/www/svn/svn-repository-name
mkdir /var/www/trac/svn-repository-name
svnadmin create --fs-type fsfs /var/www/svn/svn-repository-name
trac-admin /var/www/trac/
initenv
chown -R apache.apache /var/www/svn/svn-repository-name
chown -R apache.apache /var/www/trac/svn-repository-name
</pre>
<p>Now you'll need to make a subversion.conf file for apache to load up. One might already exist (it did in my case), but even if it doesn't, that's ok.</p>
<pre class="brush: bash; title: ;">vim /etc/httpd/conf.d/subversion.conf</pre>
<pre class="brush: xml; title: ;">
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so
&lt;VirtualHost *:80&gt;
	ServerName svn.astrosmurf.com
	DocumentRoot /path/to/existing/empty/directory
	&lt;Location /&gt;
		DAV svn
		SVNParentPath /path/to/svn/root
		SVNListParentPath on
		Order allow,deny
		Allow from all
	&lt;/Location&gt;
	&lt;LocationMatch /.+&gt;
		# access differences per repository in here:
		AuthzSVNAccessFile /path/to/svn/root/svn-acl-conf

		# means to authenticate the user
		AuthType Basic
		AuthName &quot;Astrosmurf Subversion&quot;
		AuthUserFile /path/to/svn/root/svn.htpasswd

		# only authenticated users may access the repository
		Require valid-user
	&lt;/LocationMatch&gt;
	ErrorLog /var/www/logs/error.svn.log
	CustomLog /var/www/logs/access.svn.log combined
&lt;/VirtualHost&gt;
</pre>
<p>You'll also want to do the same for Trac</p>
<pre class="brush: bash; title: ;">vim /etc/httpd/conf.d/trac.conf</pre>
<p>and the contents should look similar to this (of course, modify both of these to suit your needs):</p>
<pre class="brush: xml; title: ;">
&lt;IfModule mod_python.c&gt;
	&lt;VirtualHost *:80&gt;
		ServerName trac.yourserver.com
		DocumentRoot /path/to/existing/empty/directory
		&lt;Location /&gt;
			SetHandler mod_python
			PythonInterpreter main_interpreter
			PythonHandler trac.web.modpython_frontend
			PythonOption TracEnvParentDir /path/to/trac/root
			PythonOption TracUriRoot /
			PythonOption PYTHON_EGG_CACHE /usr/lib/trac/plugins-cache
			Order allow,deny
			Allow from all
		&lt;/Location&gt;
		&lt;LocationMatch /[^/P]+/.*&gt;
			# means to authenticate the user
			AuthType Basic
			AuthName &quot;Subversion&quot;
			AuthUserFile /path/to/svn/root/svn.htpasswd
			AuthGroupFile /path/to/svn/root/svn-authgroup-conf
			# only authenticated users may access the repository
			Require group staff
		&lt;/LocationMatch&gt;
		&lt;LocationMatch /Pub[^/]+/.*&gt;
			# means to authenticate the user
			AuthType Basic
			AuthName &quot;Subversion&quot;
			AuthUserFile /path/to/svn/root/svn.htpasswd
			# only authenticated users may access the repository
			Require valid-user
		&lt;/LocationMatch&gt;

		ErrorLog /var/www/logs/error.trac.log
		CustomLog /var/www/logs/access.trac.log combined
	&lt;/VirtualHost&gt;
&lt;/IfModule&gt;
</pre>
<p>Now, make the different auth files. This file contains users and groups... in fact I don't remember why i have both the acl and the authgroup file, but whatever...</p>
<pre class="brush: bash; title: ;">vim /var/www/svn/svn-acl-conf</pre>
<pre class="brush: plain; title: ;">
[groups]
staff = user1, user2, user3
rb = reviewboard

[/]
@rb = rw

[name_of_staff_repo:/]
@staff = rw

[name_of_user1_repo:/]
user1 = rw

[Public:/]
@staff = rw
public = r
</pre>
<p>and set up authgroup. This file contains group names and who belongs to them</p>
<pre class="brush: bash; title: ;">vim /var/www/svn/svn-authgroup-conf</pre>
<pre class="brush: plain; title: ;">
staff: user1 user2 user3
</pre>
<p>And the passwords file</p>
<pre class="brush: bash; title: ;">
touch /var/www/svn/svn.htpasswd
htpasswd -m /var/www/svn/svn.htpasswd user1
htpasswd -m /var/www/svn/svn.htpasswd user2
htpasswd -m /var/www/svn/svn.htpasswd user3
</pre>
<p>You can also create a public user and somehow let the public know that they can enter your repository with username <em>public</em> and password <em>public</em>.</p>
<pre class="brush: bash; title: ;">htpasswd -m /var/www/svn/svn.htpasswd public</pre>
<p>Restart httpd and you should should now be able to go to http://svn.yourhost.com/ and http://trac.yourhost.com/</p>
<pre class="brush: bash; title: ;">service httpd restart</pre>
<p>A lot of this info came from several different sources, some from googling and others form these places:</p>
<ul>
<li><a href="http://biotext.org.uk/updating-subversion-on-64-bit-linux-centos-5/">http://biotext.org.uk/updating-subversion-on-64-bit-linux-centos-5/</a></li>
<li><a href="http://www.daniel-skinner.co.uk/setup-subversion-and-trac-on-centos-5/06/01/2008">http://www.daniel-skinner.co.uk/setup-subversion-and-trac-on-centos-5/06/01/2008</a></li>
<li><a href="http://www.techyouruniverse.com/software/installing-trac-with-subversion-on-cent-os-5-with-neon-and-quicksilver">http://www.techyouruniverse.com/software/installing-trac-with-subversion-on-cent-os-5-with-neon-and-quicksilver</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2010/01/18/installing-trac-and-subversion-on-centos5/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Forwarding all mail to your ISP&#8217;s smtp server with postfix and CentOS 5.4</title>
		<link>http://blog.bennyland.com/2010/01/18/forwarding-all-mail-to-your-isps-smtp-server-with-postfix-and-centos-5-4/</link>
		<comments>http://blog.bennyland.com/2010/01/18/forwarding-all-mail-to-your-isps-smtp-server-with-postfix-and-centos-5-4/#comments</comments>
		<pubDate>Mon, 18 Jan 2010 19:01:44 +0000</pubDate>
		<dc:creator>benny</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[guide]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[smtp]]></category>

		<guid isPermaLink="false">http://blog.bennyland.com/?p=13</guid>
		<description><![CDATA[I needed to forward all server mails to external email addresses and I had to use my ISP's smtp to do it. This is because everything is blocked by my ISP to combat spammers just making their own smtp server at home and then running it all hours of the day. To do this, you should have postfix installed and running already.

This tutorial will show you how to set up postfix to use your ISP's smtp, and also how to forward internal emails to external accounts.]]></description>
			<content:encoded><![CDATA[<p>I needed to forward all server mails to external email addresses and I had to use my ISP's smtp to do it.  This is because everything is blocked by my ISP to combat spammers just making their own smtp server at home and then running it all hours of the day.  To do this, you should have postfix installed and running already.<span id="more-13"></span></p>
<h3>Setting up Postfix to use your ISP's smtp</h3>
<p>Note: if your ISP requires authentication for smtp, you'll have to do some googleing.  If they don't (mine doesn't) then this will work fine for you.</p>
<pre class="brush: bash; title: ;">vim /etc/postfix/main.cf</pre>
<p>Next, you need to add the relayhost variable, you may have to enclose the domain in brackets, I didn't have to but I've read everywhere that you should:</p>
<pre class="brush: plain; title: ;">relayhost = [your.isps.smtp.com]</pre>
<p>Other items to potentially edit:</p>
<pre class="brush: plain; title: ;">myhostname = your.host.name
mydomain = your.domain
myorigin = $mydomain
inet_interfaces = all
mydestination = $myhostname, localhost.$mydomain, localhost
unknown_local_recipient_reject_code = 550
relay_domains = $mydestination</pre>
<p>Now, restart postfix</p>
<pre class="brush: bash; title: ;">service postfix reload</pre>
<h3>Forwarding name@yourdomain.com to a real e-mail address</h3>
<p>First things first, you need to edit main.cf</p>
<pre class="brush: bash; title: ;">vim /etc/postfix/main.cf</pre>
<p>Next, either add or edit the following lines.  You can add as many domains as you want.  If you followed the <a href="http://www.howtoforge.com/perfect-server-centos-5.4-x86_64-ispconfig-3">Perfect Server</a> tutorial, you'll have all sorts of junk in these variables, just replace them.</p>
<pre class="brush: plain; title: ;">virtual_alias_domains = yourdomain.com yourotherdomain.com.
virtual_alias_maps = hash:/etc/postfix/virtual</pre>
<p>Now, edit the /etc/postfix/virtual file to add your forward rules:</p>
<pre class="brush: bash; title: ;">vim /etc/postfix/virtual</pre>
<p>forward all mail sent to *@yourdomain.com to your@email.com and name@yourotherdomain.com to your@email.com:</p>
<pre class="brush: plain; title: ;">@yourdomain.com your@email.com
name@yourotherdomain.com your@email.com</pre>
<p>Now, restart postfix</p>
<pre class="brush: bash; title: ;">postmap /etc/postfix/virtual
service postfix reload</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.bennyland.com/2010/01/18/forwarding-all-mail-to-your-isps-smtp-server-with-postfix-and-centos-5-4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

