Chef – berkshelf lesson for dummies like me Ermahgerd!

I feel like some of the explanations on berkshelf on the internet are confusing.
So i felt like doing a small write up myself

berkshelf is pretty much a replacement for the “knife cookbook” command.
The big win with berkshelf is that it also resolves dependencies of a cookbook like apt or yum.
It reads a file called “Berksfile” for other cookbooks the current cookbook needs and what repositories to fetch them from and pulls them to your local system.

I will use the logstash cookbook at as an example
If you read the Berksfile at
it will show you what other cookbooks the logstash cookbook needs

So in order to get going

gem install berkshelf
git clone
cd logstash
berks install
berks upload

That installed berkshelf, cloned the logstash cookbook, resolved dependencies for the logstash cookbook and uploaded logstash cookbook and its dependencies to your chef-server

Additionally berkshelf installs its configuration file at : ~/.berkshelf/config.json
You may need to edit some stuff there to match your ~/.chef/knife.rb file


Adding EBS Volumes with Opscode’s AWS cookbook

1. Download opscode’s aws cookbook and put it into your own cookbook repo

$ git clone opscode-cookbooks
$ cp -r opscode-cookbooks/aws my-cookbooks/
$ cd my-cookbooks 

2. Create a new cookbook that will utilize the aws cookbook

$ knife cookbook create aws-tests

3. Set the cookbook to have the dependency of the opscode aws cookbook

$ vi my-cookbooks/aws-tests/metadata.rb
maintainer       "YOUR_COMPANY_NAME"
maintainer_email "YOUR_EMAIL"
license          "All rights reserved"
description      "Installs/Configures aws_tests"
long_description, ''))
version          "0.0.1"

depends "aws"

4. Create your recipe to create and attach a new EBS volume to your ec2 instance

$ vi vi my-cookbooks/aws-tests/recipes/default.rb
# Create and attach your new EBS volume
aws_ebs_volume "new_ebs_volume" do
  aws_access_key "MYAPIKEY"
  aws_secret_access_key "MYAPIKEYSECRET"
  size 1
  device "/dev/xvdi"
  action [ :create, :attach ]

5. Create a filesystem and mount your new volume

# Create your partition and filesystem for ext4
bash "create_filesystem" do
  user "root"
  code <<-EOH
    parted /dev/xvdi mklabel gpt
    parted /dev/xvdi mkpart logical ext4 1 -1
    parted /dev/xvdi set 1 lvm on
    yes | parted /dev/xvdi mkpart logical ext4 1 -- "-1"
    mkfs.ext4 /dev/xvdi1
  not_if "parted /dev/xvdi1 |grep ext4"

directory "/mnt/test" do
  owner "root"
  group "root"
  mode "0755"
  recursive true

mount "/mnt/test" do
  device "/dev/xvdi1"
  options "rw noatime"
  fstype "ext4"
  action [ :enable, :mount ]
  not_if "cat /proc/mounts |grep /mnt/test"

6. Add aws and aws_tests recipes to your node

