class RokuBuilder::Packager

Public Class Methods

commands() click to toggle source
# File lib/roku_builder/plugins/packager.rb, line 8
def self.commands
  {
    package: {device: true, source: true, stage: true, exclude: true, keyed: true},
    genkey: {device: true},
    key: {device: true, source: true, keyed: true}
  }
end
dependencies() click to toggle source
# File lib/roku_builder/plugins/packager.rb, line 33
def self.dependencies
  [Loader, Inspector]
end
parse_options(parser:, options:) click to toggle source
# File lib/roku_builder/plugins/packager.rb, line 16
def self.parse_options(parser:, options:)
  parser.separator "Commands:"
  parser.on("-p", "--package", "Package an app") do
    options[:package] = true
  end
  parser.on("-k", "--key", "Change device key") do
    options[:key] = true
  end
  parser.on("--genkey", "Generate a new key") do
    options[:genkey] = true
  end
  parser.separator "Options:"
  parser.on("--inspect-package", "Inspect package after packaging") do
    options[:inspect_package] = true
  end
end

Public Instance Methods

dev_id() click to toggle source

Get the current dev id @return [String] The current dev id

# File lib/roku_builder/plugins/packager.rb, line 100
def dev_id
  path = "/plugin_package"
  conn = simple_connection
  response = conn.get path

  dev_id = /Your Dev ID:\s*<font[^>]*>([^<]*)<\/font>/.match(response.body)
  dev_id ||= /Your Dev ID:[^>]*<\/label> ([^<]*)/.match(response.body)
  dev_id = dev_id[1] if dev_id
  dev_id ||= "none"
  dev_id
end
genkey(options:) click to toggle source
# File lib/roku_builder/plugins/packager.rb, line 55
def genkey(options:)
  password, dev_id = generate_new_key()
  @logger.unknown("Password: "+password)
  @logger.info("DevID: "+dev_id)

  out = @config.out
  out[:file] ||= "key_"+dev_id+".pkg"
  @config.out = out

  config_copy = @config.dup
  config_copy.root_dir = ""
  config_copy.in[:folder] = File.dirname(__FILE__)
  config_copy.in[:file] = "key_template.zip"
  loader = Loader.new(config: config_copy)
  options[:in] = true
  loader.sideload(options: options)
  sign_package(app_name_version: "key_"+dev_id, password: password, stage: options[:stage])
  @logger.unknown("Keyed PKG: #{File.join(@config.out[:folder], @config.out[:file])}")
end
key(options:) click to toggle source

Sets the key on the roku device @param keyed_pkg [String] Path for a package signed with the desired key @param password [String] Password for the package @return [Boolean] True if key changed, false otherwise

# File lib/roku_builder/plugins/packager.rb, line 79
def key(options:)
  oldId = dev_id

  raise ExecutionError, "Missing Key Config" unless @config.key

  # upload new key with password
  payload =  {
    mysubmit: "Rekey",
    passwd: @config.key[:password],
    archive: Faraday::UploadIO.new(@config.key[:keyed_pkg], 'application/octet-stream')
  }
  multipart_connection.post "/plugin_inspect", payload

  # check key
  newId = dev_id
  @logger.info("Key did not change") unless newId != oldId
  @logger.debug(oldId + " -> " + newId)
end
package(options:) click to toggle source
# File lib/roku_builder/plugins/packager.rb, line 37
def package(options:)
  check_options(options)
  #sideload
  loader = Loader.new(config: @config)
  loader.sideload(options: options)
  loader.squash(options: options) if @config.stage[:squash]
  #rekey
  key(options: options)
  #package
  sign_package(app_name_version: "", password: @config.key[:password], stage: options[:stage])
  #inspect
  if options[:inspect_package]
    @config.in = @config.out
    options[:password] = @config.key[:password]
    Inspector.new(config: @config).inspect(options: options)
  end
end

Private Instance Methods

check_options(options) click to toggle source
# File lib/roku_builder/plugins/packager.rb, line 114
def check_options(options)
  raise InvalidOptions, "Can not use '--in' for packaging" if options[:in]
  raise InvalidOptions, "Can not use '--ref' for packaging" if options[:ref]
  raise InvalidOptions, "Can not use '--current' for packaging" if options[:current]
end
generate_new_key() click to toggle source

Uses the device to generate a new signing key

@return [Array<String>] Password and dev_id for the new key
# File lib/roku_builder/plugins/packager.rb, line 168
def generate_new_key()
  telnet_config = {
    'Host' => @roku_ip_address,
    'Port' => 8080
  }
  connection = Net::Telnet.new(telnet_config)
  connection.puts("genkey")
  waitfor_config = {
    'Match' => /./,
    'Timeout' => false
  }
  password = nil
  dev_id = nil
  while password.nil? or dev_id.nil?
    connection.waitfor(waitfor_config) do |txt|
      while line = txt.slice!(/^.*\n/) do
        words = line.split
        if words[0] == "Password:"
          password = words[1]
        elsif words[0] == "DevID:"
          dev_id = words[1]
        end
      end
    end
  end
  connection.close
  return password, dev_id
end
sign_package(app_name_version:, password:, stage: nil) click to toggle source

Sign and download the currently sideloaded app

# File lib/roku_builder/plugins/packager.rb, line 121
def sign_package(app_name_version:, password:, stage: nil)
  payload =  {
    mysubmit: "Package",
    app_name: app_name_version,
    passwd: password,
    pkg_time: Time.now.to_i
  }
  response = multipart_connection.post "/plugin_package", payload

  # Check for error
  failed = /(Failed: [^\.]*\.)/.match(response.body)
  raise ExecutionError, failed[1] if failed

  # Download signed package
  pkg = /<a href="pkgs[^>]*>([^<]*)</.match(response.body)[1]
  path = "/pkgs/#{pkg}"
  conn = Faraday.new(url: @url) do |f|
    f.request :digest, @dev_username, @dev_password
    f.adapter Faraday.default_adapter
  end
  response = conn.get path
  raise ExecutionError, "Failed to download signed package" if response.status != 200
  out_file = nil
  unless @config.out[:file]
    out = @config.out
    build_version = Manifest.new(config: @config).build_version
    if stage
      out[:file] = "#{@config.project[:app_name]}_#{stage}_#{build_version}"
    else
      out[:file] = "#{@config.project[:app_name]}_working_#{build_version}"
    end
    @config.out = out
  end
  out_file = File.join(@config.out[:folder], @config.out[:file])
  out_file = out_file+".pkg" unless out_file.end_with?(".pkg")
  File.open(out_file, 'w+b') {|fp| fp.write(response.body)}
  if File.exist?(out_file)
    pkg_size = File.size(out_file).to_f / 2**20
    raise ExecutionError, "PKG file size is too large (#{pkg_size.round(2)} MB): #{out_file}" if pkg_size > 4.0
    @logger.info("Outfile: #{out_file}")
  else
    @logger.warn("Outfile Missing: #{out_file}")
  end
end