ead78c7e9a4d

This is why I drink.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Wed, 28 Sep 2011 23:28:56 -0400
parents 57686202ddc5
children dc8ba07e8157
branches/tags (none)
files project.clj puppet/base.pp puppet/modules/redis/LICENSE puppet/modules/redis/README.md puppet/modules/redis/files/redis-server.init puppet/modules/redis/files/redis-server.logrotate puppet/modules/redis/manifests/dependencies.pp puppet/modules/redis/manifests/init.pp puppet/modules/redis/manifests/install.pp puppet/modules/redis/manifests/overcommit.pp puppet/modules/redis/manifests/server.pp puppet/modules/redis/templates/redis.conf.erb src/newseasons/models/users.clj src/newseasons/templates/main.clj src/newseasons/views/main.clj

Changes

--- a/project.clj	Wed Sep 28 21:30:50 2011 -0400
+++ b/project.clj	Wed Sep 28 23:28:56 2011 -0400
@@ -1,8 +1,10 @@
 (defproject newseasons "0.1.0-SNAPSHOT"
             :description "FIXME: write this!"
             :dependencies [[org.clojure/clojure "1.2.1"]
+                           [org.clojure/clojure-contrib "1.2.0"]
                            [noir "1.1.0"]
                            [cheshire "2.0.2"]
-                           [clj-http "0.2.1"]]
+                           [clj-http "0.2.1"]
+                           [aleph "0.2.0-beta2"]]
             :main newseasons.server)
 
