Skip to content

Instantly share code, notes, and snippets.

@mmrwoods
Last active September 2, 2021 20:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mmrwoods/5064969 to your computer and use it in GitHub Desktop.
Save mmrwoods/5064969 to your computer and use it in GitHub Desktop.
Bootstrap configuration for a new RHEL or CentOS VM (set hostname, basic firewall rules, etc.)
#!/bin/bash
# Boostrap a new CentOS VM (also works with RHEL!)
# Has been used on CentOS 5.10, 6.2, 6.3, 6.4 and 6.5
# Based on https://gist.github.com/thickpaddy/1759284
# TODO: set ssh port
if test $(whoami) != "root" ; then
echo "Error: $0 must be run as root, exiting..."
exit 1
fi
LOG_DIR=$(cd `dirname $0` && pwd)
LOG_FILE=$LOG_DIR/$(basename ${0/.[a-z]*/.log})
rm -rf $LOG_FILE
echo $(date) > $LOG_FILE
TMP_DIR=$(cd `dirname $0` && pwd)
TMP_FILE=$TMP_DIR/$(basename ${0/.[a-z]*/.tmp})
rm -rf $TMP_FILE
# From https://gist.github.com/davejamesmiller/1965569
ask() {
while true; do
if [ "${2:-}" = "Y" ]; then
prompt="Y/n"
default=Y
elif [ "${2:-}" = "N" ]; then
prompt="y/N"
default=N
else
prompt="y/n"
default=
fi
# Ask the question
read -p "$1 [$prompt] " REPLY
# Default?
if [ -z "$REPLY" ]; then
REPLY=$default
fi
# Check if the reply is valid
case "$REPLY" in
Y*|y*) return 0 ;;
N*|n*) return 1 ;;
esac
done
}
run_cmd() {
echo -e "\e[33m$1\e[0m"
echo $1 >> $LOG_FILE
eval $1
}
warning() {
echo -e "\e[31m$1\e[0m"
if ! ask "Continue anyway?"; then
exit 1
fi
}
heading() {
echo -e "\n\e[32m$1...\e[0m"
}
clean_up() {
heading "Cleaning up"
run_cmd "rm -f $TMP_FILE"
}
keep_original() {
# note: --no-clobber option not supported in old versions of gnu cp
run_cmd "if ! test -f $1.original; then cp -p $1 $1.original; fi"
}
if test $(who | wc -l | tr -d "\n") == "1" ; then
warning "
Only one logon session detected!
$0 makes changes to iptables and sudoers.
You might want to open another logon session so you can rescue yourself if something goes wrong.
"
fi
trap "echo -e '\nInterrupted...'; exit" INT
trap exit QUIT TERM
trap clean_up EXIT
update_packages() {
heading "Updating installed packages"
run_cmd "yum update -y"
}
set_host_name() {
heading "Setting host name"
echo "Enter fully qualified domain name for this server:"
read hostname
run_cmd "hostname $hostname"
config_file="/etc/sysconfig/network"
keep_original $config_file
run_cmd "cp -p $config_file $TMP_FILE"
run_cmd "sed '/HOSTNAME=/d' $TMP_FILE > $config_file"
run_cmd "echo 'HOSTNAME=$hostname' >> $config_file"
run_cmd "rm -f $TMP_FILE"
config_file="/etc/hosts"
ip_address=`ifconfig eth0 | grep "inet addr" | cut -f2 -d: | cut -f1 -d' '`
keep_original $config_file
run_cmd "sed '/$ip_address $hostname/d' $config_file.original > $config_file"
run_cmd "echo \"$ip_address $hostname\" >> /etc/hosts"
if test -f /etc/init.d/rsyslog; then
run_cmd "service rsyslog restart"
else
run_cmd "service syslog restart"
fi
}
set_time_zone() {
heading "Setting time zone"
zone=`grep ZONE /etc/sysconfig/clock | tr -d "\n" | cut -f2 -d= | sed 's/"//g'`
echo "Default time zone is $zone"
if ask "Select a different time zone?"; then
echo "Choose a region:"
select region in Africa America Antartica Artic Asia Atlantic Australia Europe Indian Pacific; do
echo "Choose a city:"
select city in `find /usr/share/zoneinfo/Europe/ -type f | awk -F'/' '{ print $NF }' | sort`; do
break;
done
break;
done
zone="$region/$city"
fi
echo "Setting time zone to $zone"
config_file=/etc/sysconfig/clock
keep_original $config_file
run_cmd "sed '/ZONE=*/d' $config_file > $TMP_FILE"
run_cmd "echo 'ZONE=\"$zone\"' >> $TMP_FILE"
run_cmd "mv $TMP_FILE $config_file"
config_file=/etc/localtime
keep_original $config_file
run_cmd "rm -rf $config_file"
run_cmd "ln -sf /usr/share/zoneinfo/$zone $config_file"
}
install_ntpd() {
heading "Installing ntpd and setting time"
run_cmd "yum install -y ntp ntpdate"
run_cmd "ntpdate pool.ntp.org"
run_cmd "service ntpd start"
run_cmd "chkconfig ntpd on"
}
disable_selinux() {
heading "Disabling SELinux"
config_file=/etc/selinux/config
keep_original $config_file
run_cmd "sed 's/^SELINUX=[a-z]*/SELINUX\=disabled/' $config_file.original > $config_file"
run_cmd "setenforce 0"
}
configure_sudoers() {
heading "Configuring sudoers"
config_file=/etc/sudoers
keep_original $config_file
echo "Allowing members of wheel group to run all commands via sudo..."
run_cmd "cp -p $config_file $TMP_FILE"
run_cmd "sed 's/#[ \t]*\(%wheel[ \t]*ALL=(ALL)[ \t]*ALL\)/\1/' $config_file > $TMP_FILE"
run_cmd "chmod 440 $TMP_FILE"
# check format of tmp file before continuing
visudo -q -c -s -f $TMP_FILE
if [ $? -ne 0 ]; then
warning "Failed to grant sudo to members of wheel group - visudo says $TMP_FILE is invalid"
else
grep '^%wheel' $TMP_FILE
if [ $? -ne 0 ] ; then
warning "Failed to grant sudo to members of wheel group - %wheel line not found in $TMP_FILE"
else
run_cmd "mv $TMP_FILE $config_file"
fi
fi
if ! egrep "^\#includedir[[:space:]]+/etc/sudoers.d" /etc/sudoers > /dev/null; then
echo "Updating sudoers config to include files from sudoers.d..."
config_file=/etc/sudoers
keep_original $config_file
run_cmd "mkdir -p /etc/sudoers.d"
run_cmd "cp -p $config_file $TMP_FILE"
run_cmd "echo '#includedir /etc/sudoers.d' >> $TMP_FILE"
visudo -q -c -s -f $TMP_FILE
if [ $? -ne 0 ]; then
warning "Failed to update sudoers - visudo says $TMP_FILE is invalid"
else
run_cmd "mv $TMP_FILE $config_file"
fi
fi
}
create_admins_group() {
heading "Creating admins group"
run_cmd "groupadd admins"
# create custom sudoers config for members of admins group,
# grant all privileges except abilty to run interactive shell
echo "Allowing admins to run all commands except shells as root..."
config_file=/etc/sudoers.d/admins
run_cmd "rm -rf $TMP_FILE"
run_cmd "touch $TMP_FILE"
run_cmd "echo '# Allow admins to run all commands except an interactive shell' >> $TMP_FILE"
run_cmd "echo 'Cmnd_Alias SHELLS = /bin/sh, /bin/csh, /bin/tcsh, /bin/bash' >> $TMP_FILE"
run_cmd "echo 'Cmnd_Alias SU = /bin/su' >> $TMP_FILE"
run_cmd "echo '%admins ALL = ALL, !SU, !SHELLS' >> $TMP_FILE"
run_cmd "chmod 440 $TMP_FILE"
visudo -q -c -s -f $TMP_FILE
if [ $? -ne 0 ]; then
warning "Failed to grant sudo privileges to admins - visudo says $TMP_FILE is invalid"
else
run_cmd "mv $TMP_FILE $config_file"
fi
}
create_admin_user() {
heading "Creating admin user"
if test $(logname) != "root" ; then
username=$(logname)
run_cmd "usermod -a -G admins $username"
else
read -p "Enter a username: " username
run_cmd "useradd -m -G admins $username"
run_cmd "passwd $username"
fi
echo "Allowing $username to run all commands as root, including shells..."
# note: add user to sudoers, just to be on the safe side
config_file=/etc/sudoers.d/admins
run_cmd "sed '/^$username */d' $config_file > $TMP_FILE"
run_cmd "chmod 440 $TMP_FILE"
run_cmd "echo '$username ALL=(ALL) ALL' >> $TMP_FILE"
# check format of tmp file before continuing
visudo -q -c -s -f $TMP_FILE
if [ $? -ne 0 ]; then
warning "Failed to allow $username to run all commands - visudo says $TMP_FILE is invalid"
else
run_cmd "mv $TMP_FILE $config_file"
fi
}
# TODO: other ssdh config settings:
# LoginGraceTime 30
# MaxAuthTries 3
# MaxStartups 3:50:10
disable_root_login() {
heading "Disabling root login"
config_file=/etc/ssh/sshd_config
keep_original $config_file
run_cmd "cp -p $config_file $TMP_FILE"
run_cmd "sed 's/#[ \t]*PermitRootLogin[ a-zA-Z]*/PermitRootLogin no/' $TMP_FILE > $config_file"
run_cmd "rm -f $TMP_FILE"
grep '^PermitRootLogin no' $config_file
if [ $? -ne 0 ] ; then
warning "Failed to disable root login in $config_file"
else
run_cmd "service sshd restart"
fi
}
forward_root_email() {
heading "Configuring email forwarding for root"
echo "Enter forwarding address for emails to root:"
read email_address
if ! [[ $email_address = *@*.* ]] ; then
warning "Forwarding address '$email_address' does not appear to be a valid email address"
fi
config_file="/root/.forward"
if test -f $config_file; then
keep_original $config_file
fi
run_cmd "echo '$email_address' > $config_file"
}
set_firewall_rules() {
heading "Setting firewall rules"
if iptables -L INPUT | grep ^ACCEPT > /dev/null; then
echo "Existing firewall rules found!"
if ! ask "Replace existing rules?"; then
return
fi
fi
ssh_port_config=$(grep ^Port /etc/ssh/sshd_config)
if [ $? -eq 0 ] ; then
ssh_ports=$(echo $ssh_port_config | sed 's/[^0-9 ]*//g')
else
ssh_ports=22
fi
run_cmd "iptables -F"
run_cmd "iptables -A INPUT -i lo -j ACCEPT"
run_cmd "iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT"
run_cmd "iptables -A INPUT -s 10.0.0.0/255.0.0.0 -j DROP"
run_cmd "iptables -A INPUT -s 169.254.0.0/255.255.0.0 -j DROP"
run_cmd "iptables -A INPUT -s 172.16.0.0/255.240.0.0 -j DROP"
run_cmd "iptables -A INPUT -s 127.0.0.0/255.0.0.0 -j DROP"
run_cmd "iptables -A INPUT -s 224.0.0.0/240.0.0.0 -j DROP"
run_cmd "iptables -A INPUT -d 224.0.0.0/240.0.0.0 -j DROP"
run_cmd "iptables -A INPUT -s 240.0.0.0/248.0.0.0 -j DROP"
run_cmd "iptables -A INPUT -d 240.0.0.0/248.0.0.0 -j DROP"
run_cmd "iptables -A INPUT -s 0.0.0.0/255.0.0.0 -j DROP"
run_cmd "iptables -A INPUT -d 0.0.0.0/255.0.0.0 -j DROP"
run_cmd "iptables -A INPUT -d 239.255.255.0/255.255.255.0 -j DROP"
run_cmd "iptables -A INPUT -d 255.255.255.255 -j DROP"
run_cmd "iptables -A INPUT -p icmp -m icmp --icmp-type 17 -j DROP"
run_cmd "iptables -A INPUT -p icmp -m icmp --icmp-type 13 -j DROP"
run_cmd "iptables -A INPUT -p icmp -m icmp --icmp-type any -m limit --limit 1/sec -j ACCEPT"
run_cmd "iptables -A INPUT -m state --state INVALID -j DROP"
run_cmd "iptables -A INPUT -p tcp -m tcp --tcp-flags RST RST -m limit --limit 2/sec --limit-burst 2 -j ACCEPT"
run_cmd "iptables -A INPUT -m recent --rcheck --seconds 86400 --name portscan --rsource -j DROP"
run_cmd "iptables -A INPUT -m recent --remove --name portscan --rsource"
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j LOG --log-prefix 'Portscan:'"
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j DROP"
for ssh_port in $ssh_ports; do
run_cmd "iptables -A INPUT -p tcp -m tcp --dport $ssh_port -j ACCEPT"
done
if ask "Is this a web server?"; then
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT" # http
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT" # https
fi
if ask "Is this a mail server?"; then
# TODO: allow message transfers and submissions?
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 25 -j ACCEPT" # smtp
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 587 -j ACCEPT" # submission
# TODO: allow pop3 and pop3 over ssl?
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 110 -j ACCEPT" # pop3
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 993 -j ACCEPT" # pop3s
# TODO: allow imap and imap over ssl?
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 143 -j ACCEPT" # imap
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 995 -j ACCEPT" # imaps
fi
if ask "Is this an ftp server?"; then
# TODO: allow ftp data?
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 20 -j ACCEPT" # ftp data
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 21 -j ACCEPT" # ftp control
fi
if ask "Is this a dns server?"; then
run_cmd "iptables -A INPUT -p udp -m udp --dport 53 -j ACCEPT" # udp query (max 512 bytes)
run_cmd "iptables -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT" # tcp query and zone transfers
fi
run_cmd "iptables -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT"
run_cmd "iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable"
run_cmd "iptables -A FORWARD -m state --state INVALID -j DROP"
run_cmd "iptables -A FORWARD -m recent --rcheck --seconds 86400 --name portscan --rsource -j DROP"
run_cmd "iptables -A FORWARD -m recent --remove --name portscan --rsource"
run_cmd "iptables -A FORWARD -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j LOG --log-prefix 'Portscan:'"
run_cmd "iptables -A FORWARD -p tcp -m tcp --dport 139 -m recent --set --name portscan --rsource -j DROP"
run_cmd "iptables -A FORWARD -j REJECT --reject-with icmp-port-unreachable"
run_cmd "iptables -A OUTPUT -m state --state INVALID -j DROP"
run_cmd "iptables -A OUTPUT -o lo -j ACCEPT"
run_cmd "iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT"
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 21 -j ACCEPT" # ftp
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 22 -j ACCEPT" # ssh
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 25 -j ACCEPT" # smtp
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 43 -j ACCEPT" # whois
run_cmd "iptables -A OUTPUT -p udp -m udp --dport 53 -j ACCEPT" # dns
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 53 -j ACCEPT" # dns
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 80 -j ACCEPT" # http
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 110 -j ACCEPT" # pop3
run_cmd "iptables -A OUTPUT -p udp -m udp --dport 123 -j ACCEPT" # ntp
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 143 -j ACCEPT" # imap
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT" # https
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 587 -j ACCEPT" # submission
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 993 -j ACCEPT" # pop3s
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 995 -j ACCEPT" # imaps
run_cmd "iptables -A OUTPUT -p tcp -m tcp --dport 9418 -j ACCEPT" # git
run_cmd "iptables -A OUTPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT"
run_cmd "iptables -A OUTPUT -j REJECT --reject-with icmp-port-unreachable"
run_cmd "service iptables save"
run_cmd "chkconfig iptables on"
run_cmd "service iptables restart"
}
set_kernel_params() {
heading "Setting kernel parameters"
params="
# Reboot after a kernel panic
kernel.panic = 10
# Panic kernel when out of memory
vm.panic_on_oom = 1
# Enable TCP SYN Cookie Protection
net.ipv4.tcp_syncookies = 1
# Disable IP Source Routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Disable ICMP Redirect Acceptance
net.ipv4.conf.all.accept_redirects = 0
# Enable IP Spoofing Protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Disable TCP timestamps (information leakage)
net.ipv4.tcp_timestamps = 0
# Don't allow outsiders to alter the routing tables
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
# Don't pass traffic between networks or act as a router
net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Allow ICMP echo for basic monitoring
net.ipv4.icmp_echo_ignore_all = 0
net.ipv4.icmp_echo_ignore_broadcasts = 0
# Enable Bad Error Message Protection
net.ipv4.icmp_ignore_bogus_error_responses = 1
"
if test -f "/proc/sys/net/ipv6/conf/all/disable_ipv6"; then
ipv6_params="
# Disable IPv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
"
params="$params$ipv6_params"
else
warning "Unable to disable ipv6 in kernel, /proc/sys/net/ipv6/conf/all/disable_ipv6 not found"
fi
params=$(echo "$params" | sed 's/^[ \t]*//')
config_file=/etc/sysctl.conf
if test -f "$config_file.original"; then
echo "$config_file.original exists, restoring"
run_cmd "mv $config_file.original $config_file"
fi
keep_original $config_file
# note: multiple echo comamnds are easier to log
while read -r line; do
run_cmd "echo \"$line\" >> $config_file"
done <<< "$params"
run_cmd "sysctl -p > /dev/null"
}
disable_ipv6_networking() {
heading "Disabling IPv6 in network config"
config_file=/etc/sysconfig/network
keep_original $config_file
run_cmd "cp -p $config_file $TMP_FILE"
run_cmd "sed '/NETWORKING_IPV6*/d' $TMP_FILE > $config_file"
run_cmd "echo 'NETWORKING_IPV6=no' >> $config_file"
run_cmd "rm -f $TMP_FILE"
run_cmd "service ip6tables stop"
run_cmd "chkconfig ip6tables off"
run_cmd "service network restart"
if test -f /etc/init.d/postfix; then
run_cmd "postconf -e inet_protocols=ipv4"
run_cmd "service postfix restart"
fi
}
add_epel_repo() {
heading "Adding Extra Packages for Enterprise Linux repo"
redhat_release_major_version=$(sed -r 's/.*release ([0-9])\.[0-9].*/\1/' /etc/redhat-release | tr -d '\n')
case $redhat_release_major_version in
5)
run_cmd "rpm -Uvh http://www.mirrorservice.org/sites/dl.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.rpm"
;;
6)
run_cmd "rpm -Uvh http://www.mirrorservice.org/sites/dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm"
;;
*)
warning "Failed to add EPEL repo, unknown RHEL version '$redhat_release_major_version'"
;;
esac
}
install_fail2ban() {
heading "Installing fail2ban"
run_cmd "yum install -y fail2ban"
heading "Configuring fail2ban"
config_file=/etc/fail2ban/jail.local
if test -f $config_file; then
keep_original $config_file
fi
# configure ignoreip...
login_user=$(logname)
login_ip=$(who | grep $login_user | head -n1 | awk '{ print $NF }' | tr -d '()')
ignore_ip="127.0.0.1 $login_ip"
echo "Setting ignoreip to '$ignore_ip' (note: your ip is $login_ip)"
if ask "Override with a custom value?"; then
read -p "Enter ip addresses and/or host names separated by spaces: " input
ignore_ip="$input"
fi
config="
[DEFAULT]
ignoreip = $ignore_ip
"
# add local jail for non-standard ssh port
non_standard_ssh_port=$(grep ^Port /etc/ssh/sshd_config | grep -v 22 | awk '{ print $NF }')
if test "$non_standard_ssh_port" != ""; then
non_standard_ssh_port_config="
[ssh-iptables-$non_standard_ssh_port]
enabled = true
filter = sshd
action = iptables[name=SSH, port=$non_standard_ssh_port, protocol=tcp]
sendmail-whois[name=SSH, dest=root, sender=fail2ban@example.com]
logpath = /var/log/secure
maxretry = 5
"
config="$config$non_standard_ssh_port_config"
fi
# write config to jail.local, line by line so we can log it
config_file=/etc/fail2ban/jail.local
run_cmd "rm -rf $config_file"
run_cmd "touch $config_file"
while read -r line; do
run_cmd "echo \"$line\" >> $config_file"
done <<< "$config"
run_cmd "chkconfig fail2ban on"
if $(service fail2ban status &> /dev/null); then
run_cmd "service fail2ban restart"
else
run_cmd "service fail2ban start"
fi
}
install_packages() {
heading "Installing cron, ruby, git and some other utils"
run_cmd "yum install -y ruby ruby-devel rubygems git vim-enhanced nc bind-utils mailx"
run_cmd "yum info cronie &> /dev/null && yum install -y cronie"
}
remove_anacron() {
heading "Removing anacron and reverting to old style daily jobs"
run_cmd "yum info cronie-noanacron &> /dev/null && yum install -y cronie-noanacron; yum remove -y cronie-anacron"
}
disable_fingerprint_auth() {
# Disable authentication with fingerprint readers to avoid errors in
# secure log, e.g. PAM unable to dlopen(/lib64/security/pam_fprintd.so)
authconfig --disablefingerprint --update
}
update_packages
set_host_name
set_time_zone
install_ntpd
disable_selinux
update_authconfig
configure_sudoers
create_admins_group
create_admin_user
disable_root_login
forward_root_email
set_firewall_rules
set_kernel_params
disable_ipv6_networking
add_epel_repo
install_fail2ban
install_packages
remove_anacron
disable_fingerprint_auth
@macarthy
Copy link

