necktie 0.3.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|