#!/usr/bin/env ruby -wKU # == Synopsis # # upload [-k «keyfile»] [-d «destination»] [-m «json»] «file» # # == Usage # # --help: # show help. # # --keyfile/-k: # Keyfile used for signing. # # --destination/-d: # Which GitHub ‘«user»/«repository»’ we should upload to. # # --merge/-m: # JSON that should be placed in the result dictionary. # # --description/-s: # Optional description. # require 'getoptlong' require "rubygems" require "net/netrc" require "json" require 'base64' require 'openssl' require 'digest/sha1' def sign_file(path, keyfile, password) # %x{openssl dgst -dss1 -sign '#{keyfile}' -passin 'pass:#{password}' '#{path}'|openssl enc -base64}.chomp key = OpenSSL::PKey::DSA.new(File.read(keyfile), password) or abort "*** error reading keyfile: ‘#{keyfile}’." digest = Digest::SHA1.digest(File.read(path)) signature = key.syssign(digest) Base64.encode64(signature).gsub("\n", '') end def aws_upload(path, url, key, acl, filename, content_type, access_key, policy, signature) rc = %x{curl -sw'%{http_code}' --show-error -o/dev/null \ -F "key=#{key}" \ -F "acl=#{acl}" \ -F "success_action_status=201" \ -F "Filename=#{filename}" \ -F "Content-Type=#{content_type}" \ -F "AWSAccessKeyId=#{access_key}" \ -F "Policy=#{policy}" \ -F "Signature=#{signature}" \ -F "file=@#{path}" \ #{url} } STDERR << "*** error uploading to #{url} (#{rc})\n" unless rc == '201' return rc == '201' end def create_download(path, repository, description, content_type) payload = { 'name' => File::basename(path), 'size' => File::size(path), 'content_type' => content_type || 'application/octet-stream' } payload['description'] = description unless description.nil? open("|curl -snd '#{payload.to_json}' https://api.github.com/repos/#{repository}/downloads") do |io| github = JSON.parse(io.read) abort "github error: #{github['errors'].inspect} for #{repository}" if github.include?('errors') abort "github unexpected response: #{github.inspect} for #{repository}" unless github.include?('html_url') return github['html_url'] if aws_upload(path, github['s3_url'], github['path'], github['acl'], payload['name'], payload['content_type'], github['accesskeyid'], github['policy'], github['signature']) open("|curl -snX DELETE https://api.github.com/repos/#{repository}/downloads/#{github['id']}") { |io| STDERR << "Removing partial upload\n" } abort end nil end if __FILE__ == $PROGRAM_NAME opts = GetoptLong.new( [ '--help', '-h', GetoptLong::NO_ARGUMENT ], [ '--keyfile', '-k', GetoptLong::REQUIRED_ARGUMENT ], [ '--destination', '-d', GetoptLong::REQUIRED_ARGUMENT ], [ '--merge', '-m', GetoptLong::REQUIRED_ARGUMENT ], [ '--description', '-s', GetoptLong::REQUIRED_ARGUMENT ] ) keyfile = nil destination = 'textmate/textmate' base_info = { } description = nil netinfo = Net::Netrc.locate("sign.textmate.org") or abort "*** missing passphrase in ~/.netrc." opts.each do |opt, arg| case opt when '--help' then RDoc::usage when '--keyfile' then keyfile = arg when '--destination' then destination = arg when '--merge' then base_info = JSON.parse(arg) when '--description' then description = arg end end abort 'No signing key provided' if keyfile.nil? if path = ARGV.shift info = base_info.merge({ 'url' => create_download(path, destination, description, 'application/x-bzip2'), 'signature' => sign_file(path, keyfile, netinfo.password), 'signee' => netinfo.login }) STDOUT << info.to_json end end