macarthy commented Mar 7, 2013

Y u not use chef ?

@mmrwoods
Copy link
Author

mmrwoods commented Mar 7, 2013

Can't always, but I still want a quick way of setting some basic config stuff on a new vm

@macarthy
Copy link

macarthy commented Mar 7, 2013

Just kidding :-) How is London these days?

@mmrwoods
Copy link
Author

mmrwoods commented Mar 8, 2013

Busy place, but lots of interesting stuff going on. Where are you these days? Still in Laos? Enjoying it?

@deepj
Copy link

deepj commented Mar 11, 2013

You should really use Chef/Puppet, Mark :)

@mmrwoods
Copy link
Author

Thanks Martin, you're right, where did I go wrong? ;-)

@deepj
Copy link

deepj commented Mar 12, 2013

Anywhere :) I know it wasn't useful advice :) I just have A nightmare from Bash every time when I see it :) I prefer Ruby -> Chef use :) Anyway, are you still in London? I'm going to London at 90 % for few days this summer (probably in August).

@mmrwoods
Copy link
Author

Hey Martin, yeah, I'm still in London. Let me know if you do make it over for a few days, we should meet up for lunch or a coffee. Maybe see if Andy is around too.

@deepj
Copy link

deepj commented Mar 18, 2013

Of course! Sorry for delay. Github haven't notified me about your last response. I'll let you know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment