--- 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 "/"))