dokuen 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Gemfile.lock +18 -0
- data/README.md +95 -0
- data/bin/dokuen +13 -0
- data/bin/install_launchdaemon +6 -0
- data/bin/pre-receive +122 -0
- data/bin/restart_nginx +3 -0
- data/dokuen.gemspec +28 -0
- data/lib/dokuen/cli.rb +244 -0
- data/lib/dokuen/config.rb +11 -0
- data/lib/dokuen/deploy.rb +173 -0
- data/lib/dokuen.rb +46 -0
- metadata +124 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Dokuen, a Personal App Platform
|
2
|
+
|
3
|
+
Dokuen is a "personal app platform". It's the same idea as all of these PaaS and IaaS services out there, except you host it on your own machine. It's specifically for hosting multiple 12-factor applications on one Mac.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
* [Gitolite](https://github.com/sitaramc/gitolite)
|
8
|
+
* [Nginx](http://wiki.nginx.org/Main)
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
|
13
|
+
### Step 1
|
14
|
+
|
15
|
+
```
|
16
|
+
gem install dokuen
|
17
|
+
```
|
18
|
+
|
19
|
+
### Step 2
|
20
|
+
|
21
|
+
Install nginx using homebrew.
|
22
|
+
|
23
|
+
### Step 3
|
24
|
+
|
25
|
+
Create a `git` user and install gitolite according to [package directions](http://sitaramc.github.com/gitolite/qi.html). You'll be making changes to the config later, but for now you should be able to create a new repository and push to it.
|
26
|
+
|
27
|
+
### Step 4
|
28
|
+
|
29
|
+
Run this:
|
30
|
+
|
31
|
+
```
|
32
|
+
$ sudo dokuen setup
|
33
|
+
```
|
34
|
+
|
35
|
+
This will ask you a few questions, set up a few directories, and install a few useful commands. It'll also show you some things you need to do.
|
36
|
+
|
37
|
+
## Creating an App
|
38
|
+
|
39
|
+
```
|
40
|
+
$ ssh git@<your_host> dokuen create <name>
|
41
|
+
Creating new application named <name>
|
42
|
+
Remote: git@<your_host>:<name>.git
|
43
|
+
|
44
|
+
$ git remote add dokuen git@<your_host>:<name>.git
|
45
|
+
```
|
46
|
+
|
47
|
+
### Add some environment variables
|
48
|
+
|
49
|
+
```
|
50
|
+
$ ssh git@<your_host> dokuen config <name> set -V BUILDPACK_URL="https://github.com/heroku/heroku-buildpack-ruby.git" PORT=12345 DOKUEN_SCALE="web=1"
|
51
|
+
```
|
52
|
+
|
53
|
+
### Deploy
|
54
|
+
```
|
55
|
+
$ git push dokuen master
|
56
|
+
<deploy transcript>
|
57
|
+
```
|
58
|
+
|
59
|
+
### Check it out!
|
60
|
+
```
|
61
|
+
$ open http://<your_host>:12345/
|
62
|
+
```
|
63
|
+
|
64
|
+
## Available "app" Sub-commands
|
65
|
+
|
66
|
+
* create <name>
|
67
|
+
* config <name>
|
68
|
+
* set <key>=<value> ...
|
69
|
+
* delete <key> ...
|
70
|
+
* restart_app <name>
|
71
|
+
* scale <name> <type>=<num>
|
72
|
+
* buildpacks
|
73
|
+
* install_buildpack
|
74
|
+
* remove_buildpack
|
75
|
+
|
76
|
+
## DNS Setup
|
77
|
+
|
78
|
+
I have my home router set up to forward ports 80 and 443 to my mac mini, and I have a dynamic DNS system set up with a wildcard CNAME Unfortunately this setup is hard to automate so Dokuen doesn't manage any of it for you.
|
79
|
+
|
80
|
+
What it does do is set up Nginx server configs for you that you can choose to use. If you want to use them, put this at the bottom of the `http` section of `nginx.conf`:
|
81
|
+
|
82
|
+
```
|
83
|
+
include /usr/local/var/dokuen/nginx/*.conf;
|
84
|
+
```
|
85
|
+
|
86
|
+
Then, force a restart of your app:
|
87
|
+
|
88
|
+
```
|
89
|
+
$ ssh git@<your_host> dokuen restart_app <name>
|
90
|
+
```
|
91
|
+
|
92
|
+
## Rails
|
93
|
+
|
94
|
+
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
|
95
|
+
|
data/bin/dokuen
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'dokuen'
|
7
|
+
rescue LoadError => e
|
8
|
+
path = File.expand_path '../../lib', __FILE__
|
9
|
+
$:.unshift(path) if File.directory?(path) && !$:.include?(path)
|
10
|
+
require 'dokuen'
|
11
|
+
end
|
12
|
+
|
13
|
+
Dokuen::Cli.start()
|
data/bin/pre-receive
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
MASON = "/usr/local/Cellar/ruby/1.9.3-p125/lib/ruby/gems/1.9.1/gems/mason-0.0.11/bin/mason"
|
8
|
+
BASE_BUILD_DIR = '/usr/local/var/dokuen/build'
|
9
|
+
BASE_RELEASE_DIR = '/usr/local/var/dokuen/release'
|
10
|
+
BASE_NGINX_CONF = '/usr/local/var/dokuen/nginx'
|
11
|
+
BASE_ENV_DIR = '/usr/local/var/dokuen/env'
|
12
|
+
|
13
|
+
should_deploy = `git config hooks.deploy`.chomp
|
14
|
+
subdomain = `git config hooks.deploy.subdomain`.chomp
|
15
|
+
port = `git config hooks.deploy.port`.chomp
|
16
|
+
foreman = `git config hooks.deploy.foreman`.chomp
|
17
|
+
buildpack_url = `git config hooks.deploy.buildpack`.chomp
|
18
|
+
after_deploy = `git config hooks.deploy.after`.chomp
|
19
|
+
|
20
|
+
exit(0) if should_deploy != "true"
|
21
|
+
|
22
|
+
def sys(cmd)
|
23
|
+
system(cmd) or exit(1)
|
24
|
+
end
|
25
|
+
|
26
|
+
File.umask(0022)
|
27
|
+
|
28
|
+
git_dir = Dir.getwd
|
29
|
+
cache_dir = "#{BASE_BUILD_DIR}/#{subdomain}"
|
30
|
+
now = Time.now().utc().strftime("%Y%m%dT%H%M%S")
|
31
|
+
release_dir = "#{BASE_RELEASE_DIR}/#{subdomain}/#{now}"
|
32
|
+
env_dir = "#{BASE_ENV_DIR}/#{subdomain}"
|
33
|
+
|
34
|
+
FileUtils.mkdir_p(cache_dir, :mode => 0777)
|
35
|
+
FileUtils.mkdir_p(release_dir, :mode => 0777)
|
36
|
+
FileUtils.mkdir_p(env_dir, :mode => 0777)
|
37
|
+
|
38
|
+
rev = ""
|
39
|
+
STDIN.each do |line|
|
40
|
+
puts line
|
41
|
+
parts = line.split(/\s/)
|
42
|
+
next if parts[2] != "refs/heads/master"
|
43
|
+
rev = parts[1]
|
44
|
+
end
|
45
|
+
|
46
|
+
ENV['GIT_DIR'] = nil
|
47
|
+
ENV['PATH'] = "/usr/local/bin:#{ENV['PATH']}"
|
48
|
+
|
49
|
+
clone_dir = Dir.mktmpdir
|
50
|
+
|
51
|
+
|
52
|
+
sys("git clone #{git_dir} #{clone_dir}")
|
53
|
+
|
54
|
+
Dir.chdir clone_dir
|
55
|
+
|
56
|
+
sys("git reset --hard #{rev}")
|
57
|
+
|
58
|
+
sys("#{MASON} build #{clone_dir} -b #{buildpack_url} -o #{release_dir} -c #{cache_dir}")
|
59
|
+
sys("chmod -R a+r #{release_dir}")
|
60
|
+
sys("find #{release_dir} -type d -exec chmod a+x {} \\;")
|
61
|
+
|
62
|
+
if after_deploy != ""
|
63
|
+
Dir.chdir release_dir
|
64
|
+
sys("envdir #{env_dir} foreman run #{after_deploy}")
|
65
|
+
end
|
66
|
+
|
67
|
+
base_port = port.to_i - 200
|
68
|
+
|
69
|
+
launch_agent_contents = <<HERE
|
70
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
71
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
72
|
+
<plist version="1.0">
|
73
|
+
<dict>
|
74
|
+
<key>KeepAlive</key>
|
75
|
+
<true/>
|
76
|
+
<key>Label</key>
|
77
|
+
<string>info.bugsplat.#{subdomain}</string>
|
78
|
+
<key>ProgramArguments</key>
|
79
|
+
<array>
|
80
|
+
<string>/usr/local/bin/envdir</string>
|
81
|
+
<string>#{BASE_ENV_DIR}/#{subdomain}</string>
|
82
|
+
<string>/usr/local/bin/foreman</string>
|
83
|
+
<string>start</string>
|
84
|
+
<string>-c</string>
|
85
|
+
<string>#{foreman}</string>
|
86
|
+
<string>-f</string>
|
87
|
+
<string>#{release_dir}/Procfile</string>
|
88
|
+
<string>-p</string>
|
89
|
+
<string>#{base_port}</string>
|
90
|
+
</array>
|
91
|
+
<key>RunAtLoad</key>
|
92
|
+
<true/>
|
93
|
+
<key>UserName</key>
|
94
|
+
<string>peter</string>
|
95
|
+
<key>WorkingDirectory</key>
|
96
|
+
<string>#{release_dir}</string>
|
97
|
+
</dict>
|
98
|
+
</plist>
|
99
|
+
HERE
|
100
|
+
|
101
|
+
puts "Installing LaunchDaemon"
|
102
|
+
File.open("#{release_dir}/info.bugsplat.#{subdomain}.plist", "w+") do |file|
|
103
|
+
file.write launch_agent_contents
|
104
|
+
end
|
105
|
+
sys("sudo /usr/local/bin/install_launchdaemon #{release_dir}/info.bugsplat.#{subdomain}.plist")
|
106
|
+
|
107
|
+
puts "Restarting Nginx"
|
108
|
+
File.open("#{BASE_NGINX_CONF}/info.bugsplat.#{subdomain}.conf", "w+") do |file|
|
109
|
+
file.write <<HERE
|
110
|
+
server {
|
111
|
+
server_name #{subdomain}.bugsplat.info;
|
112
|
+
listen 443;
|
113
|
+
ssl on;
|
114
|
+
location / {
|
115
|
+
proxy_pass http://localhost:#{port}/;
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
HERE
|
120
|
+
end
|
121
|
+
|
122
|
+
sys("sudo /usr/local/bin/restart_nginx")
|
data/bin/restart_nginx
ADDED
data/dokuen.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'dokuen'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.date = '2012-05-19'
|
5
|
+
|
6
|
+
s.summary = 'A Personal Application Platform for Macs'
|
7
|
+
s.description = 'Like Heroku but Personal'
|
8
|
+
|
9
|
+
s.author = 'Pete Keen'
|
10
|
+
s.email = 'pete@bugsplat.info'
|
11
|
+
|
12
|
+
s.require_paths = %w< lib >
|
13
|
+
|
14
|
+
s.bindir = 'bin'
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.test_files = s.files.select {|path| path =~ /^test\/.*.rb/ }
|
21
|
+
|
22
|
+
s.add_development_dependency('rake')
|
23
|
+
s.add_dependency('thor')
|
24
|
+
s.add_dependency('mason')
|
25
|
+
s.add_dependency('foreman')
|
26
|
+
|
27
|
+
s.homepage = 'https://github.com/peterkeen/dokuen'
|
28
|
+
end
|
data/lib/dokuen/cli.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'thor'
|
3
|
+
require 'fileutils'
|
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/releases',
|
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
|
72
|
+
|
73
|
+
In your gitolite.conf file, add the following:
|
74
|
+
|
75
|
+
repo apps/[a-zA-Z0-9].*
|
76
|
+
C = @all
|
77
|
+
RW+ = CREATOR
|
78
|
+
config hooks.pre = "/usr/local/bin/dokuen deploy"
|
79
|
+
|
80
|
+
In your nginx.conf, add the following to your http section:
|
81
|
+
|
82
|
+
include "/usr/local/var/dokuen/nginx/*.conf";
|
83
|
+
|
84
|
+
Run "sudo visudo" and add the following line:
|
85
|
+
|
86
|
+
git ALL=NOPASSWD: /usr/local/bin/dokuen
|
87
|
+
|
88
|
+
HERE
|
89
|
+
)
|
90
|
+
|
91
|
+
end
|
92
|
+
|
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"
|
102
|
+
end
|
103
|
+
|
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"
|
112
|
+
end
|
113
|
+
|
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
|
124
|
+
end
|
125
|
+
|
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
|
135
|
+
|
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"
|
142
|
+
end
|
143
|
+
|
144
|
+
desc "restart_nginx", "Restart Nginx", :hide => true
|
145
|
+
def restart_nginx
|
146
|
+
raise "Must be run as root" unless Process.uid == 0
|
147
|
+
Dokuen.sys("/usr/local/sbin/nginx -s reload")
|
148
|
+
end
|
149
|
+
|
150
|
+
desc "install_launchdaemon [PATH]", "Install a launch daemon", :hide => true
|
151
|
+
def install_launchdaemon(path)
|
152
|
+
raise "Must be run as root" unless Process.uid == 0
|
153
|
+
basename = File.basename(path)
|
154
|
+
destpath = "/Library/LaunchDaemons/#{basename}"
|
155
|
+
|
156
|
+
if File.exists?(destpath)
|
157
|
+
Dokuen.sys("launchctl unload -wF #{destpath}")
|
158
|
+
end
|
159
|
+
|
160
|
+
Dokuen.sys("cp #{path} #{destpath}")
|
161
|
+
Dokuen.sys("launchctl load -wF #{destpath}")
|
162
|
+
end
|
163
|
+
|
164
|
+
desc "run_command [APP] [COMMAND]", "Run a command in the given app's environment"
|
165
|
+
def run_command(app="", command="")
|
166
|
+
check_app(app)
|
167
|
+
read_env(app)
|
168
|
+
|
169
|
+
Dir.chdir(ENV['DOKUEN_RELEASE_DIR']) do
|
170
|
+
Dokuen.sys("foreman run #{command}")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
desc "config [APP] [set/delete]", "Add or remove config variables"
|
175
|
+
method_option :vars, :aliases => '-V', :desc => "Variables to set or remove", :type => :array
|
176
|
+
def config(app="", subcommand="")
|
177
|
+
check_app(app)
|
178
|
+
case subcommand
|
179
|
+
when "set"
|
180
|
+
set_vars(app)
|
181
|
+
restart_app(app)
|
182
|
+
when "delete"
|
183
|
+
delete_vars(app)
|
184
|
+
restart_app(app)
|
185
|
+
else
|
186
|
+
show_vars(app)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
desc "install_buildpack [URL]", "Add a buildpack to the mason config"
|
191
|
+
def install_buildpack(url="")
|
192
|
+
raise "URL required" unless url != ""
|
193
|
+
Dokuen.sys("/usr/local/bin/mason buildpacks:install #{url}")
|
194
|
+
end
|
195
|
+
|
196
|
+
desc "remove_buildpack [NAME]", "Remove a buildpack from the mason config"
|
197
|
+
def remove_buildpack(name)
|
198
|
+
raise "Name required" unless name != ""
|
199
|
+
Dokuen.sys("/usr/local/bin/mason buildpacks:uninstall #{name}")
|
200
|
+
end
|
201
|
+
|
202
|
+
desc "buildpacks", "List the available buildpacks"
|
203
|
+
def buildpacks
|
204
|
+
Dokuen.sys("/usr/local/bin/mason buildpacks")
|
205
|
+
end
|
206
|
+
|
207
|
+
no_tasks do
|
208
|
+
|
209
|
+
def set_vars(app)
|
210
|
+
vars = options[:vars]
|
211
|
+
vars.each do |var|
|
212
|
+
key, val = var.split(/\=/)
|
213
|
+
Dokuen.set_env(app, key, val)
|
214
|
+
end
|
215
|
+
puts "Vars set"
|
216
|
+
end
|
217
|
+
|
218
|
+
def delete_vars(app)
|
219
|
+
vars = options[:vars]
|
220
|
+
|
221
|
+
vars.each do |var|
|
222
|
+
Dokuen.rm_env(app, var)
|
223
|
+
end
|
224
|
+
puts "Vars removed"
|
225
|
+
end
|
226
|
+
|
227
|
+
def show_vars(app)
|
228
|
+
read_env(app)
|
229
|
+
ENV.each do |key, val|
|
230
|
+
puts "#{key}=#{val}"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def read_env(app)
|
235
|
+
Dokuen.read_env("_common")
|
236
|
+
Dokuen.read_env(app)
|
237
|
+
end
|
238
|
+
|
239
|
+
def check_app(app)
|
240
|
+
Dokuen.app_exists?(app) or raise "App '#{app}' does not exist!"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,173 @@
|
|
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
|
+
|
data/lib/dokuen.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'dokuen/cli'
|
2
|
+
require 'dokuen/deploy'
|
3
|
+
|
4
|
+
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
|
31
|
+
end
|
32
|
+
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
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dokuen
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Pete Keen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-19 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: thor
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mason
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: foreman
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Like Heroku but Personal
|
79
|
+
email: pete@bugsplat.info
|
80
|
+
executables:
|
81
|
+
- dokuen
|
82
|
+
- install_launchdaemon
|
83
|
+
- pre-receive
|
84
|
+
- restart_nginx
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- Gemfile
|
89
|
+
- Gemfile.lock
|
90
|
+
- README.md
|
91
|
+
- bin/dokuen
|
92
|
+
- bin/install_launchdaemon
|
93
|
+
- bin/pre-receive
|
94
|
+
- bin/restart_nginx
|
95
|
+
- dokuen.gemspec
|
96
|
+
- lib/dokuen.rb
|
97
|
+
- lib/dokuen/cli.rb
|
98
|
+
- lib/dokuen/config.rb
|
99
|
+
- lib/dokuen/deploy.rb
|
100
|
+
homepage: https://github.com/peterkeen/dokuen
|
101
|
+
licenses: []
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 1.8.19
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: A Personal Application Platform for Macs
|
124
|
+
test_files: []
|