buzzville 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -11,10 +11,10 @@ begin
11
11
  gem.homepage = "http://github.com/buzzware/buzzville"
12
12
  gem.authors = ["buzzware"]
13
13
  gem.rubyforge_project = "buzzware"
14
- gem.add_dependency('buzzcore', '>= 0.2.5')
15
- gem.add_dependency('yore', '>= 0.0.5')
14
+ gem.add_dependency('buzzcore', '>= 0.3.3')
15
+ gem.add_dependency('yore', '>= 0.2.1')
16
16
  gem.add_dependency('cmdparse', '>= 2.0.2')
17
- gem.add_development_dependency "thoughtbot-shoulda"
17
+ gem.add_development_dependency "shoulda"
18
18
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
19
  end
20
20
  Jeweler::RubyforgeTasks.new do |rubyforge|
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.1.4
@@ -0,0 +1,73 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{buzzville}
8
+ s.version = "0.1.4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["buzzware"]
12
+ s.date = %q{2010-01-28}
13
+ s.description = %q{Capistrano recipes and ruby code relating to deployment}
14
+ s.email = %q{contact@buzzware.com.au}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "buzzville.gemspec",
27
+ "buzzville.vpj",
28
+ "buzzville.vpw",
29
+ "lib/buzzville.rb",
30
+ "lib/buzzville/cap_utils.rb",
31
+ "lib/buzzville/ftp_extra.rb",
32
+ "lib/buzzville/recipes.rb",
33
+ "lib/buzzville/recipes/data.rb",
34
+ "lib/buzzville/recipes/files.rb",
35
+ "lib/buzzville/recipes/ssl.rb",
36
+ "lib/buzzville/recipes/user.rb",
37
+ "lib/buzzville_dev.rb",
38
+ "test/buzzville_test.rb",
39
+ "test/test_helper.rb"
40
+ ]
41
+ s.homepage = %q{http://github.com/buzzware/buzzville}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.rubyforge_project = %q{buzzware}
45
+ s.rubygems_version = %q{1.3.5}
46
+ s.summary = %q{Capistrano recipes and ruby code relating to deployment}
47
+ s.test_files = [
48
+ "test/buzzville_test.rb",
49
+ "test/test_helper.rb"
50
+ ]
51
+
52
+ if s.respond_to? :specification_version then
53
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
57
+ s.add_runtime_dependency(%q<buzzcore>, [">= 0.3.3"])
58
+ s.add_runtime_dependency(%q<yore>, [">= 0.2.1"])
59
+ s.add_runtime_dependency(%q<cmdparse>, [">= 2.0.2"])
60
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
61
+ else
62
+ s.add_dependency(%q<buzzcore>, [">= 0.3.3"])
63
+ s.add_dependency(%q<yore>, [">= 0.2.1"])
64
+ s.add_dependency(%q<cmdparse>, [">= 2.0.2"])
65
+ s.add_dependency(%q<shoulda>, [">= 0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<buzzcore>, [">= 0.3.3"])
69
+ s.add_dependency(%q<yore>, [">= 0.2.1"])
70
+ s.add_dependency(%q<cmdparse>, [">= 2.0.2"])
71
+ s.add_dependency(%q<shoulda>, [">= 0"])
72
+ end
73
+ end
@@ -8,6 +8,18 @@ require 'net/sftp'
8
8
 
9
9
  module CapUtils
10
10
 
11
+ # for debugging deploy :
12
+ #
13
+ # same as original, but without file removal on rollback
14
+ #task :update_code, :except => { :no_release => true } do
15
+ # on_rollback do
16
+ # require 'ruby-debug'; debugger
17
+ # end
18
+ # strategy.deploy!
19
+ # finalize_update
20
+ #end
21
+
22
+
11
23
  # upload the given local file to the remote server and set the given mode.
12
24
  # 0644 is the default mode
13
25
  #
@@ -101,7 +113,7 @@ module CapUtils
101
113
  def file_exists?(path)
102
114
  result = nil
103
115
  run "if [[ -e #{path} ]]; then echo 'true'; else echo 'false'; fi", :shell => false do |ch,stream,text|
104
- result = (text.strip! == 'true')
116
+ result = (text.strip == 'true')
105
117
  end
106
118
  result
107
119
  end
@@ -124,9 +136,9 @@ module CapUtils
124
136
  ensure_link("#{aSharedFolder}","#{aReleaseFolder}",nil,"#{user}:#{apache_user}")
125
137
  end
126
138
 
127
- def select_target_file(aFile)
128
- ext = MiscUtils.file_extension(aFile,false)
129
- no_ext = MiscUtils.file_no_extension(aFile,false)
139
+ def select_target_file(aFile,aExtendedExtension=false)
140
+ ext = MiscUtils.file_extension(aFile,aExtendedExtension)
141
+ no_ext = MiscUtils.file_no_extension(aFile,aExtendedExtension)
130
142
  dir = File.dirname(aFile)
131
143
  run "#{sudo} mv -f #{no_ext}.#{target}.#{ext} #{aFile}"
132
144
  run "#{sudo} rm -f #{no_ext}.*.#{ext}"
@@ -147,7 +159,7 @@ module CapUtils
147
159
  #sudo find . -type f -exec echo {} \;
148
160
  cmd = []
149
161
  cmd << "sudo" if aSudo
150
- cmd << "find #{aPath}"
162
+ cmd << "find #{MiscUtils.append_slash(aPath)}"
151
163
  cmd << "-wholename '#{aPattern}'" if aPattern
152
164
  cmd << "-prune -o" if aInvertPattern
153
165
  cmd << case aFilesOrDirs.to_s[0,1]
@@ -157,13 +169,42 @@ module CapUtils
157
169
  end
158
170
  cmd << "-exec"
159
171
  cmd << aCommand
160
- cmd << '{} \;'
172
+ cmd << "'{}' \\;"
161
173
  cmd = cmd.join(' ')
162
174
  run cmd
163
175
  end
164
176
 
165
- #run_for_all("chmod g+s #{File.expand_path(dir,public_dir)}",public_dir,:dirs)
177
+ # just quickly ensures user can deploy. Only for use before deploying, and should be followed by
178
+ # more secure settings
179
+ def permissions_for_deploy(aUser=nil,aGroup=nil,aPath=nil)
180
+ aUser ||= user
181
+ aGroup ||= apache_user
182
+ aPath ||= deploy_to
183
+ run "#{sudo} chown -R #{aUser}:#{aGroup} #{MiscUtils.append_slash(aPath)}"
184
+ run "#{sudo} chmod u+rw #{MiscUtils.append_slash(aPath)}"
185
+ run_for_all("chmod u+x",aPath,:dirs)
186
+ end
187
+
188
+ # set standard permissions for web sites - readonly for apache user
189
+ def permissions_for_web(aPath=nil,aUser=nil,aApacheUser=nil,aHideScm=nil)
190
+ aPath ||= deploy_to
191
+ aUser ||= user
192
+ aApacheUser ||= apache_user
193
+
194
+ run "#{sudo} chown -R #{aUser}:#{aApacheUser} #{MiscUtils.append_slash(aPath)}"
195
+ run "#{sudo} chmod -R 644 #{MiscUtils.append_slash(aPath)}"
196
+ run_for_all("chmod +x",aPath,:dirs)
197
+ run_for_all("chmod g+s",aPath,:dirs)
198
+ case aHideScm
199
+ when :svn then run_for_all("chown -R #{aUser}:#{aUser}",aPath,:dirs,"*/.svn")
200
+ end
201
+ end
166
202
 
203
+ # run this after permissions_for_web() on dirs that need to be writable by group (apache)
204
+ def permissions_for_web_writable(aPath)
205
+ run "#{sudo} chmod -R g+w #{MiscUtils.append_slash(aPath)}"
206
+ end
207
+
167
208
  def set_dir_permissions_r(aStartPath,aUser=nil,aGroup=nil,aMode=nil,aSetGroupId=false)
168
209
  cmd = []
169
210
  if aGroup
@@ -200,6 +241,12 @@ module CapUtils
200
241
  set_permissions(aStartPath,aUser,aGroup,aMode,aSetGroupId,aSudo)
201
242
  end
202
243
 
244
+ def make_public_cache_dir(aStartPath)
245
+ run "#{sudo} mkdir -p #{aStartPath}"
246
+ permissions_for_web(aStartPath)
247
+ permissions_for_web_writable(aStartPath)
248
+ end
249
+
203
250
  # if aGroup is given, that will be the users only group
204
251
  def adduser(aNewUser,aPassword,aGroup=nil)
205
252
  run "#{sudo} adduser --gecos '' #{aGroup ? '--ingroup '+aGroup : ''} #{aNewUser}" do |ch, stream, out|
@@ -288,7 +335,8 @@ module CapUtils
288
335
 
289
336
  cmd << ensure_folder(design_views,aUser,aApacheUser,750)
290
337
  # copy files from database to shared/design
291
- cmd << "cp -rf #{aBrowserCmsRoot}/tmp/views/* #{design_views}/"
338
+ # if [[ ! -L libtool ]]; then echo 'true'; fi !!! need NOT
339
+ cmd << "if [ ! -L #{aBrowserCmsRoot}/tmp/views ]; then cp -rf #{aBrowserCmsRoot}/tmp/views/* #{design_views}/ ; fi"
292
340
  cmd << ensure_folder(design_views+'/layouts/templates',aUser,aApacheUser,750)
293
341
  cmd << ensure_folder(design_views+'/partials',aUser,aApacheUser,750)
294
342
 
@@ -306,7 +354,7 @@ module CapUtils
306
354
  ## link templates into ftp folder
307
355
  cmd << ensure_link_cmd(design_views,'views',aFtpPath,"#{aUser}:#{aApacheUser}")
308
356
  #run "#{sudo} chgrp -h www-data #{deploy_to}/current" # this is to change owner of link, not target
309
- cmd_s = cmd.join(" && ")
357
+ cmd_s = cmd.join("\n")
310
358
  end
311
359
 
312
360
  def install_script_from_string(aScriptString,aFilepath)
@@ -316,6 +364,10 @@ module CapUtils
316
364
  set_permissions(aFilepath,'root',user,750,false,true)
317
365
  end
318
366
 
367
+ def svn_command(aCommand)
368
+ run "#{sudo} svn --trust-server-cert --non-interactive --username #{svn_user} --password #{svn_password} #{aCommand}"
369
+ end
370
+
319
371
  end
320
372
 
321
373
  class CapUtilsClass
@@ -2,8 +2,6 @@
2
2
  # require 'buzzville/recipes' or
3
3
  # require 'buzzville/recipes/data'
4
4
 
5
- require 'ruby-debug'; debugger
6
-
7
5
  require 'capistrano'
8
6
  require 'capistrano/cli'
9
7
 
@@ -1,7 +1,6 @@
1
+ require_paths_first File.join(File.dirname(__FILE__),'../../../../yore/lib');
1
2
  require 'yore/yore_core'
2
3
 
3
- require 'ruby-debug'; debugger
4
-
5
4
  @@cap_config.load do
6
5
  # cap stage -Dapp=spree yore:pull:p_to_d
7
6
  # cap stage -Dapp=spree yore:push:d_to_p
@@ -19,22 +18,24 @@ require 'ruby-debug'; debugger
19
18
  end
20
19
 
21
20
  def pull_internal(aRemoteEnv,aLocalEnv)
22
- remote_app_path = ''
23
- local_app_path = ''
24
- remote_file = 'blah'
25
- local_file = 'something'
21
+ local_yore = YoreCore::Yore.launch(nil,{:kind => kind,:RAILS_ENV => aLocalEnv},{:basepath=>ENV['PWD']})
22
+ remote_app_path = File.join(cap.deploy_to,'current')
23
+ local_app_path = local_yore.config[:basepath]
24
+ filename = cap.unique_app_name+"-"+Time.now.strftime('%Y%m%d-%H%M%S')
25
+ remote_file = File.join("/tmp",filename)
26
+ local_file = File.join("/tmp",filename)
27
+
26
28
  # assume yore installed remotely
27
29
  cmd = "cd #{remote_app_path}; yore save"
28
30
  cmd += " --kind=#{kind}" if kind
29
31
  cmd += " --RAILS_ENV=#{aRemoteEnv} #{remote_file}"
30
32
  run cmd
31
33
  download(remote_file,local_file,:via => :scp)
32
- local_yore = YoreCore::Yore.launch(nil,{:kind => kind,:RAILS_ENV => aLocalEnv,:basepath=>ENV['PWD']})
33
34
  local_yore.load(local_file)
34
35
  end
35
36
 
36
37
  def push_internal(aLocalEnv,aRemoteEnv)
37
- local_yore = YoreCore::Yore.launch(nil,{:kind => kind,:RAILS_ENV => aLocalEnv,:basepath=>ENV['PWD']})
38
+ local_yore = YoreCore::Yore.launch(nil,{:kind => kind,:RAILS_ENV => aLocalEnv},{:basepath=>ENV['PWD']})
38
39
  remote_app_path = File.join(cap.deploy_to,'current')
39
40
  local_app_path = local_yore.config[:basepath]
40
41
  filename = cap.unique_app_name+"-"+Time.now.strftime('%Y%m%d-%H%M%S')
@@ -42,7 +43,6 @@ require 'ruby-debug'; debugger
42
43
  local_file = File.join("/tmp",filename)
43
44
  local_yore.save(local_file)
44
45
  upload(local_file,remote_file,:via => :scp)
45
-
46
46
  run "echo $PATH"
47
47
  # assume yore installed remotely
48
48
  cmd = "cd #{remote_app_path}; yore load"
@@ -60,6 +60,8 @@ require 'ruby-debug'; debugger
60
60
  end
61
61
  namespace :push do
62
62
  task :d2p do
63
+ extend CapUtils
64
+ files.apply_deploy_permissions
63
65
  push_internal('development','production')
64
66
  files.apply_permissions
65
67
  deploy.restart
@@ -9,10 +9,33 @@ require 'yore/yore_core'
9
9
  @@cap_config
10
10
  end
11
11
 
12
- def internal_permissions
13
- case kind
12
+ def internal_apply_deploy_permissions(aKind)
13
+ cap.extend CapUtils
14
+ case aKind
15
+ when 'rails' then
16
+ permissions_for_deploy(cap.user,cap.apache_user,cap.current_path)
17
+ uploads = cap.shared_path+'/uploads'
18
+ if remote_file_exists?(uploads)
19
+ permissions_for_web(uploads,cap.user,cap.apache_user,true)
20
+ permissions_for_web_writable(uploads)
21
+ end
22
+ #uploads_path = File.expand_path('../shared/uploads',current_path)
23
+ #mkdir_permissions(uploads_path,user,apache_user,770,true)
14
24
  when 'spree' then
15
-
25
+ internal_apply_deploy_permissions('rails')
26
+ when 'browsercms' then
27
+ internal_apply_deploy_permissions('rails')
28
+ end
29
+ end
30
+
31
+ task :apply_deploy_permissions do
32
+ internal_apply_deploy_permissions(cap.kind)
33
+ end
34
+
35
+ def internal_permissions(aKind)
36
+ cap.extend CapUtils
37
+ case aKind
38
+ when 'rails' then
16
39
  #puts "set permissions for dirs and files"
17
40
  #run_for_all("chmod 750",app_dir,:dirs)
18
41
  #run_for_all("chmod 640",app_dir,:files)
@@ -21,19 +44,35 @@ require 'yore/yore_core'
21
44
  #run_for_all("chmod 770","#{public_dir}/images",:dirs)
22
45
  #run_for_all("chmod 660","#{public_dir}/images",:files)
23
46
 
47
+
24
48
  run "#{sudo} chgrp -h #{cap.apache_user} #{cap.current_path}" # this is to change owner of link, not target
25
- run "#{sudo} chown -R #{cap.user}:#{cap.apache_user} #{cap.current_path}/" # the # is reqd to work for symlinks
26
- run "#{sudo} chmod -R g+w #{cap.current_path}/" # unfortunately necessary as capistrano forgets to do this later with sudo
27
- run "#{sudo} chown #{cap.apache_user}:#{cap.apache_user} #{cap.current_path}/config/environment.rb" # very important for passenger, which uses the owner of this file to run as
49
+
50
+ cap.permissions_for_web(cap.current_path,cap.user,cap.apache_user,true)
51
+
52
+ #run "#{sudo} chown -R #{cap.user}:#{cap.apache_user} #{cap.current_path}/" # the # is reqd to work for symlinks
53
+ #run "#{sudo} chmod -R g+w #{cap.current_path}/" # unfortunately necessary as capistrano forgets to do this later with sudo
54
+
55
+ uploads = cap.shared_path+'/uploads'
56
+ if remote_file_exists?(uploads)
57
+ permissions_for_web(uploads,cap.user,cap.apache_user,true)
58
+ permissions_for_web_writable(uploads)
59
+ end
60
+ #uploads_path = File.expand_path('../shared/uploads',current_path)
61
+ #mkdir_permissions(uploads_path,user,apache_user,770,true)
62
+
63
+ run "#{sudo} chown #{cap.user}:#{cap.user} #{cap.current_path}/config/environment.rb" # very important for passenger, which uses the owner of this file to run as
28
64
  run "#{sudo} touch #{cap.current_path}/log/production.log"
29
65
  run "#{sudo} chmod 666 #{cap.current_path}/log/production.log"
30
- when 'rails' then
31
- # dfdsf
66
+
67
+ when 'spree' then
68
+ internal_permissions('rails')
69
+ when 'browsercms' then
70
+ internal_permissions('rails')
32
71
  end
33
72
  end
34
73
 
35
74
  task :apply_permissions do
36
- internal_permissions
75
+ internal_permissions(cap.kind)
37
76
  end
38
77
 
39
78
  end
@@ -8,12 +8,16 @@
8
8
  @@cap_config
9
9
  end
10
10
 
11
+ #task :check_key do
12
+ # #sudo openssl rsa -check -noout -in ~/.ssh/iarts.ppk
13
+ #end
14
+
11
15
  # should set key_dir
12
16
  task :new_key do
13
17
  set :local_key_dir,File.expand_path('~/.ssh') unless (local_key_dir rescue false)
14
18
  if !(key_name rescue false)
15
19
  default_key_name = (new_user rescue nil) || 'new_key'
16
- key_name_response = Capistrano::CLI.ui.ask("Enter a name for the key without extension (default: #{default_key_name}):").strip!
20
+ key_name_response = Capistrano::CLI.ui.ask("Enter a name for the key without extension (default: #{default_key_name}):").strip
17
21
  set :key_name, (key_name_response.nil? || key_name_response.empty?) ? default_key_name : key_name_response
18
22
  end
19
23
  pub = File.join(local_key_dir,key_name+'.pub')
@@ -21,14 +25,14 @@
21
25
  raise StandardError.new("#{pub} or #{ppk} already exists") if File.exists?(pub) || File.exists?(ppk)
22
26
  passphrase = Capistrano::CLI.password_prompt("Enter a passphrase (>= 5 characters):")
23
27
  # create key - asks for name & passphrase and stores in ~/.ssh
24
- shell "ssh-keygen -N #{passphrase} -f #{File.join(local_key_dir,key_name)}"
28
+ shell "ssh-keygen #{passphrase.to_s.empty? ? '' : '-N '+passphrase} -f #{File.join(local_key_dir,key_name)}"
25
29
  shell "#{sudo} mv #{File.join(local_key_dir,key_name)} #{ppk}"
26
30
  shell "#{sudo} sed -i -e 's/== .*$/== #{key_name}.pub/' #{pub}"
27
31
  shell "#{sudo} chown $USER #{pub}"
28
32
  shell "#{sudo} chown $USER #{ppk}"
29
33
  Capistrano::CLI.ui.say "created #{pub}"
30
34
  Capistrano::CLI.ui.say "created #{ppk}"
31
- Capistrano::CLI.ui.say "Recommended: ssh-add #{ppk}"
35
+ Capistrano::CLI.ui.say "Recommended: ssh-add -k #{ppk}"
32
36
  end
33
37
 
34
38
  task :install_key do
@@ -57,7 +61,7 @@
57
61
  run "#{sudo} echo '' | #{sudo} tee -a #{auth_keys}" # new line
58
62
  run "#{sudo} cat #{File.join(admin_dir,key_name+'.pub')} | #{sudo} tee -a #{auth_keys}"
59
63
  run "#{sudo} rm #{File.join(admin_dir,key_name+'.pub')}"
60
- run "#{sudo} sudo /etc/init.d/ssh restart"
64
+ run "#{sudo} /etc/init.d/ssh restart"
61
65
  end
62
66
 
63
67
  # add to sshd_config AllowUsers if not already
@@ -75,11 +79,15 @@
75
79
 
76
80
  You probably need to append :
77
81
 
78
- Host #{host}
82
+ Host #{new_user}
79
83
  IdentityFile #{File.join(local_key_dir,key_name+'.ppk')}
80
84
  User #{new_user}
85
+ Hostname #{host}
81
86
  Port #{sessions.values.first.transport.peer[:port]}
82
-
87
+ TCPKeepAlive yes
88
+ IdentitiesOnly yes
89
+ PreferredAuthentications publickey
90
+
83
91
  to your ~/.ssh/config file
84
92
 
85
93
  and run locally :
@@ -88,7 +96,7 @@ and run locally :
88
96
 
89
97
  and then you should be able to :
90
98
 
91
- ssh #{new_user}@#{host}
99
+ ssh #{new_user}
92
100
 
93
101
  without entering a password and the passphrase will be in your keychain.
94
102
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: buzzville
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - buzzware
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-06 00:00:00 +08:00
12
+ date: 2010-01-28 00:00:00 +08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.2.5
23
+ version: 0.3.3
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: yore
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.0.5
33
+ version: 0.2.1
34
34
  version:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: cmdparse
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: 2.0.2
44
44
  version:
45
45
  - !ruby/object:Gem::Dependency
46
- name: thoughtbot-shoulda
46
+ name: shoulda
47
47
  type: :development
48
48
  version_requirement:
49
49
  version_requirements: !ruby/object:Gem::Requirement
@@ -68,6 +68,7 @@ files:
68
68
  - README.rdoc
69
69
  - Rakefile
70
70
  - VERSION
71
+ - buzzville.gemspec
71
72
  - buzzville.vpj
72
73
  - buzzville.vpw
73
74
  - lib/buzzville.rb