forked from peterkeen/dokuen
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 50a94bd
Showing
6 changed files
with
299 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# Dokuen, a Personal App Platform | ||
|
||
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 machine. | ||
|
||
## Requirements | ||
|
||
* [Gitolite](https://github.com/sitaramc/gitolite) | ||
* [Nginx](http://wiki.nginx.org/Main) | ||
* [daemontools](http://cr.yp.to/daemontools.html) | ||
|
||
## Installation | ||
|
||
|
||
### Step 1 | ||
|
||
``` | ||
gem install dokuen | ||
``` | ||
|
||
### Step 2 | ||
|
||
Install daemontools and nginx using homebrew. | ||
|
||
### Step 3 | ||
|
||
Create a `git` user and install gitolite according to package directions. 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. | ||
|
||
### Step 4 | ||
|
||
Run this: | ||
|
||
``` | ||
$ sudo dokuen setup | ||
``` | ||
|
||
This will ask you a few questions, set up a few directories, and install a few useful commands. | ||
|
||
### Step 5 | ||
|
||
Edit your `.gitolite.rc` file and add this to the `COMMANDS` section: | ||
|
||
``` | ||
'app' => 1 | ||
``` | ||
|
||
### Step 6 | ||
|
||
Edit your sudoers file and add this line: | ||
|
||
``` | ||
git ALL=NOPASSWD: /usr/local/bin/dokuen | ||
``` | ||
|
||
## Creating an App | ||
|
||
``` | ||
$ ssh git@<your_host> app create <name> | ||
Creating new application named <name> | ||
Remote: git@<your_host>:<name>.git | ||
$ git remote add dokuen git@<your_host>:<name>.git | ||
``` | ||
|
||
### Add some environment variables | ||
|
||
``` | ||
$ ssh git@<your_host> app config <name> add BUILDPACK_URL="https://github.com/heroku/heroku-buildpack-ruby.git" BASE_PORT=12345 FOREMAN="web=1" | ||
``` | ||
|
||
### Deploy | ||
``` | ||
$ git push deploy master | ||
<deploy transcript> | ||
``` | ||
|
||
### Check it out! | ||
``` | ||
$ open http://<your_host>:12345/ | ||
``` | ||
|
||
## Available "app" Sub-commands | ||
|
||
* create <name> | ||
* config <name> | ||
* add <key>=<value> ... | ||
* delete <key> ... | ||
* restart <name> | ||
* scale <name> <type>=<num> | ||
* deploy <name> | ||
|
||
## DNS Concerns | ||
|
||
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 CNAMEs for each of my Dokuen-managed projects where the CNAME matches the name of the app. Unfortunately this setup is hard to automate way so Dokuen doesn't manage any of it for you. | ||
|
||
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`: | ||
|
||
``` | ||
include /usr/local/var/dokuen/nginx/*.conf; | ||
``` | ||
|
||
Then, force a redeploy for the apps you've already deployed: | ||
|
||
``` | ||
$ ssh git@<your_host> app deploy <name> | ||
``` | ||
|
||
## Rails | ||
|
||
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#!/usr/bin/env ruby | ||
|
||
require "rubygems" | ||
|
||
begin | ||
require 'dokuen' | ||
rescue LoadError => e | ||
path = File.expand_path '../../lib', __FILE__ | ||
$:.unshift(path) if File.directory?(path) && !$:.include?(path) | ||
require 'dokuen' | ||
end | ||
|
||
Dokuen::Cli.start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/bash | ||
|
||
NAME=`basename $1` | ||
cp $1 /Library/LaunchDaemons | ||
launchctl unload -wF /Library/LaunchDaemons/$NAME | ||
launchctl load -wF /Library/LaunchDaemons/$NAME |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
#!/usr/bin/env ruby | ||
|
||
require 'tmpdir' | ||
require 'fileutils' | ||
require 'time' | ||
|
||
MASON = "/usr/local/Cellar/ruby/1.9.3-p125/lib/ruby/gems/1.9.1/gems/mason-0.0.11/bin/mason" | ||
BASE_BUILD_DIR = '/usr/local/var/dokuen/build' | ||
BASE_RELEASE_DIR = '/usr/local/var/dokuen/release' | ||
BASE_NGINX_CONF = '/usr/local/var/dokuen/nginx' | ||
BASE_ENV_DIR = '/usr/local/var/dokuen/env' | ||
|
||
should_deploy = `git config hooks.deploy`.chomp | ||
subdomain = `git config hooks.deploy.subdomain`.chomp | ||
port = `git config hooks.deploy.port`.chomp | ||
foreman = `git config hooks.deploy.foreman`.chomp | ||
buildpack_url = `git config hooks.deploy.buildpack`.chomp | ||
after_deploy = `git config hooks.deploy.after`.chomp | ||
|
||
exit(0) if should_deploy != "true" | ||
|
||
def sys(cmd) | ||
system(cmd) or exit(1) | ||
end | ||
|
||
File.umask(0022) | ||
|
||
git_dir = Dir.getwd | ||
cache_dir = "#{BASE_BUILD_DIR}/#{subdomain}" | ||
now = Time.now().utc().strftime("%Y%m%dT%H%M%S") | ||
release_dir = "#{BASE_RELEASE_DIR}/#{subdomain}/#{now}" | ||
env_dir = "#{BASE_ENV_DIR}/#{subdomain}" | ||
|
||
FileUtils.mkdir_p(cache_dir, :mode => 0777) | ||
FileUtils.mkdir_p(release_dir, :mode => 0777) | ||
FileUtils.mkdir_p(env_dir, :mode => 0777) | ||
|
||
rev = "" | ||
STDIN.each do |line| | ||
puts line | ||
parts = line.split(/\s/) | ||
next if parts[2] != "refs/heads/master" | ||
rev = parts[1] | ||
end | ||
|
||
ENV['GIT_DIR'] = nil | ||
ENV['PATH'] = "/usr/local/bin:#{ENV['PATH']}" | ||
|
||
clone_dir = Dir.mktmpdir | ||
|
||
|
||
sys("git clone #{git_dir} #{clone_dir}") | ||
|
||
Dir.chdir clone_dir | ||
|
||
sys("git reset --hard #{rev}") | ||
|
||
sys("#{MASON} build #{clone_dir} -b #{buildpack_url} -o #{release_dir} -c #{cache_dir}") | ||
sys("chmod -R a+r #{release_dir}") | ||
sys("find #{release_dir} -type d -exec chmod a+x {} \\;") | ||
|
||
if after_deploy != "" | ||
Dir.chdir release_dir | ||
sys("envdir #{env_dir} foreman run #{after_deploy}") | ||
end | ||
|
||
base_port = port.to_i - 200 | ||
|
||
launch_agent_contents = <<HERE | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>KeepAlive</key> | ||
<true/> | ||
<key>Label</key> | ||
<string>info.bugsplat.#{subdomain}</string> | ||
<key>ProgramArguments</key> | ||
<array> | ||
<string>/usr/local/bin/envdir</string> | ||
<string>#{BASE_ENV_DIR}/#{subdomain}</string> | ||
<string>/usr/local/bin/foreman</string> | ||
<string>start</string> | ||
<string>-c</string> | ||
<string>#{foreman}</string> | ||
<string>-f</string> | ||
<string>#{release_dir}/Procfile</string> | ||
<string>-p</string> | ||
<string>#{base_port}</string> | ||
</array> | ||
<key>RunAtLoad</key> | ||
<true/> | ||
<key>UserName</key> | ||
<string>peter</string> | ||
<key>WorkingDirectory</key> | ||
<string>#{release_dir}</string> | ||
</dict> | ||
</plist> | ||
HERE | ||
|
||
puts "Installing LaunchDaemon" | ||
File.open("#{release_dir}/info.bugsplat.#{subdomain}.plist", "w+") do |file| | ||
file.write launch_agent_contents | ||
end | ||
sys("sudo /usr/local/bin/install_launchdaemon #{release_dir}/info.bugsplat.#{subdomain}.plist") | ||
|
||
puts "Restarting Nginx" | ||
File.open("#{BASE_NGINX_CONF}/info.bugsplat.#{subdomain}.conf", "w+") do |file| | ||
file.write <<HERE | ||
server { | ||
server_name #{subdomain}.bugsplat.info; | ||
listen 443; | ||
ssl on; | ||
location / { | ||
proxy_pass http://localhost:#{port}/; | ||
} | ||
} | ||
HERE | ||
end | ||
|
||
sys("sudo /usr/local/bin/restart_nginx") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/bin/bash | ||
|
||
/usr/local/sbin/nginx -s reload |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
require 'rubygems' | ||
require 'thor' | ||
require 'fileutils' | ||
|
||
module Dokuen | ||
class Cli < Thor | ||
desc "setup", "Set up relevant things. Needs to be run as sudo." | ||
def setup | ||
raise "Must be run as root" unless Process.uid == 0 | ||
end | ||
|
||
desc "create [APP]", "Create a new application." | ||
def create(app="") | ||
raise "app name required" if app.nil? || app == "" | ||
puts "Creating new application named #{app}" | ||
end | ||
|
||
desc "restart [APP]", "Restart an existing app" | ||
def restart(app="") | ||
end | ||
|
||
desc "scale [APP] [SCALE_SPEC]", "Scale an existing app up or down" | ||
def scale(app="", scale_spec="") | ||
end | ||
|
||
desc "deploy [APP]", "Force a fresh deploy of an app" | ||
def deploy(app="") | ||
end | ||
|
||
desc "restart_nginx", "Restart Nginx" | ||
def restart_nginx | ||
raise "Must be run as root" unless Process.uid == 0 | ||
end | ||
|
||
desc "install_launchdaemon [APP] [RELEASE_PATH]", "Install a launch daemon" | ||
def install_launchdaemon(app="", release_path="") | ||
raise "Must be run as root" unless Process.uid == 0 | ||
end | ||
|
||
desc "run_command [APP] [COMMAND]", "Run a command in the given app's environment" | ||
def run_command(app="", command="") | ||
end | ||
|
||
end | ||
end |