$ knife node edit i-fff4f8c
  "chef_environment": "_default",
  "name": "i-fff4f8",
  "run_list": [
  "normal": {
    "tags": [


7. Run chef-client on your node

$ chef-client

Debugging knife configure -i

If you ever see something like this

root@chefserver01:/etc/chef# knife configure -i -V
Overwrite /root/.chef/knife.rb? (Y/N) y
Please enter the chef server URL: [http://chefserver01:4000]
Please enter a clientname for the new client: [root] jtran7
Please enter the existing admin clientname: [chef-webui]
Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem]
Please enter the validation clientname: [chef-validator]
Please enter the location of the validation key: [/etc/chef/validation.pem]
Please enter the path to a chef repository (or leave blank):
Creating initial API user...
INFO: HTTP Request Returned 500 Internal Server Error: Connection reset by peer
ERROR: Server returned error for http://chefserver01:4000/clients, retrying 1/5 in 4s
INFO: HTTP Request Returned 409 Conflict: Client already exists
INFO: HTTP Request Returned 500 Internal Server Error: Connection reset by peer
ERROR: Server returned error for http://chefserver01:4000/clients/jtran7, retrying 1/5 in 4s
INFO: HTTP Request Returned 500 Internal Server Error: Connection reset by peer
ERROR: Server returned error for http://chefserver01:4000/clients/jtran7, retrying 2/5 in 5s

it’s likely your ampq password is incorrect in /etc/chef/server.rb

# amqp_pass sets the password for the AMQP virtual host in rabbitmq-server.
amqp_pass "testing"

If you had deleted your rabbitmq mnesia table you had to do something like this.
The word “testing” is your ampq password

sudo rabbitmqctl add_vhost /chef
sudo rabbitmqctl add_user chef testing
sudo rabbitmqctl set_permissions -p /chef chef ".*" ".*" ".*"

Clustered AMQP Rabbit-MQ

RabbitMQ already has excellent documentation at:

But this will be a bit more chef specific
server01 = existing chef-server
server02 = new rabbitmq server to be added to cluster

On Server01

On your existing chef-server
*I assume you’re using the latest rabbitmq and that server02 will also install matching version
Check what the cluster output looks like

root@server01:~# rabbitmqctl cluster_status
Cluster status of node rabbit@server01 ..

Copy your rabbitmq cookie to server02

root@server01:~# scp /var/lib/rabbitmq/.erlang.cookie root@server02:/var/lib/rabbitmq/
Are you sure you want to continue connecting (yes/no)? yes
root@server02's password: 
.erlang.cookie                                                                                        100%   20     0.0KB/s   00:00    

On Server02

Install rabbitmq

root@server02:~# echo "deb testing main" |tee -a /etc/apt/sources.list
root@server02:~# wget
root@server02:~# apt-key add rabbitmq-signing-key-public.asc
root@server02:~# apt-get update
root@server02:/var/chef/cache# apt-get -y install rabbitmq-server 

Delete existing mnesia database and start rabbitmq

root@server02:~# service rabbitmq-server stop
root@server02:~# rm -fr /var/lib/rabbitmq/mnesia 
root@server02:~# service rabbitmq-server start
 * Starting message broker rabbitmq-server

Join Server02 to Server01

root@server02:~# rabbitmqctl cluster_status
Cluster status of node rabbit@server02 ...
root@server02:~# rabbitmqctl stop_app
Stopping node rabbit@server02 ...
root@server02~# rabbitmqctl reset   
Resetting node rabbit@server02 ...
root@server02:~# rabbitmqctl cluster rabbit@server01 rabbit@server02
Clustering node rabbit@server02 with [rabbit@server01,rabbit@server02] ...
root@server02:~# rabbitmqctl start
root@server02:~# rabbitmqctl cluster_status
Cluster status of node rabbit@server01 ...

Verify on server01 that cluster shows up as expected

root@server01:~# rabbitmqctl cluster_status
Cluster status of node rabbit@server01 ...

Make sure chef still works

root@server01:~# knife node list

root@server01:~# knife client list
root@server01:~# knife bootstrap -x root server02 --template-file ~/ubuntu12.04.rb 
Bootstrapping Chef on server02
Failed to authenticate root - trying password auth
Enter your password: 
server02 Updating installed gems

root@server01:~# knife node list

Essential Knife Plugins for chef

This plugin helps you control versioning of your cookbooks and also prevents you from accidently commiting things you don’t want to.

$ gem install knife-spork

Obviously this is a connector to ec2. I use Amazon ec2 so i am biased

$ gem install knife-ec2

A preflight plugin for Chef::Knife which lets you see which nodes and roles use a particular cookbook before you upload it.

$ gem install knife-preflight

A plugin for Chef::Knife which will diff the cookbook versions of two or more environments.

$ gem install knife-env-diff

Other mentionables

Threaded bootstrapping of aws nodes

Cluster Deployments

Search for more knife plugins:

$ gem search -r knife-

Multiple AWS Accounts with Knife Admin

I Recently stumbled across a predicament of multiple aws accounts.
This is a minor predicament but a predicament nonethless.
I have a situation where i have

1. A personal AWS account

2. A work AWS account

3. A vendor AWS account

These three AWS accounts all use the same chef-server. So to make my life easier i decided to organize them.
I created the following structure:

$ mkdir -p ~/chef-aws/{personal,work,thirdparty}/.chef

I copied my knife.rb from ~/.chef/knife.rb into each of these folders.

$ cp -p ~/.chef/knife.rb ~/chef-aws/personal
$ cp -p ~/.chef/knife.rb ~/chef-aws/work
$ cp -p ~/.chef/knife.rb ~/chef-aws/thirdparty

Here’s an example of the knife.rb file
You can find details on setting up knife with ec2 here : Knife-EC2 Configuration

current_dir = File.dirname(__FILE__)
log_level                :info
log_location             STDOUT
node_name                "neosirex"
client_key               "/home/James/.chef/myuser.pem"
validation_client_name   "neosirex-validator"
validation_key           "/home/James/.chef/random-validator.pem"
chef_server_url          ""
cache_type               'BasicFile'
cache_options( :path => "#{ENV['HOME']}/.chef/checksums" )
cookbook_path            ["#{current_dir}/../cookbooks"]

Here’s the snippet that’s added to each AWS specific knife.rb

knife[:aws_access_key_id] ='< AWS ACCESS KEY GOES HERE >'
knife[:aws_secret_access_key] ='< AWS SECRET KEY GOES HERE >'

So now in order to use different AWS accounts what i do is change into each of those aws directories and run knife commands from there.
Each of the following commands would give me the output only of the relevant AWS server

$ cd ~/chef-aws/personal && knife ec2 server list
$ cd ~/chef-aws/work && knife ec2 server list
$ cd ~/chef-aws/thirdparty && knife ec2 server list

I Leave my default ~/.chef/knife.rb file without AWS credentials in it.
This is because i don’t want to accidently deploy to the wrong AWS account.
There’s still room for human error but i suppose it’s better than nothing
If someone has a better approach to this i’d like to know about it.

Keys and Values from Hashes in Ruby Templates

ERB recognizes certain tags in the provided template and converts them based on the rules below:

<% Ruby code -- inline with output %>
<%= Ruby expression -- replace with result %>
<%# comment -- ignored -- useful in testing %>
% a line of Ruby code -- treated as <% line %> (optional -- see
%% replaced with % if first thing on a line and % processing is used
<%% or %%> -- replace with <% or %> respectively

Create a new “motd” cookbook

$ knife cookbook create motd

Example: ~/cookbooks/motd/recipes/default.rb

# Cookbook Name:: motd
# Recipe:: default
# Copyright 2012, YOUR_COMPANY_NAME
# All rights reserved - Do Not Redistribute

template "/home/motd" do
  source "motd.erb"
  owner "root"

Example: ~/cookbooks/motd/template/default/motd.erb
* You can find out what hashes are defined on a system by running “ohai”

Hostname : <%= node["hostname"] %>
Platform : <%= node["platform"] %>

Memory Usage
<% node["memory"].each_pair do |k,v| %>
<%= k %>     : <%= v%>
<% end %>

Block Devices
<% node["block_device"].each_pair do |k,v| %>
Key: <%= k %>   -   <%= v%>
<% end %>

Network Info
<% node["network"]["interfaces"].keys.each do |k| %>
Key: <%= k %>
<% end %>

<% node["network"]["interfaces"].values.each do |v| %>
Value: <%= v %>
<% end %>

Run chef-client with the “motd” cookbook installed and look at the output at

$ cat /home/motd
Hostname: ubuntu01
Platform: ubuntu

Memory Usage
vmalloc_total     : 34359738367kB
anon_pages     : 129892kB
writeback     : 0kB
dirty     : 0kB
vmalloc_used     : 266104kB
vmalloc_chunk     : 34359469948kB
active     : 186264kB
buffers     : 31252kB
commit_limit     : 773540kB
nfs_unstable     : 0kB
slab_unreclaim     : 11684kB
bounce     : 0kB
slab_reclaimable     : 17644kB
mapped     : 11580kB
cached     : 190268kB
slab     : 29328kB
inactive     : 165136kB
free     : 105708kB
total     : 502612kB
committed_as     : 926460kB
page_tables     : 4324kB
swap     : cached0kBfree522236kBtotal522236kB

Block Devices
Key: sda   -   timeout30modelVMware Virtual Sremovable0vendorVMware,rev1.0size41943040staterunning
Key: sr0   -   timeout30modelVMware IDE CDR10removable1vendorNECVMWarrev1.00size1401432staterunning
Key: fd0   -   removable1size0
Key: loop7   -   removable0size0
Key: loop6   -   removable0size0
Key: loop5   -   removable0size0
Key: loop4   -   removable0size0
Key: loop3   -   removable0size0
Key: loop2   -   removable0size0
Key: loop1   -   removable0size0
Key: loop0   -   removable0size0
Key: ram15   -   removable0size131072
Key: ram14   -   removable0size131072
Key: ram13   -   removable0size131072
Key: ram12   -   removable0size131072
Key: ram11   -   removable0size131072
Key: ram10   -   removable0size131072
Key: ram9   -   removable0size131072
Key: ram8   -   removable0size131072
Key: ram7   -   removable0size131072
Key: ram6   -   removable0size131072
Key: ram5   -   removable0size131072
Key: ram4   -   removable0size131072
Key: ram3   -   removable0size131072
Key: ram2   -   removable0size131072
Key: ram1   -   removable0size131072
Key: ram0   -   removable0size131072

Network Info
Key: eth0
Key: lo

Value: mtu16436encapsulationLoopbackflagsLOOPBACKUPLOWER_U....< i truncated output >
Value: mtu1500typeethencapsulationEthernetflagsBROADCASTM....< i truncated output >

Also for shits and giggles
Example Ohai output:

root@ubuntu01:~# ohai
  "idletime": "35 minutes 35 seconds",
  "uptime": "36 minutes 47 seconds",
  "dmi": {
    "base_board": {
      "chassis_handle": "0x0000",
      "location_in_chassis": "Not Specified",
      "product_name": "440BX Desktop Reference Platform",
      "serial_number": "None",
      "manufacturer": "Intel Corporation",
      "version": "None",
      "type": "Unknown",
      "features": "None",
      "contained_object_handles": "0",
      "all_records": [
  .. < i truncated output >

Knife EC2 Extension – Install and Use


$ apt-get install -y libxslt-dev libxml2-dev
$ gem install knife-ec2
$ gem install net-ssh-multi

Get Your AWS Keys

Login to your AWS account at
Go to My Account/Console -> Security Credentials

Scroll Down to The Certificates and Secret Keys Menu and generate your new access keys as needed

Generate the Keypair associated with your new ec2 instances ( for ssh )

Create a new keypair. This should result in a pem file output to you. If you lose this file you will not be able to access any ec2 instances associated with it unless you have alternate accounts you can login with.


$ cd ~/.chef
$ vi knife.rb

Append the following to your knife.rb

### AWS Configuration ###

## The below lines allow you to use the ec2 api
knife[:aws_access_key_id] ='< AWS ACCESS KEY GOES HERE >'
knife[:aws_secret_access_key] ='< AWS SECRET KEY GOES HERE >'

## The below allow you to ssh into new ec2 instance that are associated with the keypair below
## You can alternately choose to specify the username and key location on the knife command line
# knife[:aws_ssh_key_id] ='james-aws'
# knife[:identity_file] ="/home/james/.ssh/james-aws.pem"

Test knife-ec2 command

bootstrap file squeeze.rb can be grabbed from here:

$ knife ec2 server list
$ knife ec2 server create -I ami-e00df089 -f t1.micro -Z us-east-1a -G "default_security" -k james-aws --ssh-key /home/james/.ssh/james-aws.pem --template-file /home/james/bootstrap/squeeze.rb

knife-ec2 command reference list

knife ec2 server create --help
knife ec2 server create (options)
    -Z, --availability-zone ZONE     The Availability Zone
    -A, --aws-access-key-id KEY      Your AWS Access Key ID
    -K SECRET,                       Your AWS API Secret Access Key
        --user-data USER_DATA_FILE   The EC2 User Data file to provision the instance with
        --bootstrap-version VERSION  The version of Chef to install
    -N, --node-name NAME             The Chef node name for your new node
        --server-url URL             Chef Server URL
    -k, --key KEY                    API Client Key
        --color                      Use colored output
    -c, --config CONFIG              The configuration file to use
        --defaults                   Accept default values for all questions
    -d, --distro DISTRO              Bootstrap a distro using a template
        --ebs-no-delete-on-term      Do not delete EBS volumn on instance termination
        --ebs-size SIZE              The size of the EBS volume in GB, for EBS-backed instances
    -e, --editor EDITOR              Set the editor to use for interactive commands
    -E, --environment ENVIRONMENT    Set the Chef environment
    -f, --flavor FLAVOR              The flavor of server (m1.small, m1.medium, etc)
    -F, --format FORMAT              Which format to use for output
    -i IDENTITY_FILE,                The SSH identity file used for authentication
    -I, --image IMAGE                The AMI for the server
        --no-color                   Don't use colors in the output
    -n, --no-editor                  Do not open EDITOR, just accept the data as is
        --no-host-key-verify         Disable host key verification
    -u, --user USER                  API Client Username
        --prerelease                 Install the pre-release chef gems
        --print-after                Show the data after a destructive operation
        --region REGION              Your AWS region
    -r, --run-list RUN_LIST          Comma separated list of roles/recipes to apply
    -G, --groups X,Y,Z               The security groups for this server
    -S, --ssh-key KEY                The AWS SSH key id
    -P, --ssh-password PASSWORD      The ssh password
    -x, --ssh-user USERNAME          The ssh username
    -s, --subnet SUBNET-ID           create node in this Virtual Private Cloud Subnet ID (implies VPC mode)
        --template-file TEMPLATE     Full path to location of template to use
    -V, --verbose                    More verbose output. Use twice for max verbosity
    -v, --version                    Show chef version
    -y, --yes                        Say yes to all prompts for confirmation
    -h, --help                       Show this message

Migrating Chef CouchDB to Multi-Master CouchDB

* assuming you are using ubuntu/debian
chef-server =
couchdb01 =
couchdb02 =

Enable chef-server couchdb to listen on all interfaces

root@chefserver:~# sed -i bak s/bind_address = = /etc/couchdb/default.ini
root@chefserver:~# /etc/init.d/couchdb restart

Install CouchDB on couchdb01/couchdb02 and set to listen on all interfaces

root@couchdb01:~# apt-get -y install couchdb
root@couchdb01:~# /etc/init.d/couchdb stop
root@couchdb01:~# sed -i bak s/bind_address = = /etc/couchdb/default.ini
root@couchdb01:~# /etc/init.d/couchdb start

Create the empty chef table on couchdb01/couchdb02

root@couchdb01:~# curl -X PUT http://localhost:5984/chef

root@couchdb02:~# curl -X PUT http://localhost:5984/chef

Push the chef table from chef-server to couchdb01/02 and enable a continuous replication

To Couchdb02

root@chef-server:/var/lib/couchdb# curl -X POST http://localhost:5984/_replicate -H "Content-Type: application/json" -d '{"source":"chef","target":"","continuous":true}'

To Couchdb01

root@chef-server:~# curl -X POST http://localhost:5984/_replicate -H "Content-Type: application/json" -d '{"source":"chef","target":"","continuous":true}'

Setup the multi-master replication for couchdb01/02

Enable continuous replication FROM couchdb01 to couchdb02

root@couchdb01:/var/lib/couchdb# curl -X POST http://localhost:5984/_replicate -H "Content-Type: application/json" -d '{"source":"chef","target":"","continuous":true}'

Enable continuous replication FROM couchdb02 to couchdb01

root@couchdb02:/var/lib/couchdb# curl -X POST http://localhost:5984/_replicate -H "Content-Type: application/json" -d '{"source":"chef","target":"","continuous":true}'

Install Apache and generate config on Chef Server

root@chefserver:~# apt-get -y install apache2
root@chefserver:~# mkdir -p /usr/share/chef-server/public
root@chefserver:~# for i in rewrite proxy status proxy_http proxy_balancer headers ; do a2enmod $i ; done
root@chefserver:~# cd /etc/apache2/sites-available
root@chefserver:~# echo "Listen 5984" | tee -a chef_couchdb_loadbalancer
root@chefserver:~# echo '<VirtualHost *:5984>' | tee -a chef_couchdb_loadbalancer
root@chefserver:~# MYHOST=$(hostname -f)
root@chefserver:~# echo "ServerName ${MYHOST}-couchdb" |tee -a chef_couchdb_loadbalancer
root@chefserver:~# cat>>chef_couchdb_loadbalancer<<EOF
<Proxy balancer://couchlb>
ProxyPass / balancer://couchlb
ProxyPassReverse / balancer://couchlb
DocumentRoot /usr/share/chef-server/public
LogLevel info
ErrorLog /var/log/chef/chef_couchdb_apache2-error.log
CustomLog /var/log/chef/chef_couchdb_apache2-access.log combined
RewriteEngine On
RewriteRule ^/(.*)$ balancer://couchlb%{REQUEST_URI} [P,QSA,L]

Stop Couchdb on Chef-Server

*This will stop the continuous replication to couchdb01/couchdb02

root@chefserver:~# /etc/init.d/couchdb stop

Start Apache load balancer on Chef-Server

root@chefserver:~# a2ensite chef_couchdb_loadbalancer
root@chefserver:~# /etc/init.d/apache2 restart

Test your couchdb balancer:


You probably also want to edit your init scripts on couchdb01/02 to automatically restart the continuous replication upon start or restart
The replication does not persist after you stop a couchdb instance unless you explicitly issue the command again

Chef Server – Threading Merb – Chef Server API Service

This will only make your api system faster if you have enough cpus to support it.
It should be 1 merb worker per core ( * i think * )

Install Apache and enable the necessary mods

$ apt-get -y install apache2
$ mkdir -p /usr/share/chef-server/public
$ for i in rewrite proxy status proxy_http proxy_balancer headers ; do a2enmod $i ; done

Stop Chef-Server

$ /etc/init.d/chef-server stop

Edit your Chef configuration file ( Replace Worker Threads and Port Numbers as needed )

$ sed -i s/PORT=4000/PORT=5000/g /etc/default/chef-server
$ echo "WORKERTHREADS=4" | tee -a /etc/default/chef-server

Edit the Chef init script ( Back it up first )

$ cp /etc/init.d/chef-server /etc/init.d/chef-server.original
$ sed -i '35s/DAEMON_OPTS="-p/DAEMON_OPTS="-c $WORKERTHREADS -p/g' /etc/init.d/chef-server
$ sed -i '42s/(ps/#(ps/g' /etc/init.d/chef-server
$ sed -i '/#(ps/ i (ps -fp $pid | egrep -q "merb.*( chef-server .*api.* spawner|worker .* $PORT)") || return 1' /etc/init.d/chef-server

Run diff to see the init script differences

$ diff /etc/init.d/chef-server /etc/init.d/chef-server.original
< (ps -fp $pid | egrep -q "merb.*( chef-server .*api.* spawner|worker .* $PORT)") || return 1
<   #(ps -fp $pid | egrep -q "merb.*(merb : master|worker.*$PORT)") || return 1 --- >   (ps -fp $pid | egrep -q "merb.*(merb : master|worker.*$PORT)") || return 1

Get variables from your Chef config for apache config construction

$ THREADSCT=$(grep "WORKERTHREADS" /etc/default/chef-server |awk -F"=" '{print $2}')
$ PORTPREFIX=$(grep "PORT" /etc/default/chef-server |awk -F"=" '{print $2}'| sed -e s/[0-9][0-9]$//g )
$ MYHOST=$(hostname -f)

Generate Your Apache Config – Generate the Load Balance Members

$ cd /etc/apache2/sites-available
$ echo "Listen 4000" |tee -a chef_loadbalancer
$ echo '<VirtualHost *:4000>' |tee -a chef_loadbalancer
$ echo "ServerName $MYHOST" |tee -a chef_loadbalancer
$ echo "" |tee -a chef_loadbalancer
$ echo "<Proxy balancer://cheflb>" | tee -a chef_loadbalancer
$ seq -w 00 $NEWCOUNT | while read i ; do echo "BalancerMember${PORTPREFIX}${i}" |tee -a chef_loadbalancer ; done

Append your Apache config with the rest of the relevant information

$ cat>>chef_loadbalancer<<EOF
ProxyPass / balancer://cheflb
ProxyPassReverse / balancer://cheflb
DocumentRoot /usr/share/chef-server/public
LogLevel info
ErrorLog /var/log/chef/chef_server_apache2-error.log
CustomLog /var/log/chef/chef_server_apache2-access.log combined
RewriteEngine On
RewriteRule ^/(.*)$ balancer://cheflb%{REQUEST_URI} [P,QSA,L]

Enable your new apache config and start Apache and Chef-Server

$ a2ensite chef_loadbalancer
$ /etc/init.d/apache2 restart
$ /etc/init.d/chef-server start

Test your chef server

$ time knife node list
$ time knife role list


Much of this information was stolen from :
If this technique is outdated please make me aware of it or if my apache configuration is awful ( which i’m sure it is, i’m just too lazy to improve it )