caplets 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/CHANGELOG +2 -0
- data/MIT-LICENSE +20 -0
- data/MODULES.md +148 -0
- data/README.md +105 -0
- data/lib/caplets.rb +8 -0
- data/lib/caplets/basic.rb +11 -0
- data/lib/caplets/bundle.rb +19 -0
- data/lib/caplets/cache.rb +11 -0
- data/lib/caplets/db.rb +61 -0
- data/lib/caplets/deploy.rb +148 -0
- data/lib/caplets/git-tag.rb +16 -0
- data/lib/caplets/logs.rb +3 -0
- data/lib/caplets/memcached.rb +30 -0
- data/lib/caplets/mongrel.rb +46 -0
- data/lib/caplets/networkfs.rb +30 -0
- data/lib/caplets/passenger.rb +19 -0
- data/lib/caplets/thinking-sphinx.rb +72 -0
- data/lib/caplets/unicorn.rb +83 -0
- data/lib/caplets/unicorn_rails.rb +17 -0
- data/lib/caplets/utils.rb +16 -0
- data/lib/caplets/version.rb +10 -0
- data/lib/caplets/web.rb +13 -0
- data/lib/caplets/whenever.rb +32 -0
- data/lib/caplets/yaml.rb +17 -0
- metadata +98 -0
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Dean Strelau, Mint Digital
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/MODULES.md
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
Caplets Modules
|
2
|
+
===============
|
3
|
+
|
4
|
+
caplets/deploy
|
5
|
+
--------------
|
6
|
+
|
7
|
+
#### Variables
|
8
|
+
|
9
|
+
`:bin_cmd` - Command used to execute binaries within the context of the app.
|
10
|
+
For instance, `caplets/bundle` sets this to `gem exec`. (default: `nil`)
|
11
|
+
|
12
|
+
`:environment` - The environment for this deployment, used as RAILS_ENV among
|
13
|
+
other things. (default: `production`)
|
14
|
+
|
15
|
+
`:required_children` - Directories that need to exist in the project root.
|
16
|
+
These will be created during `deploy:setup`. This is the caplets replacement
|
17
|
+
of `:shared_children`. (default: `%w[config log public tmp/pids]`)
|
18
|
+
|
19
|
+
`:server_processes` - Number of backend processes (ie, mongrels or unicorn
|
20
|
+
workers) to run. (default: `2`)
|
21
|
+
|
22
|
+
`:server_port` - Port number on which to bind backend processes.
|
23
|
+
(default: `8000`)
|
24
|
+
|
25
|
+
`:user` - UNIX user as which to login and run deploys. (default: `deploy`)
|
26
|
+
|
27
|
+
### caplets/bundle
|
28
|
+
|
29
|
+
Bundler 0.9+ support. By default, run after every code update, this `bundle
|
30
|
+
install`s gems into the project (not into system gems to avoid using `sudo`).
|
31
|
+
|
32
|
+
#### Variables
|
33
|
+
|
34
|
+
`:bundle_exclude` - Bundler groups to exclude from installation. Passed to
|
35
|
+
bundler's `--without` switch. (default: `%w[development test]`)
|
36
|
+
|
37
|
+
`:bundle_roles` - The server roles on which to bundle. (default: `[:app]`)
|
38
|
+
|
39
|
+
`:bundle_to` - Subdirectory of the application in which to put installed gems.
|
40
|
+
Passed as an argument to `bundle install`. (default: `vendor/bundled_gems`)
|
41
|
+
|
42
|
+
#### Tasks
|
43
|
+
|
44
|
+
`deploy:bundle:install` - Runs a `bundle install` to install needed gems from
|
45
|
+
the application's Gemfile.
|
46
|
+
|
47
|
+
#### Hooks
|
48
|
+
|
49
|
+
`deploy:bundle:install` after `deploy:update_code`
|
50
|
+
|
51
|
+
### caplets/db
|
52
|
+
|
53
|
+
Tasks to support using ActiveRecord within your applications. This module
|
54
|
+
adds functionality to write out your `database.yml` file and provides a
|
55
|
+
`deploy:migrations` task.
|
56
|
+
|
57
|
+
Differently from capistrano defaults, caplets does not expect your code to be
|
58
|
+
deployed to your DB server. This means you don't even have to list it in your
|
59
|
+
deploy file. Instead, it expects to run your migrations from your `:primary
|
60
|
+
:app` server.
|
61
|
+
|
62
|
+
#### Variables
|
63
|
+
|
64
|
+
`:db_host` - default: `localhost`
|
65
|
+
`:db_adapter` - default: `mysql`
|
66
|
+
`:db_database` - default: `<application>_<environment>`
|
67
|
+
`:db_username` - default: `<user>`
|
68
|
+
`:db_password` - default: `<prompt for value>`
|
69
|
+
`:db_encoding` - default: `utf8'
|
70
|
+
|
71
|
+
These variables are used to set the corresponding values in your
|
72
|
+
`database.yml` file. These values will be scoped appropriately under a key
|
73
|
+
for your current `:environment`. If you'd rather, you can specify the entire
|
74
|
+
`database.yml` yourself using `:db_confg`
|
75
|
+
|
76
|
+
`:db_extra` - A hash of values merged into your :db_config, outside of any
|
77
|
+
environment key. Useful for adding access to slaves or other DBs.
|
78
|
+
(default: {})
|
79
|
+
|
80
|
+
#### Tasks
|
81
|
+
|
82
|
+
`deploy:db:config` - Write a generated `database.yml` to your project's config
|
83
|
+
directory.
|
84
|
+
|
85
|
+
`deploy:migrate` - Run `rake db:migrate` on your primary app server.
|
86
|
+
|
87
|
+
`deploy:migrations` - Do a deploy with migrations, including doing a
|
88
|
+
`web:disable` first and restarting (not reloading) the backends
|
89
|
+
|
90
|
+
#### Hooks
|
91
|
+
|
92
|
+
`deploy:db:config` after `deploy:setup`
|
93
|
+
|
94
|
+
### caplets/git-tag
|
95
|
+
|
96
|
+
Automatically tag a revision on deploy.
|
97
|
+
|
98
|
+
#### Tasks
|
99
|
+
|
100
|
+
`git:tag_current_release` - Tag the `:current_revision` with a tag like
|
101
|
+
`deploy-<environment>-<timestamp>` and push it to the origin.
|
102
|
+
|
103
|
+
#### Hooks
|
104
|
+
|
105
|
+
`git:tag_current_release` after `deploy:migrations`
|
106
|
+
`git:tag_current_release` after `deploy:quick`
|
107
|
+
`git:tag_current_release` after `deploy:rebuild`
|
108
|
+
|
109
|
+
### caplets/memcached
|
110
|
+
|
111
|
+
Support for keeping track of memcached nodes and writing a `memcached.yml`
|
112
|
+
config file based on the `:memcached` role.
|
113
|
+
|
114
|
+
### Variables
|
115
|
+
|
116
|
+
`:memcached_servers` - An array of 'host:port' strings of memcached servers.
|
117
|
+
If any of your servers have the `:memcached` role, this is constructed
|
118
|
+
dynamically based on those servers' `:private_ip`s. Otherwise:
|
119
|
+
(default: `%w[localhost:11211]`)
|
120
|
+
|
121
|
+
### Tasks
|
122
|
+
|
123
|
+
`deploy:memcached:config` - Write a generated `memcached.yml` to your
|
124
|
+
projet's config directory.
|
125
|
+
|
126
|
+
### Hooks
|
127
|
+
|
128
|
+
`deploy:memcached:config` after `deploy:setup`
|
129
|
+
|
130
|
+
### caplets/mongrel
|
131
|
+
|
132
|
+
### caplets/networkfs
|
133
|
+
|
134
|
+
## caplets/passenger
|
135
|
+
|
136
|
+
## caplets/thinking-sphinx
|
137
|
+
|
138
|
+
## caplets/unicorn
|
139
|
+
|
140
|
+
## caplets/unicorn_rails
|
141
|
+
|
142
|
+
## caplets/utils
|
143
|
+
|
144
|
+
## caplets/web
|
145
|
+
|
146
|
+
## caplets/whenever
|
147
|
+
|
148
|
+
## caplets/yaml
|
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
Caplets
|
2
|
+
=======
|
3
|
+
|
4
|
+
Capistrano is old and busted, right? Time to move on? WRONG! Caplets makes
|
5
|
+
capistrano the new hotness once again. It bring capistrano into the future
|
6
|
+
with all your favorite goodies: git, bundler, unicorn, and more.
|
7
|
+
|
8
|
+
|
9
|
+
WARNING: Caplets has evolved out of Mint Digital's real-world deployments of
|
10
|
+
many large Rails/Rack applications. Although we have tried to keep it as
|
11
|
+
generic and customizable as possible, it is still very opinionated. That is
|
12
|
+
to say, it is largely tailored to how we like to deploy applications. If
|
13
|
+
caplets is missing something important to you, [submit an issue][issues] or
|
14
|
+
[fork away][fork].
|
15
|
+
|
16
|
+
[issues]: http://github.com/mintdigital/caplets/issues
|
17
|
+
[fork]: http://github.com/mintdigital/caplets/fork
|
18
|
+
|
19
|
+
Quickstart
|
20
|
+
----------
|
21
|
+
|
22
|
+
|
23
|
+
$ gem install caplets
|
24
|
+
|
25
|
+
# config/deploy.rb
|
26
|
+
require 'caplets'
|
27
|
+
load 'caplets/memcached'
|
28
|
+
load 'caplets/whenever'
|
29
|
+
# etc...
|
30
|
+
|
31
|
+
Roles
|
32
|
+
-----
|
33
|
+
|
34
|
+
Caplets depends heavily on capistrano's concept of server roles to apply tasks
|
35
|
+
only to the servers that require them. Most often, your sever definitions will
|
36
|
+
look like this:
|
37
|
+
|
38
|
+
server 'app1.mintdigital.com',
|
39
|
+
:app, :memcached, :sphinx, :assets,
|
40
|
+
:primary => true
|
41
|
+
|
42
|
+
Modules
|
43
|
+
-------
|
44
|
+
|
45
|
+
Caplets is a series of `load`able capistrano extensions. This given you
|
46
|
+
maximum flexibility in your deployments without having to remember to
|
47
|
+
set/unset loads of capistrano variables. Only the tasks you need get run.
|
48
|
+
|
49
|
+
See the [MODULES][] file for descriptions of all the available modules, along
|
50
|
+
with what tasks they add and what variables they use.
|
51
|
+
|
52
|
+
[MODULES]: http://github.com/mintdigital/caplets/blob/master/MODULES.md
|
53
|
+
|
54
|
+
caplets/deploy
|
55
|
+
--------------
|
56
|
+
|
57
|
+
The one module you will _always_ get, even if you just require
|
58
|
+
`caplets/basic` is `caplets/deploy`. This file is the heart of caplets; it
|
59
|
+
sets up the essentials of what caplets considers a modern deployment.
|
60
|
+
|
61
|
+
The biggest change to the standard capistrano setup is that we use git to
|
62
|
+
manage releases. Let me say that again as it's important:
|
63
|
+
|
64
|
+
** Instead of using subdirectories and symlinks to manage releases, caplets
|
65
|
+
uses git.**
|
66
|
+
|
67
|
+
That means:
|
68
|
+
|
69
|
+
- You will not have `releases` or `shared` directories.
|
70
|
+
- Your code will be directly inside your `:deploy_to` directory.
|
71
|
+
- `:current_path`, `:current_release`, `:release_path`, `:latest_release`,
|
72
|
+
and `:shared_path` will all be the same.
|
73
|
+
- Rollbacks still work, through the power of `git reset`
|
74
|
+
- "shared" files are really just files written to your project root but not
|
75
|
+
under git version control -- no symlinks needed.
|
76
|
+
|
77
|
+
Here are a few other changes that `caplets/deploy` makes:
|
78
|
+
|
79
|
+
- `:use_sudo` is false by default
|
80
|
+
- `:rails_env` is replaced with `:environment`, which caplets expects you to
|
81
|
+
set in your deploy file.
|
82
|
+
- `:user` and `:group` default to `deploy`
|
83
|
+
- `:shared_children` is not used. Use `:required_children` instead.
|
84
|
+
- The standard `deploy` task is disabled. Use `deploy:quick` to do an
|
85
|
+
`deploy:update` + `deploy:reload`. Load `caplets/db` for caplets'
|
86
|
+
replacement `deploy:migrations` task.
|
87
|
+
- Capistrano's built-in `deploy:web:enable` and `deploy:web:disable` are
|
88
|
+
disabled. Load `caplets/web` for caplets' replacements.
|
89
|
+
|
90
|
+
require 'caplets'
|
91
|
+
-----------------
|
92
|
+
|
93
|
+
Requiring `caplets` gets you the most common modules in one go. If you don't
|
94
|
+
want them all, you can always require `caplets/basic` instead.
|
95
|
+
|
96
|
+
# config/deploy.rb
|
97
|
+
require 'caplets'
|
98
|
+
|
99
|
+
# Equivalent to...
|
100
|
+
require 'caplets/basic'
|
101
|
+
load 'caplets/bundle'
|
102
|
+
load 'caplets/db'
|
103
|
+
load 'caplets/logs'
|
104
|
+
load 'caplets/web'
|
105
|
+
load 'caplets/yaml'
|
data/lib/caplets.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'caplets/version'
|
2
|
+
require 'caplets/utils'
|
3
|
+
|
4
|
+
config = Capistrano::Configuration.instance(:raise_on_error)
|
5
|
+
|
6
|
+
# Apparently $: isn't good enough for Capistrano.
|
7
|
+
config.instance_eval do
|
8
|
+
@load_paths.unshift File.expand_path('..',File.dirname(__FILE__))
|
9
|
+
end
|
10
|
+
|
11
|
+
config.load 'caplets/deploy'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
set :bundle_roles, [:app]
|
2
|
+
set :bundle_exclude, %w[development test]
|
3
|
+
set :bundle_to, 'vendor/bundled_gems'
|
4
|
+
set :bin_cmd, 'bundle exec'
|
5
|
+
|
6
|
+
## Tasks
|
7
|
+
namespace :deploy do
|
8
|
+
namespace :bundle do
|
9
|
+
desc "Install gems from Gemfile"
|
10
|
+
task :install, :roles => lambda { fetch(:bundle_roles) },
|
11
|
+
:except => {:no_release => true} do
|
12
|
+
without = fetch(:bundle_exclude).map{|g| "--without #{g}"}.join(' ')
|
13
|
+
run_current "bundle install #{fetch(:bundle_to)} #{without}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
## Hooks
|
19
|
+
after 'deploy:update_code', 'deploy:bundle:install'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
## Defaults
|
2
|
+
_cset :cache_dir, 'cache'
|
3
|
+
|
4
|
+
## Tasks
|
5
|
+
namespace :deploy do
|
6
|
+
namespace :cache do
|
7
|
+
task :clear, :roles => :app, :only => { :primary => true } do
|
8
|
+
run "find #{fetch(:current_path)}/public/#{fetch(:cache_dir)} -type f -delete"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/caplets/db.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# In deploy:migrations, we use web.enable and web.disable
|
2
|
+
# so we need to make sure this has been loaded.
|
3
|
+
load 'caplets/web'
|
4
|
+
|
5
|
+
## Defaults
|
6
|
+
_cset :db_host, 'localhost'
|
7
|
+
_cset :db_adapter, 'mysql'
|
8
|
+
_cset(:db_database) { "#{fetch(:application)}_#{fetch(:environment)}" }
|
9
|
+
_cset(:db_username) { user }
|
10
|
+
_cset(:db_password) {
|
11
|
+
Capistrano::CLI.password_prompt(
|
12
|
+
"Please enter MySQL password for user #{fetch(:db_username)}: "
|
13
|
+
)
|
14
|
+
}
|
15
|
+
_cset :db_encoding, 'utf8'
|
16
|
+
_cset(:db_config) {
|
17
|
+
fetch(:db_extra).merge({
|
18
|
+
fetch(:environment).to_s => {
|
19
|
+
'host' => fetch(:db_host).to_s,
|
20
|
+
'adapter' => fetch(:db_adapter).to_s,
|
21
|
+
'database' => fetch(:db_database).to_s,
|
22
|
+
'username' => fetch(:db_username).to_s,
|
23
|
+
'password' => fetch(:db_password).to_s,
|
24
|
+
'encoding' => fetch(:db_encoding).to_s
|
25
|
+
}
|
26
|
+
})
|
27
|
+
}
|
28
|
+
_cset :db_extra, {}
|
29
|
+
|
30
|
+
## Tasks
|
31
|
+
namespace :deploy do
|
32
|
+
namespace :db do
|
33
|
+
desc "write out database.yml"
|
34
|
+
task :config, :roles => :app do
|
35
|
+
put YAML.dump(fetch(:db_config)), "#{shared_path}/config/database.yml"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# migrate runs on the app server (no need for the code on the DB server)
|
40
|
+
task :migrate, :roles => :app, :only => { :primary => true } do
|
41
|
+
rake 'db:migrate'
|
42
|
+
end
|
43
|
+
|
44
|
+
desc <<-DESC
|
45
|
+
Do a deploy with migrations.
|
46
|
+
|
47
|
+
Use this when you need to completely disable the site and run migrations.
|
48
|
+
This will put up a maintenance page, run the migrations and completely
|
49
|
+
restart the app server processes.
|
50
|
+
DESC
|
51
|
+
task :migrations do
|
52
|
+
web.disable
|
53
|
+
update
|
54
|
+
migrate
|
55
|
+
restart
|
56
|
+
web.enable
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
## Hooks
|
61
|
+
after 'deploy:setup', 'deploy:db:config'
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# A lot of this taken from GitHub's deployment:
|
2
|
+
# http://github.com/blog/470-deployment-script-spring-cleaning
|
3
|
+
|
4
|
+
# Overwrite cap defaults
|
5
|
+
set :use_sudo, false
|
6
|
+
set :deploy_via, :git # not used
|
7
|
+
set :shared_children, %w[] # not relevant
|
8
|
+
set(:rails_env) { environment } ## some built-in tasks use :rails_env
|
9
|
+
default_run_options[:pty] = true
|
10
|
+
|
11
|
+
# Setup basics
|
12
|
+
_cset :user, 'deploy'
|
13
|
+
_cset(:group) { user }
|
14
|
+
_cset :environment, 'production'
|
15
|
+
_cset :required_children, %w[config log public tmp/pids]
|
16
|
+
|
17
|
+
# Server basics
|
18
|
+
_cset :server_processes, 2
|
19
|
+
_cset :server_port, 8000
|
20
|
+
|
21
|
+
# Use one directory for everything
|
22
|
+
set(:current_path) { fetch(:deploy_to) }
|
23
|
+
set(:latest_release) { fetch(:current_path) }
|
24
|
+
set(:release_path) { fetch(:current_path) }
|
25
|
+
set(:current_release) { fetch(:current_path) }
|
26
|
+
set(:shared_path) { fetch(:current_path) }
|
27
|
+
|
28
|
+
# Behold, the power of git
|
29
|
+
set(:current_revision) {
|
30
|
+
capture("cd #{current_path}; git rev-parse --short HEAD").strip
|
31
|
+
}
|
32
|
+
set(:latest_revision) {
|
33
|
+
capture("cd #{current_path}; git rev-parse --short HEAD").strip
|
34
|
+
}
|
35
|
+
set(:previous_revision) {
|
36
|
+
capture("cd #{current_path}; git rev-parse --short HEAD@{1}").strip
|
37
|
+
}
|
38
|
+
|
39
|
+
###########
|
40
|
+
## Tasks ##
|
41
|
+
###########
|
42
|
+
|
43
|
+
namespace :deploy do
|
44
|
+
[:default, :cleanup, :symlink].each do |t|
|
45
|
+
task t do
|
46
|
+
abort "Task disabled by caplets."
|
47
|
+
end
|
48
|
+
end
|
49
|
+
[:enable, :disable].each do |t|
|
50
|
+
namespace :web do
|
51
|
+
task t do
|
52
|
+
abort "Task disabled by caplets. Load 'caplets/web' to re-enable."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "Setup a Git-based deploy"
|
58
|
+
task :setup, :except => {:no_release => true} do
|
59
|
+
run_multi(
|
60
|
+
"if [ -d #{fetch(:current_path)}/.git ]",
|
61
|
+
"then cd #{fetch(:current_path)}",
|
62
|
+
"#{try_sudo} git fetch",
|
63
|
+
"else #{try_sudo} git clone #{fetch(:repository)} #{fetch(:current_path)}",
|
64
|
+
"fi",
|
65
|
+
"mkdir -p " + fetch(:required_children,[]).
|
66
|
+
map {|dir| "#{fetch(:current_path)}/#{dir}" }.
|
67
|
+
join(' ')
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
desc <<-DESC
|
72
|
+
Do a zero-downtime deploy.
|
73
|
+
|
74
|
+
Use this when you have small code changes that you want to go out with
|
75
|
+
minimal impact. This does not put up the maintanance page nor run migrations,
|
76
|
+
and will tell your app servers to 'reload' in-place instead of restarting
|
77
|
+
completely if they support it.
|
78
|
+
DESC
|
79
|
+
task :quick do
|
80
|
+
update
|
81
|
+
reload
|
82
|
+
end
|
83
|
+
|
84
|
+
task :update do
|
85
|
+
update_code
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "Update the deployed code from git"
|
89
|
+
task :update_code, :except => { :no_release => true } do
|
90
|
+
run_multi "cd #{fetch(:current_path)}",
|
91
|
+
"git fetch origin",
|
92
|
+
"git reset --hard #{fetch(:branch)}"
|
93
|
+
finalize_update
|
94
|
+
end
|
95
|
+
|
96
|
+
# Just timestamps, please
|
97
|
+
task :finalize_update, :except => { :no_release => true } do
|
98
|
+
if fetch(:normalize_asset_timestamps, false)
|
99
|
+
stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
|
100
|
+
asset_paths = %w(images stylesheets javascripts).map {|p|
|
101
|
+
"#{latest_release}/public/#{p}"
|
102
|
+
}.join(" ")
|
103
|
+
run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true",
|
104
|
+
:env => { "TZ" => "UTC" }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
%w[start stop restart reload].each do |taskname|
|
109
|
+
desc "#{taskname.capitalize} the application server(s)"
|
110
|
+
task taskname, :roles => :app, :except => {:no_release => true} do
|
111
|
+
deploy.send(fetch(:_server)).send(taskname)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
namespace :rollback do
|
116
|
+
task :code do
|
117
|
+
abort "Task disabled by caplets"
|
118
|
+
end
|
119
|
+
|
120
|
+
desc <<-DESC
|
121
|
+
[internal] Rollback repo.
|
122
|
+
|
123
|
+
Moves the repo back one release by checking out HEAD@{1}
|
124
|
+
DESC
|
125
|
+
task :repo, :except => { :no_release => true } do
|
126
|
+
set :branch, "HEAD@{1}"
|
127
|
+
deploy.update_code
|
128
|
+
end
|
129
|
+
|
130
|
+
desc <<-DESC
|
131
|
+
[internal] Rewrite git reflog.
|
132
|
+
This makes HEAD@{1} continue to point at the next previous release.
|
133
|
+
DESC
|
134
|
+
task :cleanup, :except => { :no_release => true } do
|
135
|
+
run_multi "cd #{fetch(:current_path)}",
|
136
|
+
'git reflog delete --rewrite HEAD@{1}',
|
137
|
+
'git reflog delete --rewrite HEAD@{1}'
|
138
|
+
end
|
139
|
+
|
140
|
+
desc "Rolls back to the previously deployed version."
|
141
|
+
task :default do
|
142
|
+
rollback.repo
|
143
|
+
rollback.cleanup
|
144
|
+
deploy.reload
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
## Tasks
|
2
|
+
namespace :git do
|
3
|
+
desc 'Tag and push tag for current release'
|
4
|
+
task :tag_current_release, :roles => :app, :only => {:primary => true} do
|
5
|
+
if ENV['NO_TAG'].nil?
|
6
|
+
tag = "deploy-#{environment}-#{Time.now.to_i}"
|
7
|
+
`git tag #{tag} #{fetch(:current_revision)}`
|
8
|
+
`git push origin #{tag}`
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
## Hooks
|
14
|
+
after 'deploy:migrations', 'git:tag_current_release'
|
15
|
+
after 'deploy:quick', 'git:tag_current_release'
|
16
|
+
after 'deploy:rebuild', 'git:tag_current_release'
|
data/lib/caplets/logs.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
## Defaults
|
2
|
+
_cset(:memcached_servers) do
|
3
|
+
if roles[:memcached].servers.any?
|
4
|
+
unless roles[:memcached].servers.all? {|s| s.options[:private_ip] }
|
5
|
+
abort "Set :private_ip for all :memcached servers or "+
|
6
|
+
"set :memcached_servers explicitly."
|
7
|
+
end
|
8
|
+
roles[:memcached].servers.map {|s| s.options[:private_ip] + ":11211" }
|
9
|
+
else
|
10
|
+
%w[localhost:11211]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
## Tasks
|
15
|
+
namespace :deploy do
|
16
|
+
namespace :memcached do
|
17
|
+
desc "Generate the memcached.yml in the shared directory"
|
18
|
+
task :config, :roles => :app do
|
19
|
+
config = {
|
20
|
+
environment => {
|
21
|
+
'servers' => fetch(:memcached_servers)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
put YAML.dump(config), "#{shared_path}/config/memcached.yml"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
## Hooks
|
30
|
+
after 'deploy:setup', 'deploy:memcached:config'
|
@@ -0,0 +1,46 @@
|
|
1
|
+
set :_server, :mongrel
|
2
|
+
|
3
|
+
namespace :deploy do
|
4
|
+
namespace :mongrel do
|
5
|
+
desc "Write the cluster config."
|
6
|
+
task :config, :roles => :app, :except => {:no_release => true} do
|
7
|
+
config = {
|
8
|
+
'user' => user,
|
9
|
+
'group' => group,
|
10
|
+
'cwd' => current_path,
|
11
|
+
'environment' => environment,
|
12
|
+
'port' => server_port,
|
13
|
+
'address' => '0.0.0.0',
|
14
|
+
'pid_file' => "#{shared_path}/tmp/pids/mongrel.pid",
|
15
|
+
'servers' => server_processes
|
16
|
+
}
|
17
|
+
put YAML.dump(config), "#{shared_path}/config/cluster.yml"
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Start the mongrel_cluster"
|
21
|
+
task :start, :roles => :app do
|
22
|
+
run "cd #{current_path} && " +
|
23
|
+
"mongrel_rails cluster::start -C #{shared_path}/config/cluster.yml --clean"
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Stop the mongrel_cluster"
|
27
|
+
task :stop, :roles => :app do
|
28
|
+
run "cd #{current_path} && " +
|
29
|
+
"mongrel_rails cluster::stop -C #{shared_path}/config/cluster.yml --clean"
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Restart the mongrel_cluster"
|
33
|
+
task :restart, :roles => :app do
|
34
|
+
run "cd #{current_path} && " +
|
35
|
+
"mongrel_rails cluster::restart -C #{shared_path}/config/cluster.yml --clean"
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Restart the mongrel_cluster, as mongrel does not support reloading"
|
39
|
+
task :reload, :roles => :app do
|
40
|
+
restart
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
## Hooks
|
46
|
+
after 'deploy:setup', 'deploy:mongrel:config'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# loading this file will modify your deploy for use with a networked filesystem
|
2
|
+
# Mostly, this means you can define paths that will get symlinked to the FS
|
3
|
+
|
4
|
+
## Defaults
|
5
|
+
_cset :networkfs_path, '/srv/share'
|
6
|
+
_cset :networkfs_resources, []
|
7
|
+
|
8
|
+
## Tasks
|
9
|
+
namespace :deploy do
|
10
|
+
namespace :networkfs do
|
11
|
+
desc "Create the application's directory on the network FS"
|
12
|
+
task :setup, :roles => :app, :only => {:primary => true} do
|
13
|
+
try_sudo "mkdir -p #{fetch(:networkfs_path)}/#{fetch(:application)}"
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Symlink networkfs_resources to the network FS"
|
17
|
+
task :symlink, :roles => [:app, :web] do
|
18
|
+
cmds = fetch(:networkfs_resources, []).map do |resource|
|
19
|
+
"ln -nfs" +
|
20
|
+
" #{fetch(:networkfs_path)}/#{fetch(:application)}/#{resource}" +
|
21
|
+
" #{fetch(:release_path)}/#{resource}"
|
22
|
+
end
|
23
|
+
try_sudo cmds.join(" && ")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
## Hooks
|
29
|
+
after 'deploy:setup', 'deploy:networkfs:setup'
|
30
|
+
after 'deploy:setup', 'deploy:networkfs:symlink'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
set :_server, :passenger
|
2
|
+
|
3
|
+
namespace :deploy do
|
4
|
+
namespace :passenger do
|
5
|
+
# These are no-ops for Passenger
|
6
|
+
task :start, :roles => :app, :except => {:no_release => true} do; end
|
7
|
+
task :stop, :roles => :app, :except => {:no_release => true} do; end
|
8
|
+
|
9
|
+
desc 'Restart the Passenger processes by touching tmp/restart.txt'
|
10
|
+
task :restart, :roles => :app, :except => {:no_release => true} do
|
11
|
+
run "touch #{current_path}/tmp/restart.txt"
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Restart the Passenger process by touching tmp/restart.txt'
|
15
|
+
task :restart, :roles => :app, :except => {:no_release => true} do
|
16
|
+
run "touch #{current_path}/tmp/restart.txt"
|
17
|
+
end
|
18
|
+
end # namespace :passenger
|
19
|
+
end # namespace :deploy
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# We require web:enable and web:disable for the rebuild task
|
2
|
+
load 'caplets/web'
|
3
|
+
|
4
|
+
## Defaults
|
5
|
+
_cset(:sphinx_address) {
|
6
|
+
roles[:sphinx].servers.detect {|s| s.options[:primary] }.options[:private_ip] ||
|
7
|
+
'127.0.0.1'
|
8
|
+
}
|
9
|
+
_cset(:sphinx_max_matches) { 1000 }
|
10
|
+
|
11
|
+
## Tasks
|
12
|
+
namespace :deploy do
|
13
|
+
desc <<-DESC
|
14
|
+
Do a deploy with migrations and a sphinx rebuild.
|
15
|
+
|
16
|
+
Use this when you need to completely rebuild the sphinx index on deploy,
|
17
|
+
for instance if you have changed the index definitions. This task puts up
|
18
|
+
the maintenance page, runs migrations, rebuilds the sphinx index and restarts
|
19
|
+
the app server processes.
|
20
|
+
DESC
|
21
|
+
task :rebuild do
|
22
|
+
web.disable
|
23
|
+
update
|
24
|
+
migrate
|
25
|
+
ts.rebuild
|
26
|
+
restart
|
27
|
+
web.enable
|
28
|
+
end
|
29
|
+
|
30
|
+
namespace :sphinx do
|
31
|
+
desc "Generate the sphinx.yml file in the shared directory"
|
32
|
+
task :config, :roles => [:app, :sphinx] do
|
33
|
+
config = {
|
34
|
+
fetch(:environment) => {
|
35
|
+
'address' => fetch(:sphinx_address),
|
36
|
+
'max_matches' => fetch(:sphinx_max_matches)
|
37
|
+
}
|
38
|
+
}
|
39
|
+
put YAML.dump(config), "#{shared_path}/config/sphinx.yml"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :ts do
|
44
|
+
desc "Generate the Thinking Sphinx config file"
|
45
|
+
task :config, :roles => [:app, :sphinx] do
|
46
|
+
rake 'ts:config'
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Rebuild Thinking Sphinx config and indexes and restart Sphinx"
|
50
|
+
task :rebuild, :roles => :sphinx do
|
51
|
+
rake 'ts:rebuild'
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Run a Sphinx index via Thinking Sphinx"
|
55
|
+
task :index, :roles => :sphinx do
|
56
|
+
rake 'ts:in'
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "Start Sphinx searchd via Thinking Sphinx"
|
60
|
+
task :start, :roles => :sphinx do
|
61
|
+
rake 'ts:start'
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "Stop Sphinx searchd via Thinking Sphinx"
|
65
|
+
task :stop, :roles => :sphinx do
|
66
|
+
rake 'ts:stop'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
## Hooks
|
72
|
+
after 'deploy:setup', 'deploy:sphinx:config'
|
@@ -0,0 +1,83 @@
|
|
1
|
+
set :_server, :unicorn
|
2
|
+
|
3
|
+
namespace :deploy do
|
4
|
+
namespace :unicorn do
|
5
|
+
|
6
|
+
def start_cmd
|
7
|
+
"#{fetch(:_unicorn_cmd,'unicorn')} -D" +
|
8
|
+
" -c config/unicorn.rb" +
|
9
|
+
" -E #{fetch(:environment)}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def stop_cmd(signal='QUIT', pid='unicorn.pid')
|
13
|
+
pid = "tmp/pids/#{pid}"
|
14
|
+
"if [ -f #{pid} ] ; then kill -#{signal} $(cat #{pid}) ; fi"
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait_for_pid(pid, to_disappear=false)
|
18
|
+
"while [ #{'!' unless to_disappear} -f tmp/pids/#{pid} ] ; do sleep 1; done"
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Write the unicorn config to the shared directory."
|
22
|
+
task :config, :roles => :app, :except => {:no_release => true} do
|
23
|
+
config = File.read(__FILE__).split(/^__END__$/, 2)[1]
|
24
|
+
put ERB.new(config,nil,'-').result(binding.dup),
|
25
|
+
fetch(:current_path) + '/config/unicorn.rb'
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Start the unicorn master and workers."
|
29
|
+
task :start, :roles => :app, :except => {:no_release => true} do
|
30
|
+
run_current start_cmd
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Stop the unicorn master and workers."
|
34
|
+
task :stop, :roles => :app, :except => {:no_release => true} do
|
35
|
+
run_current stop_cmd
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Restart the unicorn master and workers."
|
39
|
+
task :restart, :roles => :app, :except => {:no_release => true} do
|
40
|
+
run_current stop_cmd,
|
41
|
+
wait_for_pid('unicorn.pid', :to_disappear),
|
42
|
+
start_cmd,
|
43
|
+
wait_for_pid('unicorn.pid'),
|
44
|
+
'sleep 5' # app start-up
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Reload the unicorn master and workers with zero downtime."
|
48
|
+
task :reload, :roles => :app, :except => {:no_release => true} do
|
49
|
+
run_current stop_cmd('USR2'),
|
50
|
+
wait_for_pid('unicorn.pid.oldbin'),
|
51
|
+
wait_for_pid('unicorn.pid'),
|
52
|
+
'sleep 5', # app start-up
|
53
|
+
stop_cmd('QUIT', 'unicorn.pid.oldbin')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
## Hooks
|
59
|
+
after 'deploy:setup', 'deploy:unicorn:config'
|
60
|
+
|
61
|
+
__END__
|
62
|
+
pid '<%= fetch(:current_path) %>/tmp/pids/unicorn.pid'
|
63
|
+
|
64
|
+
working_directory "<%= fetch(:current_path) %>"
|
65
|
+
|
66
|
+
worker_processes <%= fetch(:server_processes, 4) %>
|
67
|
+
|
68
|
+
# Pre-load for fast worker forks and COW-friendliness
|
69
|
+
preload_app <%= fetch(:preload_app, true) %>
|
70
|
+
|
71
|
+
timeout <%= fetch(:server_timeout, 60) %>
|
72
|
+
|
73
|
+
listen '0.0.0.0:<%= fetch(:server_port, 8000) %>'
|
74
|
+
|
75
|
+
stderr_path "<%= current_path %>/log/unicorn.log"
|
76
|
+
stdout_path "<%= current_path %>/log/unicorn.log"
|
77
|
+
|
78
|
+
# http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
|
79
|
+
GC.respond_to?(:copy_on_write_friendly=) and
|
80
|
+
GC.copy_on_write_friendly = true
|
81
|
+
|
82
|
+
<%= fetch(:_unicorn_ar_config,'') %>
|
83
|
+
<%= fetch(:unicorn_extra_config,'') %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
set :_server, :unicorn
|
2
|
+
set :_unicorn_cmd, 'unicorn_rails'
|
3
|
+
set :_unicorn_ar_config, <<-CONF
|
4
|
+
before_fork do |server, worker|
|
5
|
+
# master process doesn't need a connection
|
6
|
+
defined?(ActiveRecord::Base) and
|
7
|
+
ActiveRecord::Base.connection.disconnect!
|
8
|
+
end
|
9
|
+
|
10
|
+
after_fork do |server, worker|
|
11
|
+
# workers need to connect individually
|
12
|
+
defined?(ActiveRecord::Base) and
|
13
|
+
ActiveRecord::Base.establish_connection
|
14
|
+
end
|
15
|
+
CONF
|
16
|
+
|
17
|
+
load 'caplets/unicorn'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This is just a collection of handy methods to use while writing tasks
|
2
|
+
module Caplets::Utils
|
3
|
+
def rake(cmd)
|
4
|
+
run_current "RAILS_ENV=#{fetch(:environment)} rake #{cmd}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def run_current(*cmds)
|
8
|
+
run ["cd #{fetch(:current_path)}"].concat(cmds).join(' && ')
|
9
|
+
end
|
10
|
+
|
11
|
+
def run_multi(*cmds)
|
12
|
+
run cmds.join(' ; ')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Capistrano::Configuration.send :include, Caplets::Utils
|
data/lib/caplets/web.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
namespace :deploy do
|
2
|
+
namespace :web do
|
3
|
+
desc 'Enable maintenance mode'
|
4
|
+
task :disable, :roles => :web do
|
5
|
+
try_sudo "touch #{fetch(:current_release)}/public/SHOW_MAINTENANCE_PAGE"
|
6
|
+
end
|
7
|
+
|
8
|
+
desc 'Disable maintenance mode'
|
9
|
+
task :enable, :roles => :web do
|
10
|
+
try_sudo "rm -f #{fetch(:current_release)}/public/SHOW_MAINTENANCE_PAGE"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Uses the whenever gem to update the server's crontab, using a combination
|
2
|
+
# of role and environment to pick which crontab to use.
|
3
|
+
#
|
4
|
+
# Put per-server crontabs inside whenever/{environment}.{role}.rb
|
5
|
+
#
|
6
|
+
namespace :deploy do
|
7
|
+
namespace :whenever do
|
8
|
+
def update_cmd_for(role)
|
9
|
+
load_file = "config/whenever/#{fetch(:environment)}.#{role}.rb"
|
10
|
+
[ "cd #{fetch(:current_path)} &&",
|
11
|
+
"if [ -f #{load_file} ] ; then",
|
12
|
+
"#{fetch(:bin_cmd)} whenever",
|
13
|
+
"--update-crontab #{fetch(:application)}.#{fetch(:environment)}.#{role}",
|
14
|
+
"--load-file #{load_file}",
|
15
|
+
"--set environment=#{fetch(:environment)}",
|
16
|
+
"; fi"
|
17
|
+
].join(' ')
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Update the crontab files by running 'whenever'"
|
21
|
+
task :update, :except => { :no_release => true } do
|
22
|
+
parallel do |session|
|
23
|
+
roles.keys.each do |role|
|
24
|
+
session.when "in?(#{role.inspect})", update_cmd_for(role)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
after "deploy:update", "deploy:whenever:update"
|
data/lib/caplets/yaml.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
## Defaults
|
2
|
+
_cset :config_files, {}
|
3
|
+
|
4
|
+
## Tasks
|
5
|
+
namespace :deploy do
|
6
|
+
namespace :yaml do
|
7
|
+
desc "Write any defined custom YAML config files."
|
8
|
+
task :config, :except => { :no_release => true } do
|
9
|
+
fetch(:config_files,{}).each do |name, data|
|
10
|
+
put YAML.dump(data), "#{fetch(:shared_path)}/config/#{name}.yml"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
## Hooks
|
17
|
+
after 'deploy:setup', 'deploy:yaml:config'
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: caplets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Dean Strelau
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-06-08 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: capistrano
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 5
|
30
|
+
- 0
|
31
|
+
version: 2.5.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description: " Caplets modernizes your capistrano deployments. At its most basic, it\n provides a fast, efficient git-based deployment without copying release\n trees or symlink tomfoolery. In addition, it includes modules for common\n tasks such as writing config files and crontabs, working with bundler,\n and using a networked filesystem.\n"
|
35
|
+
email: dean@mintdigital.com
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
files:
|
43
|
+
- lib/caplets/basic.rb
|
44
|
+
- lib/caplets/bundle.rb
|
45
|
+
- lib/caplets/cache.rb
|
46
|
+
- lib/caplets/db.rb
|
47
|
+
- lib/caplets/deploy.rb
|
48
|
+
- lib/caplets/git-tag.rb
|
49
|
+
- lib/caplets/logs.rb
|
50
|
+
- lib/caplets/memcached.rb
|
51
|
+
- lib/caplets/mongrel.rb
|
52
|
+
- lib/caplets/networkfs.rb
|
53
|
+
- lib/caplets/passenger.rb
|
54
|
+
- lib/caplets/thinking-sphinx.rb
|
55
|
+
- lib/caplets/unicorn.rb
|
56
|
+
- lib/caplets/unicorn_rails.rb
|
57
|
+
- lib/caplets/utils.rb
|
58
|
+
- lib/caplets/version.rb
|
59
|
+
- lib/caplets/web.rb
|
60
|
+
- lib/caplets/whenever.rb
|
61
|
+
- lib/caplets/yaml.rb
|
62
|
+
- lib/caplets.rb
|
63
|
+
- CHANGELOG
|
64
|
+
- MIT-LICENSE
|
65
|
+
- README.md
|
66
|
+
- MODULES.md
|
67
|
+
has_rdoc: true
|
68
|
+
homepage: http://mintdigital.github.com/caplets
|
69
|
+
licenses: []
|
70
|
+
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.3.6
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: Capistrano super powers
|
97
|
+
test_files: []
|
98
|
+
|