2
0
mirror of https://github.com/moebooru/moebooru synced 2025-08-22 01:47:48 +00:00
moebooru/lib/mirrors.rb
2024-01-08 19:39:01 +09:00

205 lines
7.1 KiB
Ruby

module Mirrors
class MirrorError < Exception; end
def ssh_open_pipe(mirror, command, timeout = 30)
remote_user_host = "#{mirror[:user]}@#{mirror[:host]}"
ret = nil
IO.popen("/usr/bin/ssh -o ServerAliveInterval=30 -o Compression=no -o BatchMode=yes -o ConnectTimeout=#{timeout} #{remote_user_host} '#{command}'") do |f|
ret = yield(f)
end
if ($CHILD_STATUS & 0xFF) != 0
raise MirrorError, "Command \"%s\" to %s exited with signal %i" % [ command, mirror[:host], $CHILD_STATUS & 0xFF ]
end
if ($CHILD_STATUS >> 8) != 0
raise MirrorError, "Command \"%s\" to %s exited with status %i" % [ command, mirror[:host], $CHILD_STATUS >> 8 ]
end
ret
end
module_function :ssh_open_pipe
def filter_mirror_list(options)
results = []
CONFIG["mirrors"].each do |mirror|
next if options[:previews_only] != mirror[:previews_only]
results << mirror
end
results
end
module_function :filter_mirror_list
def create_mirror_paths(dirs, options = {})
dirs = dirs.uniq
target_mirrors = filter_mirror_list(options)
target_mirrors.each do |mirror|
remote_user_host = "#{mirror[:user]}@#{mirror[:host]}"
remote_dirs = []
dirs.each do |dir|
remote_dirs << mirror[:data_dir] + "/" + dir
end
# Create all directories in one go.
system("/usr/bin/ssh", "-o", "Compression=no", "-o", "BatchMode=yes",
remote_user_host, "mkdir -p #{remote_dirs.uniq.join(" ")}")
end
end
module_function :create_mirror_paths
# Copy a file to all mirrors. file is an absolute path which must be
# located in public/data; the files will land in the equivalent public/data
# on each mirror.
#
# If options[:previews_only] is true, copy only to :previews_only mirrors; otherwise
# copy only to others.
#
# Because we have no mechanism for indicating that a file is only available on
# certain mirrors, if any mirror fails to upload, MirrorError will be thrown
# and the file should be treated as completely unwarehoused.
def copy_file_to_mirrors(file, options = {})
# CONFIG[:data_dir] is equivalent to our local_base.
local_base = "#{Rails.root}/public/data/"
options = { timeout: 30 }.merge(options)
if file[0, local_base.length] != local_base
raise "Invalid filename to mirror: \"%s" % file
end
expected_md5 = Moebooru::Hasher.compute_one(file, :md5)
target_mirrors = filter_mirror_list(options)
target_mirrors.each do |mirror|
remote_user_host = "#{mirror[:user]}@#{mirror[:host]}"
remote_filename = "#{mirror[:data_dir]}/#{file[local_base.length, file.length]}"
# Tolerate a few errors in case of communication problems.
retry_count = 0
begin
# Check if the file is already mirrored before we spend time uploading it.
# Linux needs md5sum; FreeBSD needs md5 -q.
actual_md5 = Mirrors.ssh_open_pipe(mirror,
"if [ -f #{remote_filename} ]; then (which md5sum >/dev/null) && md5sum #{remote_filename} || md5 -q #{remote_filename}; fi",
timeout = options[:timeout]) { |f| f.gets }
if actual_md5 =~ /^[0-9a-f]{32}/
actual_md5 = actual_md5.slice(0, 32)
if expected_md5 == actual_md5
next
end
end
unless system("/usr/bin/scp", "-pq", "-o", "Compression no", "-o", "BatchMode=yes",
"-o", "ConnectTimeout=%i" % timeout,
file, "#{remote_user_host}:#{remote_filename}")
raise MirrorError, "Error copying #{file} to #{remote_user_host}:#{remote_filename}"
end
# Don't trust scp; verify the files.
actual_md5 = Mirrors.ssh_open_pipe(mirror, "if [ -f #{remote_filename} ]; then (which md5sum >/dev/null) && md5sum #{remote_filename} || md5 -q #{remote_filename}; fi") { |f| f.gets }
if actual_md5 !~ /^[0-9a-f]{32}/
raise MirrorError, "Error verifying #{remote_user_host}:#{remote_filename}: #{actual_md5}"
end
actual_md5 = actual_md5.slice(0, 32)
if expected_md5 != actual_md5
raise MirrorError, "Verifying #{remote_user_host}:#{remote_filename} failed: got #{actual_md5}, expected #{expected_md5}"
end
rescue MirrorError
retry_count += 1
raise if retry_count == 3
retry
end
end
end
module_function :copy_file_to_mirrors
# Return a URL prefix for a file. If not warehoused, always returns the main
# server. If a seed is specified, seeds the server selection; otherwise, each
# IP will always use the same server.
#
# If :zipfile is set, ignore mirrors with the :nozipfile flag.
if CONFIG["image_store"] == :remote_hierarchy
def select_main_image_server
return CONFIG["url_base"] if !CONFIG["image_servers"] || CONFIG["image_servers"].empty?
raise 'CONFIG["url_base"] is set incorrectly; please see config/default_config.rb' if CONFIG["image_servers"][0].class == String
CONFIG["image_servers"][0][:server]
end
def select_image_server(is_warehoused, seed = 0, options = {})
return CONFIG["url_base"] if !CONFIG["image_servers"] || CONFIG["image_servers"].empty?
raise 'CONFIG["url_base"] is set incorrectly; please see config/default_config.rb' if CONFIG["image_servers"][0].class == String
unless is_warehoused
# return CONFIG["url_base"]
return CONFIG["image_servers"][0][:server]
end
mirrors = CONFIG["image_servers"]
if options[:preview]
mirrors = mirrors.select do |mirror|
mirror[:nopreview] != true
end
end
unless options[:preview]
mirrors = mirrors.select do |mirror|
mirror[:previews_only] != true
end
end
if options[:zipfile]
mirrors = mirrors.select do |mirror|
mirror[:nozipfile] != true
end
end
raise "No usable mirrors" if mirrors.empty?
total_weights = 0
mirrors.each { |s| total_weights += s[:traffic] }
seed += Thread.current["danbooru-ip_addr_seed"] || 0
seed %= total_weights
server = nil
mirrors.each do |s|
w = s[:traffic]
if seed < w
server = s
break
end
seed -= w
end
server ||= mirrors[0]
# If this server has aliases, and an ID has been specified, pick a random server alias
# with the ID as a seed so we always use the same server name for the same image.
if !options[:id].nil? && !server[:aliases].nil? && options[:use_aliases]
server_count = server[:aliases].length + 1
server_index = options[:id] % server_count
if server_index == 0
return server[:server]
else
return server[:aliases][server_index - 1]
end
end
server[:server]
end
else
def select_main_image_server
CONFIG["url_base"]
end
def select_image_server(_is_warehoused, _seed = 0, _options = {})
CONFIG["url_base"]
end
end
module_function :select_main_image_server
module_function :select_image_server
end