From 9873ef7c90d87f9adc8c6088ef5139aa36afa881 Mon Sep 17 00:00:00 2001 From: John Bintz Date: Tue, 3 Jul 2012 17:54:07 -0400 Subject: [PATCH] initial commit --- .gitignore | 17 +++++ Gemfile | 4 ++ LICENSE | 22 +++++++ README.md | 6 ++ Rakefile | 2 + bin/personal_diety | 26 ++++++++ lib/personal_diety.rb | 16 +++++ lib/personal_diety/capistrano.rb | 106 +++++++++++++++++++++++++++++++ lib/personal_diety/version.rb | 3 + personal_diety.gemspec | 20 ++++++ skel/bin/god.conf | 47 ++++++++++++++ skel/capistrano/Gemfile | 4 ++ skel/capistrano/god | 8 +++ skel/capistrano/god.conf | 2 + skel/capistrano/schedule.rb | 4 ++ 15 files changed, 287 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100755 bin/personal_diety create mode 100644 lib/personal_diety.rb create mode 100644 lib/personal_diety/capistrano.rb create mode 100644 lib/personal_diety/version.rb create mode 100644 personal_diety.gemspec create mode 100644 skel/bin/god.conf create mode 100644 skel/capistrano/Gemfile create mode 100755 skel/capistrano/god create mode 100644 skel/capistrano/god.conf create mode 100644 skel/capistrano/schedule.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d87d4be --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..cc14b25 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in personal_diety.gemspec +gemspec diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..87b491e --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 John Bintz + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c52a1db --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +Run your own God. Good for apps that run in userspace (like, all your Rails apps and their dependencies.) +I'm also sick of giving userspace hooks for apps I deploy to a system-level God process. + +Geared toward starting Web services with Thin, but also provides handy defaults for whatever else +you may need to run. + diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..f57ae68 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +#!/usr/bin/env rake +require "bundler/gem_tasks" diff --git a/bin/personal_diety b/bin/personal_diety new file mode 100755 index 0000000..205bc4c --- /dev/null +++ b/bin/personal_diety @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require 'thor' +require 'personal_diety' + +module PersonalDiety + class Cli < Thor + include Thor::Actions + + def self.source_root ; PersonalDiety.skel.bin ; end + + desc "install", "Install personal_diety hooks into Capistrano deployment" + def install + self.destination_root = 'config' + append_file "deploy.rb", <<-RB + require 'personal_diety/capistrano' + RB + + copy_file 'god.conf', 'god.conf' + end + + default_task :install + end +end + +PersonalDiety::Cli.start diff --git a/lib/personal_diety.rb b/lib/personal_diety.rb new file mode 100644 index 0000000..b6a6fba --- /dev/null +++ b/lib/personal_diety.rb @@ -0,0 +1,16 @@ +require "personal_diety/version" + +module PersonalDiety + class Skel + def self.root + Pathname(File.expand_path('../../skel', __FILE__)) + end + + def self.method_missing(method) + root.join(method.to_s) + end + end + + def self.skel ; Skel ; end +end + diff --git a/lib/personal_diety/capistrano.rb b/lib/personal_diety/capistrano.rb new file mode 100644 index 0000000..33df589 --- /dev/null +++ b/lib/personal_diety/capistrano.rb @@ -0,0 +1,106 @@ +require 'capistrano' +require 'personal_diety' +require 'erb' + +Capistrano::Configuration.instance(true).load do + def _cset(name, *args, &block) + set(name, *args, &block) if !exists?(name) + end + + _cset(:god_port) { 23132 } + _cset(:god_log_level) { :warn } + _cset(:personal_diety_local_app_config) { 'config/god.conf' } + + def personal_diety_target + @personal_diety_target ||= Pathname(capture("echo $HOME/personal_diety").strip) + end + + def personal_diety_config_dir + Pathname("#{personal_diety_target}/god.d") + end + + def personal_diety_command + personal_diety_target.join("god") + end + + def generate_personal_diety_command(*args) + "cd #{personal_diety_target} && #{personal_diety_command} #{args.join(' ')}" + end + + def run_personal_diety_command(*args) + run generate_personal_diety_command(*args) + "; true" + end + + namespace :personal_diety do + desc "Install the God config for this app" + task :install do + template = ERB.new(File.read(personal_diety_local_app_config)).result(binding) + upload_target = personal_diety_config_dir.join("#{application}.god") + top.upload StringIO.new(template), upload_target.to_s + + run_personal_diety_command :load, upload_target.to_s + end + + namespace :service do + desc "Set up a copy of God to run for this user" + task :setup do + run "mkdir -p #{personal_diety_config_dir.to_s}" + + config_path = "#{personal_diety_target}/god.conf" + + PersonalDiety.skel.capistrano.find do |file| + if file.file? + template = ERB.new(file.read).result(binding) + upload_target = personal_diety_target.join(file.relative_path_from(PersonalDiety.skel.capistrano)) + top.upload StringIO.new(template), upload_target.to_s + run "chmod #{file.stat.mode.to_s(8)[-3..-1]} #{upload_target}" + end + end + + run "cd #{personal_diety_target} && bundle install --path gems" + run "cd #{personal_diety_target} && bundle exec whenever -i god -f schedule.rb" + end + + desc "Stop the God service" + task :stop do + run_personal_diety_command :quit + end + + desc "Start the God service" + task :start do + run_personal_diety_command + end + + desc "Restart the God service" + task :restart do + top.personal_diety.service.stop + top.personal_diety.service.start + end + end + + desc "Stop the God process for this application" + task :stop do + run_personal_diety_command :stop, application + end + + desc "Start the God process for this application" + task :start do + run_personal_diety_command :start, application + end + + desc "Restart the God process for this application" + task :restart do + run_personal_diety_command :restart, application + end + end + + before 'deploy:update_symlink', 'personal_diety:install' + before 'deploy:symlink', 'personal_diety:install' + + namespace :deploy do + task(:stop) { top.personal_diety.stop } + task(:start) { top.personal_diety.start } + task(:restart) { top.personal_diety.restart } + end +end + diff --git a/lib/personal_diety/version.rb b/lib/personal_diety/version.rb new file mode 100644 index 0000000..9eb1266 --- /dev/null +++ b/lib/personal_diety/version.rb @@ -0,0 +1,3 @@ +module PersonalDiety + VERSION = "0.0.1" +end diff --git a/personal_diety.gemspec b/personal_diety.gemspec new file mode 100644 index 0000000..6b32b12 --- /dev/null +++ b/personal_diety.gemspec @@ -0,0 +1,20 @@ +# -*- encoding: utf-8 -*- +require File.expand_path('../lib/personal_diety/version', __FILE__) + +Gem::Specification.new do |gem| + gem.authors = ["John Bintz"] + gem.email = ["john@coswellproductions.com"] + gem.description = %q{TODO: Write a gem description} + gem.summary = %q{TODO: Write a gem summary} + gem.homepage = "" + + gem.files = `git ls-files`.split($\) + gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + gem.name = "personal_diety" + gem.require_paths = ["lib"] + gem.version = PersonalDiety::VERSION + + gem.add_dependency 'god' + gem.add_dependency 'thin' +end diff --git a/skel/bin/god.conf b/skel/bin/god.conf new file mode 100644 index 0000000..f35d840 --- /dev/null +++ b/skel/bin/god.conf @@ -0,0 +1,47 @@ +# since we're running god via bundler, we need to un-bundle any commands executed +def wrap_command(*args) + "bash -c 'unset BUNDLE_GEMFILE ; unset BUNDLE_BIN_PATH ; cd <%= current_path %> && #{args.join(' ')}'" +end + +# a default email contact for when a service dies and needs to be restarted +God.contact(:email) do |c| + c.name = "john" + c.to_email = "email@example.com" + c.delivery_method = :sendmail +end + +# example for a thin-powered app, which is what I use the most of +<%= thin_servers %>.times do |port_id| + port = <%= thin_port %> + port_id + + God.watch do |w| + # assign all watches to this group so personal_diety can stop/start them all at once + w.group = "<%= application %>" + w.name = "<%= application %>-thin-#{port}" + + pid = "<%= shared_path %>/pids/thin.#{port}.pid" + command = "bundle exec thin -P #{pid} -p #{port}" + + w.pid_file = pid + w.log = "<%= shared_path %>/god.log" + + start = "#{command} -e <%= stage %> -d start" + w.start = wrap_command(start) + w.start_grace = 20.seconds + + stop = "#{command} stop" + w.stop = wrap_command(stop) + + w.restart = wrap_command("#{stop} && #{start}") + w.restart_grace = 20.seconds + + w.start_if do |start| + start.condition(:process_running) do |c| + c.running = false + c.interval = 5.seconds + c.notify = "john" + end + end + end +end + diff --git a/skel/capistrano/Gemfile b/skel/capistrano/Gemfile new file mode 100644 index 0000000..c972ac9 --- /dev/null +++ b/skel/capistrano/Gemfile @@ -0,0 +1,4 @@ +source :rubygems + +gem 'god' +gem 'whenever' diff --git a/skel/capistrano/god b/skel/capistrano/god new file mode 100755 index 0000000..1e771c6 --- /dev/null +++ b/skel/capistrano/god @@ -0,0 +1,8 @@ +#!/bin/bash + +base_command="bundle exec god -p <%= god_port %>" +if [ -z $1 ]; then + $base_command --log-level <%= god_log_level %> -l <%= personal_diety_target %>/god.log -c <%= config_path %> -P <%= personal_diety_target %>/god.pid +else + $base_command $@ +fi diff --git a/skel/capistrano/god.conf b/skel/capistrano/god.conf new file mode 100644 index 0000000..680fc67 --- /dev/null +++ b/skel/capistrano/god.conf @@ -0,0 +1,2 @@ +Dir["<%= personal_diety_config_dir.to_s %>/*.god"].each { |file| load file } + diff --git a/skel/capistrano/schedule.rb b/skel/capistrano/schedule.rb new file mode 100644 index 0000000..8965f5c --- /dev/null +++ b/skel/capistrano/schedule.rb @@ -0,0 +1,4 @@ +job_type :god, '<%= generate_personal_diety_command %>' + +every(:reboot) { god "run" } +