sunzi-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7d4434d8e754ea42e09e0b32fb93d466dd8ffcb4
4
+ data.tar.gz: 4159fb253fdc588e033f8f8df4e1c59a85a48296
5
+ SHA512:
6
+ metadata.gz: 9ead76c9956fa2747ef0ba5100b1f6d8e98ca05ec5278dd8e1845daf36ece3f57afe9f15bb49573c4cd20d8b7b3bd51c0361f5ff89ec8532c3896fae8728cf6f
7
+ data.tar.gz: 7358cbfbd5cee07e3a2a06dda5ec4cfe25d75d26fd905bca5356451a444e373c97005d82870cf4d86ed96b018ed70c597f8afe490df3e0bd14663639110a8a2e
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ sandbox
6
+ .ruby-version
7
+ /compiled
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,200 @@
1
+ Sunzi-Rails
2
+ ===========
3
+
4
+ ```
5
+ "The supreme art of war is to subdue the enemy without fighting." - Sunzi
6
+ ```
7
+
8
+ Sunzi is the easiest [server provisioning](http://en.wikipedia.org/wiki/Provisioning#Server_provisioning) utility designed for mere mortals. If Chef or Puppet is driving you nuts, try Sunzi!
9
+
10
+ Sunzi assumes that modern Linux distributions have (mostly) sane defaults and great package managers.
11
+
12
+ Its design goals are:
13
+
14
+ * **It's just shell script.** No clunky Ruby DSL involved. Most of the information about server configuration on the web is written in shell commands. Just copy-paste them, rather than translate it into an arbitrary DSL. Also, Bash is the greatest common denominator on minimum Linux installs.
15
+ * **Focus on diff from default.** No big-bang overwriting. Append or replace the smallest possible piece of data in a config file. Loads of custom configurations make it difficult to understand what you are really doing.
16
+ * **Always use the root user.** Think twice before blindly assuming you need a regular user - it doesn't add any security benefit for server provisioning, it just adds extra verbosity for nothing. However, it doesn't mean that you shouldn't create regular users with Sunzi - feel free to write your own recipes.
17
+ * **Minimum dependencies.** No configuration server required. You don't even need a Ruby runtime on the remote server.
18
+
19
+ Quickstart
20
+ ----------
21
+
22
+ Install:
23
+
24
+ ```bash
25
+ $ [sudo] gem install sunzi-rails
26
+ ```
27
+
28
+ Go into your Rails project directory, then:
29
+
30
+ ```bash
31
+ $ sunzi-cap create
32
+ ```
33
+
34
+ It generates a `config/sunzi` folder along with subdirectories and templates. Inside `sunzi`, there are `sunzi.yml` and `install.sh`. Those two are the most important files that you mainly work on.
35
+
36
+ Go into your `config/deploy.rb` and make sure to have these set:
37
+
38
+ ```ruby
39
+ set :ruby_version, IO.read("#{File.dirname(__FILE__)}/../../.ruby-version").strip
40
+ set :admin_name, 'admin'
41
+ set :deployer_name, 'deployer'
42
+ ```
43
+
44
+ Go into your `config/deploy/[stage].rb` and make sure to have these set:
45
+
46
+ ```ruby
47
+ set :server, 'example.com'
48
+ server fetch(:server), user: fetch(:deployer_name), roles: %w[app web db]
49
+ ```
50
+
51
+ Go into your `config/secrets.yml` and make sure to have these set:
52
+
53
+ ```
54
+ [stage]:
55
+ deployer_password: password
56
+ deployer_public_key: "ssh-rsa ...== deployer@example.com"
57
+ ```
58
+
59
+ Finally, add `/compiled` to your `.gitignore` file.
60
+
61
+ All those settings can be overriden in your `sunzi.yml` file within `attributes`.
62
+
63
+ Go into the project directory, then run `sunzi-cap deploy`:
64
+
65
+ ```bash
66
+ $ sunzi-cap deploy staging admin --sudo
67
+ $ sunzi-cap deploy staging deployer
68
+ ```
69
+
70
+ Now, what it actually does is:
71
+
72
+ 1. Compile `sunzi.yml` to generate attributes and retrieve remote recipes, then copy files scoped to `admin` role into the `compiled` directory
73
+ 1. SSH to `user@example.com` defined within your Cpistrano `config/deploy/staging.rb` file
74
+ 1. Transfer the content of the `compiled` directory to the remote server and extract in `$HOME/sunzi`
75
+ 1. Run `install.sh` on the remote server with sudo mode turned on
76
+ 1. Idem for the `deployer` role, but with sudo mode turned off
77
+
78
+ As you can see, all you need to do is edit `install.sh` and add some shell commands. That's it.
79
+
80
+ A Sunzi project without any recipes or roles is totally fine, so that you can start small, go big as you get along.
81
+
82
+ Commands
83
+ --------
84
+
85
+ ```bash
86
+ $ sunzi-cap # Show command help
87
+ $ sunzi-cap create # Create a new Sunzi project
88
+ $ sunzi-cap compile [stage] [role] [--sudo] # Compile Sunzi project
89
+ $ sunzi-cap deploy [stage] [role] [--sudo] # Deploy Sunzi project
90
+ ```
91
+
92
+ Directory structure
93
+ -------------------
94
+
95
+ Here's the directory structure that `sunzi-cap create` automatically generates:
96
+
97
+ ```bash
98
+ compiled/ # everything under this folder will be transferred to the
99
+ # remote server (do not edit directly)
100
+ config/sunzi/
101
+ install.sh # main script
102
+ sunzi.yml # add custom attributes and remote recipes here
103
+
104
+ recipes/ # put commonly used scripts here, referred from install.sh
105
+ sunzi.sh
106
+ roles/ # when role is specified, scripts here will be concatenated
107
+ db.sh # to install.sh in the compile phase
108
+ web.sh
109
+ files/ # put any files to be transferred
110
+ ```
111
+
112
+ How do you pass dynamic values?
113
+ -------------------------------
114
+
115
+ There are two ways to pass dynamic values to the script - ruby and bash.
116
+
117
+ **For ruby**: In the compile phase, attributes defined in `sunzi.yml` are accessible from any files in the form of `<%= @attributes.ruby_version %>`.
118
+
119
+ **For bash**: In the compile phase, attributes defined in `sunzi.yml` are split into multiple files in `compiled/attributes`, one per attribute. Now you can refer to it by `$(cat attributes/ruby_version)` in the script.
120
+
121
+ For instance, given the following `install.sh`:
122
+
123
+ ```bash
124
+ echo "Goodbye <%= @attributes.goodbye %>, Hello <%= @attributes.hello %>!"
125
+ ```
126
+
127
+ With `sunzi.yml`:
128
+
129
+ ```yaml
130
+ attributes:
131
+ goodbye: Chef
132
+ hello: Sunzi
133
+ ```
134
+
135
+ Now, you get the following result.
136
+
137
+ ```
138
+ Goodbye Chef, Hello Sunzi!
139
+ ```
140
+
141
+ Remote Recipes
142
+ --------------
143
+
144
+ Recipes can be retrieved remotely via HTTP. Put a URL in the recipes section of `sunzi.yml`, and Sunzi will automatically load the content and put it into the `compiled/recipes` folder in the compile phase.
145
+
146
+ For instance, if you have the following line in `sunzi.yml`,
147
+
148
+ ```yaml
149
+ recipes:
150
+ rvm: https://raw.github.com/kenn/sunzi-recipes/master/ruby/rvm.sh
151
+ ```
152
+
153
+ `rvm.sh` will be available and you can refer to that recipe by `source recipes/rvm.sh`.
154
+
155
+ You may find sample recipes in this repository useful: https://github.com/kenn/sunzi-recipes
156
+
157
+ Role-based configuration
158
+ ------------------------
159
+
160
+ You probably have different configurations between **web servers** and **database servers**.
161
+
162
+ No problem - how Sunzi handles role-based configuration is refreshingly simple.
163
+
164
+ Shell scripts under the `roles` directory, such as `web.sh` or `db.sh`, are automatically recognized as a role. The role script will be appended to `install.sh` at deploy, so you should put common configurations in `install.sh` and role specific procedures in the role script.
165
+
166
+ For instance, when you set up a new web server, deploy with a role name:
167
+
168
+ ```bash
169
+ sunzi-cap deploy production web
170
+ ```
171
+
172
+ It is equivalent to running `install.sh`, followed by `web.sh`.
173
+
174
+ Vagrant
175
+ -------
176
+
177
+ If you're using Sunzi with [Vagrant](http://vagrantup.com/), make sure that you have a root access via SSH.
178
+
179
+ An easy way is to edit `Vagrantfile`:
180
+
181
+ ```ruby
182
+ Vagrant.configure("2") do |config|
183
+ config.vm.provision "shell",
184
+ inline: "sudo echo 'root:vagrant' | /usr/sbin/chpasswd"
185
+ end
186
+ end
187
+ ```
188
+
189
+ and now run `vagrant up`, it will change the root password to `vagrant`.
190
+
191
+ Also keep in mind that you need to specify the port number 2222 within `config/deploy/vagrant.rb`.
192
+
193
+ ```bash
194
+ $ sunzi-cap deploy vagrant deployer
195
+ ```
196
+
197
+ Demonstration Videos
198
+ -------
199
+
200
+ You can watch video on how to deploy a Rails 4.1 app with Sunzi and Capistrano 3 at http://youtu.be/3mwupXqtkmg
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/test*.rb']
7
+ t.verbose = true
8
+ end
9
+ task :default => :test
data/bin/sunzi-cap ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Abort beautifully with ctrl+c.
4
+ Signal.trap(:INT) { abort "\nAborting." }
5
+
6
+ # Load the main lib and invoke CLI.
7
+ if ENV['RAILS_ENV'] == 'development'
8
+ require './lib/sunzi'
9
+ else
10
+ require 'sunzi'
11
+ end
12
+ require 'active_support/core_ext/hash/slice'
13
+ require 'active_support/core_ext/hash/indifferent_access'
14
+ require 'active_support/ordered_hash'
15
+ require 'active_support/hash_with_indifferent_access'
16
+
17
+ Sunzi::Cli.start(ARGV, { destination_root: 'config' })
@@ -0,0 +1,43 @@
1
+ module Sunzi
2
+ module Cli::Capistrano
3
+ extend self
4
+
5
+ attr_accessor :env
6
+ @env = {}
7
+
8
+ def load_env(stage)
9
+ deploy_path = File.expand_path('config/deploy.rb')
10
+ stage_path = File.expand_path("config/deploy/#{stage}.rb")
11
+ instance_eval(File.read(deploy_path), deploy_path)
12
+ instance_eval(File.read(stage_path), stage_path)
13
+ @env = HashWithIndifferentAccess.new(@env)
14
+ end
15
+
16
+ def lock(version); end
17
+ def namespace(options = {}); end
18
+
19
+ def set(key, value)
20
+ @env[key] = value
21
+ end
22
+
23
+ def fetch(key, value = nil)
24
+ if @env.has_key?(key)
25
+ @env[key]
26
+ else
27
+ @env[key] = value
28
+ end
29
+ end
30
+
31
+ def server(name, properties = {})
32
+ @env[:server] = {name: name}.merge(properties)
33
+ end
34
+
35
+ def method_missing(name, *args, &block)
36
+ if caller.join.include? 'load_env'
37
+ # do nothing
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ module Sunzi
2
+ module Cli::Database
3
+ extend self
4
+
5
+ attr_accessor :env
6
+
7
+ def load_env(stage)
8
+ path = File.expand_path('config/database.yml')
9
+ @env = YAML.load(File.read(path))[stage]
10
+ scope_keys
11
+ @env = HashWithIndifferentAccess.new(@env)
12
+ end
13
+
14
+ private
15
+
16
+ def scope_keys
17
+ @env = @env.reduce({}) do |env, (key, value)|
18
+ env[:"db_#{key}"] = value
19
+ env
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+
3
+ module Sunzi
4
+ module Cli::Secrets
5
+ extend self
6
+
7
+ attr_accessor :env
8
+
9
+ def load_env(stage)
10
+ path = File.expand_path('config/secrets.yml')
11
+ @env = HashWithIndifferentAccess.new(YAML.load(File.read(path))[stage])
12
+ end
13
+ end
14
+ end
data/lib/sunzi/cli.rb ADDED
@@ -0,0 +1,161 @@
1
+ require 'open3'
2
+ require 'ostruct'
3
+ require 'net/ssh'
4
+
5
+ module Sunzi
6
+ class Cli < Thor
7
+ include Thor::Actions
8
+
9
+ desc 'create', 'Create sunzi project'
10
+ def create(project = 'sunzi')
11
+ do_create(project)
12
+ end
13
+
14
+ desc 'deploy [stage] [role] [--sudo]', 'Deploy sunzi project'
15
+ method_options :sudo => false
16
+ def deploy(first, *args)
17
+ do_deploy(first, *args)
18
+ end
19
+
20
+ desc 'compile', 'Compile sunzi project'
21
+ def compile(first, *args)
22
+ do_compile(first, *args)
23
+ end
24
+
25
+ desc 'version', 'Show version'
26
+ def version
27
+ puts Gem.loaded_specs['sunzi'].version.to_s
28
+ end
29
+
30
+ no_tasks do
31
+ include Sunzi::Utility
32
+
33
+ def self.source_root
34
+ File.expand_path('../../',__FILE__)
35
+ end
36
+
37
+ def do_create(project)
38
+ copy_file 'templates/create/.gitignore', "#{project}/.gitignore"
39
+ copy_file 'templates/create/sunzi.yml', "#{project}/sunzi.yml"
40
+ copy_file 'templates/create/install.sh', "#{project}/install.sh"
41
+ copy_file 'templates/create/recipes/sunzi.sh', "#{project}/recipes/sunzi.sh"
42
+ copy_file 'templates/create/roles/db.sh', "#{project}/roles/db.sh"
43
+ copy_file 'templates/create/roles/web.sh', "#{project}/roles/web.sh"
44
+ copy_file 'templates/create/files/.gitkeep', "#{project}/files/.gitkeep"
45
+ end
46
+
47
+ def do_deploy(first, *args)
48
+ # compile attributes and recipes
49
+ do_compile(first, *args)
50
+
51
+ # The host key might change when we instantiate a new VM, so
52
+ # we remove (-R) the old host key from known_hosts.
53
+ `ssh-keygen -R #{@host} 2> /dev/null`
54
+
55
+ remote_commands = <<-EOS
56
+ rm -rf ~/sunzi &&
57
+ mkdir ~/sunzi &&
58
+ cd ~/sunzi &&
59
+ tar xz &&
60
+ #{@sudo}bash install.sh
61
+ EOS
62
+
63
+ remote_commands.strip! << ' && rm -rf ~/sunzi' if @config['preferences'] and @config['preferences']['erase_remote_folder']
64
+
65
+ local_commands = <<-EOS
66
+ cd compiled
67
+ tar cz . | ssh -o 'StrictHostKeyChecking no' #{@user}@#{@host} -p #{@port} '#{remote_commands}'
68
+ EOS
69
+
70
+ Open3.popen3(local_commands) do |stdin, stdout, stderr|
71
+ stdin.close
72
+ t = Thread.new do
73
+ while (line = stderr.gets)
74
+ print line.color(:red)
75
+ end
76
+ end
77
+ while (line = stdout.gets)
78
+ print line.color(:green)
79
+ end
80
+ t.join
81
+ end
82
+ end
83
+
84
+ def do_compile(first, *args)
85
+ load_env(first, *args)
86
+
87
+ compile_attributes
88
+ copy_remote_recipes
89
+ copy_local_files
90
+ build_install
91
+ end
92
+
93
+ def load_env(first, *args)
94
+ @stage = first
95
+ @role = args[0]
96
+
97
+ abort_with 'You must have a sunzi folder' unless File.exists?(based("sunzi.yml"))
98
+ abort_with "#{@role} doesn't exist!" unless File.exists?(based("roles/#{@role}.sh"))
99
+
100
+ cap = Capistrano.load_env(@stage)
101
+ if options.sudo?
102
+ @sudo = 'sudo '
103
+ @user = cap[:sys_admin]
104
+ else
105
+ @user = cap[:server][:user]
106
+ end
107
+ @host = cap[:server][:name]
108
+ @port = cap[:port]
109
+
110
+ @config = YAML.load(File.read(based("sunzi.yml")))
111
+
112
+ @attributes = Database.load_env(@stage)
113
+ .merge(cap.slice(:ruby_version, :deployer_name))
114
+ .merge(Secrets.load_env(@stage).slice(:deployer_password, :deployer_public_key))
115
+ .merge(@config['attributes'] || {})
116
+ end
117
+
118
+ def compile_attributes
119
+ # Break down attributes into individual files
120
+ @attributes.each {|key, value| create_file compiled("attributes/#{key}"), value }
121
+ @attributes = OpenStruct.new(@attributes)
122
+ end
123
+
124
+ def copy_remote_recipes
125
+ # Retrieve remote recipes via HTTP
126
+ cache_remote_recipes = @config['preferences'] && @config['preferences']['cache_remote_recipes']
127
+ (@config['recipes'] || []).each do |key, value|
128
+ next if cache_remote_recipes and File.exists?(compiled("recipes/#{key}.sh"))
129
+ get value, "compiled/recipes/#{key}.sh"
130
+ end
131
+ end
132
+
133
+ def copy_local_files
134
+ files = Dir["{config/sunzi/recipes,config/sunzi/roles,config/sunzi/files}/**/*"].select { |file| File.file?(file) }
135
+
136
+ files.each do |file|
137
+ template based(file), compiled(file)
138
+ end
139
+
140
+ (@config['files'] || []).each do |file|
141
+ template based(file), compiled("files/#{File.basename(file)}")
142
+ end
143
+ end
144
+
145
+ def build_install
146
+ _install_path = compiled("_install.sh")
147
+ template based("install.sh"), _install_path
148
+ content = File.binread(_install_path) << "\n" << File.binread(compiled("roles/#{@role}.sh"))
149
+ create_file compiled("install.sh"), content
150
+ end
151
+
152
+ def based(file)
153
+ File.expand_path("config/sunzi/#{file.sub('config/sunzi/', '')}")
154
+ end
155
+
156
+ def compiled(file)
157
+ File.expand_path("compiled/#{file.sub('config/sunzi/', '')}")
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,17 @@
1
+ module Sunzi
2
+ module Logger
3
+ class << self
4
+ def info(text)
5
+ puts text.bright
6
+ end
7
+
8
+ def success(text)
9
+ puts text.color(:green).bright
10
+ end
11
+
12
+ def error(text)
13
+ puts text.color(:red).bright
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Sunzi
2
+ module Utility
3
+ def abort_with(text)
4
+ Logger.error text
5
+ abort
6
+ end
7
+
8
+ def exit_with(text)
9
+ Logger.success text
10
+ exit
11
+ end
12
+
13
+ def say(text)
14
+ Logger.info text
15
+ end
16
+ end
17
+ end
data/lib/sunzi.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'thor'
2
+ require 'rainbow'
3
+ require 'yaml'
4
+
5
+ # Starting 2.0.0, Rainbow no longer patches string with the color method by default.
6
+ require 'rainbow/version'
7
+ require 'rainbow/ext/string' unless Rainbow::VERSION < '2.0.0'
8
+
9
+ module Sunzi
10
+ if ENV['RAILS_ENV'] == 'development'
11
+ autoload :Capistrano, './lib/sunzi/cli/capistrano'
12
+ autoload :Database, './lib/sunzi/cli/database'
13
+ autoload :Secrets, './lib/sunzi/cli/secrets'
14
+ autoload :Cli, './lib/sunzi/cli'
15
+ autoload :Logger, './lib/sunzi/logger'
16
+ autoload :Utility, './lib/sunzi/utility'
17
+ else
18
+ autoload :Capistrano, 'sunzi/cli/capistrano'
19
+ autoload :Database, 'sunzi/cli/database'
20
+ autoload :Secrets, 'sunzi/cli/secrets'
21
+ autoload :Cli, 'sunzi/cli'
22
+ autoload :Logger, 'sunzi/logger'
23
+ autoload :Utility, 'sunzi/utility'
24
+ end
25
+ end
@@ -0,0 +1 @@
1
+ <%= @attributes.deployer_public_key %>
@@ -0,0 +1 @@
1
+ <%= @attributes.deployer_name %> ALL=(ALL) NOPASSWD:ALL
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Load base utility functions like sunzi.mute() and sunzi.install()
5
+ source recipes/sunzi.sh
6
+
7
+ # This line is necessary for automated provisioning for Debian/Ubuntu.
8
+ # Remove if you're not on Debian/Ubuntu.
9
+ export DEBIAN_FRONTEND=noninteractive
10
+ export TERM=linux
11
+
12
+ # Add Dotdeb repository. Recommended if you're using Debian. See http://www.dotdeb.org/about/
13
+ # source recipes/dotdeb.sh
14
+ # source recipes/backports.sh
@@ -0,0 +1,24 @@
1
+ if sunzi.to_be_done "install librairies"; then
2
+ sunzi.install "autoconf"
3
+ sunzi.install "bison"
4
+ sunzi.install "libncurses5-dev"
5
+ sunzi.install "libgdbm3"
6
+ sunzi.install "libgdbm-dev"
7
+
8
+ sunzi.install "git-core"
9
+ sunzi.install "zlib1g-dev"
10
+ sunzi.install "build-essential"
11
+ sunzi.install "libssl-dev"
12
+ sunzi.install "libreadline-dev"
13
+ sunzi.install "libyaml-dev"
14
+ sunzi.install "libsqlite3-dev"
15
+ sunzi.install "sqlite3"
16
+ sunzi.install "libxml2-dev"
17
+ sunzi.install "libxslt1-dev"
18
+ sunzi.install "libcurl4-openssl-dev"
19
+ sunzi.install "python-software-properties"
20
+ sunzi.install "libffi-dev"
21
+ sunzi.install "imagemagick"
22
+
23
+ sunzi.done "install librairies"
24
+ fi
@@ -0,0 +1,19 @@
1
+ DB_NAME=<%= @attributes.db_database %>
2
+ DB_USER=<%= @attributes.db_username %>
3
+ DB_PWD=<%= @attributes.db_password %>
4
+ DB_ROOT_PWD=<%= @attributes.mysql_root_password %>
5
+
6
+ if sunzi.to_be_done "install mysql"; then
7
+ sudo debconf-set-selections <<< "mysql-server mysql-server/root_password password $DB_ROOT_PWD"
8
+ sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $DB_ROOT_PWD"
9
+ sunzi.install "mysql-server"
10
+ sunzi.install "mysql-client"
11
+ sunzi.install "libmysqlclient-dev"
12
+
13
+ sudo -u root mysql -proot -e "create database $DB_NAME;"
14
+ sudo -u root mysql -proot -e "create user '$DB_USER'@'localhost' identified by '$DB_PWD';"
15
+ sudo -u root mysql -proot -e "grant all privileges on $DB_NAME.* to '$DB_USER'@'%' identified by '$DB_PWD';"
16
+ sudo -u root mysql -proot -e "flush privileges;"
17
+
18
+ sunzi.done "install mysql"
19
+ fi
@@ -0,0 +1,6 @@
1
+ if sunzi.to_be_done "install nodejs"; then
2
+ curl -sL https://deb.nodesource.com/setup | sudo bash -
3
+ sunzi.install "nodejs"
4
+
5
+ sunzi.done "install nodejs"
6
+ fi
@@ -0,0 +1,19 @@
1
+ DEPLOYER_NAME=<%= @attributes.deployer_name %>
2
+ DEPLOYER_PATH=/home/$DEPLOYER_NAME
3
+ RBENV_PATH=$DEPLOYER_PATH/.rbenv
4
+
5
+ if sunzi.to_be_done "install passenger"; then
6
+ sunzi.mute "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7"
7
+ sunzi.install "apt-transport-https"
8
+ sunzi.install "ca-certificates"
9
+ sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main > /etc/apt/sources.list.d/passenger.list'
10
+ sunzi.mute "apt-get update"
11
+
12
+ sunzi.install "nginx-extras"
13
+ sunzi.install "passenger"
14
+
15
+ sed -i -e "s|# passenger_root|passenger_root|g" /etc/nginx/nginx.conf
16
+ sed -i -e "s|# passenger_ruby /usr/bin/passenger_free_ruby|passenger_ruby $RBENV_PATH/shims/ruby|g" /etc/nginx/nginx.conf
17
+
18
+ sunzi.done "install passenger"
19
+ fi
@@ -0,0 +1,14 @@
1
+ DB_NAME=<%= @attributes.db_database %>
2
+ DB_USER=<%= @attributes.db_username %>
3
+ DB_PWD=<%= @attributes.db_password %>
4
+
5
+ if sunzi.to_be_done "install postgres"; then
6
+ sunzi.install "postgresql"
7
+ sunzi.install "postgresql-contrib"
8
+ sunzi.install "libpq-dev"
9
+
10
+ sudo -u postgres psql -c "create user $DB_USER with password '$DB_PWD';"
11
+ sudo -u postgres psql -c "create database $DB_NAME owner $DB_USER;"
12
+
13
+ sunzi.done "install postgres"
14
+ fi
@@ -0,0 +1,28 @@
1
+ DEPLOYER_NAME=<%= @attributes.deployer_name %>
2
+ DEPLOYER_PATH=/home/$DEPLOYER_NAME
3
+ RBENV_PATH=$DEPLOYER_PATH/.rbenv
4
+ PLUGINS_PATH=$RBENV_PATH/plugins
5
+ PROFILE=$DEPLOYER_PATH/.bashrc
6
+ RUBY_VERSION=<%= @attributes.ruby_version %>
7
+ RBENV_EXPORT_PATH="export PATH=\"$RBENV_PATH/bin:$PLUGINS_PATH/ruby-build/bin:$PATH\""
8
+ RBENV_INIT='eval "$(rbenv init -)"'
9
+
10
+ if sunzi.to_be_done "install ruby"; then
11
+ git clone git://github.com/sstephenson/rbenv.git $RBENV_PATH
12
+ git clone git://github.com/sstephenson/ruby-build.git $PLUGINS_PATH/ruby-build
13
+ git clone git://github.com/sstephenson/rbenv-gem-rehash.git $PLUGINS_PATH/rbenv-gem-rehash
14
+ git clone git://github.com/dcarley/rbenv-sudo.git $PLUGINS_PATH/rbenv-sudo
15
+
16
+ eval $RBENV_EXPORT_PATH
17
+ eval $RBENV_INIT
18
+ echo $RBENV_EXPORT_PATH >> $PROFILE
19
+ echo $RBENV_INIT >> $PROFILE
20
+
21
+ rbenv install $RUBY_VERSION
22
+ rbenv global $RUBY_VERSION
23
+ echo 'gem: --no-ri --no-rdoc' > $DEPLOYER_PATH/.gemrc
24
+ gem install bundler
25
+ gem install backup
26
+
27
+ sunzi.done "install ruby"
28
+ fi
@@ -0,0 +1,11 @@
1
+ if sunzi.to_be_done "setup system"; then
2
+ sunzi.mute "apt-get update"
3
+ yes | apt-get upgrade
4
+ sunzi.mute "timedatectl set-timezone <%= @attributes.timezone %>"
5
+ sunzi.mute "locale-gen <%= @attributes.locales %>"
6
+ sunzi.mute "dpkg-reconfigure locales"
7
+ sunzi.install "curl"
8
+ sunzi.install "ntp"
9
+
10
+ sunzi.done "setup system"
11
+ fi
@@ -0,0 +1,99 @@
1
+ # This file is used to define functions under the sunzi.* namespace.
2
+
3
+ # Set $sunzi_pkg to "apt-get" or "yum", or abort.
4
+ #
5
+ if which apt-get >/dev/null 2>&1; then
6
+ export sunzi_pkg=apt-get
7
+ elif which yum >/dev/null 2>&1; then
8
+ export sunzi_pkg=yum
9
+ fi
10
+
11
+ if [ "$sunzi_pkg" = '' ]; then
12
+ echo 'sunzi only supports apt-get or yum!' >&2
13
+ exit 1
14
+ fi
15
+
16
+ # Mute STDOUT and STDERR
17
+ #
18
+ function sunzi.mute() {
19
+ echo "Running \"$@\""
20
+ `$@ >/dev/null 2>&1`
21
+ return $?
22
+ }
23
+
24
+ function sunzi.sudo_mute() {
25
+ echo "Running \"$@\""
26
+ `sudo $@ >/dev/null 2>&1`
27
+ return $?
28
+ }
29
+
30
+ # Installer
31
+ #
32
+ function sunzi.installed() {
33
+ if [ "$sunzi_pkg" = 'apt-get' ]; then
34
+ dpkg -s $@ >/dev/null 2>&1
35
+ elif [ "$sunzi_pkg" = 'yum' ]; then
36
+ rpm -qa | grep $@ >/dev/null
37
+ fi
38
+ return $?
39
+ }
40
+
41
+ # When there's "set -e" in install.sh, sunzi.install should be used with if statement,
42
+ # otherwise the script may exit unexpectedly when the package is already installed.
43
+ #
44
+ function sunzi.install() {
45
+ if sunzi.installed "$@"; then
46
+ echo "$@ already installed"
47
+ else
48
+ echo "No packages found matching $@. Installing..."
49
+ sunzi.mute "$sunzi_pkg -y install $@"
50
+ fi
51
+ return 0
52
+ }
53
+
54
+ function sunzi.sudo_install() {
55
+ if sunzi.installed "$@"; then
56
+ echo "$@ already installed"
57
+ else
58
+ echo "No packages found matching $@. Installing..."
59
+ sunzi.sudo_mute "$sunzi_pkg -y install $@"
60
+ fi
61
+ return 0
62
+ }
63
+
64
+ function sunzi.setup_progress() {
65
+ if [[ -e "$HOME/sunzi_progress.txt" ]]; then
66
+ echo "Provisioning already started"
67
+ else
68
+ echo "New provisioning"
69
+ touch "$HOME/sunzi_progress.txt"
70
+ fi
71
+ return 0
72
+ }
73
+
74
+ function sunzi.to_be_done() {
75
+ if [[ -z $(grep -Fx "Done $@" "$HOME/sunzi_progress.txt") ]]; then
76
+ echo "Executing $@"
77
+ return 0
78
+ else
79
+ echo "Done $@"
80
+ return 1
81
+ fi
82
+ }
83
+
84
+ function sunzi.done() {
85
+ echo "Done $@" | tee -a "$HOME/sunzi_progress.txt"
86
+ return 0
87
+ }
88
+
89
+ function sunzi.start_time() {
90
+ echo $(date -u +"%s")
91
+ }
92
+
93
+ function sunzi.elapsed_time() {
94
+ start=$1
95
+ finish=$(date -u +"%s")
96
+ elapsed_time=$(($finish-$start))
97
+ echo "$(($elapsed_time / 60)) minutes and $(($elapsed_time % 60)) seconds elapsed."
98
+ return 0
99
+ }
@@ -0,0 +1,8 @@
1
+ if sunzi.to_be_done "install sysstat"; then
2
+ sunzi.install "sysstat"
3
+
4
+ sed -i 's/ENABLED="false"/ENABLED="true"/' /etc/default/sysstat
5
+ /etc/init.d/sysstat restart
6
+
7
+ sunzi.done "install sysstat"
8
+ fi
@@ -0,0 +1,2 @@
1
+ sunzi.mute "apt-get update"
2
+ yes | apt-get upgrade
@@ -0,0 +1,23 @@
1
+ DEPLOYER_NAME=<%= @attributes.deployer_name %>
2
+ DEPLOYER_PWD=<%= @attributes.deployer_password %>
3
+ DEPLOYER_PATH=/home/$DEPLOYER_NAME
4
+
5
+ if sunzi.to_be_done "create deployer"; then
6
+ adduser $DEPLOYER_NAME --gecos "First Last,RoomNumber,WorkPhone,HomePhone" --disabled-password
7
+ echo "$DEPLOYER_NAME:$DEPLOYER_PWD" | sudo chpasswd
8
+ adduser $DEPLOYER_NAME sudo
9
+
10
+ mkdir $DEPLOYER_PATH/.ssh
11
+ chmod 700 $DEPLOYER_PATH/.ssh
12
+
13
+ mv files/authorized_keys $DEPLOYER_PATH/.ssh/authorized_keys
14
+ chmod 644 $DEPLOYER_PATH/.ssh/authorized_keys
15
+
16
+ chown -R $DEPLOYER_NAME:$DEPLOYER_NAME $DEPLOYER_PATH
17
+
18
+ mv files/sudoers /etc/sudoers.d/$DEPLOYER_NAME
19
+ chown root:root /etc/sudoers.d/$DEPLOYER_NAME
20
+ chmod 0440 /etc/sudoers.d/$DEPLOYER_NAME
21
+
22
+ sunzi.done "create deployer"
23
+ fi
@@ -0,0 +1,16 @@
1
+ sunzi.setup_progress
2
+
3
+ start=$(sunzi.start_time)
4
+
5
+ source recipes/setup.sh
6
+ source recipes/libraries.sh
7
+ source recipes/nodejs.sh
8
+ source recipes/postgres.sh
9
+ # source recipes/mysql.sh
10
+ source recipes/passenger.sh
11
+ source recipes/user.sh
12
+ # source recipes/sysstat.sh
13
+
14
+ sunzi.elapsed_time $start
15
+
16
+ reboot
@@ -0,0 +1,7 @@
1
+ sunzi.setup_progress
2
+
3
+ start=$(sunzi.start_time)
4
+
5
+ source recipes/ruby.sh
6
+
7
+ sunzi.elapsed_time $start
@@ -0,0 +1,5 @@
1
+ start=$(sunzi.start_time)
2
+
3
+ source recipes/update.sh
4
+
5
+ sunzi.elapsed_time $start
@@ -0,0 +1,26 @@
1
+ ---
2
+ # Dynamic variables here will be compiled to individual files in compiled/attributes.
3
+ attributes:
4
+ timezone: America/New_York
5
+ locales: en_US en_US.UTF-8
6
+ # mysql_root_password: secret
7
+
8
+ # Remote recipes here will be downloaded to compiled/recipes.
9
+ recipes:
10
+ # rvm: https://raw.githubusercontent.com/kenn/sunzi-recipes/master/ruby/rvm.sh
11
+ # dotdeb: https://raw.githubusercontent.com/kenn/sunzi-recipes/master/debian/dotdeb-wheezy.sh
12
+ # backports: https://raw.githubusercontent.com/kenn/sunzi-recipes/master/debian/backports-wheezy.sh
13
+ # mongodb-10gen: https://raw.githubusercontent.com/kenn/sunzi-recipes/master/debian/mongodb-10gen.sh
14
+
15
+ # Files specified here will be copied to compiled/files.
16
+ # files:
17
+ # - ~/.ssh/id_rsa.pub
18
+
19
+ # Fine tune how Sunzi should work.
20
+ preferences:
21
+ # Erase the generated folder on the server after deploy.
22
+ erase_remote_folder: false
23
+
24
+ # Skip retrieving remote recipes when local copies already exist. This setting helps
25
+ # iterative deploy testing considerably faster, when you have a lot of remote recipes.
26
+ cache_remote_recipes: false
data/sunzi.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'sunzi-rails'
5
+ spec.version = '0.1.0' # retrieve this value by: Gem.loaded_specs['sunzi'].version.to_s
6
+ spec.authors = ['Kenn Ejima', 'Patrice Lebel']
7
+ spec.email = ['kenn.ejima@gmail.com']
8
+ spec.homepage = 'http://github.com/o2web/sunzi-rails'
9
+ spec.summary = %q{Server provisioning utility for minimalists}
10
+ spec.description = %q{Server provisioning utility for minimalists}
11
+ spec.license = 'MIT'
12
+
13
+ spec.files = `git ls-files`.split($/)
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.require_paths = ['lib']
17
+
18
+ spec.add_runtime_dependency 'thor'
19
+ spec.add_runtime_dependency 'rainbow'
20
+ spec.add_runtime_dependency 'net-ssh'
21
+ spec.add_runtime_dependency 'rails', "~> 4.2", ">= 4.2.0"
22
+
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'minitest'
25
+ end
File without changes
File without changes
data/test/test_cli.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'sunzi'
2
+ require 'minitest/autorun'
3
+
4
+ class TestCli < Minitest::Test
5
+ def setup
6
+ @cli = Sunzi::Cli.new
7
+ end
8
+
9
+ def test_parse_target
10
+ assert_equal ['user', 'example.com', '2222'], @cli.parse_target('user@example.com:2222')
11
+ assert_equal ['root', 'example.com', '2222'], @cli.parse_target('example.com:2222')
12
+ assert_equal ['user', 'example.com', '22'], @cli.parse_target('user@example.com')
13
+ assert_equal ['root', 'example.com', '22'], @cli.parse_target('example.com')
14
+ assert_equal ['root', '192.168.0.1', '22'], @cli.parse_target('192.168.0.1')
15
+ end
16
+
17
+ def test_parse_target_with_ssh_config
18
+ ssh_config = lambda do |host|
19
+ if host == 'example.com'
20
+ { :host_name => "buzz.example.com", :user => "foobar", :port => 2222 }
21
+ else
22
+ {}
23
+ end
24
+ end
25
+
26
+ Net::SSH::Config.stub(:for, ssh_config) do
27
+ assert_equal ['foobar', 'buzz.example.com', '2222'], @cli.parse_target('example.com')
28
+ assert_equal ['foobar', 'buzz.example.com', '8080'], @cli.parse_target('example.com:8080')
29
+ assert_equal ['piyo', 'buzz.example.com', '2222'], @cli.parse_target('piyo@example.com')
30
+ assert_equal ['piyo', 'buzz.example.com', '8080'], @cli.parse_target('piyo@example.com:8080')
31
+ assert_equal ['root', '192.168.0.1', '22'], @cli.parse_target('192.168.0.1')
32
+ end
33
+ end
34
+
35
+ def test_create
36
+ @cli.create 'sandbox'
37
+ assert File.exist?('sandbox/sunzi.yml')
38
+ FileUtils.rm_rf 'sandbox'
39
+ end
40
+
41
+ def test_copy_local_files
42
+ pwd = Dir.pwd
43
+ Dir.chdir('test/sunzi_test_dir')
44
+
45
+ @cli.copy_local_files({}, :copy_file)
46
+ assert File.exists?('compiled/files/nginx/nginx.conf')
47
+ assert File.exists?('compiled/recipes/nginx.sh')
48
+ FileUtils.rm_rf 'compiled'
49
+
50
+ Dir.chdir(pwd)
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sunzi-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kenn Ejima
8
+ - Patrice Lebel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-01-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rainbow
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: net-ssh
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rails
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '4.2'
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 4.2.0
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: '4.2'
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 4.2.0
76
+ - !ruby/object:Gem::Dependency
77
+ name: rake
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ - !ruby/object:Gem::Dependency
91
+ name: minitest
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ description: Server provisioning utility for minimalists
105
+ email:
106
+ - kenn.ejima@gmail.com
107
+ executables:
108
+ - sunzi-cap
109
+ extensions: []
110
+ extra_rdoc_files: []
111
+ files:
112
+ - ".gitignore"
113
+ - Gemfile
114
+ - README.md
115
+ - Rakefile
116
+ - bin/sunzi-cap
117
+ - lib/sunzi.rb
118
+ - lib/sunzi/cli.rb
119
+ - lib/sunzi/cli/capistrano.rb
120
+ - lib/sunzi/cli/database.rb
121
+ - lib/sunzi/cli/secrets.rb
122
+ - lib/sunzi/logger.rb
123
+ - lib/sunzi/utility.rb
124
+ - lib/templates/create/files/authorized_keys
125
+ - lib/templates/create/files/sudoers
126
+ - lib/templates/create/install.sh
127
+ - lib/templates/create/recipes/libraries.sh
128
+ - lib/templates/create/recipes/mysql.sh
129
+ - lib/templates/create/recipes/nodejs.sh
130
+ - lib/templates/create/recipes/passenger.sh
131
+ - lib/templates/create/recipes/postgres.sh
132
+ - lib/templates/create/recipes/ruby.sh
133
+ - lib/templates/create/recipes/setup.sh
134
+ - lib/templates/create/recipes/sunzi.sh
135
+ - lib/templates/create/recipes/sysstat.sh
136
+ - lib/templates/create/recipes/update.sh
137
+ - lib/templates/create/recipes/user.sh
138
+ - lib/templates/create/roles/admin.sh
139
+ - lib/templates/create/roles/deployer.sh
140
+ - lib/templates/create/roles/updater.sh
141
+ - lib/templates/create/sunzi.yml
142
+ - sunzi.gemspec
143
+ - test/sunzi_test_dir/files/nginx/nginx.conf
144
+ - test/sunzi_test_dir/recipes/nginx.sh
145
+ - test/test_cli.rb
146
+ homepage: http://github.com/o2web/sunzi-rails
147
+ licenses:
148
+ - MIT
149
+ metadata: {}
150
+ post_install_message:
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 2.4.5.1
167
+ signing_key:
168
+ specification_version: 4
169
+ summary: Server provisioning utility for minimalists
170
+ test_files:
171
+ - test/sunzi_test_dir/files/nginx/nginx.conf
172
+ - test/sunzi_test_dir/recipes/nginx.sh
173
+ - test/test_cli.rb
174
+ has_rdoc: