mirror of
https://github.com/ViViDboarder/shoestrap.git
synced 2024-11-24 04:16:30 +00:00
initial commit
This commit is contained in:
commit
dfab18817e
150
README.md
Normal file
150
README.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# Shoestrap
|
||||||
|
|
||||||
|
Shoestrap is a simple framework to bootstrap *nix machines.
|
||||||
|
|
||||||
|
It speaks Bash so there's virtually no learning curve. More importantly, you
|
||||||
|
won't have to learn yet another DSL. Shoestrap aims to get out of your way.
|
||||||
|
|
||||||
|
You should be able to get up and running in minutes, not hours.
|
||||||
|
|
||||||
|
|
||||||
|
## What about Chef, Puppet and co.?
|
||||||
|
|
||||||
|
Chef and Puppet are great tools, but they are too complex for most use cases.
|
||||||
|
The learning curve for these tools is quite steep as they each have their own
|
||||||
|
DSL. On the other end, Shoestrap is just Bash. It does not require any
|
||||||
|
'Bash to config files' translation.
|
||||||
|
|
||||||
|
I believe Shoestrap is a great simple alternative to Chef or Puppet that will
|
||||||
|
fulfill the needs of most people.
|
||||||
|
|
||||||
|
|
||||||
|
## Terminology
|
||||||
|
|
||||||
|
Shoestrap uses some of the Chef terminology since I couldn't come up with
|
||||||
|
better names or analogies.
|
||||||
|
|
||||||
|
### Cookbook
|
||||||
|
|
||||||
|
A cookbook is a Bash script that executes different actions. For example,
|
||||||
|
it may install packages, run 'recipes'. Think of it as a dispatcher.
|
||||||
|
|
||||||
|
Cookbooks live at the root of your Shoestrap project. You can have multiple
|
||||||
|
cookbooks per project.
|
||||||
|
|
||||||
|
### Recipes
|
||||||
|
|
||||||
|
Recipes are snippets of Bash code that can be executed from a Cookbook. For
|
||||||
|
example, you may have a recipe to install `memcached`, or a recipe to setup
|
||||||
|
SSH keys on the target machine. Remember, it's just Bash, so anything goes.
|
||||||
|
|
||||||
|
### Assets
|
||||||
|
|
||||||
|
An asset is a file that will be needed by the target machine. For example,
|
||||||
|
a configuration file or an init script.
|
||||||
|
|
||||||
|
|
||||||
|
## Helpers
|
||||||
|
|
||||||
|
Shoestrap ships with many Bash helpers functions. They can be found in
|
||||||
|
`helpers/default`. You do NOT need to use the built-in helper functions,
|
||||||
|
but they will simplify many of the most common tasks you'll need to perform.
|
||||||
|
|
||||||
|
Helper functions can be used from cookbooks or recipes. You may also pass
|
||||||
|
arguments to these functions.
|
||||||
|
|
||||||
|
You may add your own helper functions in `helpers/custom`.
|
||||||
|
|
||||||
|
Here are some of the most commonly used helpers:
|
||||||
|
|
||||||
|
#### `add_line`
|
||||||
|
Concatenate a line to a text file if it's not already there.
|
||||||
|
|
||||||
|
#### `add_user`
|
||||||
|
Add a user to the system.
|
||||||
|
|
||||||
|
#### `copy`
|
||||||
|
Copy an asset file. It first looks in the assets/{cookbook} directory and falls back to assets/default if file doesn't exist.
|
||||||
|
|
||||||
|
#### `error`
|
||||||
|
Write an error to the screen and halt execution.
|
||||||
|
|
||||||
|
#### `is_installed`
|
||||||
|
Check if an element has already been installed. Useful to prevent code from running more than once. Also see `set_installed`.
|
||||||
|
|
||||||
|
#### `log`
|
||||||
|
Write a line to the screen.
|
||||||
|
|
||||||
|
#### `package`
|
||||||
|
Install a package (ie: apt-get install {package-name}).
|
||||||
|
|
||||||
|
#### `package_update`
|
||||||
|
Update packages in package manager (ie: apt-get update).
|
||||||
|
|
||||||
|
#### `recipe`
|
||||||
|
Run a recipe. It first looks in the recipes/{cookbook} directory and falls back to recipes/default if file doesn't exist.
|
||||||
|
|
||||||
|
#### `set_installed`
|
||||||
|
Sets an element as 'installed'.
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Clone the `shoestrap` repo to your local machine.
|
||||||
|
`git clone https://github.com/cmer/shoestrap.git`
|
||||||
|
|
||||||
|
2. Rename `./my-cookbook` to something a little bit more meaningful. For example,
|
||||||
|
you might want to call your cookbook `web` if it bootstraps a web server. Make
|
||||||
|
sure it is executable (`chmod +x {my-cookbook}`).
|
||||||
|
|
||||||
|
3. Specify actions to take in the cookbook. For example, which recipes to run, which
|
||||||
|
packages to install or which user(s) to add. For example: `recipe 'nginx'`.
|
||||||
|
|
||||||
|
4. Create a recipe file under `recipes/default`. For example: `recipes/default/nginx`. The recipe
|
||||||
|
is the code to execute. In our example, it would be the code to run to install `nginx`.
|
||||||
|
|
||||||
|
5. Add assets (if needed) under `assets/default/{recipe}`. For example: `assets/default/nginx/nginx.conf`.
|
||||||
|
|
||||||
|
6. Upload your project to the target machine. You can use `scp`, Capistrano, Git or whatever you feel
|
||||||
|
comfortable with.
|
||||||
|
|
||||||
|
7. Run your cookbook from the target machine. For example: `sudo ./web`.
|
||||||
|
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
You can see a sample project at http://github.com/cmer/shoestrap-example
|
||||||
|
|
||||||
|
Browse the source code, it's the best way to familiarize yourself with Shoestrap. It's also a great starting
|
||||||
|
point for your own Shoestrap project.
|
||||||
|
|
||||||
|
|
||||||
|
## Example: Directory Structure of a Shoestrap Project
|
||||||
|
|
||||||
|
[assets]
|
||||||
|
[default] # Assets to be used by default
|
||||||
|
[recipe1] # Assets for 'recipe1'.
|
||||||
|
foo.conf
|
||||||
|
bar.conf
|
||||||
|
[cookbook1] # Assets for 'cookbook1'. If asset cannot be found here, fallback is 'default'
|
||||||
|
[recipe1] # Assets for 'recipe1' when executed from 'cookbook1'. Overrides anything in [default].
|
||||||
|
foo.conf
|
||||||
|
[helpers]
|
||||||
|
custom # Your custom Bash functions and helpers
|
||||||
|
default # Shoestrap's default helpers
|
||||||
|
initialize # Initialize script.
|
||||||
|
[recipes]
|
||||||
|
[default] # Recipes to be used by default
|
||||||
|
recipe1
|
||||||
|
recipe2
|
||||||
|
recipe3
|
||||||
|
[cookbook1] # Recipes for 'cookbook1'. Overrides anything in [default].
|
||||||
|
recipe1
|
||||||
|
cookbook1 # The cookbook script itself. This is your point of entry to Shoestrap
|
||||||
|
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
Shoestrap has only been tested with Ubuntu Oneiric 11.10 but should work with any/most Unix-like
|
||||||
|
operating systems. My goal is to support Ubuntu/Debian, CentOS/Red Hat and Mac OS X. I will need
|
||||||
|
help from the community to achieve this, however.
|
0
assets/default/.gitkeep
Normal file
0
assets/default/.gitkeep
Normal file
5
helpers/custom
Normal file
5
helpers/custom
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Add your custom helpers here. Remember, this is just Bash!
|
||||||
|
##############################################################################
|
360
helpers/default
Normal file
360
helpers/default
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# DO NOT MODIFY THIS FILE. Instead, modify 'helpers/custom'.
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
COOKBOOK_NAME="$(basename $0)"
|
||||||
|
DIR="$( cd "$( dirname "$0" )" && pwd )"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Run a given recipe.
|
||||||
|
#
|
||||||
|
# Arguments can be passed to the 'recipe' function. They will be accessible by
|
||||||
|
# the recipe as $2, $3, $4, etc.
|
||||||
|
#
|
||||||
|
recipe () {
|
||||||
|
CURRENT_RECIPE_NAME=$1
|
||||||
|
DEFAULT_ASSETS_PATH="$DIR/assets/default/$CURRENT_RECIPE_NAME"
|
||||||
|
COOKBOOK_ASSETS_PATH="$DIR/assets/$COOKBOOK_NAME/$CURRENT_RECIPE_NAME"
|
||||||
|
local custom_recipe="$DIR/recipes/custom/$CURRENT_RECIPE_NAME"
|
||||||
|
local default_recipe="$DIR/recipes/default/$CURRENT_RECIPE_NAME"
|
||||||
|
|
||||||
|
if [ -f $custom_recipe ]; then
|
||||||
|
log "Running recipe '$custom_recipe'..." 1
|
||||||
|
separator
|
||||||
|
. $custom_recipe
|
||||||
|
|
||||||
|
elif [ -f $default_recipe ]; then
|
||||||
|
log "Running recipe '$default_recipe'..." 1
|
||||||
|
separator
|
||||||
|
. $default_recipe
|
||||||
|
else
|
||||||
|
error "Could not find recipe for '$CURRENT_RECIPE_NAME'. Fail!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $DIR
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Prints the 'finished' banner.
|
||||||
|
#
|
||||||
|
finished () {
|
||||||
|
spacer 1
|
||||||
|
separator "="
|
||||||
|
echo " FINISHED: '$COOKBOOK_NAME'"
|
||||||
|
separator "="
|
||||||
|
spacer 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Writes a log line to the screen
|
||||||
|
#
|
||||||
|
# If specified, the first parameter is the number of empty lines to print
|
||||||
|
# before the log message.
|
||||||
|
#
|
||||||
|
# If specified, the second parameter is the number of empty lines to print
|
||||||
|
# before the log message.
|
||||||
|
#
|
||||||
|
log () {
|
||||||
|
if [[ $2 -gt 0 ]]; then
|
||||||
|
spacer $2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " * $1"
|
||||||
|
|
||||||
|
if [[ $3 -gt 0 ]]; then
|
||||||
|
spacer $3
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Writes an error log line to the screen and exit with an error code.
|
||||||
|
#
|
||||||
|
error () {
|
||||||
|
spacer 2
|
||||||
|
echo " -> $1"
|
||||||
|
spacer 2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Write one or many empty lines to the screen.
|
||||||
|
#
|
||||||
|
spacer () {
|
||||||
|
if [ $1 ]; then
|
||||||
|
local spaces=$1
|
||||||
|
else
|
||||||
|
local spaces=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for (( i=0; i<$spaces; i++ )) do
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# noop
|
||||||
|
#
|
||||||
|
noop () {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Write a separator to the screen.
|
||||||
|
#
|
||||||
|
# You can optionally specify the separator character. Default is '-'.
|
||||||
|
#
|
||||||
|
separator () {
|
||||||
|
if [ $1 ]; then
|
||||||
|
local char=$1
|
||||||
|
else
|
||||||
|
local char='-'
|
||||||
|
fi
|
||||||
|
|
||||||
|
local width=$(tput cols)
|
||||||
|
|
||||||
|
for (( i=0; i < $width-2; i++ )) do
|
||||||
|
local output="$output$char"
|
||||||
|
done
|
||||||
|
echo $output
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Update packages in package manager.
|
||||||
|
#
|
||||||
|
package_update () {
|
||||||
|
log "Updating package manager..." 0 1
|
||||||
|
detect_package_manager
|
||||||
|
|
||||||
|
if [ "$PACKAGE_MANAGER" == 'apt-get' ]; then
|
||||||
|
apt-get update -y
|
||||||
|
elif [ "$PACKAGE_MANAGER" == 'yum' ]; then
|
||||||
|
yum check-update -y
|
||||||
|
elif [ "$PACKAGE_MANAGER" == 'brew' ]; then
|
||||||
|
brew update
|
||||||
|
else
|
||||||
|
error "Unknown package manager: $PACKAGE_MANAGER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
error "An error occured while updating packages. Fail!"
|
||||||
|
else
|
||||||
|
spacer 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Install a package through package manager
|
||||||
|
#
|
||||||
|
package () {
|
||||||
|
log "Installing package '$1'..."
|
||||||
|
detect_package_manager
|
||||||
|
|
||||||
|
test_package_installed $1 > /dev/null 2>&1
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log "Package '$1' is already installed. Skipping."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$PACKAGE_MANAGER" == 'apt-get' ]; then
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y $1
|
||||||
|
elif [ "$PACKAGE_MANAGER" == 'yum' ]; then
|
||||||
|
yum install -y $1
|
||||||
|
elif [ "$PACKAGE_MANAGER" == 'brew' ]; then
|
||||||
|
brew install $1
|
||||||
|
else
|
||||||
|
error "Unknown package manager: $PACKAGE_MANAGER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
error "An error occured while installing package '$1'. Fail!"
|
||||||
|
else
|
||||||
|
spacer 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Determine if a package is installed or not.
|
||||||
|
#
|
||||||
|
# If package is installed, function will return 0. If not, it will return 1.
|
||||||
|
#
|
||||||
|
test_package_installed () {
|
||||||
|
detect_package_manager
|
||||||
|
|
||||||
|
# When many packages are specified, skip test.
|
||||||
|
if [ $# -gt 1 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$PACKAGE_MANAGER" == 'apt-get' ]; then
|
||||||
|
dpkg -l $1
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Don't know how to detect if a package is installed with other package managers.
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Determine which package manager is in use on the system.
|
||||||
|
#
|
||||||
|
detect_package_manager () {
|
||||||
|
if [ "$PACKAGE_MANAGER" != "" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command_exist apt-get; then
|
||||||
|
PACKAGE_MANAGER='apt-get'
|
||||||
|
elif command_exist yum; then
|
||||||
|
PACKAGE_MANAGER='yum'
|
||||||
|
elif command_exist brew; then
|
||||||
|
PACKAGE_MANAGER='brew'
|
||||||
|
else
|
||||||
|
error "Could not find a package manager. Fail!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Detected package manager: $PACKAGE_MANAGER"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Determines if a command exist on the system.
|
||||||
|
#
|
||||||
|
command_exist () {
|
||||||
|
command -v "$1" > /dev/null 2>&1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copy a file from the assets folder to the specified location.
|
||||||
|
#
|
||||||
|
copy () {
|
||||||
|
local cookbook_assets_source="$COOKBOOK_ASSETS_PATH/$1"
|
||||||
|
local default_assets_source="$DEFAULT_ASSETS_PATH/$1"
|
||||||
|
local target=$2
|
||||||
|
|
||||||
|
if [ -f $cookbook_assets_source ]; then
|
||||||
|
log "Copying $cookbook_assets_source to $target..."
|
||||||
|
cp $cookbook_assets_source $target
|
||||||
|
elif [ -f $default_assets_source ]; then
|
||||||
|
log "Copying $default_assets_source to $target..."
|
||||||
|
cp $default_assets_source $target
|
||||||
|
else
|
||||||
|
error "Could not find '$1' to copy. Fail!"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add a user to the system.
|
||||||
|
#
|
||||||
|
add_user () {
|
||||||
|
local user=$1
|
||||||
|
local pass=$2
|
||||||
|
local args=$3
|
||||||
|
|
||||||
|
id $user > /dev/null 2>&1
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log "User $user already exists. Skipped creation."
|
||||||
|
else
|
||||||
|
log "Adding user $user..."
|
||||||
|
[ "$pass" == "" ] && pass=generate_password
|
||||||
|
|
||||||
|
if [[ "$args" != *nohome* ]]; then
|
||||||
|
/usr/sbin/useradd --password `openssl passwd -crypt $pass` --create-home $user
|
||||||
|
else
|
||||||
|
/usr/sbin/useradd --password `openssl passwd -crypt $pass` $user
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generate a random password.
|
||||||
|
#
|
||||||
|
generate_password() {
|
||||||
|
local l=$1
|
||||||
|
[ "$l" == "" ] && l=8
|
||||||
|
tr -dc A-Za-z0-9_ < /dev/urandom | head -c ${l} | xargs
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Run a command as another user
|
||||||
|
#
|
||||||
|
run_as () {
|
||||||
|
local user=$1
|
||||||
|
local cmd=$2
|
||||||
|
log "Running command as '$user'..."
|
||||||
|
log "$cmd"
|
||||||
|
# sudo -u $user -H -s /bin/bash -c "$cmd"
|
||||||
|
# sudo -u $user -s /bin/bash -i "$cmd"
|
||||||
|
su -c "$cmd" -s /bin/bash $user
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add line to a file if line is not already present
|
||||||
|
#
|
||||||
|
add_line () {
|
||||||
|
local line=$1
|
||||||
|
local file=$2
|
||||||
|
grep "$line" $file > /dev/null 2>&1
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log "Adding '$line' to '$file'..."
|
||||||
|
echo "$line" >> $file
|
||||||
|
else
|
||||||
|
log "'$line' already in '$file'. Skipping."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Write a warning if user is not root.
|
||||||
|
#
|
||||||
|
warn_if_not_root () {
|
||||||
|
uid=`id -u` && [ "$uid" = "0" ] ||
|
||||||
|
{ echo "WARNING: You are NOT running this script as 'root'. You might want to consider that..."; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Stops the execution of the script if user is not root.
|
||||||
|
#
|
||||||
|
fail_if_not_root () {
|
||||||
|
uid=`id -u` && [ "$uid" = "0" ] ||
|
||||||
|
{ echo "You must run this as 'root'. Exiting."; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Checks if a certain element has already been installed.
|
||||||
|
#
|
||||||
|
function is_installed () {
|
||||||
|
if [ $# -gt 1 ]; then
|
||||||
|
local args=$*
|
||||||
|
local name=${args// /-}
|
||||||
|
else
|
||||||
|
local name=$1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f ~/.shoestrap/installed/$name ]]; then
|
||||||
|
log "'$name' is already installed."
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log "'$name' is not installed."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Sets an element as installed.
|
||||||
|
#
|
||||||
|
function set_installed () {
|
||||||
|
if [ $# -gt 1 ]; then
|
||||||
|
local args=$*
|
||||||
|
local name=${args// /-}
|
||||||
|
else
|
||||||
|
local name=$1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p ~/.shoestrap/installed
|
||||||
|
touch ~/.shoestrap/installed/$name
|
||||||
|
}
|
||||||
|
|
14
helpers/initialize
Normal file
14
helpers/initialize
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# DO NOT MODIFY THIS FILE. Instead, modify 'helpers/custom'.
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
. helpers/default
|
||||||
|
. helpers/custom
|
||||||
|
|
||||||
|
warn_if_not_root
|
||||||
|
|
||||||
|
spacer 1; separator "="
|
||||||
|
echo " BOOTSTRAPPING '$COOKBOOK_NAME'..."
|
||||||
|
separator "="; spacer 1
|
34
my-cookbook
Executable file
34
my-cookbook
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Initialization - DO NOT REMOVE
|
||||||
|
. helpers/initialize
|
||||||
|
|
||||||
|
##############################################################
|
||||||
|
### Customizations start here ################################
|
||||||
|
##############################################################
|
||||||
|
|
||||||
|
fail_if_not_root # Comment out if 'root' is not required.
|
||||||
|
|
||||||
|
### Install packages
|
||||||
|
# package_update
|
||||||
|
# package 'git-core'
|
||||||
|
# package 'vim screen htop curl wget traceroute'
|
||||||
|
# package 'build-essential'
|
||||||
|
# package 'libjpeg-progs'
|
||||||
|
# package 'libmagickwand-dev imagemagick'
|
||||||
|
# package 'libsqlite3-dev'
|
||||||
|
|
||||||
|
### Users
|
||||||
|
# add_user 'deploy' ; recipe 'setup_keys' 'deploy' ; recipe 'customize_bash' 'deploy' ; recipe 'add_sudoer' 'deploy'
|
||||||
|
|
||||||
|
### Run recipes
|
||||||
|
# recipe 'secure_ssh'
|
||||||
|
# recipe 'rbenv'
|
||||||
|
# recipe 'ruby' '1.9.3-p125'
|
||||||
|
# recipe 'nginx'
|
||||||
|
# recipe 'memcached' '1.4.13'
|
||||||
|
# recipe 'mariadb'
|
||||||
|
|
||||||
|
### Show the Finished banner
|
||||||
|
finished
|
||||||
|
|
0
recipes/default/.gitkeep
Normal file
0
recipes/default/.gitkeep
Normal file
Loading…
Reference in New Issue
Block a user