├── .gitignore ├── bin ├── release ├── detect ├── start-pgbouncer-stunnel ├── gen-pgbouncer-conf.sh └── compile ├── Changelog.md ├── support ├── manifest ├── package_stunnel ├── package_pgbouncer ├── package_postgresql └── aws │ ├── hmac │ └── s3 ├── LICENSE ├── CONTRIBUTING.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .anvil 2 | -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # bin/release 3 | -------------------------------------------------------------------------------- /bin/detect: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # bin/detect 3 | 4 | echo 'pgbouncer-stunnel' 5 | exit 0 6 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## v0.2.1 (July 15, 2013) 2 | 3 | * Added `PGBOUNCER_PREPARED_STATEMENTS` config var to append 4 | `?prepared_statements=false` to `PGBOUNCER_URI` when set to `false` 5 | 6 | ## v0.2 (July 10, 2013) 7 | 8 | * Now using bash fifos to crash dyno on any subprocess exit 9 | * Updated README 10 | 11 | ## v0.1 (June 24, 2013) 12 | 13 | * initial release 14 | -------------------------------------------------------------------------------- /support/manifest: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | manifest_type="$1" 6 | 7 | if [ "$manifest_type" == "" ]; then 8 | echo "usage: $0 " 9 | exit 1 10 | fi 11 | 12 | if [ "$AWS_ID" == "" ]; then 13 | echo "must set AWS_ID" 14 | exit 1 15 | fi 16 | 17 | if [ "$AWS_SECRET" == "" ]; then 18 | echo "must set AWS_SECRET" 19 | exit 1 20 | fi 21 | 22 | if [ "$S3_BUCKET" == "" ]; then 23 | echo "must set S3_BUCKET" 24 | exit 1 25 | fi 26 | 27 | basedir="$( cd -P "$( dirname "$0" )" && pwd )" 28 | 29 | # make a temp directory 30 | tempdir="$( mktemp -t pgbouncer_XXXX )" 31 | rm -rf $tempdir 32 | mkdir -p $tempdir 33 | pushd $tempdir 34 | 35 | # generate manifest 36 | $basedir/aws/s3 ls $S3_BUCKET \ 37 | | grep "^${manifest_type}" \ 38 | | sed -e "s/${manifest_type}-\([0-9.]*\)\\.tgz/\\1/" \ 39 | | awk 'BEGIN {FS="."} {printf("%03d.%03d.%03d %s\n",$1,$2,$3,$0)}' | sort -r | cut -d" " -f2 \ 40 | > manifest.${manifest_type} 41 | 42 | # upload manifest to s3 43 | $basedir/aws/s3 put $S3_BUCKET \ 44 | manifest.${manifest_type} "" "text/plain" 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License: 2 | 3 | Copyright (C) 2012 Heroku, Inc. 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /support/package_stunnel: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | stunnel_version="$1" 6 | 7 | if [ "$stunnel_version" == "" ]; then 8 | echo "usage: $0 VERSION" 9 | exit 1 10 | fi 11 | 12 | if [ "$AWS_ID" == "" ]; then 13 | echo "must set AWS_ID" 14 | exit 1 15 | fi 16 | 17 | if [ "$AWS_SECRET" == "" ]; then 18 | echo "must set AWS_SECRET" 19 | exit 1 20 | fi 21 | 22 | if [ "$S3_BUCKET" == "" ]; then 23 | echo "must set S3_BUCKET" 24 | exit 1 25 | fi 26 | 27 | basedir="$( cd -P "$( dirname "$0" )" && pwd )" 28 | 29 | # make a temp directory 30 | tempdir="$( mktemp -t stunnel_XXXX )" 31 | rm -rf $tempdir 32 | mkdir -p $tempdir 33 | cd $tempdir 34 | 35 | # build and package stunnel for heroku 36 | vulcan build -v \ 37 | -n stunnel \ 38 | -c "cd stunnel-${stunnel_version} && ./configure --prefix=/app/vendor/stunnel && make; yes . | make install" \ 39 | -p /app/vendor/stunnel \ 40 | -s https://www.stunnel.org/downloads/stunnel-${stunnel_version}.tar.gz \ 41 | -o $tempdir/stunnel-${stunnel_version}.tgz 42 | 43 | # upload stunnel to s3 44 | $basedir/aws/s3 put $S3_BUCKET \ 45 | stunnel-${stunnel_version}.tgz $tempdir/stunnel-${stunnel_version}.tgz 46 | 47 | # generate manifest 48 | $basedir/manifest stunnel 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Compiling new versions of pgbouncer and stunnel using Vulcan 2 | 3 | Install [vulcan](https://github.com/heroku/vulcan) and create your own build server. Use any 4 | app name you want and vulcan will remember it in a `~/.vulcan` config file. 5 | 6 | ``` 7 | gem install vulcan 8 | vulcan create builder-bob 9 | ``` 10 | 11 | Store your S3 credentials in `~/.aws/` 12 | 13 | ``` 14 | mkdir -p ~/.aws 15 | echo 'YOUR_AWS_KEY' > ~/.aws/key-pgbouncer.access 16 | echo 'YOUR_AWS_SECRET' > ~/.aws/key-pgbouncer.secret 17 | ``` 18 | 19 | Add a credentials exporter to your `.bash_profile` or `.bashrc` 20 | 21 | ``` 22 | setup_pgbouncer_env () { 23 | export AWS_ID=$(cat ~/.aws/key-pgbouncer.access) 24 | export AWS_SECRET=$(cat ~/.aws/key-pgbouncer.secret) 25 | export S3_BUCKET="heroku-buildpack-pgbouncer" 26 | } 27 | ``` 28 | 29 | Build: 30 | 31 | ``` 32 | setup_pgbouncer_env 33 | support/package_pgbouncer 34 | support/package_stunnel 35 | ``` 36 | 37 | ## Publishing buildpack updates 38 | 39 | ``` 40 | heroku plugins:install https://github.com/heroku/heroku-buildpacks 41 | 42 | cd heroku-buildpack-pgbouncer 43 | git checkout master 44 | heroku buildpacks:publish gregburek/pgbouncer 45 | ``` 46 | -------------------------------------------------------------------------------- /support/package_pgbouncer: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | pgbouncer_version="$1" 6 | 7 | if [ "$pgbouncer_version" == "" ]; then 8 | echo "usage: $0 VERSION" 9 | exit 1 10 | fi 11 | 12 | if [ "$AWS_ID" == "" ]; then 13 | echo "must set AWS_ID" 14 | exit 1 15 | fi 16 | 17 | if [ "$AWS_SECRET" == "" ]; then 18 | echo "must set AWS_SECRET" 19 | exit 1 20 | fi 21 | 22 | if [ "$S3_BUCKET" == "" ]; then 23 | echo "must set S3_BUCKET" 24 | exit 1 25 | fi 26 | 27 | basedir="$( cd -P "$( dirname "$0" )" && pwd )" 28 | 29 | # make a temp directory 30 | tempdir="$( mktemp -t pgbouncer_XXXX )" 31 | rm -rf $tempdir 32 | mkdir -p $tempdir 33 | cd $tempdir 34 | 35 | # build and package pgbouncer for heroku 36 | vulcan build -v \ 37 | -n pgbouncer \ 38 | -c "cd pgbouncer-${pgbouncer_version} && ./configure --prefix=/app/vendor/pgbouncer && make && make install" \ 39 | -p /app/vendor/pgbouncer \ 40 | -s http://pgfoundry.org/frs/download.php/3393/pgbouncer-${pgbouncer_version}.tar.gz \ 41 | -o $tempdir/pgbouncer-${pgbouncer_version}.tgz 42 | 43 | # upload pgbouncer to s3 44 | $basedir/aws/s3 put $S3_BUCKET \ 45 | pgbouncer-${pgbouncer_version}.tgz $tempdir/pgbouncer-${pgbouncer_version}.tgz 46 | 47 | # generate manifest 48 | $basedir/manifest pgbouncer 49 | -------------------------------------------------------------------------------- /bin/start-pgbouncer-stunnel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Adapted from https://github.com/ryandotsmith/nginx-buildpack/ 3 | 4 | DATABASE_URL=$PGBOUNCER_URI 5 | 6 | psmgr=/tmp/pgbouncer-buildpack-wait 7 | rm -f $psmgr 8 | mkfifo $psmgr 9 | 10 | #Start App Server 11 | ( 12 | #Take the command passed to this bin and start it. 13 | #E.g. bin/start-pgbouncer-stunnel bundle exec unicorn -c config/unicorn.rb 14 | exec $@ & 15 | echo "buildpack=pgbouncer at=start-app cmd=$@" 16 | wait 17 | echo 'app' >$psmgr 18 | ) & 19 | 20 | #Start stunnel 21 | ( 22 | #We expect stunnel to run in the foreground. 23 | echo 'buildpack=pgbouncer at=stunnel-start' 24 | vendor/stunnel/bin/stunnel vendor/stunnel/stunnel-pgbouncer.conf 25 | echo 'stunnel' >$psmgr 26 | ) & 27 | 28 | #Start PGBouncer 29 | ( 30 | #We expect pgbouncer to run in the foreground. 31 | echo 'buildpack=pgbouncer at=pgbouncer-start' 32 | vendor/pgbouncer/bin/pgbouncer vendor/pgbouncer/pgbouncer.ini 33 | echo 'pgbouncer' >$psmgr 34 | ) & 35 | 36 | #This read will block the process waiting on a msg to be put into the fifo. 37 | #If any of the processes defined above should exit, 38 | #a msg will be put into the fifo causing the read operation 39 | #to un-block. The process putting the msg into the fifo 40 | #will use it's process name as a msg so that we can print the offending 41 | #process to stdout. 42 | read exit_process <$psmgr 43 | echo "buildpack=pgbouncer at=exit process=$exit_process" 44 | exit 1 45 | -------------------------------------------------------------------------------- /support/package_postgresql: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | postgresql_version="$1" 6 | 7 | if [ "$postgresql_version" == "" ]; then 8 | echo "usage: $0 VERSION" 9 | exit 1 10 | fi 11 | 12 | if [ "$AWS_ID" == "" ]; then 13 | echo "must set AWS_ID" 14 | exit 1 15 | fi 16 | 17 | if [ "$AWS_SECRET" == "" ]; then 18 | echo "must set AWS_SECRET" 19 | exit 1 20 | fi 21 | 22 | if [ "$S3_BUCKET" == "" ]; then 23 | echo "must set S3_BUCKET" 24 | exit 1 25 | fi 26 | 27 | basedir="$( cd -P "$( dirname "$0" )" && pwd )" 28 | 29 | # make a temp directory 30 | tempdir="$( mktemp -t postgresql_XXXX )" 31 | rm -rf $tempdir 32 | mkdir -p $tempdir 33 | cd $tempdir 34 | 35 | # build and package postgresql for heroku 36 | vulcan build -v \ 37 | -n postgresql \ 38 | -c "cd postgresql-${postgresql_version} && \ 39 | LDFLAGS='-Wl,--as-needed -Wl,-z,now' \ 40 | CFLAGS='-fPIC -DLINUX_OOM_ADJ=0' \ 41 | ./configure --prefix=/app/vendor/postgresql \ 42 | --enable-integer-datetimes \ 43 | --enable-thread-safety \ 44 | --enable-debug \ 45 | --disable-rpath \ 46 | --with-gnu-ld \ 47 | --with-pgport=5432 \ 48 | --with-system-tzdata=/usr/share/zoneinfo \ 49 | --without-tcl \ 50 | --without-perl \ 51 | --without-python \ 52 | --with-krb5 \ 53 | --with-gssapi \ 54 | --with-openssl \ 55 | --with-libxml \ 56 | --with-libxslt \ 57 | --with-openssl && \ 58 | make && \ 59 | make install" \ 60 | -p /app/vendor/postgresql \ 61 | -s http://ftp.postgresql.org/pub/source/v${postgresql_version}/postgresql-${postgresql_version}.tar.gz \ 62 | -o $tempdir/postgresql-${postgresql_version}.tgz 63 | 64 | # upload postgresql to s3 65 | $basedir/aws/s3 put $S3_BUCKET \ 66 | postgresql-${postgresql_version}.tgz $tempdir/postgresql-${postgresql_version}.tgz 67 | 68 | # generate manifest 69 | $basedir/manifest postgresql 70 | -------------------------------------------------------------------------------- /bin/gen-pgbouncer-conf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DB=$(echo $DATABASE_URL | perl -lne 'print "$1 $2 $3 $4 $5 $6 $7" if /^postgres:\/\/([^:]+):([^@]+)@(.*?):(.*?)\/(.*?)(\\?.*)?$/') 4 | DB_URI=( $DB ) 5 | DB_USER=${DB_URI[0]} 6 | DB_PASS=${DB_URI[1]} 7 | DB_HOST=${DB_URI[2]} 8 | DB_PORT=${DB_URI[3]} 9 | DB_NAME=${DB_URI[4]} 10 | 11 | if [ "$PGBOUNCER_PREPARED_STATEMENTS" == "false" ] 12 | then 13 | export PGBOUNCER_URI=postgres://$DB_USER:$DB_PASS@localhost:6000/$DB_NAME?prepared_statements=false 14 | else 15 | export PGBOUNCER_URI=postgres://$DB_USER:$DB_PASS@localhost:6000/$DB_NAME 16 | fi 17 | 18 | 19 | mkdir -p /app/vendor/stunnel/var/run/stunnel/ 20 | cat >> /app/vendor/stunnel/stunnel-pgbouncer.conf << EOFEOF 21 | foreground = yes 22 | 23 | options = NO_SSLv2 24 | options = SINGLE_ECDH_USE 25 | options = SINGLE_DH_USE 26 | socket = r:TCP_NODELAY=1 27 | options = NO_SSLv3 28 | ciphers = HIGH:!ADH:!AECDH:!LOW:!EXP:!MD5:!3DES:!SRP:!PSK:@STRENGTH 29 | 30 | [heroku-postgres] 31 | client = yes 32 | protocol = pgsql 33 | accept = localhost:6002 34 | connect = $DB_HOST:$DB_PORT 35 | retry = yes 36 | 37 | EOFEOF 38 | 39 | cat >> /app/vendor/pgbouncer/users.txt << EOFEOF 40 | "$DB_USER" "$DB_PASS" 41 | EOFEOF 42 | 43 | cat >> /app/vendor/pgbouncer/pgbouncer.ini << EOFEOF 44 | [databases] 45 | $DB_NAME = host=localhost port=6002 46 | [pgbouncer] 47 | listen_addr = localhost 48 | listen_port = 6000 49 | auth_type = md5 50 | auth_file = /app/vendor/pgbouncer/users.txt 51 | 52 | ; When server connection is released back to pool: 53 | ; session - after client disconnects 54 | ; transaction - after transaction finishes 55 | ; statement - after statement finishes 56 | pool_mode = transaction 57 | server_reset_query = 58 | max_client_conn = 100 59 | default_pool_size = ${PGBOUNCER_DEFAULT_POOL_SIZE:-1} 60 | reserve_pool_size = ${PGBOUNCER_RESERVE_POOL_SIZE:-1} 61 | reserve_pool_timeout = ${PGBOUNCER_RESERVE_POOL_TIMEOUT:-5.0} 62 | EOFEOF 63 | 64 | -------------------------------------------------------------------------------- /bin/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # bin/compile 3 | 4 | # fail fast 5 | set -e 6 | 7 | # debug 8 | # set -x 9 | 10 | # clean up leaking environment 11 | unset GIT_DIR 12 | 13 | # config 14 | PGBOUNCER_VERSION="1.5.4" 15 | STUNNEL_VERSION="4.56" 16 | S3_BUCKET="gregburek-buildpack-pgbouncer" 17 | 18 | # parse and derive params 19 | BUILD_DIR=$1 20 | CACHE_DIR=$2 21 | LP_DIR=`cd $(dirname $0); cd ..; pwd` 22 | BUILDPACK_DIR="$(dirname $(dirname $0))" 23 | 24 | function error() { 25 | echo " ! $*" >&2 26 | exit 1 27 | } 28 | 29 | function indent() { 30 | c='s/^/ /' 31 | case $(uname) in 32 | Darwin) sed -l "$c";; 33 | *) sed -u "$c";; 34 | esac 35 | } 36 | 37 | function package_download() { 38 | engine="$1" 39 | version="$2" 40 | location="$3" 41 | 42 | mkdir -p $location 43 | package="https://${S3_BUCKET}.s3.amazonaws.com/$engine-$version.tgz" 44 | curl $package -s -o - | tar xzf - -C $location 45 | } 46 | 47 | echo "Using pgbouncer version: ${PGBOUNCER_VERSION}" | indent 48 | echo "Using stunnel version: ${STUNNEL_VERSION}" | indent 49 | 50 | # vendor directories 51 | VENDORED_PGBOUNCER="vendor/pgbouncer" 52 | VENDORED_STUNNEL="vendor/stunnel" 53 | 54 | # vendor pgbouncer into the slug 55 | PATH="$BUILD_DIR/$VENDORED_PGBOUNCER/bin:$PATH" 56 | echo "-----> Fetching and vendoring pgbouncer into slug" 57 | mkdir -p "$BUILD_DIR/$VENDORED_PGBOUNCER" 58 | package_download "pgbouncer" "${PGBOUNCER_VERSION}" "${BUILD_DIR}/${VENDORED_PGBOUNCER}" 59 | 60 | # vendor stunnel into the slug 61 | PATH="$BUILD_DIR/$VENDORED_STUNNEL/bin:$PATH" 62 | echo "-----> Fetching and vendoring stunnel into slug" 63 | mkdir -p "$BUILD_DIR/$VENDORED_STUNNEL" 64 | package_download "stunnel" "${STUNNEL_VERSION}" "${BUILD_DIR}/${VENDORED_STUNNEL}" 65 | 66 | echo "-----> Moving the configuration generation script into app/.profile.d" 67 | mkdir -p $BUILD_DIR/.profile.d 68 | cp "$BUILDPACK_DIR/bin/gen-pgbouncer-conf.sh" $BUILD_DIR/.profile.d/ 69 | chmod +x $BUILD_DIR/.profile.d/gen-pgbouncer-conf.sh 70 | 71 | echo "-----> Moving the start-pgbouncer-stunnel script into app/bin" 72 | mkdir -p $BUILD_DIR/bin 73 | cp "$BUILDPACK_DIR/bin/start-pgbouncer-stunnel" $BUILD_DIR/bin/ 74 | chmod +x $BUILD_DIR/bin/start-pgbouncer-stunnel 75 | 76 | echo "-----> pgbouncer/stunnel done" 77 | -------------------------------------------------------------------------------- /support/aws/hmac: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Implement HMAC functionality on top of the OpenSSL digest functions. 3 | # licensed under the terms of the GNU GPL v2 4 | # Copyright 2007 Victor Lowther 5 | 6 | die() { 7 | echo $* 8 | exit 1 9 | } 10 | 11 | check_deps() { 12 | local res=0 13 | while [ $# -ne 0 ]; do 14 | which "${1}" >& /dev/null || { res=1; echo "${1} not found."; } 15 | shift 16 | done 17 | (( res == 0 )) || die "aborting." 18 | } 19 | 20 | # write a byte (passed as hex) to stdout 21 | write_byte() { 22 | # $1 = byte to write 23 | printf "\\x$(printf "%x" ${1})" 24 | } 25 | 26 | # make an hmac pad out of a key. 27 | # this is not the most secure way of doing it, but it is 28 | # the most expedient. 29 | make_hmac_pad() { 30 | # using key in file $1 and byte in $2, create the appropriate hmac pad 31 | # Pad keys out to $3 bytes 32 | # if key is longer than $3, use hash $4 to hash the key first. 33 | local x y a size remainder oifs 34 | (( remainder = ${3} )) 35 | # in case someone else was messing with IFS. 36 | for x in $(echo -n "${1}" | od -v -t u1 | cut -b 9-); 37 | do 38 | write_byte $((${x} ^ ${2})) 39 | (( remainder -= 1 )) 40 | done 41 | for ((y=0; remainder - y ;y++)); do 42 | write_byte $((0 ^ ${2})) 43 | done 44 | } 45 | 46 | # utility functions for making hmac pads 47 | hmac_ipad() { 48 | make_hmac_pad "${1}" 0x36 ${2} "${3}" 49 | } 50 | 51 | hmac_opad() { 52 | make_hmac_pad "${1}" 0x5c ${2} "${3}" 53 | } 54 | 55 | # hmac something 56 | do_hmac() { 57 | # $1 = algo to use. Must be one that openssl knows about 58 | # $2 = keyfile to use 59 | # $3 = file to hash. uses stdin if none is given. 60 | # accepts input on stdin, leaves it on stdout. 61 | # Output is binary, if you want something else pipe it accordingly. 62 | local blocklen keysize x 63 | case "${1}" in 64 | sha) blocklen=64 ;; 65 | sha1) blocklen=64 ;; 66 | md5) blocklen=64 ;; 67 | md4) blocklen=64 ;; 68 | sha256) blocklen=64 ;; 69 | sha512) blocklen=128 ;; 70 | *) die "Unknown hash ${1} passed to hmac!" ;; 71 | esac 72 | cat <(hmac_ipad ${2} ${blocklen} "${1}") "${3:--}" | openssl dgst "-${1}" -binary | \ 73 | cat <(hmac_opad ${2} ${blocklen} "${1}") - | openssl dgst "-${1}" -binary 74 | } 75 | 76 | [[ ${1} ]] || die "Must pass the name of the hash function to use to ${0}". 77 | 78 | check_deps od openssl 79 | do_hmac "${@}" 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Heroku buildpack: pgbouncer 2 | ========================= 3 | 4 | This is a [Heroku buildpack](http://devcenter.heroku.com/articles/buildpacks) that 5 | allows one to run pgbouncer and stunnel in a dyno alongside application code. 6 | It is meant to be used in conjunction with other buildpacks as part of a 7 | [multi-buildpack](https://github.com/ddollar/heroku-buildpack-multi). 8 | 9 | The primary use of this buildpack is to allow for transaction pooling of 10 | PostgreSQL database connections among multiple workers in a dyno. For example, 11 | 10 unicorn workers would be able to share a single database connection, avoiding 12 | connection limits and Out Of Memory errors on the Postgres server. 13 | 14 | It uses [stunnel](http://stunnel.org/) and [pgbouncer](http://wiki.postgresql.org/wiki/PgBouncer). 15 | 16 | 17 | FAQ 18 | ---- 19 | - Q: Why should I use transaction pooling? 20 | - A: You have many workers per dyno that hold open idle Postgres connections and 21 | and you want to reduce the number of unused connections. [This is a slightly more complete answer from stackoverflow](http://stackoverflow.com/questions/12189162/what-are-advantages-of-using-transaction-pooling-with-pgbouncer) 22 | 23 | - Q: Why shouldn't I use transaction pooling? 24 | - A: If you need to use named prepared statements, advisory locks, listen/notify, or other features that operate on a session level. 25 | Please refer to PGBouncer's [feature matrix](http://wiki.postgresql.org/wiki/PgBouncer#Feature_matrix_for_pooling_modes) for all transaction pooling caveats. 26 | 27 | 28 | Disable Prepared Statements 29 | ----- 30 | Some ORMs (like ActiveRecord [3.2.9](https://github.com/rails/rails/pull/5872)) 31 | allow prepared statements to be disabled 32 | by appending `?prepared_statements=false` to the database's URI. Set 33 | the `PGBOUNCER_PREPARED_STATEMENTS` config var to `false` for the buildpack 34 | to do that for you. 35 | 36 | 37 | Usage 38 | ----- 39 | 40 | Example usage: 41 | 42 | $ ls -a 43 | .buildpacks Gemfile Gemfile.lock Procfile config/ config.ru 44 | 45 | $ heroku config:add BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git 46 | 47 | $ cat .buildpacks 48 | https://github.com/heroku/heroku-buildpack-pgbouncer.git#v0.2 49 | https://github.com/heroku/heroku-buildpack-ruby.git 50 | 51 | $ cat Procfile 52 | web: bin/start-pgbouncer-stunnel bundle exec unicorn -p $PORT -c ./config/unicorn.rb -E $RACK_ENV 53 | worker: bundle exec rake worker 54 | 55 | $ git push heroku master 56 | ... 57 | -----> Fetching custom git buildpack... done 58 | -----> Multipack app detected 59 | =====> Downloading Buildpack: https://github.com/gregburek/heroku-buildpack-pgbouncer.git 60 | =====> Detected Framework: pgbouncer-stunnel 61 | Using pgbouncer version: 1.5.4 62 | Using stunnel version: 4.56 63 | -----> Fetching and vendoring pgbouncer into slug 64 | -----> Fetching and vendoring stunnel into slug 65 | -----> Moving the configuration generation script into app/.profile.d 66 | -----> Moving the start-pgbouncer-stunnel script into app/bin 67 | -----> pgbouncer/stunnel done 68 | =====> Downloading Buildpack: https://github.com/heroku/heroku-buildpack-ruby.git 69 | =====> Detected Framework: Ruby/Rack 70 | -----> Using Ruby version: ruby-1.9.3 71 | -----> Installing dependencies using Bundler version 1.3.2 72 | ... 73 | 74 | The buildpack will install and configure pgbouncer and stunnel to connect to 75 | `DATABASE_URL` over a SSL connection. Prepend `bin/start-pgbouncer-stunnel` 76 | to any process in the Procfile to run pgbouncer and stunnel alongside that process. 77 | 78 | Tweak settings 79 | ----- 80 | Some settings are configurable through app config vars at runtime. Refer to the appropriate documentation for 81 | [pgbouncer](http://pgbouncer.projects.pgfoundry.org/doc/config.html#_generic_settings) 82 | and [stunnel](http://linux.die.net/man/8/stunnel) configurations to see what settings are right for you. 83 | 84 | - `PGBOUNCER_DEFAULT_POOL_SIZE` Default is 1 85 | - `PGBOUNCER_RESERVE_POOL_SIZE` Default is 1 86 | - `PGBOUNCER_RESERVE_POOL_TIMEOUT` Default is 5.0 seconds 87 | 88 | For more info, see [CONTRIBUTING.md](CONTRIBUTING.md) 89 | -------------------------------------------------------------------------------- /support/aws/s3: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # basic amazon s3 operations 3 | # Licensed under the terms of the GNU GPL v2 4 | # Copyright 2007 Victor Lowther 5 | 6 | set -e 7 | 8 | basedir="$( cd -P "$( dirname "$0" )" && pwd )" 9 | PATH="$basedir:$PATH" 10 | 11 | # print a message and bail 12 | die() { 13 | echo $* 14 | exit 1 15 | } 16 | 17 | # check to see if the variable name passed exists and holds a value. 18 | # Die if it does not. 19 | check_or_die() { 20 | [[ ${!1} ]] || die "Environment variable ${1} is not set." 21 | } 22 | 23 | # check to see if we have all the needed S3 variables defined. 24 | # Bail if we do not. 25 | check_s3() { 26 | local sak x 27 | for x in AWS_ID AWS_SECRET; do 28 | check_or_die ${x}; 29 | done 30 | sak="$(echo -n $AWS_SECRET | wc -c)" 31 | (( ${sak%%[!0-9 ]*} == 40 )) || \ 32 | die "S3 Secret Access Key is not exactly 40 bytes long. Please fix it." 33 | } 34 | # check to see if our external dependencies exist 35 | check_dep() { 36 | local res=0 37 | while [[ $# -ne 0 ]]; do 38 | which "${1}" >& /dev/null || { res=1; echo "${1} not found."; } 39 | shift 40 | done 41 | (( res == 0 )) || die "aborting." 42 | } 43 | 44 | check_deps() { 45 | check_dep openssl date hmac cat grep curl 46 | check_s3 47 | } 48 | 49 | urlenc() { 50 | # $1 = string to url encode 51 | # output is on stdout 52 | # we don't urlencode everything, just enough stuff. 53 | echo -n "${1}" | 54 | sed 's/%/%25/g 55 | s/ /%20/g 56 | s/#/%23/g 57 | s/\$/%24/g 58 | s/\&/%26/g 59 | s/+/%2b/g 60 | s/,/%2c/g 61 | s/:/%3a/g 62 | s/;/%3b/g 63 | s/?/%3f/g 64 | s/@/%40/g 65 | s/ /%09/g' 66 | } 67 | 68 | xmldec() { 69 | # no parameters. 70 | # accept input on stdin, put it on stdout. 71 | # patches accepted to get more stuff 72 | sed 's/\"/\"/g 73 | s/\&/\&/g 74 | s/\<//g' 76 | } 77 | 78 | ## basic S3 functionality. x-amz-header functionality is not implemented. 79 | # make an S3 signature string, which will be output on stdout. 80 | s3_signature_string() { 81 | # $1 = HTTP verb 82 | # $2 = date string, must be in UTC 83 | # $3 = bucket name, if any 84 | # $4 = resource path, if any 85 | # $5 = content md5, if any 86 | # $6 = content MIME type, if any 87 | # $7 = canonicalized headers, if any 88 | # signature string will be output on stdout 89 | local verr="Must pass a verb to s3_signature_string!" 90 | local verb="${1:?verr}" 91 | local bucket="${3}" 92 | local resource="${4}" 93 | local derr="Must pass a date to s3_signature_string!" 94 | local date="${2:?derr}" 95 | local mime="${6}" 96 | local md5="${5}" 97 | local headers="${7}" 98 | printf "%s\n%s\n%s\n%s\n%s\n%s%s" \ 99 | "${verb}" "${md5}" "${mime}" "${date}" \ 100 | "${headers}" "${bucket}" "${resource}" | \ 101 | hmac sha1 "${AWS_SECRET}" | openssl base64 -e -a 102 | } 103 | 104 | # cheesy, but it is the best way to have multiple headers. 105 | curl_headers() { 106 | # each arg passed will be output on its own line 107 | local parms=$# 108 | for ((;$#;)); do 109 | echo "header = \"${1}\"" 110 | shift 111 | done 112 | } 113 | 114 | s3_curl() { 115 | # invoke curl to do all the heavy HTTP lifting 116 | # $1 = method (one of GET, PUT, or DELETE. HEAD is not handled yet.) 117 | # $2 = remote bucket. 118 | # $3 = remote name 119 | # $4 = local name. 120 | # $5 = mime type 121 | local bucket remote date sig md5 arg inout headers 122 | # header handling is kinda fugly, but it works. 123 | bucket="${2:+/${2}}/" # slashify the bucket 124 | remote="$(urlenc "${3}")" # if you don't, strange things may happen. 125 | stdopts="--connect-timeout 10 --fail --silent" 126 | mime="${5}" 127 | [[ $CURL_S3_DEBUG == true ]] && stdopts="${stdopts} --show-error --fail" 128 | case "${1}" in 129 | GET) arg="-o" inout="${4:--}" # stdout if no $4 130 | headers[${#headers[@]}]="x-amz-acl: public-read" 131 | ;; 132 | PUT) [[ ${2} ]] || die "PUT can has bucket?" 133 | if [[ ! ${3} ]]; then 134 | arg="-X PUT" 135 | headers[${#headers[@]}]="Content-Length: 0" 136 | elif [[ -f ${4} ]]; then 137 | md5="$(openssl dgst -md5 -binary "${4}"|openssl base64 -e -a)" 138 | arg="-T" inout="${4}" 139 | headers[${#headers[@]}]="x-amz-acl: public-read" 140 | headers[${#headers[@]}]="Expect: 100-continue" 141 | if [ "$mime" != "" ]; then 142 | headers[${#headers[@]}]="Content-Type: $mime" 143 | fi 144 | else 145 | die "Cannot write non-existing file ${4}" 146 | fi 147 | ;; 148 | DELETE) arg="-X DELETE" 149 | ;; 150 | HEAD) arg="-I" ;; 151 | *) die "Unknown verb ${1}. It probably would not have worked anyways." ;; 152 | esac 153 | date="$(TZ=UTC date '+%a, %e %b %Y %H:%M:%S %z')" 154 | sig=$(s3_signature_string ${1} "${date}" "${bucket}" "${remote}" "${md5}" "${mime}" "x-amz-acl:public-read") 155 | 156 | headers[${#headers[@]}]="Authorization: AWS ${AWS_ID}:${sig}" 157 | headers[${#headers[@]}]="Date: ${date}" 158 | [[ ${md5} ]] && headers[${#headers[@]}]="Content-MD5: ${md5}" 159 | curl ${arg} "${inout}" ${stdopts} -o - -K <(curl_headers "${headers[@]}") \ 160 | "http://s3.amazonaws.com${bucket}${remote}" 161 | return $? 162 | } 163 | 164 | s3_put() { 165 | # $1 = remote bucket to put it into 166 | # $2 = remote name to put 167 | # $3 = file to put. This must be present if $2 is. 168 | # $4 = mime type 169 | s3_curl PUT "${1}" "${2}" "${3:-${2}}" "${4}" 170 | return $? 171 | } 172 | 173 | s3_get() { 174 | # $1 = bucket to get file from 175 | # $2 = remote file to get 176 | # $3 = local file to get into. Will be overwritten if it exists. 177 | # If this contains a path, that path must exist before calling this. 178 | s3_curl GET "${1}" "${2}" "${3:-${2}}" 179 | return $? 180 | } 181 | 182 | s3_test() { 183 | # same args as s3_get, but uses the HEAD verb instead of the GET verb. 184 | s3_curl HEAD "${1}" "${2}" >/dev/null 185 | return $? 186 | } 187 | 188 | # Hideously ugly, but it works well enough. 189 | s3_buckets() { 190 | s3_get |grep -o '[^>]*' |sed 's/<[^>]*>//g' |xmldec 191 | return $? 192 | } 193 | 194 | # this will only return the first thousand entries, alas 195 | # Mabye some kind soul can fix this without writing an XML parser in bash? 196 | # Also need to add xml entity handling. 197 | s3_list() { 198 | # $1 = bucket to list 199 | [ "x${1}" == "x" ] && return 1 200 | s3_get "${1}" |grep -o '[^>]*' |sed 's/<[^>]*>//g'| xmldec 201 | return $? 202 | } 203 | 204 | s3_delete() { 205 | # $1 = bucket to delete from 206 | # $2 = item to delete 207 | s3_curl DELETE "${1}" "${2}" 208 | return $? 209 | } 210 | 211 | # because this uses s3_list, it suffers from the same flaws. 212 | s3_rmrf() { 213 | # $1 = bucket to delete everything from 214 | s3_list "${1}" | while read f; do 215 | s3_delete "${1}" "${f}"; 216 | done 217 | } 218 | 219 | check_deps 220 | case $1 in 221 | put) shift; s3_put "$@" ;; 222 | get) shift; s3_get "$@" ;; 223 | rm) shift; s3_delete "$@" ;; 224 | ls) shift; s3_list "$@" ;; 225 | test) shift; s3_test "$@" ;; 226 | buckets) s3_buckets ;; 227 | rmrf) shift; s3_rmrf "$@" ;; 228 | *) die "Unknown command ${1}." 229 | ;; 230 | esac 231 | --------------------------------------------------------------------------------