Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chefification of StockAid #493

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@
/log/*
!/log/.keep
/tmp

# Ignore Chef JSON files
/chef/*.json
7 changes: 5 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ Metrics/AbcSize:
Max: 16
Metrics/ClassLength:
Max: 150
Metrics/LineLength:
Max: 120
Metrics/MethodLength:
Max: 15

Metrics/LineLength:
Max: 120
Exclude:
- chef/**/*

Rails:
Enabled: true

Expand Down
147 changes: 147 additions & 0 deletions chef/deploy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env ruby
require "json"
require "optparse"

# This script is for deploying to the StockAid instance. Unless configured via
# options, it expects to connect via the "chef" user to the "stockaid" host (you
# can set up this host in your ~/.ssh/config file, or provide the real host via
# a command line option).

REMOTE_DIR = File.expand_path("../remote", __FILE__)
CHEF_VERSION = "12.19.36".freeze
SSH_DEFAULTS = { batch: true }.freeze

DEFAULT_OPTIONS = {
user: "chef",
host: "stockaid",
port: 22,
identity: File.expand_path("~/.ssh/stockaid_rsa"),
files_dir: File.expand_path("../files", __FILE__),
repos_dir: File.expand_path("../repos", __FILE__),
json: File.expand_path("../stockaid.json", __FILE__),
clear_host: false,
ssh: false
}.freeze

OPTIONS = Hash.new { |_hash, key| DEFAULT_OPTIONS[key] }

def system_exec(cmd, options = {})
puts cmd

if options[:exec]
exec cmd
else
system cmd
end
end

def ssh_options(options = {})
options = SSH_DEFAULTS.merge(options)
result = []
result << "-o 'BatchMode yes'" if options[:batch]
result.join(" ")
end

def ssh(command, options = {})
if command.is_a?(Hash)
options = options.merge(command)
options[:batch] = false if options[:exec]
command = nil
end

user = OPTIONS[:user]
host = OPTIONS[:host]
port = OPTIONS[:port]
identity_file = OPTIONS[:identity]

if options[:exec]
system_exec "ssh '#{user}@#{host}' -p #{port} -i #{identity_file} #{ssh_options(options)}", exec: true
else
force_tty = "-t -t" unless options[:batch]
system_exec "ssh '#{user}@#{host}' -p #{port} -i #{identity_file} #{force_tty} #{ssh_options(options)} '#{command}'"
end
end

def scp(from, to, options = {})
user = OPTIONS[:user]
host = OPTIONS[:host]
port = OPTIONS[:port]
identity_file = OPTIONS[:identity]
server = "#{user}@#{host}"
from.sub!(/\Aserver:/, "#{server}:")
to.sub!(/\Aserver:/, "#{server}:")
recursive = "-r" if options[:recursive]
system_exec "scp #{recursive} -P #{port} -i #{identity_file} #{ssh_options(options)} '#{from}' '#{to}'"
end

def rsync(from, to, options = {})
user = OPTIONS[:user]
host = OPTIONS[:host]
port = OPTIONS[:port]
identity_file = OPTIONS[:identity]
server = "#{user}@#{host}"
from.sub!(/\Aserver:/, "#{server}:")
to.sub!(/\Aserver:/, "#{server}:")
system_exec %(rsync -e "ssh -p #{port} -i #{identity_file} #{ssh_options(options)}" -av '#{from}' '#{to}')
end

OptionParser.new do |opts|
opts.banner = "Usage: ./deploy.rb [options]"

opts.on "-s", "--ssh", "Just ssh to the server, don't actually deploy" do |_ssh|
OPTIONS[:ssh] = true
end

opts.on "-c", "--clear-host", "Clear the host from known hosts" do |clear|
OPTIONS[:clear_host] = clear
end

opts.on "-u", "--user USER", "User to ssh with" do |user|
OPTIONS[:user] = user
end

opts.on "-h", "--host HOST", "Host to ssh with" do |host|
OPTIONS[:host] = host
end

opts.on "-p", "--port PORT", "Port to ssh with" do |port|
OPTIONS[:port] = port.to_i
end

opts.on "-i", "--identity FILE", "Identity file to ssh with" do |identity|
OPTIONS[:identity] = File.expand_path(identity)
end

opts.on "-j", "--json FILE", "Chef json config file" do |config|
OPTIONS[:json] = File.expand_path(config)
end
end.parse!

