Skip to content

Commit

Permalink
Starting points for NodeJS support
Browse files Browse the repository at this point in the history
  • Loading branch information
kryptt committed May 10, 2013
1 parent f485a55 commit 738983f
Show file tree
Hide file tree
Showing 23 changed files with 3,466 additions and 0 deletions.
185 changes: 185 additions & 0 deletions bin/node
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/usr/bin/env bash
# bin/node <build-dir> <cache-dir>

# fail fast
set -e

# debug
# set -x

# clean up leaking environment
unset GIT_DIR

# config
SCONS_VERSION="1.2.0"
S3_BUCKET="heroku-buildpack-nodejs"

# parse and derive params
BUILD_DIR=$1
CACHE_DIR=$2
LP_DIR=`cd $(dirname $0); cd ..; pwd`

function error() {
echo " ! $*" >&2
exit 1
}

function mktmpdir() {
dir=$(mktemp -t node-$1-XXXX)
rm -rf $dir
mkdir -p $dir
echo $dir
}

function indent() {
c='s/^/ /'
case $(uname) in
Darwin) sed -l "$c";;
*) sed -u "$c";;
esac
}

function run_npm() {
command="$1"

cd $BUILD_DIR
HOME="$BUILD_DIR" $VENDORED_NODE/bin/node $VENDORED_NPM/cli.js $command 2>&1 | indent

if [ "${PIPESTATUS[*]}" != "0 0" ]; then
echo " ! Failed to $command dependencies with npm"
exit 1
fi
}

function manifest_versions() {
curl "http://${S3_BUCKET}.s3.amazonaws.com/manifest.${1}" -s -o - | tr -s '\n' ' '
}

function resolve_version() {
available_versions="$1"
requested_version="$2"
default_version="$3"

args=""
for version in $available_versions; do args="${args} -v \"${version}\""; done

if [ "$2" == "" ]; then
args="${args} -r \"${default_version}\"";
else
args="${args} -r \"${requested_version}\"";
fi

evaluated_versions=$(eval $bootstrap_node/bin/node $LP_DIR/vendor/node-semver/bin/semver ${args} || echo "")
echo "$evaluated_versions" | tail -n 1
}

function package_engine_version() {
version=$(cat $BUILD_DIR/package.json | $bootstrap_node/bin/node $LP_DIR/vendor/json/json engines.$1 2>/dev/null)
if [ $? == 0 ]; then
echo $version | sed -e 's/\([<>=]\) /\1/g'
fi
}

function package_resolve_version() {
engine="$1"
resolved_version=$(resolve_version "${engine_versions[$engine]}" "${engine_requests[$engine]}" "${engine_defaults[$engine]}")

if [ "${resolved_version}" == "" ]; then
error "Requested engine $engine version ${engine_requests[$engine]} does not match available versions: ${engine_versions[$engine]}"
else
echo $resolved_version
fi
}

function package_download() {
engine="$1"
version="$2"
location="$3"

mkdir -p $location
package="http://${S3_BUCKET}.s3.amazonaws.com/$engine-$version.tgz"
curl $package -s -o - | tar xzf - -C $location
}

function cat_npm_debug_log() {
if [ -f $BUILD_DIR/npm-debug.log ]; then
cat $BUILD_DIR/npm-debug.log
fi
}

trap cat_npm_debug_log EXIT

bootstrap_node=$(mktmpdir bootstrap_node)
package_download "nodejs" "0.4.7" $bootstrap_node

# make some associative arrays
declare -A engine_versions
declare -A engine_defaults
declare -A engine_requests

engine_defaults["node"]="0.10.x"
engine_defaults["npm"]="1.2.x"

engine_versions["node"]=$(manifest_versions "nodejs")
engine_requests["node"]=$(package_engine_version "node")

engine_versions["npm"]=$(manifest_versions "npm")
engine_requests["npm"]=$(package_engine_version "npm")

echo "-----> Resolving engine versions"

# add a warning if no version of node specified
if [ "${engine_requests["node"]}" == "" ]; then
echo
echo "WARNING: No version of Node.js specified in package.json, see:" | indent
echo "https://devcenter.heroku.com/articles/nodejs-versions" | indent
echo
fi

NODE_VERSION=$(package_resolve_version "node")
echo "Using Node.js version: ${NODE_VERSION}" | indent

NPM_VERSION=$(package_resolve_version "npm")
echo "Using npm version: ${NPM_VERSION}" | indent

# cache directories
CACHE_STORE_DIR="$CACHE_DIR/node_modules/$NODE_VERSION/$NPM_VERSION"
CACHE_TARGET_DIR="$BUILD_DIR/node_modules"

