1: Create a new Rails app - we'll call is 'deploytest'
$ rails deploytest
$ cd deploytest
2: Create a local Git repository for it
$ git init
$ git add *
$ git commit -a -m 'initial commit'
$ git status
3: Create a couple of Capistrano files
$ capify .
4: Edit config/deploy.rb
# The name of your app
set :application, "deploytest"
# The directory on the EC2 node that will be deployed to
set :deploy_to, "/var/www/apps/#{application}"
# The type of Source Code Management system you are using
set :scm, :git
# The location of the LOCAL repository relative to the current app
set :repository, "."
# The way in which files will be transferred from repository to remote host
# If you were using a hosted github repository this would be slightly different
set :deploy_via, :copy
# The address of the remote host on EC2 (the Public DNS address)
set :location, "ec2-xxx-xxx-xxx-xxx.compute-1.amazonaws.com"
# setup some Capistrano roles
role :app, location
role :web, location
role :db, location, :primary => true
# Set up SSH so it can connect to the EC2 node - assumes your SSH key is in ~/.ssh/id_rsa
set :user, "root"
ssh_options[:keys] = [File.join(ENV["HOME"], ".ssh", "id_rsa")]
The only account on a default EC2 instance is root. You probably want to create a second user that is responsible for your application.
5: Copy your SSH public key to your EC2 node
$ scp -i ~/my-ec2-keypair ~/.ssh/id_rsa.pub root@ec2-xxx-xxx-xxx-xxx.compute-1.amazonaws.com:/root/.ssh/authorized_keys2
NOTE the filename authorized_keys2 - not authorized_keys!!
6: Setup the EC2 node for Capistrano deployment.
From your LOCAL machine, not the EC2 node:
$ cap deploy:setup
7: Finally, deploy your application
$ cap deploy
You will see lots of output and with this dummy application some of those will report errors/warnings. Don't worry about that for now.
8: Check that the Deployment was successful
Connect to the EC2 node with SSH the regular way, cd to the app directory and check that everything is there. If that is all working then you are ready to deploy a real application and add custom tasks for managing the database, restarting the server etc.
Bear in mind that Capistrano add new 'releases' of your software in separate directories and symlinks the 'current' directory to the latest. So the root of your deployed application is the 'current' subdirectory.
Hope this will help you setting up your ec2 instance with capistrano.
Installing PostgreSQL 8.3
First, you’ll need to install Xcode if you haven’t already. This is available on the Snow Leopard DVD in the Optional Installs directory.
Second, if you aren’t already using it, download Mac Ports for Snow Leopard and install it. Mac Ports has come a long way in the last few years and will make your life much easier.
Once those are installed, run the following command:
sudo port install postgresql83 postgresql83-server
Setup Your First Database
At the very end of the install it tells you how to setup your first database:
sudo mkdir -p /opt/local/var/db/postgresql83/defaultdb
sudo chown postgres:postgres /opt/local/var/db/postgresql83/defaultdb
sudo su postgres -c '/opt/local/lib/postgresql83/bin/initdb -D /opt/local/var/db/postgresql83/defaultdb'
You’ll also want to setup Postgres to auto-run as a server on start up.
sudo launchctl load -w /Library/LaunchDaemons/org.macports.postgresql83-server.plist
If you want to start it right now, you can either reboot or do the following:
sudo su postgres -c '/opt/local/lib/postgresql83/bin/postgres -D /opt/local/var/db/postgresql83/defaultdb'
Make psql Available from the Command Line
The executable files for PostgreSQL get shoved into a non-standard place (just like MySQL), so you’ll need to edit the default profile.
sudo vi /etc/profile
You can also do this using sudo mate /etc/profile if you aren’t comfortable in VI.
The PATH= line needs to be changed to include the PostgreSQL bin directory.
Mine was PATH="/opt/local/bin:$PATH" and is now:
PATH="/opt/local/bin:/opt/local/sbin:/opt/local/lib/postgresql83/bin:$PATH"
If you open a new terminal window you can now type psql and it will find it.
Create a New User and Database
By default, PostgreSQL creates a postgres user for you. However, it’s not good practice to use the default and it’s a pain in the ass. Let’s just create a new database user to make it easier.
createuser --superuser macusername -U postgres
You need to change macusername to your mac username. This will make your life ALOT easier. Trust me here.
Next, create your database:
createdb my_database
Installing the PostgreSQL Ruby Gem
Unlike the MySQL driver, we don’t need to pass the ARCHFLAGS variable as 64 bit. PostgreSQL comes with both 32 and 64-bit versions. Yeah!
sudo gem install postgres-pr
Per Tom’s comment below, we should be using the native driver for better performance.
sudo env ARCHFLAGS="-arch x86_64" gem install pg
Configuring your Rails Application
Inside your Ruby on Rails application, open up config/database.yml and change your development adapter to be similar to the following:
development:
adapter: postgresql
database: defaultdb
username: defaultdb
You can change defaultdb to the name you need for your application.
SOAP4R is a Ruby library for accessing Web Services via SOAP. Recently I had a chance to explore SOAP4R. Here's how to get started with it.
Installation
Although Ruby 1.8.x comes with SOAP4R in its standard library, it is an old, buggy version. I highly recommend using the latest gem (1.5.8 as of the this update). It has one dependency, httpclient.
gem install soap4r --include-dependencies
Service
There are many services available to send SMS but I prefer to use,
MailServe-SMS
MailServe-SMS, text messaging service, is everything you need for fast, no-frills, no-fuss text messaging. A quick and easy way to send SMS.
Let’s explore these further.
Method 1: Read the WSDL at run-time
require "soap/wsdlDriver"
wsdl = "http://sms.qlc.co.in/smsapi.wsdl"
driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_drive
A single call to a driver factory reads the WSDL file, and creates a driver class for you to use, complete with the methods defined by the service. What if your service requires authentication? The driver inherits methods from httpclient, so you can specify its options as you would for httpclient:
Once driver is get initialised you need to call SMS sending API i.e. SendSMSRequest.
driver.SendSMSRequest("username", "password", "sender_no", "from_no", "message")
Once This will return you response 200 SMS sent successfully on success else if the information submitted was wrong then 500 Information submitted was incomplete.
Method 2: Generate classes from WSDL
SOAP4R installs a command-line utility called 'wsdl2ruby' which can generate a client or server.
Coming soon…..
Active Merchant makes it extremely simple to use Paypal IPN. Here is a simple guide for getting IPN up and running.
Paypal provides a sandbox environment that mimics their production environment, with the exception that it doesn’t actually process the transactions. This is extremely useful for development and testing. It allows you to create multiple fake accounts and generate bank accounts and credit cards. More information can be found on Paypal’s Testing Instant Payment Notification page.
Unfortunately, I’ve signed up for two different developer accounts and I’ve had trouble logging in with both of them. I’ve tried resetting my password, but I still can’t log in. Fortunately, I already have my sandbox accounts set up and don’t really have a need for it (except to write this guide).
Create a Personal account and add a credit card
After you sign up for your developer account, create a personal sandbox account and add a credit card.
Create a Business account and add a checking
Next, create a business sandbox account and add a checking account.
Install the money gem
sudo gem install money
Install the Active Merchant plugin
script/plugin install http://activemerchant.googlecode.com/svn/trunk/active_merchant
Create a form that submits to Paypal
Include ActiveMerchant::Billing::Integrations in a controller to add Active Merchant’s helpers.
class PaymentsController < ApplicationController
include ActiveMerchant::Billing::Integrations
def create
@enrollment = current_user.enrollments.find(params[:id])
end
end
In the view, use payment_service_for to create a form that submits to Paypal to process the payment.
<% payment_service_for @enrollment.id, PAYPAL_ACCOUNT,
:amount => @enrollment.course.deposit, :currency => 'USD',
:service => :paypal do |service|
service.customer :first_name => @enrollment.student.first_name,
:last_name => @enrollment.student.last_name,
:phone => @enrollment.student.phone,
:email => @enrollment.student.email
service.billing_address :city => @enrollment.student.city,
:address1 => @enrollment.student.street,
:state => @enrollment.student.state,
:country => 'USA',
:zip => @enrollment.student.zip
service.item_name "#{@enrollment.course.program} Deposit"
service.invoice @enrollment.invoice.id
service.tax '0.00'
service.notify_url url_for(
nly_path => false, :action => 'notify')
service.return_url url_for(
nly_path => false,
:controller => 'account', :action => 'show')
service.cancel_return_url url_for(
nly_path => false,
:controller => 'account', :action => 'show') %>
<!-- display payment summary here -->
<%= submit_tag 'Make Payment' %>
<% end %>
The code above refers to the constant PAYPAL_ACCOUNT, which I define in environment.rb. I also set Active Merchant to use test mode, which directs it to use Paypal’s sandbox:
unless RAILS_ENV == 'production'
PAYPAL_ACCOUNT = 'sandboxaccount@example.com'
ActiveMerchant::Billing::Base.mode = :test
else
PAYPAL_ACCOUNT = 'paypalaccount@example.com'
end
Create an action that processes the IPN
After the above form submits to Paypal and the user makes a payment, Paypal will post data about the transaction to your server. Set up an action to receive the post:
def notify
notify = Paypal::Notification.new(request.raw_post)
enrollment = Enrollment.find(notify.item_id)
if notify.acknowledge
@payment = Payment.find_by_confirmation(notify.transaction_id) ||
enrollment.invoice.payments.create(:amount => notify.amount,
:payment_method => 'paypal', :confirmation => notify.transaction_id,
:description => notify.params['item_name'], :status => notify.status,
:test => notify.test?)
begin
if notify.complete?
@payment.status = notify.status
else
logger.error("Failed to verify Paypal's notification, please investigate")
end
rescue => e
@payment.status = 'Error'
raise
ensure
@payment.save
end
end
render :nothing => true
end
Depending on the model for your application, this action will obviously look different. The important part is that you pass the raw post data from the request to Paypal::Notification.new, and call notify.acknowledge to connect back to Paypal to verify the data.
Enable IPN
Lastly, log into the business account that you created above, go to “Instant Payment Notification Preferences” in your profile, and set the URL that Paypal should post back to after payments. (Note: this needs to be a publicly accessible URL.)
Grant privileges to all tables in a database (select, update, insert, delete)
Eg:( Creating a read-only user in postgres)
–Function to grant access(select,insert,update,delete) to users
CREATE FUNCTION pg_grant(TEXT, TEXT, TEXT, TEXT)
RETURNS integer AS '
DECLARE obj record;
num integer;
BEGIN
num:=0;
FOR obj IN SELECT relname FROM pg_class c
JOIN pg_namespace ns ON (c.relnamespace = ns.oid) WHERE
relkind in (''r'',''v'',''S'') AND
nspname = $4 AND
relname LIKE $3
LOOP
EXECUTE ''GRANT '' || $2 || '' ON '' || obj.relname || '' TO '' || $1;
num := num + 1;
END LOOP;
RETURN num;
END;
' LANGUAGE plpgsql SECURITY DEFINER;
–Function to revoke access(select,insert,update,delete) from users
CREATE FUNCTION pg_revoke(TEXT, TEXT, TEXT, TEXT)
RETURNS integer AS '
DECLARE obj record;
num integer;
BEGIN
num:=0;
FOR obj IN SELECT relname FROM pg_class c
JOIN pg_namespace ns ON (c.relnamespace = ns.oid) WHERE
relkind in (''r'',''v'',''S'') AND
nspname = $4 AND
relname LIKE $3
LOOP
EXECUTE ''REVOKE '' || $2 || '' ON '' || obj.relname || '' FROM '' || $1;
num := num + 1;
END LOOP;
RETURN num;
END;
' LANGUAGE plpgsql SECURITY DEFINER;
–Create users for your database
CREATE USER userreadonly WITH PASSWORD 'userr3ad0nly';
CREATE USER userall WITH PASSWORD 'usersh0pa11';
–Grant respective access to users
select pg_grant('userreadonly ','select','%','public');
select pg_grant('userall ','select,insert,update,delete','%','public');
You might need to create lang for plpgsql if you had not done so
createlang plpgsql yrdatabasename
if (b.ie)
{
var filter = this.element.style.filter.replace(/alpha\s*\(\s*opacity\s*=\s*[0-9\.]{1,3}\)/, '');
this.element.style.filter = filter + 'alpha(opacity=' + parseInt(ht * 100, 10) + ')';
}
else
{
this.element.style.opacity = ht;
}
this.element.style.visibility = 'visible';
this.element.style.display = 'block'
}
Find below the code for finding location from IP address using IP location tools.
require 'net/http'
require 'rexml/document'
include REXML
class MapsController < ApplicationController
def index
@location = locateIp()
end
def locateIp
ip = request.remote_ip
ips = ip.to_s
url = "http://iplocationtools.com/ip_query.php?ip="+ips
xml_data = Net::HTTP.get_response(URI.parse(url)).body
xmldoc = REXML::Document.new(xml_data)
# Now get the root element
root = xmldoc.root
city = ""
regionName = ""
countryName = ""
# This will take country name...
xmldoc.elements.each("Response/CountryName") {
|e| countryName << e.text
}
# Now get city name...
xmldoc.elements.each("Response/City") {
|e| city << e.text
}
# This will take regionName...
xmldoc.elements.each("Response/RegionName") {
|e| regionName << e.text
}
ipLocation = city +", "+regionName+", "+countryName
return ipLocation
end #end of method locateIp
end
For many users, running FTP Sever in Amazon EC2 instance is headache at the first time. You need to experiment before being able to transfer data. The main problems are Ingress firewall in Amazon environment and NAT traversal.
Here I’m using vsftp (vsfptd) Server, which is one of the most popular and easy to configure. The instance is running from base Fedora 4 AMI but the setup should be identical to other Red Hat based distros.
Install vsftpd FTP server, if not installed earlier:
# yum install vsfptd
Its upto you which FTP method i.e. Active or Passive you want to use. The problem with active mode is that your computer is sending a request out of port 21 when all of a sudden, the server attempts to initiate a request with your computer on port 20. Since communication on port 21 does not imply communication on port 20, it appears as if some unauthorized host has attempted to initiate a new connection with your computer. Kind of sounds like a hack right? Your firewall may think so too (or your NAT router may have no idea to which computer to route the request). Active mode is not used as default method of ftp transfer in many clients these days.
On the other hand, as the Ingress firewall is running in AWS, from the firewall’s standpoint, to support passive mode FTP the following communication channels need to be opened:
FTP server’s port 21 from anywhere (Client initiates connection).
FTP server’s port 21 to ports > 1023 (Server responds to client’s control port).
FTP server’s ports > 1023 from anywhere (Client initiates data connection to random port specified by server).
FTP server’s ports > 1023 to remote ports > 1023 (Server sends ACKs (and data) to client’s data port).
That second part is the problem: FTP server listens on a random port and hands that back to the client, so the client initiates a connection to a random server port, which you must allow.
Opening up all ports > 1023 isn’t so good for security. But what you can do is allow the ports through the distributed firewall and then setup your own filtering inside your instance. Instead, you would better open a fixed number of ports (such as 1024 to 1048) and configure your FTP Server to only use that ports.
Check whether required ports are open or not in your EC2 security group. (if you are unaware about security group, it should be ‘defaul’ unless you created a new one).
# ec2-describe-group
This command will print all ports which are currently open. If you dont find port 20,21,1024-1048 then you need to open these ports but if you dont find the command itself i.e.
# ec2-describe-group
-bash: ec2-describe-group: command not found
You need to install ec2 command line tools. You can find them here and the instructions to setup/configure can be found here.
Open the ports now:
# ec2-authorize default -p 20-21
# ec2-authorize default -p 1024-1048
Here, ‘default’ is the name of security group. You can also open ports for specific IPs. For ease of use, you better install ElasticFox, a firefox extension to manage EC2 stuff. you can find more about it here.
At this moment, you can start your FTP server and if you try to connect it, the process will get failed. By checking logs, you should find something like:
Status: Connected
Status: Retrieving directory listing...
Command: PWD
Response: 257 "/" is current directory.
Command: TYPE A
Response: 200 Type set to A
Command: PASV
Response: 227 Entering Passive Mode (216,182,238,73,129,75).
Command: LIST
Error: Transfer channel can't be opened. Reason: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
Error: Could not retrieve directory listing
Time to configure vsftpd.conf file:
# vi /etc/vsftpd/vsftpd.conf
---Add following lines at the end of file---
pasv_enable=YES
pasv_min_port=1024
pasv_max_port=1048
pasv_address=Public IP of your instance
Put public IP of your EC2 instance and then Save the file. Now restart the server:
# /etc/init.d/vsftpd restart