JSON.parse(File.read(OPTIONS[:json])).fetch("meta", {}).each do |key, value|
next if OPTIONS.include?(key.to_sym)
puts "Using JSON default #{key}: #{value.inspect}"
value = File.expand_path(value) if %w(identity files_dir repos_dir).include?(key)
OPTIONS[key.to_sym] = value
end

ssh exec: true if OPTIONS[:ssh]

system_exec "ssh-keygen -R '#{OPTIONS[:host]}'" if OPTIONS[:clear_host]

unless ssh "echo ssh key is working"
pub_file = "#{OPTIONS[:identity]}.pub"
public_key = File.read(pub_file)
saved_key = ssh %(mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && echo "#{public_key}" >> ~/.ssh/authorized_keys), batch: false
abort "Failed to save authorized key!" unless saved_key
end

rsync "#{REMOTE_DIR}/", "server:~/next-stockaid-chef"
rsync OPTIONS[:files_dir], "server:~/chef_data/"
rsync OPTIONS[:repos_dir], "server:~/chef_data/"

if OPTIONS[:json]
puts "Using json file: #{OPTIONS[:json]}"
scp OPTIONS[:json], "server:~/next-stockaid-chef/solo.json"
end

ssh "sudo ~/next-stockaid-chef/install.sh '#{OPTIONS[:user]}' '#{CHEF_VERSION}'"
25 changes: 25 additions & 0 deletions chef/remote/cookbooks/stockaid/attributes/default.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
default[:stockaid][:github_url] = "https://github.com/on-site/StockAid.git"
default[:stockaid][:home] = "/home/stockaid"
default[:stockaid][:user] = "stockaid"
default[:stockaid][:group] = "stockaid"
default[:stockaid][:dir] = node[:stockaid][:home]
default[:stockaid][:repo_dir] = File.join(node[:stockaid][:dir], "StockAid")
default[:stockaid][:domain] = "orders.gratefulgarment.org"
default[:stockaid][:site_name] = "The Grateful Garment Project"

default[:stockaid][:google][:api_key] = nil # Set for some Google integrations
default[:stockaid][:google][:drive_json] = nil # Set for automatic DB backups to Google Drive

default[:stockaid][:mailer][:default_from] = "[email protected]"
default[:stockaid][:mailer][:default_host] = "orders.gratefulgarment.org"

default[:stockaid][:mailgun][:enabled] = true
default[:stockaid][:mailgun][:domain] = "mg.gratefulgarment.org"
default[:stockaid][:mailgun][:api_key] = nil # This must be set for mailgun to work

default[:stockaid][:letsencrypt][:enabled] = true
default[:stockaid][:letsencrypt][:email] = nil # This must be set for letsencrypt to work

default[:stockaid][:newrelic][:enabled] = true
default[:stockaid][:newrelic][:app_name] = "grateful-garment"
default[:stockaid][:newrelic][:license_key] = nil # This must be set for newrelic to work
108 changes: 108 additions & 0 deletions chef/remote/cookbooks/stockaid/libraries/stockaid_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
require "json"

module StockAid
class Environment
attr_reader :node

def initialize(node)
@node = node
end

def to_h
simple_env.merge(google_env).merge(mailgun_env).merge(newrelic_env)
end

private

def simple_env
{
"STOCKAID_DATABASE_HOST" => "localhost",
"STOCKAID_DATABASE_USERNAME" => "stockaid",
"STOCKAID_DATABASE_PASSWORD" => database_password,
"STOCKAID_SECRET_KEY_BASE" => secret_key_base,
"STOCKAID_DEVISE_PEPPER" => devise_pepper,
"STOCKAID_ENV_SETUP" => "3",
"STOCKAID_SITE_NAME" => node[:stockaid][:site_name],
"STOCKAID_ACTION_MAILER_DEFAULT_FROM" => node[:stockaid][:mailer][:default_from],
"STOCKAID_ACTION_MAILER_DEFAULT_HOST" => node[:stockaid][:mailer][:default_host]
}
end

def database_password
File.read(File.join(node[:stockaid][:dir], ".stockaid-db-password")).strip
end

def secret_key_base
File.read(File.join(node[:stockaid][:dir], ".stockaid-secret-key-base"))
end

def devise_pepper
File.read(File.join(node[:stockaid][:dir], ".stockaid-devise-pepper"))
end

def google_env
google_api_env.merge(google_drive_env)
end

def google_api_env
if node[:stockaid][:google][:api_key]
{ "STOCKAID_GOOGLE_API_KEY" => node[:stockaid][:google][:api_key] }
else
{}
end
end

