dokuen 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -44,6 +44,8 @@ Create a `git` user and install gitolite according to [package directions](http:
44
44
  Run this:
45
45
 
46
46
  ```
47
+ $ sudo mkdir -p /usr/local/var/dokuen
48
+ $ cd /usr/local/var/dokuen
47
49
  $ sudo dokuen setup
48
50
  ```
49
51
 
@@ -108,3 +110,4 @@ $ ssh git@<your_host> dokuen restart_app <name>
108
110
 
109
111
  Unfortunately the stock Heroku buildpacks install a vendored node.js compiled for the Heroku platform, which happens to be linux. This doesn't work for Mac, which means you have to use a slightly patched version. This one works with a homebrew-installed node.js: https://github.com/peterkeen/heroku-buildpack-ruby
110
112
 
113
+
data/bin/dokuen CHANGED
@@ -12,4 +12,4 @@ rescue LoadError => e
12
12
  require 'dokuen'
13
13
  end
14
14
 
15
- Dokuen::Cli.start()
15
+ Dokuen::CLI.start()
@@ -1,6 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
4
- require 'dokuen'
5
-
6
- Dokuen.sys("/usr/local/sbin/nginx -s reload")
3
+ system("/usr/local/sbin/nginx -s reload") or raise "Error restarting nginx!"
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ <%= @current_script %> $@ --config=<%= File.expand_path("dokuen.conf") %>
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ sudo -u <%= options[:appuser] %> <%= File.expand_path("./bin/dokuen") %> deploy --application=$APP --rev=$REV
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+
3
+ sudo -u <%= options[:appuser] %> <%= File.expand_path("./bin/dokuen") %> $@
4
+
@@ -0,0 +1,24 @@
1
+ ==== IMPORTANT INSTRUCTIONS ====
2
+
3
+ In your .gitolite.rc file, in the COMMANDS section, add the following:
4
+
5
+ 'dokuen' => 1
6
+
7
+ In your gitolite.conf file, add the following:
8
+
9
+ repo apps/[a-zA-Z0-9].*
10
+ C = @all
11
+ RW+ = CREATOR
12
+ config hooks.pre = "<%= File.expand_path('./bin/dokuen-deploy') %>"
13
+
14
+ In your nginx.conf, add the following to your http section:
15
+
16
+ include "<%= File.expand_path('nginx') %>/*.conf";
17
+
18
+ Run "sudo visudo" and add the following lines:
19
+
20
+ Runas_Alias APPUSERS = <%= options[:appuser] %>
21
+
22
+ git ALL=(APPUSERS) NOPASSWD: <%= File.expand_path('./bin/dokuen') %>
23
+ <%= options[:appuser] %> ALL=NOPASSWD: <%= @current_bin_path %>/dokuen_restart_nginx
24
+
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>info.bugpslat.dokuen</string>
7
+ <key>ProgramArguments</key>
8
+ <array>
9
+ <string><%= File.expand_path("./bin/dokuen") %></string>
10
+ <string>boot</string>
11
+ </array>
12
+ <key>RunAtLoad</key>
13
+ <true/>
14
+ <key>WorkingDirectory</key>
15
+ <string><%= File.expand_path(".") %></string>
16
+ <key>UserName</key>
17
+ <string<%= options[:appuser] %></string>
18
+ </dict>
19
+ </plist>
@@ -0,0 +1,22 @@
1
+ <% if ports.length > 0 %>
2
+ upstream <%= name %> {
3
+ <% ports.each do |port| %>
4
+ server localhost:<%= port %>;
5
+ <% end %>
6
+ }
7
+
8
+ server {
9
+ listen 80;
10
+ listen 443 default ssl;
11
+
12
+ server_name <%= name %>.<%= config.base_domain_name %>;
13
+
14
+ if ($ssl_protocol = "") {
15
+ rewrite ^ https://$server_name$request_uri? permanent;
16
+ }
17
+
18
+ location / {
19
+ proxy_pass http://<%= name %>;
20
+ }
21
+ }
22
+ <% end %>
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ hook = `git config hooks.pre`.chomp
3
+
4
+ rev = nil
5
+
6
+ STDIN.each do |line|
7
+ parts = line.split(/\s/)
8
+ next if parts[2] != "refs/heads/master"
9
+ rev = parts[1]
10
+ end
11
+
12
+ if hook != ""
13
+ name = File.basename(ENV['GL_REPO']).gsub(".git", '')
14
+ cmd = "env REV=#{rev} APP=#{name} #{hook}"
15
+ system(cmd) or raise "Error running pre-hook: #{cmd} returned #{$?}"
16
+ end
@@ -0,0 +1,5 @@
1
+ start at startup
2
+
3
+ task
4
+
5
+ exec sudo -u <%= options[:appuser] %> <%= File.expand_path("./bin/dokuen") %> boot
@@ -0,0 +1,279 @@
1
+ require 'tmpdir'
2
+ require 'fileutils'
3
+ require 'time'
4
+
5
+ class Dokuen::Application
6
+
7
+ attr_reader :name, :config
8
+
9
+ def initialize(name, config)
10
+ @name = name
11
+ @config = config
12
+ end
13
+
14
+ def get_env(var)
15
+ with_app_dir do
16
+ env_path = File.join("env", var)
17
+ if File.exists?(env_path)
18
+ return File.read(env_path)
19
+ else
20
+ return nil
21
+ end
22
+ end
23
+ end
24
+
25
+ def set_env(var, val)
26
+ with_app_dir do
27
+ env_path = File.join("env", var)
28
+ File.open(env_path, "w+") do |f|
29
+ f.write(val)
30
+ end
31
+ end
32
+ end
33
+
34
+ def delete_env(var)
35
+ with_app_dir do
36
+ File.delete(File.join("env", var)) rescue nil
37
+ end
38
+ end
39
+
40
+ def env
41
+ vars = read_env_dir("#{config.dokuen_dir}/env")
42
+ with_app_dir do
43
+ vars.merge!(read_env_dir('env'))
44
+ with_current_release do
45
+ File.open(".env") do |f|
46
+ f.lines.each do |line|
47
+ key, val = line.split('=', 2)
48
+ vars[key] = val.chomp
49
+ end
50
+ end
51
+ end
52
+ end
53
+ vars
54
+ end
55
+
56
+ def create
57
+ Dir.chdir(File.join(config.dokuen_dir, 'apps')) do
58
+ if File.exists?(name)
59
+ raise "Application #{name} exists!"
60
+ end
61
+
62
+ FileUtils.mkdir_p(name)
63
+ with_app_dir do
64
+ dirs = [
65
+ 'releases',
66
+ 'env',
67
+ 'logs',
68
+ 'build'
69
+ ]
70
+ FileUtils.mkdir_p(dirs)
71
+ end
72
+ end
73
+ end
74
+
75
+ def deploy(revision)
76
+ git_dir = Dir.getwd
77
+
78
+ with_app_dir do
79
+ now = Time.now().utc().strftime("%Y%m%dT%H%M%S")
80
+ release_dir = "releases/#{now}"
81
+ clone_dir = clone(git_dir, revision)
82
+
83
+ buildpack = get_env('BUILDPACK_URL')
84
+ if buildpack
85
+ buildpack = "-b #{buildpack}"
86
+ else
87
+ buildpack = ''
88
+ end
89
+
90
+ sys("mason build #{clone_dir} #{buildpack} -o #{release_dir} -c build")
91
+ Dir.mkdir("#{release_dir}/.dokuen")
92
+
93
+ hook = get_env('DOKUEN_AFTER_BUILD')
94
+ if hook
95
+ sys("foreman run #{hook}")
96
+ end
97
+
98
+ if File.symlink?("previous")
99
+ File.unlink("previous")
100
+ File.symlink(File.readlink("current"), "previous")
101
+ end
102
+
103
+ if File.symlink?("current")
104
+ File.unlink("current")
105
+ end
106
+
107
+ File.symlink(File.expand_path(release_dir), "current")
108
+ end
109
+
110
+ scale
111
+ if File.symlink?("previous")
112
+ shutdown(File.readlink("previous"))
113
+ end
114
+ end
115
+
116
+ def scale
117
+ puts "Scaling..."
118
+ with_current_release do
119
+ processes = running_processes
120
+ running_count_by_name = {}
121
+
122
+ processes.each do |proc, pidfile|
123
+ proc_name = proc.split('.')[0]
124
+ running_count_by_name[proc_name] ||= 0
125
+ running_count_by_name[proc_name] += 1
126
+ end
127
+
128
+ desired_count_by_name = {}
129
+ scale_spec = get_env('DOKUEN_SCALE')
130
+ if scale_spec
131
+ scale_spec.split(',').each do |spec|
132
+ proc_name, count = spec.split('=')
133
+ desired_count_by_name[proc_name] = count.to_i
134
+ end
135
+ end
136
+
137
+ to_start = []
138
+ to_stop = []
139
+
140
+ desired_count_by_name.each do |proc_name, count|
141
+ running = running_count_by_name[proc_name] || 0
142
+ if running < count
143
+ (count - running).times do |i|
144
+ index = running + i + 1
145
+ to_start << [proc_name, index]
146
+ end
147
+ elsif running > count
148
+ (running - count).times do |i|
149
+ index = count + i + 1
150
+ to_stop << [proc_name, index]
151
+ end
152
+ end
153
+ end
154
+
155
+ running_count_by_name.each do |proc_name, count|
156
+ if not desired_count_by_name.has_key?(proc_name)
157
+ count.times do |i|
158
+ to_stop << [proc_name, i]
159
+ end
160
+ end
161
+ end
162
+
163
+ to_start.each do |proc_name, index|
164
+ port = reserve_port
165
+ fork do
166
+ Dokuen::Wrapper.new(self, proc_name, index, File.join(config.dokuen_dir, 'ports', port.to_s)).run!
167
+ end
168
+ end
169
+
170
+ to_stop.each do |proc_name, index|
171
+ pid_file = processes["#{proc_name}.#{index}"]
172
+ pid = YAML.load(File.read(pid_file))['pid']
173
+ Process.kill("TERM", pid)
174
+ end
175
+ install_nginx_config
176
+ end
177
+ end
178
+
179
+ def shutdown(release)
180
+ with_release_dir(release) do
181
+ running_processes.each do |proc, pidfile|
182
+ pid = File.read(pidfile).to_i rescue nil
183
+ if pid
184
+ Process.kill("TERM", pid)
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ def install_nginx_config
191
+ puts "Installing nginx config"
192
+ sleep 2
193
+ @ssl_on = get_env('USE_SSL') ? 'on' : 'off'
194
+ @listen_port = get_env('USE_SSL') ? 443 : 80
195
+ conf = Dokuen.template('nginx', binding)
196
+ File.open(File.join(config.dokuen_dir, "nginx", "#{name}.#{config.base_domain_name}.conf"), "w+") do |f|
197
+ f.write(conf)
198
+ end
199
+
200
+ sys("sudo #{config.bin_path}/dokuen_restart_nginx")
201
+ end
202
+
203
+ private
204
+
205
+ def clone(git_dir, revision)
206
+ dir = Dir.mktmpdir
207
+ Dir.chdir(dir) do
208
+ sys("git clone #{git_dir} .")
209
+ sys("git checkout -q #{revision}")
210
+ end
211
+ dir
212
+ end
213
+
214
+ def sys(command)
215
+ system(command) or raise "Error running command: #{command}"
216
+ end
217
+
218
+ def with_app_dir
219
+ Dir.chdir(File.join(config.dokuen_dir, 'apps', name)) do
220
+ yield
221
+ end
222
+ end
223
+
224
+ def with_release_dir(release)
225
+ Dir.chdir(release) do
226
+ yield
227
+ end
228
+ end
229
+
230
+ def with_current_release
231
+ with_app_dir do
232
+ if File.symlink?("current")
233
+ with_release_dir(File.readlink("current")) do
234
+ yield
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ def running_processes
241
+ procs = {}
242
+ Dir.glob(".dokuen/*.pid").map do |pidfile|
243
+ proc_name = File.basename(pidfile).gsub('.pid', '')
244
+ proc_name = proc_name.gsub("dokuen.#{name}.", '')
245
+ procs[proc_name] = pidfile
246
+ end
247
+ procs
248
+ end
249
+
250
+ def read_env_dir(dir)
251
+ vars = {}
252
+ Dir.glob("#{dir}/*").each do |key|
253
+ vars[File.basename(key)] = File.read(key).chomp
254
+ end
255
+ vars
256
+ end
257
+
258
+ def reserve_port
259
+ ports_dir = config.dokuen_dir + "/ports"
260
+ port_range = config.max_port - config.min_port
261
+ 1000.times do
262
+ port = rand(port_range) + config.min_port
263
+ path = File.join(ports_dir, port.to_s)
264
+ if not File.exists?(path)
265
+ FileUtils.touch(path)
266
+ return port
267
+ end
268
+ end
269
+ raise "Could not find free port!"
270
+ end
271
+
272
+ def ports
273
+ _ports = []
274
+ running_processes.each do |proc_name, pidfile|
275
+ _ports << YAML.load(File.read(pidfile))['port']
276
+ end
277
+ _ports
278
+ end
279
+ end
data/lib/dokuen/cli.rb CHANGED
@@ -1,226 +1,163 @@
1
- require 'rubygems'
2
- require 'thor'
3
- require 'fileutils'
1
+ require "thor"
2
+ require "thor/shell/basic"
3
+ require "yaml"
4
4
 
5
- module Dokuen
6
- class Cli < Thor
7
- include Thor::Actions
8
-
9
- desc "setup", "Set up relevant things. Needs to be run as sudo."
10
- def setup
11
- raise "Must be run as root" unless Process.uid == 0
12
- git_username = ask("What is your git username (usually git)?")
13
- gitolite_src = ask("What is the path to your gitolite clone?")
14
- say("Installing gitolite command")
15
- create_file "#{gitolite_src}/src/commands/dokuen", <<HERE
16
- #!/bin/sh
17
- /usr/local/bin/dokuen $@
18
- HERE
19
- File.chmod(0755, "#{gitolite_src}/src/commands/dokuen")
20
- say("Installing gitolite hook")
21
- hook_path = "/Users/#{git_username}/.gitolite/hooks/common/pre-receive"
22
- create_file(hook_path, <<'HERE'
23
- #!/usr/bin/env ruby
24
- hook = `git config hooks.pre`.chomp
25
-
26
- rev = nil
27
-
28
- STDIN.each do |line|
29
- parts = line.split(/\s/)
30
- next if parts[2] != "refs/heads/master"
31
- rev = parts[1]
32
- end
33
-
34
- if hook != ""
35
- name = File.basename(ENV['GL_REPO'])
36
- cmd = "#{hook} #{name} #{rev}"
37
- system(cmd) or raise "Error running pre-hook: #{cmd} returned #{$?}"
38
- end
39
- HERE
40
- )
41
- File.chmod(0755, hook_path)
42
- say("Creating directories")
43
- dirs = [
44
- '/usr/local/var/dokuen/env',
45
- '/usr/local/var/dokuen/env/_common',
46
- '/usr/local/var/dokuen/log',
47
- '/usr/local/var/dokuen/nginx',
48
- '/usr/local/var/dokuen/build',
49
- '/usr/local/var/dokuen/release',
50
- ]
51
- FileUtils.mkdir_p(dirs, :mode => 0775)
52
- FileUtils.chown(git_username, 'staff', dirs)
53
-
54
- if yes?("Do you want to set up DNS?")
55
- basename = ask("What is your DNS base domain? For example, if have a wildcard CNAME for *.example.com, this would be example.com.")
56
- create_file("/usr/local/var/dokuen/env/_common/BASE_DOMAIN", basename)
57
- end
58
- git_hostname = `hostname`.chomp
59
- if no?("Is #{git_hostname} the correct hostname for git remotes?")
60
- git_hostname = ask("What hostname should we use instead?")
61
- end
62
- create_file("/usr/local/var/dokuen/env/_common/DOKUEN_GIT_SERVER", git_hostname)
63
- create_file("/usr/local/var/dokuen/env/_common/DOKUEN_GIT_USER", git_username)
64
-
65
- say(<<HERE
66
-
67
- ==== IMPORTANT INSTRUCTIONS ====
68
-
69
- In your .gitolite.rc file, in the COMMANDS section, add the following:
70
-
71
- 'dokuen' => 1
5
+ class Dokuen::CLI < Thor
72
6
 
73
- In your gitolite.conf file, add the following:
7
+ include Thor::Actions
74
8
 
75
- repo apps/[a-zA-Z0-9].*
76
- C = @all
77
- RW+ = CREATOR
78
- config hooks.pre = "/usr/local/bin/dokuen deploy"
9
+ def initialize(*args)
10
+ super(*args)
79
11
 
80
- In your nginx.conf, add the following to your http section:
12
+ if not options[:config].nil?
13
+ @config = Dokuen::Config.new(options[:config])
14
+ end
15
+ end
81
16
 
82
- include "/usr/local/var/dokuen/nginx/*.conf";
17
+ class_option :application, :type => :string, :desc => "Application name"
18
+ class_option :config, :type => :string, :desc => "Configuration file"
19
+ class_option :debug, :type => :boolean, :desc => "Show backtraces"
20
+
21
+ desc "setup DIR", "set up dokuen in the given directory"
22
+ method_option :gituser, :desc => "Username of git user", :default => 'git'
23
+ method_option :gitgroup, :desc => "Group of git user", :default => 'staff'
24
+ method_option :appuser, :desc => "Username of app user", :default => 'dokuen'
25
+ method_option :gitolite, :desc => "Path to gitolite directory", :default => 'GITUSER_HOME/gitolite'
26
+ method_option :platform, :desc => "Which platform to install. Can be 'mac', 'ubuntu', or 'centos'", :default => 'mac'
27
+ def setup(dir)
28
+
29
+ @current_script = File.expand_path($0)
30
+ @current_bin_path = File.dirname(@current_script)
31
+
32
+ Dir.chdir(dir) do
33
+ setup_dirs
34
+ setup_bin
35
+ setup_gitolite
36
+ write_config
37
+ install_boot_script
38
+ puts Dokuen.template('instructions', binding)
39
+ end
40
+ end
83
41
 
84
- Run "sudo visudo" and add the following line:
42
+ desc "create", "create application"
43
+ def create
44
+ Dokuen::Application.new(options[:application], @config).create
45
+ puts "git remote add dokuen #{@config.git_user}@#{@config.git_server}:apps/#{options[:application]}.git"
46
+ end
85
47
 
86
- git ALL=NOPASSWD: /usr/local/bin/dokuen_install_launchdaemon, /usr/local/bin/dokuen_restart_nginx
48
+ desc "deploy", "deploy application", :hide => true
49
+ method_option :rev, :desc => "Revision to deploy"
50
+ def deploy
51
+ ENV['GIT_DIR'] = nil
52
+ ENV['PATH'] = "#{@config.bin_path}:#{ENV['PATH']}"
53
+ ENV.each do |k,v|
54
+ puts "#{k}=#{v}"
55
+ end
56
+ Dokuen::Application.new(options[:application], @config).deploy(options[:rev])
57
+ end
87
58
 
88
- HERE
89
- )
59
+ desc "scale SCALE_SPEC", "scale to the given spec"
60
+ def scale(spec)
61
+ app = Dokuen::Application.new(options[:application], @config)
62
+ app.set_env('DOKUEN_SCALE', spec)
63
+ app.scale
64
+ end
90
65
 
66
+ desc "config", "show the config for the given app"
67
+ def config
68
+ app = Dokuen::Application.new(options[:application], @config)
69
+ app.env.each do |key, val|
70
+ puts "#{key}=#{val}"
91
71
  end
72
+ end
92
73
 
93
- desc "create [APP]", "Create a new application."
94
- def create(app="")
95
- raise "app name required" if app.nil? || app == ""
96
- read_env(app)
97
- FileUtils.mkdir_p(Dokuen.dir("env", app))
98
- FileUtils.mkdir_p(Dokuen.dir("release", app))
99
- FileUtils.mkdir_p(Dokuen.dir("build", app))
100
- puts "Created new application named #{app}"
101
- puts "Git remote: #{Dokuen.base_clone_url}:apps/#{app}.git"
74
+ desc "config_set VARS", "set some config variables"
75
+ def config_set(*vars)
76
+ app = Dokuen::Application.new(options[:application], @config)
77
+ vars.each do |var|
78
+ key, val = var.chomp.split('=', 2)
79
+ app.set_env(key, val)
102
80
  end
81
+ end
103
82
 
104
- desc "restart_app [APP]", "Restart an existing app"
105
- def restart_app(app="")
106
- check_app(app)
107
- read_env(app)
108
- deploy = Dokuen::Deploy.new(app, '', ENV['DOKUEN_RELEASE_DIR'])
109
- deploy.install_launch_daemon
110
- deploy.install_nginx_conf
111
- puts "App restarted"
83
+ desc "config_delete VARS", "delete some config variables"
84
+ def config_delete(*vars)
85
+ app = Dokuen::Application.new(options[:application], @config)
86
+ vars.each do |var|
87
+ app.delete_env(var)
112
88
  end
89
+ end
113
90
 
114
- desc "start_app [APP]", "Start an app", :hide => true
115
- def start_app(app="")
116
- check_app(app)
117
- read_env(app)
118
- ENV['PATH'] = "/usr/local/bin:#{ENV['PATH']}"
119
- Dir.chdir(ENV['DOKUEN_RELEASE_DIR']) do
120
- base_port = ENV['PORT'].to_i - 200
121
- scale = ENV['DOKUEN_SCALE'].nil? ? "" : "-c #{ENV['DOKUEN_SCALE']}"
122
- Dokuen.sys("foreman start #{scale} -p #{base_port}")
123
- end
91
+ desc "boot", "Scale all of the current applications"
92
+ def boot
93
+ Dir.glob("#{@config.dokuen_dir}/apps/*") do |appdir|
94
+ next if File.basename(appdir)[0] == '.'
95
+ app = Dokuen::Application.new(File.basename(appdir), @config)
96
+ app.scale
124
97
  end
98
+ end
125
99
 
126
- desc "scale [APP] [SCALE_SPEC]", "Scale an app to the given spec"
127
- def scale(app="", scale_spec="")
128
- check_app(app)
129
- raise "scale spec required" if scale_spec == ""
130
- read_env(app)
131
- Dokuen.set_env(app, 'DOKUEN_SCALE', scale_spec)
132
- restart_app(app)
133
- puts "Scaled to #{scale_spec}"
134
- end
100
+ private
101
+
102
+ def setup_dirs
103
+ dirs = [
104
+ 'apps',
105
+ 'env',
106
+ 'ports',
107
+ 'nginx',
108
+ 'bin'
109
+ ]
135
110
 
136
- desc "deploy [APP] [REV]", "Deploy an app for a given revision. Run within git pre-receive.", :hide => true
137
- def deploy(app="", rev="")
138
- check_app(app)
139
- read_env(app)
140
- Dokuen::Deploy.new(app, rev).run
141
- puts "App #{app} deployed"
111
+ dirs.each do |dir|
112
+ empty_directory(File.join(Dir.getwd, dir))
142
113
  end
143
114
 
144
- desc "run_command [APP]", "Run a command in the given app's environment"
145
- method_option :command, :aliases => '-C', :desc => "Command to run"
146
- def run_command(app="")
147
- check_app(app)
148
- read_env(app)
115
+ FileUtils.chown(options[:gituser], options[:gitgroup], dirs)
116
+ FileUtils.chmod(0777, ['apps', 'ports', 'nginx'])
117
+ end
149
118
 
150
- ENV['PATH'] = "/usr/local/bin:#{ENV['PATH']}"
151
- Dir.chdir(ENV['DOKUEN_RELEASE_DIR']) do
152
- Dokuen.sys("foreman run #{options[:command]}")
153
- end
154
- end
119
+ def setup_bin
120
+ @script_path = File.expand_path("bin/dokuen")
121
+ @deploy_script_path = File.expand_path("bin/dokuen-deploy")
122
+ write_template(@script_path, "bin_command", 0755)
123
+ write_template(@deploy_script_path, "deploy_command", 0755)
124
+ end
155
125
 
156
- desc "config [APP] [set/delete]", "Add or remove config variables"
157
- method_option :vars, :aliases => '-V', :desc => "Variables to set or remove", :type => :array
158
- def config(app="", subcommand="")
159
- check_app(app)
160
- case subcommand
161
- when "set"
162
- set_vars(app)
163
- restart_app(app)
164
- when "delete"
165
- delete_vars(app)
166
- restart_app(app)
167
- else
168
- show_vars(app)
169
- end
170
- end
126
+ def setup_gitolite
127
+ githome = File.expand_path("~#{options[:gituser]}")
128
+ gitolite = options[:gitolite].gsub('GITUSER_HOME', githome)
171
129
 
172
- desc "install_buildpack [URL]", "Add a buildpack to the mason config"
173
- def install_buildpack(url="")
174
- raise "URL required" unless url != ""
175
- Dokuen.sys("/usr/local/bin/mason buildpacks:install #{url}")
176
- end
130
+ write_template("#{gitolite}/src/commands/dokuen", 'gitolite_command', 0755)
131
+ write_template("#{githome}/.gitolite/hooks/common/pre-receive", 'pre_receive_hook', 0755)
132
+ end
177
133
 
178
- desc "remove_buildpack [NAME]", "Remove a buildpack from the mason config"
179
- def remove_buildpack(name)
180
- raise "Name required" unless name != ""
181
- Dokuen.sys("/usr/local/bin/mason buildpacks:uninstall #{name}")
134
+ def write_config
135
+ config = {
136
+ 'base_domain_name' => 'dokuen',
137
+ 'git_server' => `hostname`.chomp,
138
+ 'git_user' => options[:gituser],
139
+ 'app_user' => options[:appuser],
140
+ 'min_port' => 5000,
141
+ 'max_port' => 6000,
142
+ 'bin_path' => @current_bin_path,
143
+ 'dokuen_dir' => File.expand_path('.')
144
+ }
145
+
146
+ File.open("./dokuen.conf", 'w+') do |f|
147
+ YAML.dump(config, f)
182
148
  end
149
+
150
+ end
183
151
 
184
- desc "buildpacks", "List the available buildpacks"
185
- def buildpacks
186
- Dokuen.sys("/usr/local/bin/mason buildpacks")
187
- end
152
+ def write_template(filename, template, mode=0644)
153
+ t = Dokuen.template(template, binding)
154
+ create_file(filename, t)
155
+ File.chmod(mode, filename)
156
+ end
188
157
 
189
- no_tasks do
190
-
191
- def set_vars(app)
192
- vars = options[:vars]
193
- vars.each do |var|
194
- key, val = var.split(/\=/)
195
- Dokuen.set_env(app, key, val)
196
- end
197
- puts "Vars set"
198
- end
199
-
200
- def delete_vars(app)
201
- vars = options[:vars]
202
-
203
- vars.each do |var|
204
- Dokuen.rm_env(app, var)
205
- end
206
- puts "Vars removed"
207
- end
208
-
209
- def show_vars(app)
210
- read_env(app)
211
- ENV.each do |key, val|
212
- puts "#{key}=#{val}"
213
- end
214
- end
215
-
216
- def read_env(app)
217
- Dokuen.read_env("_common")
218
- Dokuen.read_env(app)
219
- end
220
-
221
- def check_app(app)
222
- Dokuen.app_exists?(app) or raise "App '#{app}' does not exist!"
223
- end
224
- end
158
+ def install_boot_script
159
+ filename, template_name = Dokuen::Platform.boot_script(options[:platform])
160
+ write_template(filename, template_name)
225
161
  end
162
+
226
163
  end
@@ -0,0 +1,23 @@
1
+ require "yaml"
2
+
3
+ class Dokuen::Config
4
+ def initialize(path)
5
+ @path = path
6
+ @config = {}
7
+ read_config
8
+ end
9
+
10
+ def read_config
11
+ @config = YAML.load(File.read(@path))
12
+ end
13
+
14
+ def method_missing(m, *args, &block)
15
+ str_meth = m.to_s
16
+ if @config.has_key? str_meth
17
+ @config[str_meth]
18
+ else
19
+ super
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,16 @@
1
+ module Dokuen
2
+ module Platform
3
+
4
+ def self.boot_script(platform)
5
+ case platform
6
+ when 'mac'
7
+ return "/Library/LaunchDaemons/dokuen.plist", "mac_launchdaemon"
8
+ when 'ubuntu'
9
+ return "/etc/init/dokuen", "ubuntu_upstart"
10
+ else
11
+ raise "Unknow platform: #{platform}"
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -1,3 +1,3 @@
1
1
  module Dokuen
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
@@ -0,0 +1,127 @@
1
+ require 'foreman'
2
+ require 'foreman/procfile'
3
+ require 'foreman/process'
4
+
5
+ class Dokuen::Wrapper
6
+
7
+ attr_reader :release_dir, :app, :proc, :index, :portfile
8
+
9
+ def initialize(app, proc, index, portfile)
10
+ @release_dir = Dir.getwd
11
+ @app = app
12
+ @proc = proc
13
+ @index = index
14
+ @portfile = portfile
15
+ end
16
+
17
+ def run!
18
+ return if daemonize! == nil
19
+ load_env
20
+ loop do
21
+ run_loop
22
+ end
23
+ end
24
+
25
+ def procname
26
+ "dokuen.#{app.name}.#{proc}.#{index}"
27
+ end
28
+
29
+ def procdir
30
+ File.join(release_dir, '.dokuen')
31
+ end
32
+
33
+ def pidfile
34
+ File.join(procdir, "#{procname}.pid")
35
+ end
36
+
37
+ def outfile
38
+ File.join(procdir, "#{procname}.out")
39
+ end
40
+
41
+ def errfile
42
+ File.join(procdir, "#{procname}.err")
43
+ end
44
+
45
+ def port
46
+ File.basename(portfile).to_i
47
+ end
48
+
49
+ def daemonize!
50
+ pf = File.join(pidfile)
51
+ raise "Process already running!" if File.exists?(pf)
52
+
53
+ return unless do_fork.nil?
54
+ write_pidfile
55
+ set_procname
56
+ redirect
57
+ end
58
+
59
+ def redirect
60
+ $stdin.reopen("/dev/null")
61
+ $stdout.sync = $stderr.sync = true
62
+ $stdout.reopen(File.new(outfile, "a"))
63
+ $stderr.reopen(File.new(errfile, "a"))
64
+ end
65
+
66
+ def write_pidfile
67
+ File.open(pidfile, "w+") do |f|
68
+ f.write(YAML.dump({'pid' => Process.pid, 'port' => port}))
69
+ end
70
+ end
71
+
72
+ def set_procname
73
+ $0 = procname
74
+ end
75
+
76
+ def do_fork
77
+ raise 'first fork failed' if (pid = fork) == -1
78
+ exit unless pid.nil?
79
+ Process.setsid
80
+ raise 'second fork failed' if (pid = fork) == -1
81
+ exit unless pid.nil?
82
+ end
83
+
84
+ def load_env
85
+ app.env.each do |key, val|
86
+ ENV[key] = val
87
+ end
88
+ end
89
+
90
+ def run_loop
91
+ reader, writer = (IO.method(:pipe).arity == 0 ? IO.pipe : IO.pipe("BINARY"))
92
+ procfile = Foreman::Procfile.new("Procfile")
93
+ entry = procfile[proc]
94
+ process = Foreman::Process.new(entry, index.to_i, port.to_i)
95
+ log_path = "../../logs/#{procname}"
96
+ log_file = File.open(log_path, 'a')
97
+
98
+ Signal.trap("USR2") do
99
+ process.kill("TERM")
100
+ end
101
+
102
+ Signal.trap("TERM") do
103
+ if not process.kill(9)
104
+ raise "Failed to kill process #{process.pid}"
105
+ end
106
+ File.delete(pidfile)
107
+ File.delete("../../../../ports/#{port}")
108
+ exit! 0
109
+ end
110
+
111
+ process.run(writer, release_dir, {})
112
+ thread = Thread.new do
113
+ loop do
114
+ data = reader.gets
115
+ next unless data
116
+ ps, message = data.split(",", 2)
117
+ log_file.puts("[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}][#{ps}] #{message}")
118
+ log_file.flush
119
+ end
120
+ end
121
+ Process.wait(process.pid)
122
+ thread.exit
123
+ reader.close
124
+ writer.close
125
+ end
126
+
127
+ end
data/lib/dokuen.rb CHANGED
@@ -1,46 +1,19 @@
1
- require 'dokuen/cli'
2
- require 'dokuen/deploy'
1
+ require "erb"
3
2
 
4
3
  module Dokuen
5
- def self.dir(name, app=nil)
6
- parts = ["/usr/local/var/dokuen", name]
7
-
8
- if not app.nil?
9
- parts << app
10
- end
11
-
12
- File.join(parts)
13
- end
14
-
15
- def self.sys(command)
16
- system(command) or raise "Error running #{command}"
17
- end
18
-
19
- def self.read_env(name)
20
- env_dir = Dokuen.dir('env', name)
21
- Dir.glob("#{env_dir}/*") do |var|
22
- var_name = File.basename(var)
23
- ENV[var_name] = File.open(var).read().chomp()
24
- end
25
- end
26
-
27
- def self.set_env(name, key, value)
28
- env_dir = Dokuen.dir('env', name)
29
- File.open(File.join(env_dir, key), "w+") do |f|
30
- f.write value
4
+ def self.template(name, bind)
5
+ path = File.expand_path("../../data/templates/#{name}.erb", __FILE__)
6
+ if File.exists?(path)
7
+ t = ERB.new(File.read(path))
8
+ t.result(bind)
9
+ else
10
+ raise "Unknown template: #{name}"
31
11
  end
32
12
  end
33
-
34
- def self.rm_env(name, key)
35
- env_dir = Dokuen.dir('env', name)
36
- File.delete(File.join(env_dir, key))
37
- end
38
-
39
- def self.base_clone_url
40
- "#{ENV['DOKUEN_GIT_USER']}@#{ENV['DOKUEN_GIT_SERVER']}"
41
- end
42
-
43
- def self.app_exists?(name)
44
- File.exists?(Dokuen.dir('env', name))
45
- end
46
13
  end
14
+
15
+ require "dokuen/cli"
16
+ require "dokuen/config"
17
+ require "dokuen/application"
18
+ require "dokuen/wrapper"
19
+ require "dokuen/platform"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dokuen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-21 00:00:00.000000000Z
12
+ date: 2012-05-29 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -91,11 +91,22 @@ files:
91
91
  - bin/dokuen
92
92
  - bin/dokuen_install_launchdaemon
93
93
  - bin/dokuen_restart_nginx
94
+ - data/templates/bin_command.erb
95
+ - data/templates/deploy_command.erb
96
+ - data/templates/gitolite_command.erb
97
+ - data/templates/instructions.erb
98
+ - data/templates/mac_launchdaemon.erb
99
+ - data/templates/nginx.erb
100
+ - data/templates/pre_receive_hook.erb
101
+ - data/templates/ubuntu_upstart.erb
94
102
  - dokuen.gemspec
95
103
  - lib/dokuen.rb
104
+ - lib/dokuen/application.rb
96
105
  - lib/dokuen/cli.rb
97
- - lib/dokuen/deploy.rb
106
+ - lib/dokuen/config.rb
107
+ - lib/dokuen/platform.rb
98
108
  - lib/dokuen/version.rb
109
+ - lib/dokuen/wrapper.rb
99
110
  homepage: https://github.com/peterkeen/dokuen
100
111
  licenses: []
101
112
  post_install_message:
data/lib/dokuen/deploy.rb DELETED
@@ -1,173 +0,0 @@
1
- require 'tmpdir'
2
- require 'fileutils'
3
- require 'time'
4
- require 'erb'
5
-
6
- module Dokuen
7
- class Deploy
8
-
9
- def initialize(app, rev, release_dir=nil)
10
- @app = app || app_name_from_env
11
- @rev = rev
12
- ENV['GIT_DIR'] = nil
13
- ENV['PATH'] = "/usr/local/bin:#{ENV['PATH']}"
14
- if release_dir
15
- @release_dir = release_dir
16
- end
17
- end
18
-
19
- def run
20
- read_app_env
21
- make_dirs
22
- clone
23
- build
24
- install_launch_daemon
25
- install_nginx_conf
26
- end
27
-
28
- def read_app_env
29
- Dokuen.read_env('_common')
30
- Dokuen.read_env(@app)
31
- end
32
-
33
- def make_dirs
34
- FileUtils.mkdir_p([
35
- env_dir,
36
- release_dir,
37
- cache_dir,
38
- nginx_dir
39
- ])
40
- end
41
-
42
- def clone
43
- Dokuen.sys("git clone #{git_dir} #{clone_dir}")
44
- Dir.chdir(clone_dir)
45
- Dokuen.sys("git checkout #{@rev}")
46
- end
47
-
48
- def build
49
- Dokuen.sys("mason build #{clone_dir} #{buildpack} -o #{release_dir} -c #{cache_dir}")
50
- Dokuen.sys("chmod -R a+r #{release_dir}")
51
- Dokuen.sys("find #{release_dir} -type d -exec chmod a+x {} \\;")
52
- Dokuen.set_env(@app, 'DOKUEN_RELEASE_DIR', release_dir)
53
-
54
- hook = ENV['DOKUEN_AFTER_BUILD']
55
- if not hook.nil?
56
- Dir.chdir release_dir do
57
- Dokuen.sys("foreman run #{hook}")
58
- end
59
- end
60
- end
61
-
62
- def buildpack
63
- if not ENV['BUILDPACK_URL'].nil?
64
- "-b #{ENV['BUILDPACK_URL']}"
65
- else
66
- ""
67
- end
68
- end
69
-
70
- def install_launch_daemon
71
- t = ERB.new(launch_daemon_template)
72
- plist_path = File.join(release_dir, "dokuen.#{@app}.plist")
73
- File.open(plist_path, "w+") do |f|
74
- f.write(t.result(binding))
75
- end
76
- Dokuen.sys("sudo /usr/local/bin/dokuen_install_launchdaemon #{plist_path}")
77
- end
78
-
79
- def install_nginx_conf
80
- t = ERB.new(nginx_template)
81
- File.open(File.join(nginx_dir, "#{@app}.#{base_domain}.conf"), "w+") do |f|
82
- f.write(t.result(binding))
83
- end
84
- Dokuen.sys("sudo /usr/local/bin/dokuen_restart_nginx")
85
- end
86
-
87
- def base_domain
88
- ENV['BASE_DOMAIN'] || 'dokuen'
89
- end
90
-
91
- def app_name_from_env
92
- File.basename(ENV['GL_REPO']).gsub(/\.git$/, '')
93
- end
94
-
95
- def env_dir
96
- Dokuen.dir("env", @app)
97
- end
98
-
99
- def clone_dir
100
- @clone_dir ||= Dir.mktmpdir
101
- end
102
-
103
- def git_dir
104
- @git_dir || Dir.getwd
105
- end
106
-
107
- def release_dir
108
- @now = Time.now().utc().strftime("%Y%m%dT%H%M%S")
109
- @release_dir ||= File.join(Dokuen.dir('release', @app), @now)
110
- end
111
-
112
- def cache_dir
113
- Dokuen.dir('build', @app)
114
- end
115
-
116
- def nginx_dir
117
- Dokuen.dir('nginx')
118
- end
119
-
120
- def server_port
121
- ENV['USE_SSL'] ? "443" : "80"
122
- end
123
-
124
- def ssl_on
125
- ENV['USE_SSL'] ? "on" : "off"
126
- end
127
-
128
- def launch_daemon_template
129
- <<HERE
130
- <?xml version="1.0" encoding="UTF-8"?>
131
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
132
- <plist version="1.0">
133
- <dict>
134
- <key>KeepAlive</key>
135
- <true/>
136
- <key>Label</key>
137
- <string>dokuen.<%= @app %></string>
138
- <key>ProgramArguments</key>
139
- <array>
140
- <string>/usr/local/bin/dokuen</string>
141
- <string>start_app</string>
142
- <string><%= @app %></string>
143
- </array>
144
- <key>RunAtLoad</key>
145
- <true/>
146
- <key>UserName</key>
147
- <string>peter</string>
148
- <key>WorkingDirectory</key>
149
- <string>/usr/local/var/dokuen</string>
150
- <key>StandardOutPath</key>
151
- <string>/usr/local/var/dokuen/log/<%= @app %>.log</string>
152
- <key>StandardErrorPath</key>
153
- <string>/usr/local/var/dokuen/log/<%= @app %>.log</string>
154
- </dict>
155
- </plist>
156
- HERE
157
- end
158
-
159
- def nginx_template
160
- <<HERE
161
- server {
162
- server_name <%= @app %>.<%= base_domain %>;
163
- listen <%= server_port %>;
164
- ssl <%= ssl_on %>;
165
- location / {
166
- proxy_pass http://localhost:<%= ENV['PORT'] %>/;
167
- }
168
- }
169
- HERE
170
- end
171
- end
172
- end
173
-