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 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. I then run
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 necktie task set the necktie_url variable and require
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
- To setup a new server:
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
- To upgrade running instances:
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
- Note: The current directory (Dir.pwd and launch_dir) is the root directory of
115
- your Necktie repository. You can depend on relative paths when accessing files
116
- in your Necktie repository.
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
- If you need to run Necktie interactively, use the necktie command. The first
170
- time you run Necktie, it clones the Git repository into the .necktie directory
171
- in your home directory. You'll need to give it the Git URL, for example:
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 GIT_URL=git@example.com:mysetup app
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 .necktie directory, run the tasks and
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__)), File.expand_path("../vendor/rush/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
@@ -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
- repo = File.expand_path(".necktie")
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 = ENV["GIT_URL"] or fail "Need GIT_URL variable to point to Git repository, can't clone without it"
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 [NAME]', "Sets the environment (defaults to 'production').",
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 = ['necktie']
104
+ options.rakelib = ['tasks']
101
105
  options.top_level_dsl = true
102
106
 
103
107
  OptionParser.new do |opts|
104
- opts.banner = "necktie git_url {options} tasks..."
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("necktie/*.rb") do |name|
128
+ glob("#{rlib}/*.rb") do |name|
126
129
  add_import name
127
130
  end
128
131
  end
@@ -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
- roles = ENV["ROLES"].to_s.gsub(',', ' ') # roles => task names
11
- sudo "necktie GIT_URL=#{necktie_url} -U #{roles} -e #{fetch(:rails_env, "production")}"
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
@@ -1,7 +1,7 @@
1
1
  require "rush"
2
2
  include Rush
3
3
 
4
- class Object
4
+ class Object #:nodoc:
5
5
  include Rush
6
6
  Rush.methods(false).each do |method|
7
7
  define_method method do |*args|
@@ -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 " ** Stopping service #{name}"
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
@@ -1,8 +1,5 @@
1
- require "necktie/rake"
1
+ require "necktie/application"
2
2
  require "necktie/files"
3
3
  require "necktie/gems"
4
4
  require "necktie/services"
5
5
  require "necktie/rush"
6
-
7
- module Necktie
8
- end
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.5"
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.3.5
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-15 00:00:00 -07:00
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