def google_drive_env
if node[:stockaid][:google][:drive_json]
{ "STOCKAID_GOOGLE_DRIVE_SERVICE_ACCOUNT_JSON" => node[:stockaid][:google][:drive_json].to_json }
else
{}
end
end

def mailgun_env
if node[:stockaid][:mailgun][:enabled]
{
"STOCKAID_MAILGUN_DOMAIN" => node[:stockaid][:mailgun][:domain],
"STOCKAID_MAILGUN_API_KEY" => node[:stockaid][:mailgun][:api_key]
}
else
{}
end
end

def newrelic_env
if node[:stockaid][:newrelic][:enabled]
{
"NEW_RELIC_APP_NAME" => node[:stockaid][:newrelic][:app_name],
"NEW_RELIC_LICENSE_KEY" => node[:stockaid][:newrelic][:license_key]
}
else
{}
end
end
end

module Helper
module_function def systemd_env_variable(key, value)
{
"\\" => "\\\\",
"\n" => "\\n"
}.each do |string, replacement|
value = value.gsub(string, replacement)
end

if value.include?('"') && value.include?("'")
raise "Please avoid using both ' and \" in an environment variable: #{value.inspect}"
elsif value.include?('"')
"Environment='#{key}=#{value}'"
else
%(Environment="#{key}=#{value}")
end
end

module_function def stockaid_environment(node)
StockAid::Environment.new(node).to_h
end
end
end
6 changes: 6 additions & 0 deletions chef/remote/cookbooks/stockaid/metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name "stockaid"
maintainer "Mike Virata-Stone"
maintainer_email "[email protected]"
license "All rights reserved"
description "Sets up and configures StockAid"
version "0.0.1"
31 changes: 31 additions & 0 deletions chef/remote/cookbooks/stockaid/recipes/auto_updates.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
directory "/var/log" do
owner "root"
group "root"
mode "0775"
recursive true
end

file "/etc/cron.weekly/apt-auto-updates" do
content %(# This file is managed by Chef
echo "**************" >> /var/log/apt-auto-updates.log
date >> /var/log/apt-auto-updates.log
apt-get update >> /var/log/apt-auto-updates.log
apt-get upgrade --assume-yes >> /var/log/apt-auto-updates.log
echo "Updates (if any) installed"
)
owner "root"
group "root"
mode "0755"
end

file "/etc/logrotate.d/apt-auto-updates" do
content %(# This file is managed by Chef
/var/log/apt-auto-updates.log {
rotate 20
weekly
size 500k
compress
notifempty
}
)
end
36 changes: 36 additions & 0 deletions chef/remote/cookbooks/stockaid/recipes/database.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
%w(
postgresql
libpq-dev
).each do |pkg|
package pkg
end

password_file = File.join(node[:stockaid][:dir], ".stockaid-db-password")

file password_file do
content lazy { `openssl rand -base64 18` }
user node[:stockaid][:user]
group node[:stockaid][:group]
mode "0600"
action :create_if_missing
end

execute "create-postgres-user" do
command lazy { %(psql -c "CREATE USER \\"stockaid\\" WITH LOGIN PASSWORD '#{File.read(password_file).strip}'") }
user "postgres"
not_if %{psql postgres -tAc "SELECT 1 FROM pg_roles WHERE LOWER(rolname) = 'stockaid'" | grep 1}, user: "postgres"
end

execute "create-postgres-database" do
command lazy { %(psql -c "CREATE DATABASE \\"stockaid_production\\" WITH OWNER \\"stockaid\\" ENCODING 'unicode'") }
user "postgres"
not_if %{psql postgres -tAc "SELECT 1 FROM pg_database WHERE LOWER(datname) = 'stockaid_production'" | grep 1},
user: "postgres"
notifies :run, "execute[grant-postgres-database-to-user]", :immediately
end

execute "grant-postgres-database-to-user" do
command %(psql -c "GRANT ALL PRIVILEGES ON DATABASE \\"stockaid_production\\" TO \\"stockaid\\"")
user "postgres"
action :nothing
end
9 changes: 9 additions & 0 deletions chef/remote/cookbooks/stockaid/recipes/default.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
include_recipe "stockaid::repo"
include_recipe "stockaid::rvm"
include_recipe "stockaid::database"
include_recipe "stockaid::rails"
include_recipe "stockaid::sidekiq"
include_recipe "stockaid::self_signed_ssl"
include_recipe "stockaid::nginx"
include_recipe "stockaid::letsencrypt" if node[:stockaid][:letsencrypt][:enabled]
include_recipe "stockaid::auto_updates"
Loading