Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial real commit

  • Loading branch information...
commit a6fc25d7c301b0d98360ce9bf74655a55105dce6 1 parent 50a94bd
@peterkeen authored
View
3  Gemfile
@@ -0,0 +1,3 @@
+source 'http://rubygems.org'
+
+gemspec
View
18 Gemfile.lock
@@ -0,0 +1,18 @@
+PATH
+ remote: .
+ specs:
+ dokuen (0.0.1)
+ thor
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ rake (0.9.2.2)
+ thor (0.14.6)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ dokuen!
+ rake
View
25 dokuen.gemspec
@@ -0,0 +1,25 @@
+Gem::Specification.new do |s|
+ s.name = 'dokuen'
+ s.version = '0.0.1'
+ s.date = '2012-05-19'
+
+ s.summary = 'A Personal Application Platform for Macs'
+ s.description = 'Like Heroku but Personal'
+
+ s.author = 'Pete Keen'
+ s.email = 'pete@bugsplat.info'
+
+ s.require_paths = %w< lib >
+
+ s.files = Dir['lib/**/*.rb'] +
+ Dir['test/*.rb'] +
+ %w< dokuen.gemspec README.md >
+
+ s.test_files = s.files.select {|path| path =~ /^test\/.*.rb/ }
+
+ s.add_development_dependency('rake')
+ s.add_dependency('thor')
+
+
+ s.homepage = 'https://github.com/peterkeen/dokuen'
+end
View
59 lib/dokuen.rb
@@ -1,45 +1,38 @@
-require 'rubygems'
-require 'thor'
-require 'fileutils'
+require 'dokuen/cli'
+require 'dokuen/deploy'
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
+ def self.dir(name, app=nil)
+ parts = ["/usr/local/var/dokuen", name]
- 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="")
+ if not app.nil?
+ parts << app
end
+
+ File.join(parts)
+ end
- desc "deploy [APP]", "Force a fresh deploy of an app"
- def deploy(app="")
- end
+ def self.sys(command)
+ system(command) or raise "Error running #{command}"
+ end
- desc "restart_nginx", "Restart Nginx"
- def restart_nginx
- raise "Must be run as root" unless Process.uid == 0
+ def self.read_env(name)
+ env_dir = Dokuen.dir('env', name)
+ Dir.glob("#{env_dir}/*") do |var|
+ var_name = File.basename(var)
+ ENV[var_name] = File.open(var).read().chomp()
end
+ 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
+ def self.set_env(name, key, value)
+ env_dir = Dokuen.dir('env', name)
+ File.open(File.join(env_dir, key), "w+") do |f|
+ f.write value
end
+ end
- desc "run_command [APP] [COMMAND]", "Run a command in the given app's environment"
- def run_command(app="", command="")
- end
-
+ def self.rm_env(name, key)
+ env_dir = Dokuen.dir('env', name)
+ File.delete(File.join(env_dir, key))
end
end
View
133 lib/dokuen/cli.rb
@@ -0,0 +1,133 @@
+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}"
+ FileUtils.mkdir_p(Dokuen.dir("env", app))
+ FileUtils.mkdir_p(Dokuen.dir("release", app))
+ FileUtils.mkdir_p(Dokuen.dir("build", app))
+ end
+
+ desc "restart_app [APP]", "Restart an existing app"
+ def restart_app(app="")
+ filename = "/Library/LaunchDaemons/dokuen.#{app}.plist"
+ Dokuen.sys("sudo dokuen restart_path #{filename}")
+ end
+
+ desc "restart_path [PATH]", "Restart a path", :hide => true
+ def restart_path(path)
+ raise "Must be run as root" unless Process.uid == 0
+ if File.exists? path
+ Dokuen.sys("launchctl unload -wF #{path}")
+ Dokuen.sys("launchctl load -wF #{path}")
+ end
+ end
+
+ desc "start_app [APP]", "Start an app", :hide => true
+ def start_app(app="")
+ Dokuen.read_env("_common")
+ Dokuen.read_env(app)
+ Dir.chdir(ENV['DOKUEN_RELEASE_DIR']) do
+ base_port = ENV['PORT'].to_i - 200
+ scale = ENV['DOKUEN_SCALE'].nil? ? "" : "-c #{ENV['DOKUEN_SCALE']}"
+ Dokuen.sys("foreman start #{scale} -p #{base_port}")
+ end
+ end
+
+ desc "scale [APP] [SCALE_SPEC]", "Scale an app to the given spec"
+ def scale(app="", scale_spec="")
+ raise "app required" if app == ""
+ raise "scale spec required" if scale_spec == ""
+ Dokuen.set_env(app, 'DOKUEN_SCALE', scale_spec)
+ restart_app(app)
+ puts "Scaled to #{scale_spec}"
+ end
+
+ desc "deploy [APP]", "Force a fresh deploy of an app", :hide => true
+ def deploy(app="")
+ Dokuen::Deploy.new(app).run
+ end
+
+ desc "restart_nginx", "Restart Nginx", :hide => true
+ def restart_nginx
+ raise "Must be run as root" unless Process.uid == 0
+ Dokuen.sys("/usr/local/bin/nginx -s reload")
+ end
+
+ desc "install_launchdaemon [APP] [RELEASE_PATH]", "Install a launch daemon", :hide => true
+ def install_launchdaemon(path)
+ raise "Must be run as root" unless Process.uid == 0
+ basename = File.basename(path)
+ destpath = "/Library/LaunchDaemons/#{basename}"
+
+ if File.exists?(destpath)
+ Dokuen.sys("launchctl unload -wF #{destpath}")
+ end
+
+ Dokuen.sys("cp #{path} #{destpath}")
+ Dokuen.sys("launchctl load -wF #{destpath}")
+ end
+
+ desc "run_command [APP] [COMMAND]", "Run a command in the given app's environment"
+ def run_command(app="", command="")
+ Dokuen.read_env("_common")
+ Dokuen.read_env(app)
+
+ Dir.chdir(ENV['DOKUEN_RELEASE_DIR']) do
+ Dokuen.sys("foreman run #{command}")
+ end
+ end
+
+ desc "config [APP] [set/delete] -- KEY=VAL...", "Add or remove config variables"
+ def config(app="", subcommand="")
+ case subcommand
+ when "set"
+ set_vars(app)
+ when "delete"
+ delete_vars(app)
+ else
+ show_vars(app)
+ end
+ end
+
+ no_tasks do
+
+ def set_vars(app)
+ split = ARGV.index("--")
+ vars = ARGV[split,ARGV.length]
+
+ vars.each do |var|
+ key, val = var.split(/\=/)
+ Dokuen.set_env(app, key, val)
+ end
+ end
+
+ def delete_vars(app)
+ split = ARGV.index("--")
+ vars = ARGV[split,ARGV.length]
+
+ vars.each do |var|
+ Dokuen.rm_env(app, var)
+ end
+ end
+
+ def show_vars(app)
+ Dokuen.read_env('_common')
+ Dokuen.read_env(app)
+ ENV.each do |key, val|
+ puts "#{key}=#{val}"
+ end
+ end
+ end
+ end
+end
View
11 lib/dokuen/config.rb
@@ -0,0 +1,11 @@
+require 'symboltable'
+
+module Dockuen
+ class Config < SymbolTable
+
+ def instance
+ @instance ||= self.new do |c|
+ c.
+ end
+ end
+end
View
168 lib/dokuen/deploy.rb
@@ -0,0 +1,168 @@
+require 'tmpdir'
+require 'fileutils'
+require 'time'
+require 'erb'
+
+module Dokuen
+ class Deploy
+
+ def initialize(app)
+ @app = app || app_name_from_env
+ end
+
+ def run
+ read_app_env
+ make_dirs
+ read_revisions
+ clone
+ build
+ install_launch_daemon
+ install_nginx_conf
+ end
+
+ def read_app_env
+ Dokuen.read_env('_common')
+ Dokuen.read_env(@app)
+ end
+
+ def make_dirs
+ FileUtils.mkdir_p([
+ env_dir,
+ release_dir,
+ cache_dir,
+ nginx_dir
+ ])
+ end
+
+ def read_revisions
+ STDIN.each do |line|
+ parts = line.split(/\s/)
+ next if parts[2] != "refs/heads/master"
+ @rev = parts[1]
+ end
+ if @rev.nil?
+ raise "Error: Dokuen can only deploy the master branch"
+ end
+ end
+
+ def clone
+ Dokuen.sys("git clone #{git_dir} #{clone_dir}")
+ Dir.chdir(clone_dir) do
+ Dokuen.sys("git reset --hard #{@rev}")
+ end
+ end
+
+ def build
+ Dokuen.sys("mason build #{clone_dir} #{buildpack} -o #{release_dir} -c #{cache_dir}")
+ Dokuen.sys("chmod -R a+r #{release_dir}")
+ Dokuen.sys("find #{release_dir} -type d -exec chmod a+x {} \\;")
+ Dokuen.set_env(@app, 'DOKUEN_RELEASE_DIR', release_dir)
+
+ hook = ENV['DOKUEN_AFTER_BUILD']
+ if not hook.nil?
+ Dir.chdir release_dir do
+ Dokuen.sys("foreman run #{hook}")
+ end
+ end
+ end
+
+ def buildpack
+ if not ENV['BUILDPACK_URL'].nil?
+ "-b #{ENV['BUILDPACK_URL']}"
+ else
+ ""
+ end
+ end
+
+ def install_launch_daemon
+ t = ERB.new(launch_daemon_template)
+ plist_path = File.join(release_dir, "dokuen.#{app}.plist")
+ File.open(plist_path) do |f|
+ f.write(t.result(binding))
+ end
+ Dokuen.sys("sudo /usr/local/bin/dokuen install_launchdaemon #{plist_path}")
+ end
+
+ def install_nginx_conf
+ t = ERB.new(nginx_template)
+ File.open(File.join(nginx_dir, "#{app}.#{base_domain}.conf")) do |f|
+ f.write(t.result(binding))
+ end
+ Dokuen.sys("sudo /usr/local/bin/dokuen restart_nginx")
+ end
+
+ def base_domain
+ ENV['BASE_DOMAIN'] || 'dokuen'
+ end
+
+ def app_name_from_env
+ File.basename(ENV['GL_REPO']).gsub(/\.git$/, '')
+ end
+
+ def env_dir
+ Dokuen.dir("env", @app)
+ end
+
+ def clone_dir
+ @clone_dir ||= Dir.mktmpdir
+ end
+
+ def git_dir
+ @git_dir ||= ENV['GIT_DIR'] || Dir.getwd
+ end
+
+ def release_dir
+ @now = Time.now().utc().strftime("%Y%m%dT%H%M%S")
+ @release_dir ||= File.join(Dokuen.dir('release', @app), @now)
+ end
+
+ def cache_dir
+ Dokuen.dir('build', @app)
+ end
+
+ def nginx_dir
+ Dokuen.dir('nginx')
+ end
+
+ def launch_daemon_template
+ <<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>dokuen.<%= app %></string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>/usr/local/bin/dokuen</string>
+ <string>start_app</string>
+ <string><%= app %></string>
+ </array>
+ <key>RunAtLoad</key>
+ <true/>
+ <key>UserName</key>
+ <string>peter</string>
+ <key>WorkingDirectory</key>
+ <string>/usr/local/var/dokuen</string>
+</dict>
+</plist>
+HERE
+ end
+
+ def nginx_template
+ <<HERE
+server {
+ server_name <%= app %>.<%= base_domain %>;
+ listen <%= server_port %>;
+ ssl <%= ssl_on %>;
+ location / {
+ proxy_pass http://localhost:<%= port %>/;
+ }
+}
+HERE
+ end
+ end
+end
+
Please sign in to comment.
Something went wrong with that request. Please try again.