Created
February 22, 2016 16:15
-
-
Save pilif/1e2610dd7aa57323e0b2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby1.9.3 | |
require 'pg' | |
require 'optparse' | |
require 'tmpdir' | |
require 'shellwords' | |
require 'socket' | |
require 'json' | |
require 'digest' | |
require 'public_suffix' | |
require 'pp' | |
CONFIG_ROOT="/srv/storage/acme/nginx" | |
ACME_STATE="/srv/storage/acme/state" | |
ACME_HOOKS="/srv/storage/acme/hooks" | |
MASTER_IP_CHECK="ip addr show | grep xxx.xxx.xxx.xxx > /dev/null" | |
POSTGRES_CONNECTION="xxxxxx" | |
RESTART_NGINX_CMD="sudo -n service nginx reload > /dev/null 2>&1" | |
def main | |
options = {} | |
p = OptionParser.new do |opt| | |
opt.on('-w', "Watch auto-update"){ |o| options[:watch] = true } | |
opt.on('-v', "Be verbose") { |o| options[:verbose] = true } | |
end | |
begin | |
p.parse! | |
rescue OptionParser::InvalidOption => e | |
puts e | |
puts p | |
exit 1 | |
end | |
unless (File.directory?(CONFIG_ROOT) && File.writable?(CONFIG_ROOT)) | |
abort "#{CONFIG_ROOT} does not exist or is not writable." | |
end | |
$verbose = !!options[:verbose] | |
if options[:watch] then | |
watch | |
else | |
conn = PG.connect(POSTGRES_CONNECTION) | |
query conn, options | |
end | |
end | |
def watch | |
conn = nil | |
while true do | |
begin | |
if conn == nil | |
conn = PG.connect POSTGRES_CONNECTION | |
conn.exec "listen routing_updated" | |
query conn | |
end | |
conn.wait_for_notify do |event, pid| | |
STDERR.puts "Received change notification" if $verbose | |
query conn | |
end | |
rescue Interrupt | |
exit 0 | |
rescue PG::Error => e | |
STDERR.puts "Postgres error #{e}. Reconnecting in 30s" if $verbose | |
conn.close if conn | |
conn = nil | |
sleep 30 | |
end | |
end | |
end | |
def query(conn, options={}) | |
unless master? | |
puts "Not doing anything as I'm not master" | |
return | |
end | |
q = " | |
select unnest(acme_hosts) as acme_host from customer_routing where acme_hosts is not null | |
union | |
select customizer || '.' || environment || '.xxxxx' as acme_host | |
from customer_routing where environment != 'production' | |
"; | |
seen = {} | |
by_suffix = {} | |
need_restart = false | |
conn.exec_params(q) do |res| | |
res.each do |row| | |
host = row["acme_host"] | |
next unless resolves? host | |
suffix = PublicSuffix.parse(host).domain; | |
by_suffix[suffix] ||= [] | |
by_suffix[suffix].push(host) | |
by_suffix[suffix].uniq! | |
seen[host] = true | |
end | |
end | |
mark_wanted by_suffix | |
need_restart = true if write_config by_suffix | |
need_restart = true if check_deleted(seen) | |
restart_nginx if need_restart | |
end | |
def check_deleted(seen_hosts) | |
work_done = false; | |
Dir.glob(CONFIG_ROOT + '/*.conf').each { |f| | |
host = (File.basename f).gsub(/\.conf$/, '') | |
unless seen_hosts[host] | |
puts "Host #{host} is gone. Removing config" if $verbose | |
File.unlink f | |
unwant host | |
work_done = true | |
end | |
} | |
work_done | |
end | |
def restart_nginx | |
puts "would restart nginx" if $verbose | |
system(RESTART_NGINX_CMD) | |
end | |
def mark_wanted(by_suffix) | |
by_suffix.each do |suffix,hosts| | |
want hosts | |
end | |
end | |
# not setting the HOOKS env dir. We manage restarting on our own | |
def unwant(host) | |
system( | |
{"ACME_STATE_DIR" => ACME_STATE}, | |
Shellwords.join(["acmetool", "unwant", host]) | |
) | |
end | |
def want(hosts) | |
puts "telling acmetool we want: #{hosts.join(", ")}" if $verbose | |
system( | |
{"ACME_STATE_DIR" => ACME_STATE}, | |
Shellwords.join(["acmetool", "want"].concat(hosts)) | |
) | |
end | |
def write_config(by_suffix) | |
need_restart = false; | |
by_suffix.values().flatten().each do |host| | |
outfile = File.join(CONFIG_ROOT, "#{host}.conf") | |
next if (File.exists?(outfile)) | |
tempname = Dir::Tmpname.create('acmecfg', CONFIG_ROOT) { } | |
File.open(tempname, 'w') do |fh| | |
fh.write <<-eot | |
server { | |
listen 443 ssl spdy; | |
listen [::]:443 ssl spdy; | |
server_name #{host}; | |
ssl_certificate /srv/storage/acme/state/live/#{host}/fullchain; | |
ssl_certificate_key /srv/storage/acme/state/live/#{host}/privkey; | |
include /etc/nginx/xxxx/acme-ssl.conf; | |
} | |
eot | |
end | |
File.rename(tempname, outfile) | |
puts "config written to #{outfile}" if $verbose | |
need_restart = true | |
end | |
need_restart | |
end | |
def master?() | |
system(MASTER_IP_CHECK) | |
end | |
def resolves?(host) | |
begin | |
return !!Socket.gethostbyname(host) | |
rescue SocketError | |
return false | |
end | |
end | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment