Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@pilif
Created February 22, 2016 16:15
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pilif/1e2610dd7aa57323e0b2 to your computer and use it in GitHub Desktop.
Save pilif/1e2610dd7aa57323e0b2 to your computer and use it in GitHub Desktop.
#!/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