sunzi-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: