necktie 0.3.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +20 -14
- data/bin/necktie +3 -1
- data/example/Necktie +12 -0
- data/example/etc/cron/snapshot +46 -0
- data/example/etc/init.d/unicorn +53 -0
- data/example/etc/nginx/unicorn.conf +51 -0
- data/example/gems/unicorn-0.93.3.gem +0 -0
- data/example/tasks/app.rb +49 -0
- data/example/tasks/current.rb +49 -0
- data/example/tasks/db.rb +30 -0
- data/lib/necktie/{rake.rb → application.rb} +34 -31
- data/lib/necktie/capistrano.rb +2 -2
- data/lib/necktie/files.rb +13 -0
- data/lib/necktie/gems.rb +8 -0
- data/lib/necktie/rush.rb +1 -1
- data/lib/necktie/services.rb +18 -1
- data/lib/necktie.rb +1 -4
- data/necktie.gemspec +2 -2
- metadata +11 -3
data/README.rdoc
CHANGED
@@ -20,6 +20,10 @@ I use it to:
|
|
20
20
|
- Setup MySQL to use a mounted (EBS) volume
|
21
21
|
- Install and update cron scripts
|
22
22
|
|
23
|
+
To install Necktie:
|
24
|
+
|
25
|
+
$ gem install necktie --source http://gemcutter.org
|
26
|
+
|
23
27
|
|
24
28
|
== Tasking
|
25
29
|
|
@@ -75,20 +79,19 @@ only runs once, before that directory exists.
|
|
75
79
|
|
76
80
|
== cap necktie
|
77
81
|
|
78
|
-
I use Capistrano to setup new instances and upgrade existing ones.
|
79
|
-
deploy:cold on the new instances, or deploy to upgrade running instances.
|
82
|
+
I use Capistrano to setup new instances and upgrade existing ones.
|
80
83
|
|
81
|
-
To use the
|
82
|
-
necktie/capistrano:
|
84
|
+
To use the Necktie task, set the necktie_url variable to the URL of your
|
85
|
+
Necktie repository, and require necktie/capistrano. For example:
|
83
86
|
|
84
87
|
require "necktie/capistrano"
|
85
88
|
set :necktie_url, "git@example.com:mysetup"
|
86
89
|
|
87
|
-
|
90
|
+
Setting up a new EC2 instance:
|
88
91
|
|
89
92
|
cap necktie ROLES=app HOSTS=ec2-75-101-239-12.compute-1.amazonaws.com
|
90
93
|
|
91
|
-
|
94
|
+
Upgrading running instances with new configuration:
|
92
95
|
|
93
96
|
git push && cap necktie
|
94
97
|
|
@@ -111,9 +114,9 @@ also:
|
|
111
114
|
chmod 0755, "/etc/init.d/unicorn"
|
112
115
|
sh "service start unicorn"
|
113
116
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
+
The current directory (Dir.pwd and launch_dir) is the root directory of your
|
118
|
+
Necktie repository. You can depend on relative paths when accessing files in
|
119
|
+
your Necktie repository.
|
117
120
|
|
118
121
|
|
119
122
|
== Role play
|
@@ -137,6 +140,9 @@ the default task instead of setting ROLES each time. In your Necktie file add:
|
|
137
140
|
|
138
141
|
task :default=>:app
|
139
142
|
|
143
|
+
You may also want to take advantage of different environments, customizing your
|
144
|
+
setup differently between production, staging, etc.
|
145
|
+
|
140
146
|
Note: As with Rake, command line arguments are either options (see necktie -H),
|
141
147
|
task names (executed in order), or name=value pairs that set environment
|
142
148
|
variables (e.g. RAILS_ENV=production).
|
@@ -166,14 +172,14 @@ rubygems.rb task is all you need:
|
|
166
172
|
|
167
173
|
== One on one
|
168
174
|
|
169
|
-
|
170
|
-
time you run Necktie, it clones the Git repository into
|
171
|
-
|
175
|
+
You can also run Necktie from the command line directly on the server you're
|
176
|
+
configuring. The first time you run Necktie, it clones the Git repository into
|
177
|
+
the ~/.necktie directory. You'll need to give it the Git URL, like this:
|
172
178
|
|
173
|
-
necktie
|
179
|
+
necktie --source git@example.com:mysetup app
|
174
180
|
|
175
181
|
Subsequent runs use that repository. You can launch the necktie command from
|
176
|
-
anywhere, it will always switch to the
|
182
|
+
anywhere, it will always switch to the ~/.necktie directory, run the tasks and
|
177
183
|
switch back to the previous working directory.
|
178
184
|
|
179
185
|
If you want to pull changes from the Git repository, use the -U option:
|
data/bin/necktie
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
$LOAD_PATH.unshift File.expand_path("../lib", File.dirname(__FILE__)),
|
3
|
-
File.expand_path("../vendor/rake/lib", File.dirname(__FILE__)),
|
3
|
+
File.expand_path("../vendor/rake/lib", File.dirname(__FILE__)),
|
4
|
+
File.expand_path("../vendor/rush/lib", File.dirname(__FILE__)),
|
4
5
|
File.expand_path("../vendor/session/lib", File.dirname(__FILE__))
|
5
6
|
require "necktie"
|
6
7
|
|
8
|
+
# Mystery revealed: Necktie is really just a shell on top of Rake!
|
7
9
|
Rake.application = Necktie::Application.new
|
8
10
|
Rake.application.run
|
data/example/Necktie
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!ruby
|
2
|
+
@hostname = "example.com"
|
3
|
+
@git_url = "git@example.com:myapp"
|
4
|
+
@deploy_to = "/var/myapp"
|
5
|
+
|
6
|
+
task :environment do
|
7
|
+
profile = File.expand_path("~/.bash_profile")
|
8
|
+
append profile, "\nexport RAILS_ENV=#{Necktie.env}\n" unless read(profile)["export RAILS_ENV"]
|
9
|
+
# Change command line prompt for production (LIVE in red) and and staging (STAGE in blue).
|
10
|
+
ps = Necktie.env == "staging" ? "\\e[0;34mSTAGE\\e[m \\w $ " : "\\e[0;31mLIVE\\e[m \\w $ "
|
11
|
+
append profile, "PS1=\"#{ps}\"\n" unless read(profile)["PS1=\"#{ps}\""]
|
12
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
require "mysql"
|
3
|
+
require "time"
|
4
|
+
require "date"
|
5
|
+
|
6
|
+
# NOTE: Change these to match your setup!
|
7
|
+
ENV["EC2_PRIVATE_KEY"] = "/mnt/keys/pk-....pem"
|
8
|
+
ENV["EC2_CERT"] = "/mnt/keys/cert-....pem"
|
9
|
+
ENV["JAVA_HOME"] = "/usr/lib/jvm/java-6-openjdk"
|
10
|
+
DB_USER = "root"
|
11
|
+
DB_PASSWORD = "..."
|
12
|
+
MOUNT = "/vol"
|
13
|
+
|
14
|
+
instance = `curl -s http://instance-data.ec2.internal/latest/meta-data/instance-id`
|
15
|
+
volume = `ec2-describe-volumes`[/ATTACHMENT\t(.*)\t#{instance}\t/, 1]
|
16
|
+
|
17
|
+
c = Mysql.new("localhost", DB_USER, DB_PASSWORD)
|
18
|
+
c.query "FLUSH LOCAL TABLES"
|
19
|
+
c.query "FLUSH TABLES WITH READ LOCK"
|
20
|
+
c.query "SHOW MASTER STATUS"
|
21
|
+
system "sync"
|
22
|
+
system "xfs_freeze -f #{MOUNT}"
|
23
|
+
system "ec2-create-snapshot #{volume} | logger -t snapshot" rescue nil
|
24
|
+
system "xfs_freeze -u #{MOUNT}"
|
25
|
+
c.query "UNLOCK TABLES"
|
26
|
+
c.close
|
27
|
+
|
28
|
+
|
29
|
+
# Snaps is an array of [snapshot-id, time]
|
30
|
+
snaps = `ec2-describe-snapshots`.scan(/(snap-.*)\t#{volume}\tcompleted\t(.*)\t/).map { |(id, time)| [id, Time.parse(time)] }
|
31
|
+
now = Time.now
|
32
|
+
today = now.to_date
|
33
|
+
hr24 = (now - 86400)
|
34
|
+
# Keep all snaps from past 24 hours
|
35
|
+
snaps.delete_if { |s| s.last >= hr24 }
|
36
|
+
# Keep only one from each day
|
37
|
+
dated = snaps.group_by { |s| s.last.to_date }
|
38
|
+
# Keep every day's snapshot from the last week. (We start with today, which is already excluded, so iterate 8 days total)
|
39
|
+
today.downto(today - 7).each do |date|
|
40
|
+
dated[date].delete dated[date].sort_by(&:last).last if dated[date]
|
41
|
+
end
|
42
|
+
# Map to list of snaps
|
43
|
+
deleting = dated.inject([]) { |a,(k,v)| a<<v.map(&:first) }.flatten
|
44
|
+
deleting.each do |snap|
|
45
|
+
`ec2-delete-snapshot #{snap} > /dev/null`
|
46
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
set -u
|
3
|
+
set -e
|
4
|
+
# Example init script, this can be used with nginx, too,
|
5
|
+
# since nginx and unicorn accept the same signals
|
6
|
+
|
7
|
+
# Feel free to change any of the following variables for your app:
|
8
|
+
APP_ROOT=/var/myapp/current
|
9
|
+
PID=$APP_ROOT/tmp/pids/unicorn.pid
|
10
|
+
ENV=production
|
11
|
+
CMD="/usr/local/bin/unicorn_rails -D -E $ENV -c config/unicorn.rb"
|
12
|
+
|
13
|
+
old_pid="$PID.oldbin"
|
14
|
+
|
15
|
+
cd $APP_ROOT || exit 1
|
16
|
+
|
17
|
+
sig () {
|
18
|
+
test -s "$PID" && kill -$1 `cat $PID`
|
19
|
+
}
|
20
|
+
|
21
|
+
oldsig () {
|
22
|
+
test -s $old_pid && kill -$1 `cat $old_pid`
|
23
|
+
}
|
24
|
+
|
25
|
+
case $1 in
|
26
|
+
start)
|
27
|
+
sig 0 && echo >&2 "Already running" && exit 0
|
28
|
+
$CMD
|
29
|
+
;;
|
30
|
+
stop)
|
31
|
+
sig QUIT && exit 0
|
32
|
+
echo >&2 "Not running"
|
33
|
+
;;
|
34
|
+
force-stop)
|
35
|
+
sig TERM && exit 0
|
36
|
+
echo >&2 "Not running"
|
37
|
+
;;
|
38
|
+
restart|reload)
|
39
|
+
sig HUP && echo reloaded OK && exit 0
|
40
|
+
echo >&2 "Couldn't reload, starting '$CMD' instead"
|
41
|
+
$CMD
|
42
|
+
;;
|
43
|
+
upgrade)
|
44
|
+
sig USR2 && sleep 2 && sig 0 && oldsig QUIT && exit 0
|
45
|
+
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
|
46
|
+
$CMD
|
47
|
+
;;
|
48
|
+
*)
|
49
|
+
echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop>"
|
50
|
+
exit 1
|
51
|
+
;;
|
52
|
+
esac
|
53
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
upstream unicorn {
|
2
|
+
server unix:/var/myapp/current/tmp/sockets/unicorn.sock;
|
3
|
+
}
|
4
|
+
|
5
|
+
server {
|
6
|
+
listen 0.0.0.0:80;
|
7
|
+
server_name _;
|
8
|
+
|
9
|
+
access_log /var/log/nginx/localhost.access.log;
|
10
|
+
|
11
|
+
location / {
|
12
|
+
root /var/myapp/current/public/;
|
13
|
+
if (-f $request_filename) {
|
14
|
+
expires 4h;
|
15
|
+
break; # Static asset
|
16
|
+
} if (-f $document_root/system/maintenance.html) {
|
17
|
+
return 503; # Temporarily unavailable
|
18
|
+
} if (!-f $document_root/system/maintenance.html) {
|
19
|
+
error_page 500 501 502 503 504 /500.html;
|
20
|
+
proxy_pass http://unicorn;
|
21
|
+
}
|
22
|
+
|
23
|
+
proxy_set_header Host $host;
|
24
|
+
proxy_set_header X-Real-IP $remote_addr;
|
25
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
26
|
+
client_max_body_size 10m;
|
27
|
+
client_body_buffer_size 128k;
|
28
|
+
proxy_connect_timeout 90;
|
29
|
+
proxy_send_timeout 90;
|
30
|
+
proxy_read_timeout 90;
|
31
|
+
proxy_buffer_size 16k;
|
32
|
+
proxy_buffers 32 16k;
|
33
|
+
proxy_busy_buffers_size 64k;
|
34
|
+
|
35
|
+
error_page 503 /system/maintenance.html;
|
36
|
+
location /system/maintenance.html {
|
37
|
+
error_page 405 /system/maintenance.html;
|
38
|
+
rewrite ^ /system/maintenance.html break;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
# output compression saves bandwidth
|
43
|
+
gzip on;
|
44
|
+
gzip_http_version 1.0;
|
45
|
+
gzip_comp_level 2;
|
46
|
+
gzip_proxied any;
|
47
|
+
gzip_types text/plain text/html text/css text/xml application/x-javascript application/atom+xml;
|
48
|
+
|
49
|
+
}
|
50
|
+
|
51
|
+
|
Binary file
|
@@ -0,0 +1,49 @@
|
|
1
|
+
task :rubygems do
|
2
|
+
# These gems are needed in the enviroment (i.e not bundled with your Rails app).
|
3
|
+
# For example: unicorn, rake, mysql, starling. To install a new gem or upgrade
|
4
|
+
# existing one:
|
5
|
+
# $ cp /opt/local/lib/ruby/gems/1.9.1/cache/unicorn-0.93.3.gem gems/
|
6
|
+
# $ git add gems
|
7
|
+
# $ git commit -m "Added Unicorn gem"
|
8
|
+
# $ git push && cap necktie
|
9
|
+
Dir["gems/*.gem"].each do |gem|
|
10
|
+
install_gem gem
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
task :memcached do
|
15
|
+
# Out of the box, memcached listens to local requests only. We want all servers
|
16
|
+
# in the same security group to access each other's memcached.
|
17
|
+
unless processes.find { |p| p.cmdline[/memcached\s.*-l\s0.0.0.0/] }
|
18
|
+
update "/etc/memcached.conf", /^-l 127.0.0.1/, "-l 0.0.0.0"
|
19
|
+
services.start "memcached"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
task :unicorn=>[:rubygems, "#{@deploy_to}/current"] do
|
24
|
+
# Install init.d script to manage Unicorn, before we can start it.
|
25
|
+
cp "etc/init.d/unicorn", "/etc/init.d/"
|
26
|
+
chmod 0755, "/etc/init.d/unicorn"
|
27
|
+
services.start "unicorn"
|
28
|
+
end
|
29
|
+
|
30
|
+
task :nginx=>:unicorn do
|
31
|
+
# We only care about one Nginx configuration, so enable it and disable all others.
|
32
|
+
unless services.running?("nginx")
|
33
|
+
rm_rf Dir["/etc/nginx/sites-enabled/*"]
|
34
|
+
cp "etc/nginx/unicorn.conf", "/etc/nginx/sites-available/"
|
35
|
+
ln_sf "/etc/nginx/sites-available/unicorn.conf", "/etc/nginx/sites-enabled/"
|
36
|
+
services.start "nginx"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :email do
|
41
|
+
# Have postfix send emails on behalf of our host, and start it.
|
42
|
+
unless services.running?("postfix")
|
43
|
+
update "/etc/postfix/main.cf", /^myhostname\s*=.*$/, "myhostname = #{@hostname}"
|
44
|
+
write "/etc/mailname", @hostname
|
45
|
+
services.start "postfix"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
task :app=>[:environment, :memcached, :nginx, :email]
|
@@ -0,0 +1,49 @@
|
|
1
|
+
user, group = "nginx", "nginx"
|
2
|
+
|
3
|
+
shared_path = "#{@deploy_to}/shared"
|
4
|
+
cached_copy = "#{shared_path}/cached-copy"
|
5
|
+
releases_path = "#{@deploy_to}/releases"
|
6
|
+
release = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
7
|
+
release_path = "#{releases_path}/#{release}"
|
8
|
+
current_path = "#{@deploy_to}/current"
|
9
|
+
|
10
|
+
file @deploy_to do
|
11
|
+
mkdir_p @deploy_to
|
12
|
+
chown user, group, @deploy_to
|
13
|
+
chmod 0770, @deploy_to
|
14
|
+
end
|
15
|
+
|
16
|
+
file shared_path=>@deploy_to do
|
17
|
+
mkdir_p ["#{shared_path}/system", "#{shared_path}/pids", "#{shared_path}/log"]
|
18
|
+
chown_R user, group, shared_path
|
19
|
+
chmod_R 0770, shared_path
|
20
|
+
end
|
21
|
+
|
22
|
+
file releases_path=>@deploy_to do
|
23
|
+
mkdir_p releases_path
|
24
|
+
chown user, group, releases_path
|
25
|
+
chmod 0770, releases_path
|
26
|
+
end
|
27
|
+
|
28
|
+
file cached_copy=>shared_path do
|
29
|
+
sh "git clone #{@git_url} #{cached_copy}"
|
30
|
+
chown_R user, group, cached_copy
|
31
|
+
end
|
32
|
+
|
33
|
+
file release_path=>[releases_path, cached_copy] do
|
34
|
+
cp_r cached_copy, release_path
|
35
|
+
revision = bash("git --git-dir=#{cached_copy}/.git rev-parse --verify HEAD")
|
36
|
+
write "#{release_path}/REVISION", revision
|
37
|
+
touch release_path
|
38
|
+
sh "chmod -R g+w #{release_path}"
|
39
|
+
rm_rf ["#{release_path}/log", "#{release_path}/public/system", "#{release_path}/tmp/pids"]
|
40
|
+
mkdir_p ["#{release_path}/public", "#{release_path}/tmp"]
|
41
|
+
ln_s "#{shared_path}/log", "#{release_path}/log"
|
42
|
+
ln_s "#{shared_path}/system", "#{release_path}/public/system"
|
43
|
+
ln_s "#{shared_path}/pids", "#{release_path}/tmp/pids"
|
44
|
+
end
|
45
|
+
|
46
|
+
file current_path=>[release_path, shared_path] do
|
47
|
+
rm_f current_path
|
48
|
+
ln_s release_path, current_path
|
49
|
+
end
|
data/example/tasks/db.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
task "/etc/cron/snapshot" do
|
2
|
+
cp "etc/cron/snapshot", "/etc/cron.hourly/"
|
3
|
+
chmod 0755, "/etc/cron/snapshot"
|
4
|
+
end
|
5
|
+
|
6
|
+
task "/vol" do
|
7
|
+
# Assumes we attach EBS volume to /dev/sdh, formatted it to XFS, mounted to /vol.
|
8
|
+
append "/etc/fstab", "/dev/sdh /vol xfs noatime,nobarrier 0 0\n" unless read("/etc/fstab")["/dev/sdh "]
|
9
|
+
sh "mount /vol"
|
10
|
+
end
|
11
|
+
|
12
|
+
task "/etc/mysql"=>"/vol" do
|
13
|
+
# Mount the respective MySQL directories. Make sure they exist on your EBS volume first, see:
|
14
|
+
# http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1663
|
15
|
+
mounts = { "/vol/etc/mysql"=>"/etc/mysql",
|
16
|
+
"/vol/lib/mysql"=>"/var/lib/mysql",
|
17
|
+
"/vol/log/mysql"=>"/var/log/mysql" }
|
18
|
+
mounts.each do |vol, path|
|
19
|
+
mkdir_p path
|
20
|
+
append "/etc/fstab", "#{vol} #{path} none bind\n" unless read("/etc/fstab")["#{vol} "]
|
21
|
+
sh "mount #{path}"
|
22
|
+
end
|
23
|
+
chmod 0755, "/etc/mysql/debian-start"
|
24
|
+
end
|
25
|
+
|
26
|
+
task "mysql"=>"/etc/mysql" do
|
27
|
+
services.start "mysql" unless services.running?("mysql")
|
28
|
+
end
|
29
|
+
|
30
|
+
task :db=>[:environment, :mysql, "/etc/cron/snapshot"]
|
@@ -13,17 +13,18 @@ module Necktie
|
|
13
13
|
|
14
14
|
def run
|
15
15
|
standard_exception_handling do
|
16
|
-
init
|
17
|
-
|
16
|
+
init "necktie"
|
17
|
+
puts "(#{options.env})"
|
18
|
+
repo = File.expand_path("~/.necktie")
|
18
19
|
if File.exist?(repo)
|
19
20
|
Dir.chdir repo do
|
20
|
-
puts "Pulling latest updates ..."
|
21
|
+
puts "Pulling latest updates to #{repo} ..."
|
21
22
|
sh "git pull origin #{ENV["BRANCH"] || "master"}", :verbose=>false
|
22
23
|
end if options.pull
|
23
24
|
else
|
24
|
-
git_url
|
25
|
-
puts "Cloning #{git_url} to #{repo}"
|
26
|
-
sh "git clone #{git_url} #{repo.inspect}", :verbose=>false
|
25
|
+
options.git_url or fail "Need to set Git URL: use --source command line option"
|
26
|
+
puts "Cloning #{options.git_url} to #{repo}"
|
27
|
+
sh "git clone #{options.git_url} #{repo.inspect}", :verbose=>false
|
27
28
|
end
|
28
29
|
Dir.chdir repo do
|
29
30
|
load_rakefile
|
@@ -34,11 +35,27 @@ module Necktie
|
|
34
35
|
|
35
36
|
def necktie_options
|
36
37
|
[
|
37
|
-
['--env', '-e
|
38
|
+
['--env', '-e NAME', "Sets the environment (defaults to 'production').",
|
38
39
|
lambda { |value|
|
39
40
|
options.env = value
|
40
41
|
}
|
41
42
|
],
|
43
|
+
['--source', '-S GIT_URL', "Git URL to your Necktie repository",
|
44
|
+
lambda { |value| options.git_url = value }
|
45
|
+
],
|
46
|
+
['--update', '-U', "Update .necktie directory (git pull)",
|
47
|
+
lambda { |value| options.pull = true }
|
48
|
+
],
|
49
|
+
['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.",
|
50
|
+
lambda { |value|
|
51
|
+
options.show_tasks = :tasks
|
52
|
+
options.show_task_pattern = Regexp.new(value || '')
|
53
|
+
Rake::TaskManager.record_task_metadata = true
|
54
|
+
}
|
55
|
+
],
|
56
|
+
['--prereqs', '-P', "Display the tasks and dependencies, then exit.",
|
57
|
+
lambda { |value| options.show_prereqs = true }
|
58
|
+
],
|
42
59
|
['--describe', '-D [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
|
43
60
|
lambda { |value|
|
44
61
|
options.show_tasks = :describe
|
@@ -46,6 +63,13 @@ module Necktie
|
|
46
63
|
TaskManager.record_task_metadata = true
|
47
64
|
}
|
48
65
|
],
|
66
|
+
['--where', '-W [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
|
67
|
+
lambda { |value|
|
68
|
+
options.show_tasks = :lines
|
69
|
+
options.show_task_pattern = Regexp.new(value || '')
|
70
|
+
Rake::TaskManager.record_task_metadata = true
|
71
|
+
}
|
72
|
+
],
|
49
73
|
['--execute-print', '-p CODE', "Execute some Ruby code, print the result, then exit.",
|
50
74
|
lambda { |value|
|
51
75
|
puts eval(value)
|
@@ -56,16 +80,6 @@ module Necktie
|
|
56
80
|
"Execute some Ruby code, then continue with normal task processing.",
|
57
81
|
lambda { |value| eval(value) }
|
58
82
|
],
|
59
|
-
['--prereqs', '-P', "Display the tasks and dependencies, then exit.",
|
60
|
-
lambda { |value| options.show_prereqs = true }
|
61
|
-
],
|
62
|
-
['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.",
|
63
|
-
lambda { |value|
|
64
|
-
options.show_tasks = :tasks
|
65
|
-
options.show_task_pattern = Regexp.new(value || '')
|
66
|
-
Rake::TaskManager.record_task_metadata = true
|
67
|
-
}
|
68
|
-
],
|
69
83
|
['--trace', '-t', "Turn on invoke/execute tracing, enable full backtrace.",
|
70
84
|
lambda { |value|
|
71
85
|
options.trace = true
|
@@ -82,26 +96,16 @@ module Necktie
|
|
82
96
|
exit
|
83
97
|
}
|
84
98
|
],
|
85
|
-
['--where', '-W [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
|
86
|
-
lambda { |value|
|
87
|
-
options.show_tasks = :lines
|
88
|
-
options.show_task_pattern = Regexp.new(value || '')
|
89
|
-
Rake::TaskManager.record_task_metadata = true
|
90
|
-
}
|
91
|
-
],
|
92
|
-
['--update', '-U', "Update .necktie directory (git pull)",
|
93
|
-
lambda { |value| options.pull = true }
|
94
|
-
]
|
95
99
|
]
|
96
100
|
end
|
97
101
|
|
98
102
|
# Read and handle the command line options.
|
99
103
|
def handle_options
|
100
|
-
options.rakelib = ['
|
104
|
+
options.rakelib = ['tasks']
|
101
105
|
options.top_level_dsl = true
|
102
106
|
|
103
107
|
OptionParser.new do |opts|
|
104
|
-
opts.banner = "necktie
|
108
|
+
opts.banner = "necktie {options} tasks..."
|
105
109
|
opts.separator ""
|
106
110
|
opts.separator "Options are ..."
|
107
111
|
|
@@ -111,7 +115,6 @@ module Necktie
|
|
111
115
|
end
|
112
116
|
|
113
117
|
necktie_options.each { |args| opts.on(*args) }
|
114
|
-
opts.environment('RAKEOPT')
|
115
118
|
end.parse!
|
116
119
|
|
117
120
|
Rake::DSL.include_in_top_scope
|
@@ -122,7 +125,7 @@ module Necktie
|
|
122
125
|
fail "No Necktie file found (looking for: #{@rakefiles.join(', ')})" if @rakefile.nil?
|
123
126
|
Rake::Environment.load_rakefile(File.expand_path(@rakefile)) if @rakefile && @rakefile != ''
|
124
127
|
options.rakelib.each do |rlib|
|
125
|
-
glob("
|
128
|
+
glob("#{rlib}/*.rb") do |name|
|
126
129
|
add_import name
|
127
130
|
end
|
128
131
|
end
|
data/lib/necktie/capistrano.rb
CHANGED
@@ -7,7 +7,7 @@ Capistrano::Configuration.instance.load do
|
|
7
7
|
gem_file = File.join(Gem.dir, "cache", gem_spec.file_name)
|
8
8
|
upload gem_file, File.basename(gem_file), :via=>:scp
|
9
9
|
sudo "gem install #{File.basename(gem_file)}"
|
10
|
-
|
11
|
-
sudo "necktie
|
10
|
+
tasks = ENV["ROLES"].to_s.split(",") # ROLES => task names
|
11
|
+
sudo "necktie --source #{necktie_url} --update --environment #{fetch(:rails_env, "production")} #{task.join(" ")}"
|
12
12
|
end
|
13
13
|
end
|
data/lib/necktie/files.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
# Return the contents of the file (same as File.read).
|
1
2
|
def read(name)
|
2
3
|
File.read(name)
|
3
4
|
end
|
4
5
|
|
6
|
+
# Writes contents to a new file, or overwrites existing file.
|
7
|
+
# Takes string as second argument, or yields to block. For example:
|
8
|
+
# write "/etc/mailname", "example.com"
|
9
|
+
# write("/var/run/bowtie.pid") { Process.pid }
|
5
10
|
def write(name, contents = nil)
|
6
11
|
contents ||= yield
|
7
12
|
File.open name, "w" do |f|
|
@@ -9,6 +14,9 @@ def write(name, contents = nil)
|
|
9
14
|
end
|
10
15
|
end
|
11
16
|
|
17
|
+
# Append contents to a file, creating it if necessary.
|
18
|
+
# Takes string as second argument, or yields to block. For example:
|
19
|
+
# append "/etc/fstab", "/dev/sdh /vol xfs\n" unless read("/etc/fstab")["/dev/sdh "]
|
12
20
|
def append(name, contents = nil)
|
13
21
|
contents ||= yield
|
14
22
|
File.open name, "a" do |f|
|
@@ -16,6 +24,11 @@ def append(name, contents = nil)
|
|
16
24
|
end
|
17
25
|
end
|
18
26
|
|
27
|
+
# Updates a file: read contents, substitue and write it back.
|
28
|
+
# Takes two arguments for substitution, or yields to block.
|
29
|
+
# These two are equivalent:
|
30
|
+
# update "/etc/memcached.conf", /^-l 127.0.0.1/, "-l 0.0.0.0"
|
31
|
+
# update("/etc/memcached.conf") { |s| s.sub(/^-l 127.0.0.1/, "-l 0.0.0.0") }
|
19
32
|
def update(name, from = nil, to = nil)
|
20
33
|
contents = File.read(name)
|
21
34
|
if from && to
|
data/lib/necktie/gems.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
require "rubygems/dependency_installer"
|
2
2
|
|
3
|
+
# Installs the specified gem, if not already installed. First argument is the
|
4
|
+
# name of the gem, or file containing the gem. Second argument is version requirement.
|
5
|
+
# For example:
|
6
|
+
# install_gem "unicorn", "~>0.93"
|
7
|
+
#
|
8
|
+
# Dir["gems/*.gem"].each do |gem|
|
9
|
+
# install_gem gem
|
10
|
+
# end
|
3
11
|
def install_gem(name, version = nil)
|
4
12
|
installer = Gem::DependencyInstaller.new
|
5
13
|
spec = installer.find_spec_by_name_and_version(name, version).first.first
|
data/lib/necktie/rush.rb
CHANGED
data/lib/necktie/services.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
class Services
|
2
|
+
# Enables service to run after boot and starts it. Same as:
|
3
|
+
# update_rc.d <name> defaults
|
4
|
+
# service <name> start
|
2
5
|
def start(name)
|
3
6
|
puts " ** Starting service #{name}"
|
4
7
|
system "update-rc.d #{name} defaults" and
|
@@ -6,10 +9,14 @@ class Services
|
|
6
9
|
fail "failed to start #{name}"
|
7
10
|
end
|
8
11
|
|
12
|
+
# Enables service to run after boot.
|
9
13
|
def enable(name)
|
10
14
|
system "update-rc.d #{name} defaults" or "cannot enable #{name}"
|
11
15
|
end
|
12
16
|
|
17
|
+
# Disables service and stops it. Same as:
|
18
|
+
# service <name> stop
|
19
|
+
# update_rc.d <name> remove
|
13
20
|
def stop(name)
|
14
21
|
puts " ** Stopping service #{name}"
|
15
22
|
system "service #{name} stop" and
|
@@ -17,21 +24,31 @@ class Services
|
|
17
24
|
fail "failed to stop #{name}"
|
18
25
|
end
|
19
26
|
|
27
|
+
# Disables service from running after boot.
|
20
28
|
def disable(name)
|
21
29
|
system "update-rc.d -f #{name} remove" or fail "cannot disable #{name}"
|
22
30
|
end
|
23
31
|
|
32
|
+
# Restart service. Same as:
|
33
|
+
# service <name> restart
|
24
34
|
def restart(name)
|
25
|
-
puts " **
|
35
|
+
puts " ** Restarting service #{name}"
|
26
36
|
system "service #{name} restart" or fail "failed to restart #{name}"
|
27
37
|
end
|
28
38
|
|
39
|
+
# Checks if service is running. Returns true or false based on the outcome
|
40
|
+
# of service <name> status, and nil if service doesn't have a status command.
|
41
|
+
# (Note: Not all services report their running state, or do so reliably)
|
29
42
|
def running?(name)
|
30
43
|
status = File.read("|service --status-all 2>&1")[/^ \[ (.) \] #{Regexp.escape name}$/,1]
|
31
44
|
status == "+" ? true : status == "-" ? false : nil
|
32
45
|
end
|
33
46
|
end
|
34
47
|
|
48
|
+
# Returns Services object. Examples:
|
49
|
+
# services.restart "nginx"
|
50
|
+
# services.start "mysql" unless services.running?("mysql")
|
51
|
+
# services.enable "memcached" # but don't start yet
|
35
52
|
def services
|
36
53
|
@services ||= Services.new
|
37
54
|
end
|
data/lib/necktie.rb
CHANGED
data/necktie.gemspec
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "necktie"
|
3
|
-
spec.version = "0.
|
3
|
+
spec.version = "1.0.0"
|
4
4
|
spec.author = "Assaf Arkin"
|
5
5
|
spec.email = "assaf@labnotes.org"
|
6
6
|
spec.homepage = "http://github.com/assaf/necktie"
|
7
7
|
spec.summary = "Dress to impress"
|
8
8
|
spec.description = "Configure your servers remotely using Ruby and Git"
|
9
9
|
|
10
|
-
spec.files = Dir["{bin,lib,vendor}/**/*", "*.{gemspec,rdoc}"]
|
10
|
+
spec.files = Dir["{bin,lib,vendor,example}/**/*", "*.{gemspec,rdoc}"]
|
11
11
|
spec.executable = "necktie"
|
12
12
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: necktie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Assaf Arkin
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-10-
|
12
|
+
date: 2009-10-16 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -23,10 +23,10 @@ extra_rdoc_files: []
|
|
23
23
|
|
24
24
|
files:
|
25
25
|
- bin/necktie
|
26
|
+
- lib/necktie/application.rb
|
26
27
|
- lib/necktie/capistrano.rb
|
27
28
|
- lib/necktie/files.rb
|
28
29
|
- lib/necktie/gems.rb
|
29
|
-
- lib/necktie/rake.rb
|
30
30
|
- lib/necktie/rush.rb
|
31
31
|
- lib/necktie/services.rb
|
32
32
|
- lib/necktie.rb
|
@@ -236,6 +236,14 @@ files:
|
|
236
236
|
- vendor/session/lib/session-2.4.0.rb
|
237
237
|
- vendor/session/lib/session.rb
|
238
238
|
- vendor/session/test/session.rb
|
239
|
+
- example/etc/cron/snapshot
|
240
|
+
- example/etc/init.d/unicorn
|
241
|
+
- example/etc/nginx/unicorn.conf
|
242
|
+
- example/gems/unicorn-0.93.3.gem
|
243
|
+
- example/Necktie
|
244
|
+
- example/tasks/app.rb
|
245
|
+
- example/tasks/current.rb
|
246
|
+
- example/tasks/db.rb
|
239
247
|
- necktie.gemspec
|
240
248
|
- README.rdoc
|
241
249
|
has_rdoc: true
|