Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 312 lines (256 sloc) 7.72 KB
#!/usr/bin/env ruby
## Usage:
## tolya_deployer all
## tolya_deployer production_config
## tolya_deployer jekyll
## tolya_deployer gzip_and_upload
## tolya_deployer superfeedr_ping
##
## Dependencies:
## gem install jekyll ansi
PUBLIC = 'public' # Jekyll build dir. Keep up to date with _config.yml.
STAGING = 'staging' # Gzipped files, ready to upload.
HOST = 'n12v.com'
CONFIG = '_production.yml'
# Files to gzip
EXTENSIONS = %w{
html
css
js
xml
svg
atom
txt
plist
ico
}
require 'fileutils'
require 'ansi/code'
def system_run(param)
puts(ANSI.blue{param})
system(param)
end
def info(text)
puts(ANSI.yellow{text})
end
def warn(text)
puts(ANSI.red{text})
end
# http://stackoverflow.com/a/9381776/16185
def deep_merge(first, second)
merger = proc { |key, v1, v2|
Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
}
first.merge(second, &merger)
end
def all_files_in(directory)
file_paths = []
require 'find'
Dir.chdir(directory) do
Find.find('.') {|path|
path.sub!(/^\.\//, '')
if FileTest.directory?(path)
first_char = File.basename(path)[0]
if first_char == '_' or (first_char == '.' and path.size > 1)
info "Ignored directory: #{path}"
Find.prune # Don't look any further into this directory.
else
next
end
else
file_paths.push(path)
end
}
end
file_paths
end
def fix_modified_time(source_dir, target_dir, verbose: false)
# Fix modified time of generated files.
# public/main.css should have modified time of source/main.css.
# Currently, it has modified time of the last build.
info("Update 'modified' attributes for all files in '#{target_dir}'")
file_paths = all_files_in(target_dir)
file_paths.each{|path|
source_file = File.join(source_dir, path)
if File.exist?(source_file)
target_file = File.join(target_dir, path)
time_source = File.mtime source_file
time_public = File.mtime target_file
if time_source.to_i == time_public.to_i
verbose and info "Time is the same. Nothing to do."
verbose and info " #{time_source} #{source_file}\n #{time_public} #{target_file}"
next
end
if time_source.to_i > time_public.to_i
info "#{source_file} is newer than #{target_file}. Forgot to build Jekyll?"
info " #{time_source} #{source_file}\n #{time_public} #{target_file}"
next
end
# Fix modified time
verbose and info "#{target_file} date updated:\n #{time_public}\n #{time_source}"
File.utime(time_source, time_source, target_file)
end
}
end
def modified_time_copy(source, destination)
time_source = File.mtime source
time_destination = File.mtime destination
if time_source.to_i == time_destination.to_i
info "Time is the same. Nothing to do."
info " #{time_source} #{source}\n #{time_destination} #{destination}"
return
end
info "#{destination} - timestamp is about to be transfered:"
info " from #{time_source} #{source}"
info " to #{time_destination} #{destination}"
File.utime(time_source, time_source, destination)
end
def gzip_and_replace_one(path)
public_file = File.join(PUBLIC, path)
build_file = File.join(STAGING, path)
system_run("zopfli --i30 -v -c #{public_file} > #{build_file}")
size_orig = File.size(public_file)
size_gzip = File.size(build_file)
if size_gzip < size_orig * 0.95 # Only Gzip when the benefit is >5%
return true
else
FileUtils.cp(public_file, build_file)
return false
end
end
def gzip_and_replace(updated_files)
not_zipped = []
updated_files.each{|path|
didZip = gzip_and_replace_one(path)
if didZip
public_file = File.join(PUBLIC, path)
build_file = File.join(STAGING, path)
modified_time_copy(public_file, build_file)
else
not_zipped << path
end
}
info("Not zipped #{not_zipped.join(', ')}.") unless not_zipped.empty?
return not_zipped
end
module Tolya
def Tolya.production_config
info "Merge _config.yml and production.yml into #{CONFIG}"
require 'yaml'
config = YAML.load_file('_config.yml')
production = YAML.load_file('production.yml')
yaml = deep_merge(config, production).to_yaml
File.open(CONFIG, 'w') {|f|
f.write(yaml)
}
system_run("jekyll doctor --config #{CONFIG}")
end
def Tolya.jekyll
info("Build Jekyll from source/ to #{PUBLIC}/")
system_run("rm #{PUBLIC}/min.js")
system_run("jekyll build --config #{CONFIG}")
system_run('rm -rf _asset_bundler_cache') # Asset bundler’s cache acts strange
fix_modified_time('source', PUBLIC)
end
def Tolya.gzip_and_upload
deleted = []
added = []
modified = []
Dir.chdir('public') do
git_porcelain = `git status -z --porcelain=v1`
git_porcelain.split(/\0/).each{|item|
path = item[3..-1]
if item[0] == "?" || item[0] == "A"
added << path
elsif item[0] == "M" || item[1] == "M"
modified << path
elsif item[0] == "D" || item[1] == "D"
deleted << path
else
info("Unknown git status item: #{item}")
end
}
system_run 'git diff --compact-summary'
end
if deleted.empty? and added.empty? and modified.empty?
puts "Nothing changed. Exiting."
exit 0
end
new_files = added + modified
puts "About to gzip"
ask_continue
unzipped = gzip_and_replace(new_files)
puts "Upload new files to s3://#{HOST}?"
ask_continue do
# --cf-invalidate doesn't work in S3cmd v2.0.2 https://github.com/s3tools/s3cmd/issues/1046
# At ./vendor/s3cmd/s3cmd and have a copy of the latest version from the s3cmd repository.
delete_removed_flag = deleted.empty? ? "" : "--delete-removed"
system_run("./vendor/s3cmd/s3cmd sync --verbose --no-mime-magic --cf-invalidate --acl-public --no-preserve #{delete_removed_flag} ./#{STAGING}/ s3://#{HOST}/")
# --dry-run
new_files.each{|path|
next if unzipped.include? path
remote_path = File.join(HOST, path)
system_run("./vendor/s3cmd/s3cmd modify --add-header='Content-Encoding: gzip' --verbose --acl-public --no-mime-magic --no-preserve s3://#{remote_path}")
# --dry-run
}
end
Dir.chdir('public') do
commit_count = `git rev-list --count HEAD`
commit_message = "r#{commit_count.to_i + 1}"
puts "About to commit #{commit_message}"
ask_continue
system_run 'git add .'
system_run "git commit -m #{commit_message}"
system_run 'git push backup --force'
end
end
def Tolya.superfeedr_ping
# Do the pubsubhubbub thing.
require 'net/http'
puts 'POST n12v.superfeedr.com'
res = Net::HTTP.post_form(URI.parse('https://n12v.superfeedr.com/'), {
'hub.mode' => 'publish',
'hub.url' => 'https://n12v.com/posts.atom'
})
puts "#{res.code} #{res.message}"
puts res.body
end
end
def ask_continue(&block)
printf "Continue? Y/n: "
prompt = STDIN.gets.chomp().downcase
if !prompt.empty? && prompt != 'y' && prompt != 'yes'
if !block
exit 0
end
else
block.call if block
end
end
def print_help
$stderr.puts File.read(__FILE__).lines.grep(/^##[^!]/) {|line|
line.sub /^## ?/, ''
}.join
exit 1
end
if $stdin.tty? and ARGV.empty? or ARGV.delete'-h' or ARGV.delete'--help'
print_help
else
case ARGV.first
when "all"
Tolya.production_config
Tolya.jekyll
Tolya.gzip_and_upload
when "production_config"
Tolya.production_config
when "jekyll"
Tolya.jekyll
when "gzip_and_upload"
Tolya.gzip_and_upload
when "superfeedr_ping"
Tolya.superfeedr_ping
else
$stderr.puts "Unknown arg '#{ARGV.first}'\n\n"
print_help
end
end
You can’t perform that action at this time.