shuttle-deploy 0.2.0.beta1

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.
Files changed (43) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +25 -0
  3. data/.magnum.yml +1 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +18 -0
  7. data/README.md +230 -0
  8. data/Rakefile +10 -0
  9. data/bin/shuttle +60 -0
  10. data/examples/rails.yml +22 -0
  11. data/examples/static.yml +10 -0
  12. data/examples/wordpress.yml +34 -0
  13. data/lib/shuttle/config.rb +5 -0
  14. data/lib/shuttle/deploy.rb +58 -0
  15. data/lib/shuttle/deployment/nodejs.rb +48 -0
  16. data/lib/shuttle/deployment/php.rb +17 -0
  17. data/lib/shuttle/deployment/rails.rb +116 -0
  18. data/lib/shuttle/deployment/ruby.rb +40 -0
  19. data/lib/shuttle/deployment/static.rb +5 -0
  20. data/lib/shuttle/deployment/wordpress/cli.rb +27 -0
  21. data/lib/shuttle/deployment/wordpress/core.rb +50 -0
  22. data/lib/shuttle/deployment/wordpress/plugins.rb +112 -0
  23. data/lib/shuttle/deployment/wordpress/vip.rb +84 -0
  24. data/lib/shuttle/deployment/wordpress.rb +191 -0
  25. data/lib/shuttle/errors.rb +5 -0
  26. data/lib/shuttle/helpers.rb +50 -0
  27. data/lib/shuttle/runner.rb +153 -0
  28. data/lib/shuttle/session.rb +52 -0
  29. data/lib/shuttle/support/bundler.rb +45 -0
  30. data/lib/shuttle/support/foreman.rb +7 -0
  31. data/lib/shuttle/support/thin.rb +59 -0
  32. data/lib/shuttle/target.rb +23 -0
  33. data/lib/shuttle/tasks.rb +264 -0
  34. data/lib/shuttle/version.rb +3 -0
  35. data/lib/shuttle.rb +35 -0
  36. data/shuttle-deploy.gemspec +28 -0
  37. data/spec/deploy_spec.rb +4 -0
  38. data/spec/fixtures/.gitkeep +0 -0
  39. data/spec/fixtures/static.yml +11 -0
  40. data/spec/helpers_spec.rb +42 -0
  41. data/spec/spec_helper.rb +15 -0
  42. data/spec/target_spec.rb +41 -0
  43. metadata +232 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZTM3MDZmNjBlN2EzYTRkZmRlNDc5OTk1ZWM5MGU0MzhmMGVhZTg2Mg==
5
+ data.tar.gz: !binary |-
6
+ NGRkOTVmY2IzYzU3MDZmMTRmNWQ1YzE3MTYzMjk4NGFlZjc4NzdkNQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZjBhNjhmOGQyNjNjMTA3MDJjNzM3MjcxOTBhMjIzMmJmYmU2YTk2ZmI0MjMz
10
+ MDM0MTc5YzMwZjlmMTQxNTdmNGQyZjJhOTdlYjRkMWY5ODUwMDJhYjY2MDY5
11
+ ZjRmYTczN2VhOGYyNjNiNGY5OTc2ZWQ3YWQ0YTI3MDE2MGI1ZjM=
12
+ data.tar.gz: !binary |-
13
+ M2RkNzViNGNlZmY2ZWRjMTczOTVhNWFkNmYyMmRiZjQ3ZTE4NmNjNzViNWM3
14
+ YTVjMGI5ZjViYzhiNjQ0MzUwOGRhZDFlMDFlZmM0MDNiNTJjZGFiZGViMWE4
15
+ NTA4MTQyMTI4MGZmOGQ1YTYyMjI4ODVmNGYyMzJkNzE0MDAwNjg=
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ *.tmproj
5
+ *~
6
+ .DS_Store
7
+ .\#*
8
+ .bundle
9
+ .config
10
+ .yardoc
11
+ Gemfile.lock
12
+ InstalledFiles
13
+ \#*
14
+ _yardoc
15
+ coverage
16
+ doc/
17
+ lib/bundler/man
18
+ pkg
19
+ rdoc
20
+ spec/reports
21
+ test/tmp
22
+ test/version_tmp
23
+ tmp
24
+ tmtags
25
+ Gemfile.lock
data/.magnum.yml ADDED
@@ -0,0 +1 @@
1
+ ruby: 1.9.3
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=documentation
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2012-2013 Dan Sosedoff.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # Shuttle
2
+
3
+ Shuttle is a minimalistic application deployment tool designed for small applications
4
+ and one-server deployments. Configuration is stored as YAML-encoded file, no need to use ruby code.
5
+ Operations are performed on SSH connection with target server.
6
+
7
+ *Under heavy development*
8
+
9
+ ## Install
10
+
11
+ Clone repository and run:
12
+
13
+ ```
14
+ rake install
15
+ ```
16
+
17
+ Supported ruby versions:
18
+
19
+ - 1.8.7
20
+ - 1.9.2
21
+ - 1.9.3
22
+ - 2.0.0
23
+
24
+ ## Structure
25
+
26
+ Deployment structure is very similar to capistrano.
27
+
28
+ Application files will be stored in `deploy_to` path that you specify in config.
29
+ Directory structure:
30
+
31
+ - `releases` - Main directory to store all application releases
32
+ - `current` - Symbolic link to the latest release
33
+ - `shared` - Shared directory to store assets, configs, etc
34
+ - `scm` - Code repository directory
35
+ - `version` - File that contains current release number
36
+
37
+ Shared directory structure:
38
+
39
+ - `tmp` - Temporary files
40
+ - `pids` - Shared process IDs files
41
+ - `log` - Shared log files
42
+
43
+ ## Process
44
+
45
+ Deployment flow is split into steps:
46
+
47
+ - Establish connection with target server
48
+ - Prepare application structure. It'll create all required directories or skip if they already exist.
49
+ - Clone repository or check out latest code. Submodules will be automatically updated as well.
50
+ - Switch to specified branch (`master` by default)
51
+ - Create a new release directory and checkout application code
52
+ - Perform strategy-related tasks.
53
+ - Create a symbolic link to the latest release
54
+ - Clean up old releases (default count: 5)
55
+
56
+ ## Strategies
57
+
58
+ Available deployment strategies:
59
+
60
+ - `static`
61
+ - `wordpress`
62
+ - `ruby`
63
+ - `rails`
64
+ - `nodejs`
65
+
66
+ ### Static Strategy
67
+
68
+ This is a default strategy that does not perform any application-related tasks.
69
+ Example configuration:
70
+
71
+ ```yaml
72
+ app:
73
+ name: my-application
74
+ strategy: static
75
+ git: git@github.com:my-site.git
76
+
77
+ target:
78
+ host: my-host.com
79
+ user: username
80
+ password: password
81
+ deploy_to: /home/deployer/www
82
+ ```
83
+
84
+ ### Wordpress Strategy
85
+
86
+ This strategy is designed to deploy wordpress sites developed as a separate theme.
87
+ It requires `subversion` installed on the server (will be automatically installed).
88
+
89
+ ## Deployment Config
90
+
91
+ Deployment config has a few main sections: `app` and `target`.
92
+
93
+ ### Application
94
+
95
+ Application section defines deployment strategy, source code location and other options:
96
+
97
+ ```yml
98
+ app:
99
+ name: my-app
100
+ strategy: static
101
+ git: https://site-url.com/repo.git
102
+ branch: master
103
+ keep_releases: 5
104
+ ```
105
+
106
+ Options:
107
+
108
+ - `name` - Your application name
109
+ - `strategy` - Deployment strategy. Defaults to `static`
110
+ - `git` - Git repository url
111
+ - `branch` - Git repository branch. Defaults to `master`
112
+ - `keep_releases` - Number of releases to keep. Defaults to `10`
113
+
114
+ You can also use Subversion as a main source:
115
+
116
+ ```yml
117
+ app:
118
+ svn: http://site-url.com/repo.git
119
+ ```
120
+
121
+ If your repository requires authentication, use url in the following format:
122
+
123
+ ```
124
+ http://username:password@yourdomain.com/project
125
+ ```
126
+
127
+ ### Target
128
+
129
+ Target is a set of remote machine credentials:
130
+
131
+ ```yml
132
+ target:
133
+ host: yourdomain.com
134
+ user: deployer
135
+ password: password
136
+ deploy_to: /home/deployer/myapp
137
+ ```
138
+
139
+ Options:
140
+
141
+ - `host` - Remote server host or ip
142
+ - `user` - Remote server user account
143
+ - `password` - Optional password. Use passwordless authentication if possible.
144
+ - `deploy_to` - Primary directory where all releases will be stored
145
+
146
+ You can also define multiple targets per config if environments does not have any specific
147
+ configuration settings:
148
+
149
+ ```yml
150
+ targets:
151
+ production:
152
+ host: mydomain.com
153
+ user: deployer
154
+ deploy_to: /home/production/myapp
155
+ staging:
156
+ host: mydomain.com
157
+ user: deployer
158
+ deploy_to: /home/staging/myapp
159
+ ```
160
+
161
+ ## Usage
162
+
163
+ To execute a new deploy, simply type (in your project folder):
164
+
165
+ ```
166
+ shuttle deploy
167
+ ```
168
+
169
+ Output will look like this:
170
+
171
+ ```
172
+ Shuttle v0.2.0
173
+
174
+ -----> Connected to deployer@mysite.com
175
+ -----> Preparing application structure
176
+ -----> Fetching latest code
177
+ -----> Using branch 'master'
178
+ -----> Linking release
179
+ -----> Release v35 has been deployed
180
+ -----> Cleaning up old releases: 1
181
+
182
+ Execution time: 2s
183
+ ```
184
+
185
+ If using multiple targets in config, you can specify which target to use with:
186
+
187
+ ```
188
+ shuttle staging deploy
189
+ ```
190
+
191
+ Specify a path to config with `-f` flag:
192
+
193
+ ```
194
+ shuttle -f /path/to/config.yml deploy
195
+ ```
196
+
197
+ To run in debug mode, add `-d` flag:
198
+
199
+ ```
200
+ shuttle deploy -d
201
+ ```
202
+
203
+ ## Test
204
+
205
+ To run project test suite execute:
206
+
207
+ ```
208
+ bundle exec rake test
209
+ ```
210
+
211
+ ## License
212
+
213
+ Copyright (c) 2012-2013 Dan Sosedoff.
214
+
215
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
216
+ this software and associated documentation files (the "Software"), to deal in
217
+ the Software without restriction, including without limitation the rights to
218
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
219
+ the Software, and to permit persons to whom the Software is furnished to do so,
220
+ subject to the following conditions:
221
+
222
+ The above copyright notice and this permission notice shall be included in all
223
+ copies or substantial portions of the Software.
224
+
225
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
226
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
227
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
228
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
229
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
230
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:test) do |t|
6
+ t.pattern = 'spec/*_spec.rb'
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => :test
data/bin/shuttle ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'rubygems'
7
+ require 'optparse'
8
+ require 'shuttle'
9
+
10
+ config_name = Dir.pwd.split('/').last + ".yml"
11
+
12
+ options = {
13
+ :path => File.join(ENV['HOME'], '.shuttle', config_name),
14
+ :target => 'production',
15
+ :log => false
16
+ }
17
+
18
+ optparse = OptionParser.new do |opts|
19
+ opts.on('-v', '--version', 'Show version') do
20
+ puts "Shuttle version #{Shuttle::VERSION}"
21
+ exit 0
22
+ end
23
+
24
+ opts.on('-e', '--environment NAME', 'Deployment target environment') do |v|
25
+ options[:target] = v
26
+ end
27
+
28
+ opts.on('-d', '--debug', 'Enable debugging') do
29
+ options[:log] = true
30
+ end
31
+
32
+ opts.on('-f', '--file PATH', 'Configuration file path') do |v|
33
+ options[:path] = v
34
+ end
35
+ end
36
+
37
+ begin
38
+ optparse.parse!
39
+ rescue OptionParser::ParseError => e
40
+ puts "Error: #{e.message}"
41
+ exit 1
42
+ end
43
+
44
+ case ARGV.size
45
+ when 2
46
+ options[:target] = ARGV.shift
47
+ command = ARGV.shift
48
+ when 1
49
+ command = ARGV.shift
50
+ else
51
+ puts "Command required"
52
+ exit 1
53
+ end
54
+
55
+ begin
56
+ runner = Shuttle::Runner.new(options)
57
+ runner.execute(command.dup)
58
+ rescue Shuttle::ConfigError => err
59
+ puts "Error: #{err.message}."
60
+ end
@@ -0,0 +1,22 @@
1
+ app:
2
+ name: rails-app
3
+ strategy: rails
4
+ git: git@github.com:user/repo.git
5
+
6
+ target:
7
+ host: rails-app.com
8
+ user: deployer
9
+ password: password
10
+ deploy_to: /home/deployer/www
11
+
12
+ rails:
13
+ environment: production
14
+ precompile_assets: true
15
+ start_server: true
16
+ shared_paths:
17
+ static_assets: public/static
18
+
19
+ thin:
20
+ host: 127.0.0.1
21
+ port: 9000
22
+ servers: 5
@@ -0,0 +1,10 @@
1
+ app:
2
+ name: sample-site
3
+ strategy: static
4
+ git: git@github.com/username/sample-site.git
5
+
6
+ target:
7
+ host: sample-site.com
8
+ user: deployer
9
+ password: password
10
+ deploy_to: /home/deployer/www
@@ -0,0 +1,34 @@
1
+ app:
2
+ name: sample-site
3
+ strategy: wordpress
4
+ git: git@github.com/username/sample-site.git
5
+ branch: production
6
+
7
+ target:
8
+ host: sample-site.com
9
+ user: deployer
10
+ password: password
11
+ deploy_to: /home/deployer/www
12
+
13
+ wordpress:
14
+ theme: theme_name
15
+ site:
16
+ title: "Site Title"
17
+ url: "http://sample-site.com"
18
+ admin_name: "admin"
19
+ admin_email: "admin@admin.com"
20
+ admin_password: "password"
21
+ mysql:
22
+ host: 127.0.0.1
23
+ user: mysql-user
24
+ password: mysql-password
25
+ database: mysql-dadtabase
26
+ plugins:
27
+ - advanced-custom-fields: git://github.com/elliotcondon/acf.git
28
+ - be-subpages-widget
29
+ - contact-form-7
30
+ - contact-form-7-to-database-extension
31
+ - featured-page-widget
32
+ - share-this
33
+ - twitter-widget-pro
34
+ - types
@@ -0,0 +1,5 @@
1
+ module Shuttle
2
+ class Config
3
+ # TODO
4
+ end
5
+ end
@@ -0,0 +1,58 @@
1
+ module Shuttle
2
+ class Deploy
3
+ include Shuttle::Tasks
4
+ include Shuttle::Helpers
5
+
6
+ attr_reader :ssh
7
+ attr_reader :target
8
+ attr_reader :environment
9
+ attr_reader :version
10
+ attr_reader :config
11
+
12
+ def initialize(config, ssh, target, environment)
13
+ @config = config
14
+ @target = target
15
+ @ssh = ssh
16
+ @environment = environment
17
+
18
+ if ssh.file_exists?(version_path)
19
+ res = ssh.capture("cat #{version_path}")
20
+ @version = (res.empty? ? 1 : Integer(res) + 1).to_s
21
+ else
22
+ @version = 1
23
+ end
24
+ end
25
+
26
+ def deploy_path(path=nil)
27
+ [target.deploy_to, path].compact.join('/')
28
+ end
29
+
30
+ def shared_path(path=nil)
31
+ [deploy_path, 'shared', path].compact.join('/')
32
+ end
33
+
34
+ def current_path(path=nil)
35
+ [deploy_path, 'current', path].compact.join('/')
36
+ end
37
+
38
+ def version_path
39
+ deploy_path('version')
40
+ end
41
+
42
+ def release_path(path=nil)
43
+ [deploy_path, 'releases', version, path].compact.join('/')
44
+ end
45
+
46
+ def scm_path
47
+ deploy_path('scm')
48
+ end
49
+
50
+ def deploy
51
+ setup
52
+ update_code
53
+ checkout_code
54
+ link_release
55
+ cleanup_releases
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,48 @@
1
+ module Shuttle
2
+ class Nodejs < Shuttle::Deploy
3
+ def setup
4
+ if node_installed?
5
+ log "Using Node.js v#{node_version}, Npm v#{npm_version}"
6
+ else
7
+ error "Node.js is not installed."
8
+ end
9
+
10
+ super
11
+ end
12
+
13
+ def deploy
14
+ setup
15
+ update_code
16
+ checkout_code
17
+ install_dependencies
18
+ link_release
19
+ cleanup_releases
20
+ end
21
+
22
+ private
23
+
24
+ def node_installed?
25
+ ssh.run("which node").success?
26
+ end
27
+
28
+ def node_version
29
+ ssh.run("node -v").output.strip.gsub('v', '')
30
+ end
31
+
32
+ def npm_version
33
+ ssh.run("npm -v").output.strip
34
+ end
35
+
36
+ def install_dependencies
37
+ if ssh.file_exists?("#{release_path}/package.json")
38
+ log "Installing application dependencies"
39
+
40
+ result = ssh.run("cd #{release_path} && npm install")
41
+
42
+ if result.failure?
43
+ error "Unable to install dependencies: #{result.output}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ module Shuttle
2
+ class Php < Shuttle::Deploy
3
+ def setup
4
+ unless php_installed?
5
+ error "Please install PHP first"
6
+ end
7
+
8
+ super
9
+ end
10
+
11
+ private
12
+
13
+ def php_installed?
14
+ ssh.run("which php").success?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,116 @@
1
+ module Shuttle
2
+ class Rails < Shuttle::Deploy
3
+ include Shuttle::Support::Bundler
4
+ include Shuttle::Support::Thin
5
+
6
+ def rails_env
7
+ if config.rails && config.rails.environment
8
+ config.rails.environment
9
+ else
10
+ environment
11
+ end
12
+ end
13
+
14
+ def precompile_assets?
15
+ config.rails && config.rails.precompile_assets != false
16
+ end
17
+
18
+ def start_server?
19
+ if config.rails && !config.rails.start_server.nil?
20
+ config.rails.start_server
21
+ else
22
+ true
23
+ end
24
+ end
25
+
26
+ def setup_bundler
27
+ if !bundler_installed?
28
+ log "Bundler is missing. Installing"
29
+
30
+ res = ssh.run("sudo gem install bundler")
31
+ if res.success?
32
+ log "Bundler v#{bundler_version} installed"
33
+ else
34
+ error "Unable to install bundler: #{res.output}"
35
+ end
36
+ end
37
+ end
38
+
39
+ def rake(command)
40
+ res = ssh.run("cd #{release_path} && rake #{command}")
41
+ if res.failure?
42
+ error "Unable to run rake command: #{command}. Reason: #{res.output}"
43
+ end
44
+ end
45
+
46
+ def precompile_assets
47
+ if precompile_assets?
48
+ log "Precompiling assets"
49
+ rake 'assets:precompile'
50
+ end
51
+ end
52
+
53
+ def deploy
54
+ ssh.export('RACK_ENV', rails_env)
55
+ ssh.export('RAILS_ENV', rails_env)
56
+
57
+ log "Rails environment is set to #{rails_env}"
58
+
59
+ setup
60
+ setup_bundler
61
+ update_code
62
+ checkout_code
63
+ bundle_install
64
+ migrate_database
65
+ precompile_assets
66
+ link_shared_paths
67
+
68
+ if start_server?
69
+ thin_restart
70
+ end
71
+
72
+ link_release
73
+ cleanup_releases
74
+ end
75
+
76
+ def migrate_database
77
+ return if !ssh.file_exists?(release_path('db/schema.rb'))
78
+
79
+ migrate = true # Will migrate by default
80
+ schema = ssh.read_file(release_path('db/schema.rb'))
81
+ schema_file = shared_path('schema')
82
+ checksum = Digest::SHA1.hexdigest(schema)
83
+
84
+ if ssh.file_exists?(schema_file)
85
+ old_checksum = ssh.read_file(schema_file).strip
86
+ if old_checksum == checksum
87
+ migrate = false
88
+ end
89
+ end
90
+
91
+ if migrate == true
92
+ log "Migrating database"
93
+ rake 'db:migrate'
94
+ ssh.run("echo #{checksum} > #{schema_file}")
95
+ else
96
+ log "Database migration skipped"
97
+ end
98
+ end
99
+
100
+ def link_shared_paths
101
+ ssh.run("mkdir -p #{release_path('tmp')}")
102
+ ssh.run("rm -rf #{release_path}/log")
103
+ ssh.run("ln -s #{shared_path('pids')} #{release_path('tmp/pids')}")
104
+ ssh.run("ln -s #{shared_path('log')} #{release_path('log')}")
105
+
106
+ if config.rails
107
+ if config.rails.shared_paths
108
+ config.rails.shared_paths.each_pair do |name, path|
109
+ log "Linking shared path: #{name}"
110
+ ssh.run("ln -s #{shared_path}/#{name} #{release_path}/#{path}")
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end