# s3 packages
NODE_PACKAGE="http://${S3_BUCKET}.s3.amazonaws.com/nodejs-${NODE_VERSION}.tgz"
NPM_PACKAGE="http://${S3_BUCKET}.s3.amazonaws.com/npm-${NPM_VERSION}.tgz"
SCONS_PACKAGE="http://${S3_BUCKET}.s3.amazonaws.com/scons-${SCONS_VERSION}.tgz"

# vendor directories
VENDORED_NODE="$(mktmpdir node)"
VENDORED_NPM="$(mktmpdir npm)"
VENDORED_SCONS="$(mktmpdir scons)"

# download and unpack packages
echo "-----> Fetching Node.js binaries"
package_download "nodejs" "${NODE_VERSION}" "${VENDORED_NODE}"
package_download "npm" "${NPM_VERSION}" "${VENDORED_NPM}"
package_download "scons" "${SCONS_VERSION}" "${VENDORED_SCONS}"

# vendor node into the slug
PATH="$BUILD_DIR/bin:$PATH"
echo "-----> Vendoring node into slug"
mkdir -p "$BUILD_DIR/bin"
cp "$VENDORED_NODE/bin/node" "$BUILD_DIR/bin/node"

# setting up paths for building
PATH="$VENDORED_SCONS:$VENDORED_NODE/bin:$PATH"
INCLUDE_PATH="$VENDORED_NODE/include"
export CPATH="$INCLUDE_PATH"
export CPPPATH="$INCLUDE_PATH"

# install dependencies with npm
echo "-----> Installing dependencies with npm"
run_npm "install --production"
run_npm "rebuild"
echo "Dependencies installed" | indent

echo "-----> Building runtime environment"
mkdir -p $BUILD_DIR/.profile.d
echo "export PATH=\"\$HOME/bin:\$HOME/node_modules/.bin:\$PATH\"" > $BUILD_DIR/.profile.d/nodejs.sh
79 changes: 79 additions & 0 deletions support/aws/hmac
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/bin/bash
# Implement HMAC functionality on top of the OpenSSL digest functions.
# licensed under the terms of the GNU GPL v2
# Copyright 2007 Victor Lowther <[email protected]>

die() {
echo $*
exit 1
}

check_deps() {
local res=0
while [ $# -ne 0 ]; do
which "${1}" >& /dev/null || { res=1; echo "${1} not found."; }
shift
done
(( res == 0 )) || die "aborting."
}

# write a byte (passed as hex) to stdout
write_byte() {
# $1 = byte to write
printf "\\x$(printf "%x" ${1})"
}

# make an hmac pad out of a key.
# this is not the most secure way of doing it, but it is
# the most expedient.
make_hmac_pad() {
# using key in file $1 and byte in $2, create the appropriate hmac pad
# Pad keys out to $3 bytes
# if key is longer than $3, use hash $4 to hash the key first.
local x y a size remainder oifs
(( remainder = ${3} ))
# in case someone else was messing with IFS.
for x in $(echo -n "${1}" | od -v -t u1 | cut -b 9-);
do
write_byte $((${x} ^ ${2}))
(( remainder -= 1 ))
done
for ((y=0; remainder - y ;y++)); do
write_byte $((0 ^ ${2}))
done
}

# utility functions for making hmac pads
hmac_ipad() {
make_hmac_pad "${1}" 0x36 ${2} "${3}"
}

hmac_opad() {
make_hmac_pad "${1}" 0x5c ${2} "${3}"
}

# hmac something
do_hmac() {
# $1 = algo to use. Must be one that openssl knows about
# $2 = keyfile to use
# $3 = file to hash. uses stdin if none is given.
# accepts input on stdin, leaves it on stdout.
# Output is binary, if you want something else pipe it accordingly.
local blocklen keysize x
case "${1}" in
sha) blocklen=64 ;;
sha1) blocklen=64 ;;
md5) blocklen=64 ;;
md4) blocklen=64 ;;
sha256) blocklen=64 ;;
sha512) blocklen=128 ;;
*) die "Unknown hash ${1} passed to hmac!" ;;
esac
cat <(hmac_ipad ${2} ${blocklen} "${1}") "${3:--}" | openssl dgst "-${1}" -binary | \
cat <(hmac_opad ${2} ${blocklen} "${1}") - | openssl dgst "-${1}" -binary
}

[[ ${1} ]] || die "Must pass the name of the hash function to use to ${0}".

check_deps od openssl
do_hmac "${@}"
Loading

0 comments on commit 738983f

Please sign in to comment.