--- a/puppet/base.pp	Wed Sep 28 21:30:50 2011 -0400
+++ b/puppet/base.pp	Wed Sep 28 23:28:56 2011 -0400
@@ -1,3 +1,7 @@
+Exec {
+  path => "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+}
+
 class ubuntu {
   group { "puppet": ensure => "present"; } ->
   group { "vagrant": ensure => "present"; } ->
@@ -13,9 +17,25 @@
   class { "leiningen": }
   class { "environ": }
 
-  $niceties = [ "htop", "dtach", "sudo", "vim" ]
+  $niceties = ["htop", "dtach", "sudo", "vim", "curl"]
   package { $niceties: ensure => "installed" }
 }
 
-class { "ubuntu": }
+class clojurebox {
+  class { "ubuntu": }
 
+  include redis::dependencies
+  package { $redis::dependencies::packages:
+    ensure => present,
+  }
+  class { "redis::server":
+    version => "2.4.0",
+    bind => "127.0.0.1",
+    port => 6379,
+    requirepass => "devpass",
+    aof => true,
+  }
+}
+
+class { "clojurebox": }
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/LICENSE	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,19 @@
+Copyright (C) 2011 by Eivind Uggedal <eivind@uggedal.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/README.md	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,80 @@
+Puppet Redis Module
+===================
+
+Module for configuring Redis.
+
+Tested on Debian GNU/Linux 6.0 Squeeze and Ubuntu 10.4 LTS with
+Puppet 2.6. Patches for other operating systems welcome.
+
+
+TODO
+----
+
+* Ability to configure snapshotting intervals.
+* Ability to configure the slow log.
+
+
+Installation
+------------
+
+Clone this repo to a redis directory under your Puppet
+modules directory:
+
+    git clone git://github.com/uggedal/puppet-module-redis.git redis
+
+If you don't have a Puppet Master you can create a manifest file
+based on the notes below and run Puppet in stand-alone mode
+providing the module directory you cloned this repo to:
+
+    puppet apply --modulepath=modules test_redis.pp
+
+
+Usage
+-----
+
+To install and configure Redis, include the module:
+
+    include redis::server
+
+Note that you'll need to define a global search path for the `exec`
+resource to make the `redis::server` class function properly. This
+should ideally be placed in `manifests/site.pp`:
+
+    Exec {
+      path => "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+    }
+
+You'll also need to install some build dependencies:
+
+    include redis::dependencies
+    package { $redis::dependencies::packages:
+      ensure => present,
+    }
+
+You can override defaults in the Redis config by including
+the module with this special syntax:
+
+    class { "redis::server":
+      version => "2.4.0",
+      bind => "178.79.120.100",
+      port => 6379,
+      requirepass => "MY_SUPER_SECRET_PASSWORD",
+    }
+
+You can also configure a slave which connects to another Redis master
+instance:
+
+    class { "redis::server":
+      bind => "127.0.0.1",
+      port => 6379,
+      masterip => "178.79.120.100",
+      masterport => 6379,
+      masterauth => "MY_SUPER_SECRET_PASSWORD",
+    }
+
+By default Redis saves the database to disk through snapshotting. You can
+enable AOF in stead:
+
+    class { "redis::server":
+      aof => true,
+    }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/files/redis-server.init	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,71 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:		redis-server
+# Required-Start:	$syslog $remote_fs
+# Required-Stop:	$syslog $remote_fs
+# Should-Start:		$local_fs
+# Should-Stop:		$local_fs
+# Default-Start:	2 3 4 5
+# Default-Stop:		0 1 6
+# Short-Description:	redis-server - Persistent key-value db
+# Description:		redis-server - Persistent key-value db
+### END INIT INFO
+
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/local/bin/redis-server
+DAEMON_ARGS=/etc/redis/redis.conf
+NAME=redis-server
+DESC=redis-server
+PIDFILE=/var/run/redis.pid
+
+test -x $DAEMON || exit 0
+
+set -e
+
+case "$1" in
+  start)
+	echo -n "Starting $DESC: "
+	touch $PIDFILE
+	chown redis:redis $PIDFILE
+	if start-stop-daemon --start --quiet --umask 007 --pidfile $PIDFILE --chuid redis:redis --exec $DAEMON -- $DAEMON_ARGS
+	then
+		echo "$NAME."
+	else
+		echo "failed"
+	fi
+	;;
+  stop)
+	echo -n "Stopping $DESC: "
+	if start-stop-daemon --stop --retry 10 --quiet --oknodo --pidfile $PIDFILE --exec $DAEMON
+	then
+		echo "$NAME."
+	else
+		echo "failed"
+	fi
+	rm -f $PIDFILE
+	;;
+
+  restart|force-reload)
+	${0} stop
+	${0} start
+	;;
+
+  status)
+	echo -n "$DESC is "
+	if start-stop-daemon --stop --quiet --signal 0 --name ${NAME} --pidfile ${PIDFILE}
+	then
+		echo "running"
+	else
+		echo "not running"
+		exit 1
+	fi
+	;;
+
+  *)
+	echo "Usage: /etc/init.d/$NAME {start|stop|restart|force-reload}" >&2
+	exit 1
+	;;
+esac
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/files/redis-server.logrotate	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,8 @@
+/var/log/redis/*.log {
+        weekly
+        missingok
+	copytruncate
+        rotate 12
+        compress
+        notifempty
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/manifests/dependencies.pp	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,3 @@
+class redis::dependencies {
+  $packages = ["build-essential", "curl"]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/manifests/init.pp	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,1 @@
+# Required by Puppet for loading the module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/manifests/install.pp	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,48 @@
+define redis::install($ensure=present, $bin_dir="", $tar_version=undef) {
+  include redis::dependencies
+
+  $version = $name
+  $redis_src = "/usr/local/src/redis-${version}"
+
+  if $tar_version == undef {
+    $tar_version = $version
+  }
+
+  if $ensure == 'present' {
+
+    file { $redis_src:
+      ensure => "directory",
+    }
+
+    exec { "fetch redis ${version}": 
+      command => "curl -sL https://github.com/antirez/redis/tarball/${tar_version} | tar --strip-components 1 -xz",
+      cwd => $redis_src,
+      creates => "${redis_src}/Makefile",
+      require => File[$redis_src],
+    }
+
+    exec { "install redis ${version}":
+      command => "make && /etc/init.d/redis-server stop && make install PREFIX=/usr/local",
+      cwd => "${redis_src}/src",
+      unless => "test `redis-server --version | cut -d ' ' -f 4` = '${version}'",
+      require => [Exec["fetch redis ${version}"], Package[$redis::dependencies::packages]]
+    }
+
+  } elsif $ensure == 'absent' {
+
+    file { $redis_src:
+      ensure => $ensure,
+      recurse => true,
+      purge => true,
+      force => true,
+    }
+
+    file { ["$bin_dir/redis-benchmark",
+            "$bin_dir/redis-check-aof",
+            "$bin_dir/redis-check-dump",
+            "$bin_dir/redis-cli",
+            "$bin_dir/redis-server"]:
+      ensure => $ensure,
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/manifests/overcommit.pp	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,14 @@
+class redis::overcommit($ensure=present) {
+
+  file { "/etc/sysctl.d/overcommit.conf":
+    ensure => $ensure,
+    content => "vm.overcommit_memory=1",
+  }
+
+  if $ensure == "present" {
+    exec { "overcommit-memory":
+      command => "sysctl vm.overcommit_memory=1",
+      unless => "test `sysctl -n vm.overcommit_memory` = 1",
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/manifests/server.pp	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,131 @@
+class redis::server($ensure=present,
+                    $version='2.3.9',
+                    $tar_version="2.4.0-rc6",
+                    $bind="127.0.0.1",
+                    $port=6379,
+                    $masterip="",
+                    $masterport=6379,
+                    $masterauth="",
+                    $requirepass="",
+                    $aof=false,
+                    $aof_auto_rewrite_percentage=100,
+                    $aof_auto_rewrite_min_size="64mb") {
+
+  $is_present = $ensure == "present"
+  $is_absent = $ensure == "absent"
+  $bin_dir = '/usr/local/bin'
+  $redis_home = "/var/lib/redis"
+  $redis_log = "/var/log/redis"
+
+  class { "redis::overcommit":
+    ensure => $ensure,
+  }
+
+  redis::install { $version:
+    ensure => $ensure,
+    bin_dir => $bin_dir,
+    tar_version => $tar_version,
+  }
+
+  file { "/etc/redis":
+    ensure => $ensure ? {
+      'present' => "directory",
+      default => $ensure,
+    },
+    force => $is_absent,
+    before => $ensure ? {
+      'present' => File["/etc/redis/redis.conf"],
+      default => undef,
+    },
+    require => $ensure ? {
+      'absent' => File["/etc/redis/redis.conf"],
+      default => undef,
+    },
+  }
+
+  file { "/etc/redis/redis.conf":
+    ensure => $ensure,
+    content => template("redis/redis.conf.erb"),
+    require => Redis::Install[$version],
+  }
+
+  group { "redis":
+    ensure => $ensure,
+    allowdupe => false,
+  }
+
+  user { "redis":
+    ensure => $ensure,
+    allowdupe => false,
+    home => $redis_home,
+    managehome => true,
+    gid => "redis",
+    shell => "/bin/false",
+    comment => "Redis Server",
+    require => $ensure ? {
+      'present' => Group["redis"],
+      default => undef,
+    },
+    before => $ensure ? {
+      'absent' => Group["redis"],
+      default => undef,
+    },
+  }
+
+  file { [$redis_home, $redis_log]:
+    ensure => $ensure ? {
+      'present' => directory,
+      default => $ensure,
+    },
+    owner => $ensure ? {
+      'present' => "redis",
+      default => undef,
+    },
+    group => $ensure ? {
+      'present' => "redis",
+      default => undef,
+    },
+    require => $ensure ? {
+      'present' => Group["redis"],
+      default => undef,
+    },
+    before => $ensure ? {
+      'absent' => Group["redis"],
+      default => undef,
+    },
+    force => $is_absent,
+  }
+
+  file { "/etc/init.d/redis-server":
+    ensure => $ensure,
+    source => "puppet:///modules/redis/redis-server.init",
+    mode => 744,
+  }
+
+  file { "/etc/logrotate.d/redis-server":
+    ensure => $ensure,
+    source => "puppet:///modules/redis/redis-server.logrotate",
+  }
+
+  service { "redis-server":
+    ensure => $is_present,
+    enable => $is_present,
+    pattern => "${bin_dir}/redis-server",
+    hasrestart => true,
+    subscribe => $ensure ? {
+      'present' => [File["/etc/init.d/redis-server"],
+                    File["/etc/redis/redis.conf"],
+                    Redis::Install[$version],
+                    Class["redis::overcommit"]],
+      default => undef,
+    },
+    require => $ensure ? {
+      'present' => [User["redis"], File["/etc/init.d/redis-server"]],
+      default => undef,
+    },
+    before => $ensure ? {
+      'absent' => [User["redis"], File["/etc/init.d/redis-server"]],
+      default => undef,
+    },
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/puppet/modules/redis/templates/redis.conf.erb	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,408 @@
+# Redis configuration file example
+
+# Note on units: when memory size is needed, it is possible to specifiy
+# it in the usual form of 1k 5GB 4M and so forth:
+#
+# 1k => 1000 bytes
+# 1kb => 1024 bytes
+# 1m => 1000000 bytes
+# 1mb => 1024*1024 bytes
+# 1g => 1000000000 bytes
+# 1gb => 1024*1024*1024 bytes
+#
+# units are case insensitive so 1GB 1Gb 1gB are all the same.
+
+# By default Redis does not run as a daemon. Use 'yes' if you need it.
+# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
+daemonize yes
+
+# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
+# default. You can specify a custom pid file location here.
+pidfile /var/run/redis.pid
+
+# Accept connections on the specified port, default is 6379.
+# If port 0 is specified Redis will not listen on a TCP socket.
+port <%= port %>
+
+# If you want you can bind a single interface, if the bind option is not
+# specified all the interfaces will listen for incoming connections.
+#
+bind <%= bind %>
+
+# Specify the path for the unix socket that will be used to listen for
+# incoming connections. There is no default, so Redis will not listen
+# on a unix socket when not specified.
+#
+# unixsocket /tmp/redis.sock
+
+# Close the connection after a client is idle for N seconds (0 to disable)
+timeout 300
+
+# Set server verbosity to 'debug'
+# it can be one of:
+# debug (a lot of information, useful for development/testing)
+# verbose (many rarely useful info, but not a mess like the debug level)
+# notice (moderately verbose, what you want in production probably)
+# warning (only very important / critical messages are logged)
+loglevel verbose
+
+# Specify the log file name. Also 'stdout' can be used to force
+# Redis to log on the standard output. Note that if you use standard
+# output for logging but daemonize, logs will be sent to /dev/null
+logfile /var/log/redis/redis-server.log
+
+# To enable logging to the system logger, just set 'syslog-enabled' to yes,
+# and optionally update the other syslog parameters to suit your needs.
+# syslog-enabled no
+
+# Specify the syslog identity.
+# syslog-ident redis
+
+# Specify the syslog facility.  Must be USER or between LOCAL0-LOCAL7.
+# syslog-facility local0
+
+# Set the number of databases. The default database is DB 0, you can select
+# a different one on a per-connection basis using SELECT <dbid> where
+# dbid is a number between 0 and 'databases'-1
+databases 16
+
+################################ SNAPSHOTTING  #################################
+#
+# Save the DB on disk:
+#
+#   save <seconds> <changes>
+#
+#   Will save the DB if both the given number of seconds and the given
+#   number of write operations against the DB occurred.
+#
+#   In the example below the behaviour will be to save:
+#   after 900 sec (15 min) if at least 1 key changed
+#   after 300 sec (5 min) if at least 10 keys changed
+#   after 60 sec if at least 10000 keys changed
+#
+#   Note: you can disable saving at all commenting all the "save" lines.
+
+<% if !aof -%>
+save 900 1
+save 300 10
+save 60 10000
+<% end -%>
+
+# Compress string objects using LZF when dump .rdb databases?
+# For default that's set to 'yes' as it's almost always a win.
+# If you want to save some CPU in the saving child set it to 'no' but
+# the dataset will likely be bigger if you have compressible values or keys.
+rdbcompression yes
+
+# The filename where to dump the DB
+dbfilename dump.rdb
+
+# The working directory.
+#
+# The DB will be written inside this directory, with the filename specified
+# above using the 'dbfilename' configuration directive.
+# 
+# Also the Append Only File will be created inside this directory.
+# 
+# Note that you must specify a directory here, not a file name.
+dir /var/lib/redis
+
+################################# REPLICATION #################################
+
+# Master-Slave replication. Use slaveof to make a Redis instance a copy of
+# another Redis server. Note that the configuration is local to the slave
+# so for example it is possible to configure the slave to save the DB with a
+# different interval, or to listen to another port, and so on.
+#
+# slaveof <masterip> <masterport>
+<% if masterip.any? -%>
+slaveof <%= masterip %> <%= masterport %>
+<% end -%>
+
+# If the master is password protected (using the "requirepass" configuration
+# directive below) it is possible to tell the slave to authenticate before
+# starting the replication synchronization process, otherwise the master will
+# refuse the slave request.
+#
+# masterauth <master-password>
+<% if masterauth.any? -%>
+masterauth <%= masterauth %>
+<% end -%>
+
+# When a slave lost the connection with the master, or when the replication
+# is still in progress, the slave can act in two different ways:
+#
+# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
+#    still reply to client requests, possibly with out of data data, or the
+#    data set may just be empty if this is the first synchronization.
+#
+# 2) if slave-serve-stale data is set to 'no' the slave will reply with
+#    an error "SYNC with master in progress" to all the kind of commands
+#    but to INFO and SLAVEOF.
+#
+slave-serve-stale-data yes
+
+################################## SECURITY ###################################
+
+# Require clients to issue AUTH <PASSWORD> before processing any other
+# commands.  This might be useful in environments in which you do not trust
+# others with access to the host running redis-server.
+#
+# This should stay commented out for backward compatibility and because most
+# people do not need auth (e.g. they run their own servers).
+# 
+# Warning: since Redis is pretty fast an outside user can try up to
+# 150k passwords per second against a good box. This means that you should
+# use a very strong password otherwise it will be very easy to break.
+#
+# requirepass foobared
+<% if requirepass.any? -%>
+requirepass <%= requirepass %>
+<% end -%>
+
+# Command renaming.
+#
+# It is possilbe to change the name of dangerous commands in a shared
+# environment. For instance the CONFIG command may be renamed into something
+# of hard to guess so that it will be still available for internal-use
+# tools but not available for general clients.
+#
+# Example:
+#
+# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
+#
+# It is also possilbe to completely kill a command renaming it into
+# an empty string:
+#
+# rename-command CONFIG ""
+
+################################### LIMITS ####################################
+
+# Set the max number of connected clients at the same time. By default there
+# is no limit, and it's up to the number of file descriptors the Redis process
+# is able to open. The special value '0' means no limits.
+# Once the limit is reached Redis will close all the new connections sending
+# an error 'max number of clients reached'.
+#
+# maxclients 128
+
+# Don't use more memory than the specified amount of bytes.
+# When the memory limit is reached Redis will try to remove keys with an
+# EXPIRE set. It will try to start freeing keys that are going to expire
+# in little time and preserve keys with a longer time to live.
+# Redis will also try to remove objects from free lists if possible.
+#
+# If all this fails, Redis will start to reply with errors to commands
+# that will use more memory, like SET, LPUSH, and so on, and will continue
+# to reply to most read-only commands like GET.
+#
+# WARNING: maxmemory can be a good idea mainly if you want to use Redis as a
+# 'state' server or cache, not as a real DB. When Redis is used as a real
+# database the memory usage will grow over the weeks, it will be obvious if
+# it is going to use too much memory in the long run, and you'll have the time
+# to upgrade. With maxmemory after the limit is reached you'll start to get
+# errors for write operations, and this may even lead to DB inconsistency.
+#
+# maxmemory <bytes>
+
+# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
+# is reached? You can select among five behavior:
+# 
+# volatile-lru -> remove the key with an expire set using an LRU algorithm
+# allkeys-lru -> remove any key accordingly to the LRU algorithm
+# volatile-random -> remove a random key with an expire set
+# allkeys->random -> remove a random key, any key
+# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
+# noeviction -> don't expire at all, just return an error on write operations
+# 
+# Note: with all the kind of policies, Redis will return an error on write
+#       operations, when there are not suitable keys for eviction.
+#
+#       At the date of writing this commands are: set setnx setex append
+#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
+#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
+#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
+#       getset mset msetnx exec sort
+#
+# The default is:
+#
+# maxmemory-policy volatile-lru
+
+# LRU and minimal TTL algorithms are not precise algorithms but approximated
+# algorithms (in order to save memory), so you can select as well the sample
+# size to check. For instance for default Redis will check three keys and
+# pick the one that was used less recently, you can change the sample size
+# using the following configuration directive.
+#
+# maxmemory-samples 3
+
+############################## APPEND ONLY MODE ###############################
+
+# By default Redis asynchronously dumps the dataset on disk. If you can live
+# with the idea that the latest records will be lost if something like a crash
+# happens this is the preferred way to run Redis. If instead you care a lot
+# about your data and don't want to that a single record can get lost you should
+# enable the append only mode: when this mode is enabled Redis will append
+# every write operation received in the file appendonly.aof. This file will
+# be read on startup in order to rebuild the full dataset in memory.
+#
+# Note that you can have both the async dumps and the append only file if you
+# like (you have to comment the "save" statements above to disable the dumps).
+# Still if append only mode is enabled Redis will load the data from the
+# log file at startup ignoring the dump.rdb file.
+#
+# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append
+# log file in background when it gets too big.
+
+appendonly <% if aof %>yes<% else %>no<% end%>
+
+# The name of the append only file (default: "appendonly.aof")
+# appendfilename appendonly.aof
+
+# The fsync() call tells the Operating System to actually write data on disk
+# instead to wait for more data in the output buffer. Some OS will really flush 
+# data on disk, some other OS will just try to do it ASAP.
+#
+# Redis supports three different modes:
+#
+# no: don't fsync, just let the OS flush the data when it wants. Faster.
+# always: fsync after every write to the append only log . Slow, Safest.
+# everysec: fsync only if one second passed since the last fsync. Compromise.
+#
+# The default is "everysec" that's usually the right compromise between
+# speed and data safety. It's up to you to understand if you can relax this to
+# "no" that will will let the operating system flush the output buffer when
+# it wants, for better performances (but if you can live with the idea of
+# some data loss consider the default persistence mode that's snapshotting),
+# or on the contrary, use "always" that's very slow but a bit safer than
+# everysec.
+#
+# If unsure, use "everysec".
+
+# appendfsync always
+appendfsync everysec
+# appendfsync no
+
+# When the AOF fsync policy is set to always or everysec, and a background
+# saving process (a background save or AOF log background rewriting) is
+# performing a lot of I/O against the disk, in some Linux configurations
+# Redis may block too long on the fsync() call. Note that there is no fix for
+# this currently, as even performing fsync in a different thread will block
+# our synchronous write(2) call.
+#
+# In order to mitigate this problem it's possible to use the following option
+# that will prevent fsync() from being called in the main process while a
+# BGSAVE or BGREWRITEAOF is in progress.
+#
+# This means that while another child is saving the durability of Redis is
+# the same as "appendfsync none", that in pratical terms means that it is
+# possible to lost up to 30 seconds of log in the worst scenario (with the
+# default Linux settings).
+# 
+# If you have latency problems turn this to "yes". Otherwise leave it as
+# "no" that is the safest pick from the point of view of durability.
+no-appendfsync-on-rewrite no
+
+<% if aof -%>
+# Automatic rewrite of the append only file.
+# Redis is able to automatically rewrite the log file implicitly calling
+# BGREWRITEAOF when the AOF log size will growth by the specified percentage.
+# 
+# This is how it works: Redis remembers the size of the AOF file after the
+# latest rewrite (or if no rewrite happened since the restart, the size of
+# the AOF at startup is used).
+#
+# This base size is compared to the current size. If the current size is
+# bigger than the specified percentage, the rewrite is triggered. Also
+# you need to specify a minimal size for the AOF file to be rewritten, this
+# is useful to avoid rewriting the AOF file even if the percentage increase
+# is reached but it is still pretty small.
+#
+# Specify a precentage of zero in order to disable the automatic AOF
+# rewrite feature.
+
+auto-aof-rewrite-percentage <%= aof_auto_rewrite_percentage %>
+auto-aof-rewrite-min-size <%= aof_auto_rewrite_min_size %>
+<% end -%>
+
+################################## SLOW LOG ###################################
+
+# The Redis Slow Log is a system to log queries that exceeded a specified
+# execution time. The execution time does not include the I/O operations
+# like talking with the client, sending the reply and so forth,
+# but just the time needed to actually execute the command (this is the only
+# stage of command execution where the thread is blocked and can not serve
+# other requests in the meantime).
+# 
+# You can configure the slow log with two parameters: one tells Redis
+# what is the execution time, in microseconds, to exceed in order for the
+# command to get logged, and the other parameter is the length of the
+# slow log. When a new command is logged the oldest one is removed from the
+# queue of logged commands.
+
+# The following time is expressed in microseconds, so 1000000 is equivalent
+# to one second. Note that a negative number disables the slow log, while
+# a value of zero forces the logging of every command.
+slowlog-log-slower-than 10000
+
+# There is no limit to this length. Just be aware that it will consume memory.
+# You can reclaim memory used by the slow log with SLOWLOG RESET.
+slowlog-max-len 1024
+
+############################### ADVANCED CONFIG ###############################
+
+# Hashes are encoded in a special way (much more memory efficient) when they
+# have at max a given numer of elements, and the biggest element does not
+# exceed a given threshold. You can configure this limits with the following
+# configuration directives.
+hash-max-zipmap-entries 512
+hash-max-zipmap-value 64
+
+# Similarly to hashes, small lists are also encoded in a special way in order
+# to save a lot of space. The special representation is only used when
+# you are under the following limits:
+list-max-ziplist-entries 512
+list-max-ziplist-value 64
+
+# Sets have a special encoding in just one case: when a set is composed
+# of just strings that happens to be integers in radix 10 in the range
+# of 64 bit signed integers.
+# The following configuration setting sets the limit in the size of the
+# set in order to use this special memory saving encoding.
+set-max-intset-entries 512
+
+# Similarly to hashes and lists, sorted sets are also specially encoded in
+# order to save a lot of space. This encoding is only used when the length and
+# elements of a sorted set are below the following limits:
+zset-max-ziplist-entries 128
+zset-max-ziplist-value 64
+
+# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
+# order to help rehashing the main Redis hash table (the one mapping top-level
+# keys to values). The hash table implementation redis uses (see dict.c)
+# performs a lazy rehashing: the more operation you run into an hash table
+# that is rhashing, the more rehashing "steps" are performed, so if the
+# server is idle the rehashing is never complete and some more memory is used
+# by the hash table.
+# 
+# The default is to use this millisecond 10 times every second in order to
+# active rehashing the main dictionaries, freeing memory when possible.
+#
+# If unsure:
+# use "activerehashing no" if you have hard latency requirements and it is
+# not a good thing in your environment that Redis can reply form time to time
+# to queries with 2 milliseconds delay.
+#
+# use "activerehashing yes" if you don't have such hard requirements but
+# want to free memory asap when possible.
+activerehashing yes
+
+################################## INCLUDES ###################################
+
+# Include one or more other config files here.  This is useful if you
+# have a standard template that goes to all redis server but also need
+# to customize a few per-server settings.  Include files can include
+# other files, so use this wisely.
+#
+# include /path/to/local.conf
+# include /path/to/other.conf
--- a/src/newseasons/models/users.clj	Wed Sep 28 21:30:50 2011 -0400
+++ b/src/newseasons/models/users.clj	Wed Sep 28 23:28:56 2011 -0400
@@ -0,0 +1,41 @@
+(ns newseasons.models.users
+  (:require [noir.util.crypt :as crypt])
+  (:use [aleph.redis :only (redis-client)]))
+
+
+(def r (redis-client {:host "localhost" :password "devpass"}))
+
+; "Schema" --------------------------------------------------------------------
+;
+; Users are stored as Redis hashes, with their watched shows as a separate set.
+;
+; user:<email address> = {
+;     email: the email address for ease of use
+;     pass: the user's hashed password
+; }
+; user:<email address>:shows = #(show-id, ...)
+
+; Code ------------------------------------------------------------------------
+(defn- user-key [email]
+  (str "users:" email))
+
+(defn- user-key-shows [email]
+  (str "users:" email ":shows"))
+
+(defn user-get [email]
+  (let [user @(r [:hgetall (user-key email)])]
+    (when (not (empty? user))
+      (merge user
+             {:shows @(r [:smembers (user-key-shows email)])}))))
+
+(defn user-set-email! [email new-email]
+  @(r [:hset (user-key email) "email" new-email]))
+
+(defn user-set-pass! [email new-pass]
+  @(r [:hset (user-key email) "pass" (crypt/encrypt new-pass)]))
+
+(defn user-add-show! [email show-id]
+  @(r [:sadd (user-key-shows email) show-id]))
+
+(defn user-rem-show! [email show-id]
+  @(r [:srem (user-key-shows email) show-id]))
--- a/src/newseasons/templates/main.clj	Wed Sep 28 21:30:50 2011 -0400
+++ b/src/newseasons/templates/main.clj	Wed Sep 28 23:28:56 2011 -0400
@@ -41,7 +41,9 @@
 (defpartial inner [title & content]
             (base
               [:h2.sixteen.columns title]
-              content))
+              content
+              (form-to [:post "/logout"]
+                       (submit-button "Log Out"))))
 
 
 ; Pages -----------------------------------------------------------------------
@@ -51,7 +53,7 @@
                [:form {:action "" :method "POST"}
                 (field text-field "email" "Email Address")
                 (field password-field "password" "Password")
-                (submit-button "Log in or Create Account")]]
+                (submit-button "Log In or Create Account")]]
               [:div.five.columns
                [:p "New Seasons will notify you when your favorite TV "
                    "shows have new seasons on iTunes.  That's it."]]
--- a/src/newseasons/views/main.clj	Wed Sep 28 21:30:50 2011 -0400
+++ b/src/newseasons/views/main.clj	Wed Sep 28 23:28:56 2011 -0400
@@ -1,14 +1,27 @@
 (ns newseasons.views.main
-  (:require [newseasons.templates.main :as t])
   (:use noir.core)
   (:require [noir.response :as resp])
   (:require [noir.session :as sess])
+  (:require [noir.util.crypt :as crypt])
   (:require [clj-http.client :as client])
-  (:use [cheshire.core :only (parse-string)]))
+  (:use [cheshire.core :only (parse-string)])
+  (:require [newseasons.templates.main :as t])
+  (:require [newseasons.models.users :as users]))
 
 
+; Utils -----------------------------------------------------------------------
 (def email-regex #"[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}")
+(def none-are (comp not some))
+(defn all-are [pred coll]
+  (= (count coll)
+     (count (filter pred coll))))
 
+(defn flash! [message]
+  (sess/flash-put! message)
+  nil)
+
+
+; iTunes ----------------------------------------------------------------------
 (defn itunes-search [params]
   ((parse-string (:body (client/get "http://itunes.apple.com/search"
                                     {:query-params params})))
@@ -31,18 +44,31 @@
 
 
 ; Authentication --------------------------------------------------------------
+(defn force-login []
+  (flash! "Please log in to view that page!")
+  (resp/redirect "/"))
+
 (defmacro login-required [& body]
   `(if-not (sess/get :email)
-     (do
-       (sess/flash-put! "Please log in to access that page!")
-       (resp/redirect "/")) 
-     ~@body))
+     (force-login)
+     (do ~@body)))
+
+(defn check-login [{:keys [email password]}]
+  (if-not (none-are empty? [email password])
+    (flash! "Both fields are required.  This really shouldn't be difficult.")
+    (if-not (re-find email-regex email)
+      (flash! "That's not an email address!")
+      (if-let [user (users/user-get email)]
+        (if (crypt/compare password (:pass user))
+          user
+          (flash! "Invalid login!"))
+        (do
+          (users/user-set-email! email email)
+          (users/user-set-pass! email password)
+          (users/user-get email))))))
 
 
 ; Home ------------------------------------------------------------------------
-(defn check-login [{:keys [email password]}]
-  true)
-
 (defpage [:get "/"] []
          (if-let [email (sess/get :email)]
            (resp/redirect (str "/" email))
@@ -51,13 +77,15 @@
 (defpage [:post "/"] {:as login}
          (if (check-login login)
            (resp/redirect (str "/" (:email login)))
-           (t/home)))
+           (render "/" login)))
 
 
 ; User ------------------------------------------------------------------------
 (defpage [:get ["/:email" :email email-regex]] {:keys [email]}
          (login-required
-           (t/user email)))
+           (if (not= email (sess/get email))
+             (force-login)
+             (t/user email))))
 
 
 ; Search ----------------------------------------------------------------------
@@ -72,8 +100,12 @@
 
 ; Add -------------------------------------------------------------------------
 (defpage [:post "/add"] {:as show}
-         (sess/flash-put! "Added a show to your list.")
-         (resp/redirect "/"))
+         (login-required
+           (flash! "Added a show to your list.")
+           (resp/redirect "/")))
 
 
-
+; Log Out ---------------------------------------------------------------------
+(defpage [:post "/logout"] []
+         (sess/remove! :email)
+         (resp/redirect "/"))