#!/usr/bin/ruby require "thor" require "httparty" require "logger" require "fileutils" class FactorioTools < Thor SOURCE_LINK = "https://www.factorio.com/get-download/latest/headless/linux64" TARGET_FILE = "factorio.tar.xz" BINARY_PATH = "/opt/factorio/bin/x64/factorio" class_option :quiet, type: :boolean, aliases: "-q" class_option :log, type: :string, aliases: "-l" class Factorio class << self def setup_logger(options = {}) $logger = ::Logger.new(options[:log] ? options[:log] : STDOUT) $logger.level = options[:quiet] ? ::Logger::WARN : ::Logger::INFO $logger.datetime_format = "%Y-%m-%d %H:%M:%S" $logger.formatter = proc do |severity, datetime, program_name, message| "#{datetime.strftime($logger.datetime_format)} #{severity}: #{message}\n" end $logger end def get_installed_version(options = {}) version_command = "#{BINARY_PATH} --version" if File.exist? BINARY_PATH version = `#{version_command}`[/Version: ([\d\.]+)/, 1] if version.empty? $logger.fatal "Could not parse version from command output: #{version_command}" exit end version else unless options[:silent] $logger.fatal "Could not find factorio binary at: #{BINARY_PATH}" exit end end rescue Exception => e $logger.fatal e.message exit end def get_target_time File.mtime(TARGET_FILE).to_time.to_i rescue 0 end def get_remote_info(version = "latest") source_link = SOURCE_LINK.gsub "latest", "#{version.downcase}" begin response = HTTParty.head SOURCE_LINK source_link = response.request.last_uri.to_s if response["last-modified"].nil? || response["last-modified"].empty? $logger.fatal "Could not find Last-Modified HTTP header: #{response}" exit end begin last_modified_at = DateTime.strptime(response["last-modified"], "%a, %d %b %Y %H:%M:%S %Z").to_time.to_i rescue Exception => e $logger.fatal "Could not parse version from Last-Modified HTTP header: #{response["last-modified"]}" $logger.fatal e.message exit end version = source_link[/factorio_headless_x64_([\d\.]+)\.tar/, 1] if version.empty? $logger.fatal "Could not parse version from URL: #{source_link}" exit end return OpenStruct.new( url: source_link, last_modified_at: last_modified_at, version: version, ) rescue Exception => e $logger.fatal "Could not access remote URL: #{source_link}" $logger.fatal e.message exit end end def download_package(remote_info) $logger.info "Downloading package #{remote_info.url}" File.open(TARGET_FILE, "w") do |file| response = HTTParty.get(remote_info.url, stream_body: true) do |part| file.write part end end $logger.info "Updating package mtime" FileUtils.touch TARGET_FILE, mtime: remote_info.last_modified_at end def install_package $logger.info "Extracting package" `tar -Jxf #{TARGET_FILE}` $logger.info "Changing ownership to user 'factorio'" FileUtils.chown_R "factorio", "factorio", "./factorio" $logger.info "Restarting service" `systemctl restart factorio` end end end desc "version", "Detect the currently installed version of Factorio" def version Factorio.setup_logger options if version = Factorio.get_installed_version puts version end end desc "latest", "Detect the latest version of Factorio available" def latest Factorio.setup_logger options if remote_info = Factorio.get_remote_info puts remote_info.version end end desc "install [VERSION]", "Install Factorio version (default: latest)" option :force, type: :boolean def install(version = "latest") Factorio.setup_logger options if installed_version = Factorio.get_installed_version(silent: true) $logger.fatal "Factorio is already installed. Use 'upgrade' to install a different version." exit end remote_info = Factorio.get_remote_info version if options[:force] || Factorio.get_target_time != remote_info.last_modified_at Factorio.download_package remote_info end Factorio.install_package end desc "upgrade [VERSION]", "Upgrade to Factorio version (default: latest)" option :force, type: :boolean def upgrade(version = "latest") Factorio.setup_logger options remote_info = Factorio.get_remote_info version if options[:force] || remote_info.version != Factorio.get_installed_version(silent: true) if options[:force] || Factorio.get_target_time != remote_info.last_modified_at Factorio.download_package remote_info end Factorio.install_package $logger.info "Factorio #{remote_info.version} has been installed." else $logger.info "Factorio #{remote_info.version} is already up to date." end end desc "download [VERSION]", "Download Factorio package for version (default: latest)" option :force, type: :boolean def download(version = "latest") Factorio.setup_logger options remote_info = Factorio.get_remote_info version if options[:force] || Factorio.get_target_time != remote_info.last_modified_at Factorio.download_package remote_info $logger.info "#{TARGET_FILE} has been downloaded." else $logger.info "#{TARGET_FILE} is already present and up to date." end end desc "backup", "Backup current installation to the archives folder" def backup Factorio.setup_logger options backup_path = "archived/games" version = Factorio.get_installed_version FileUtils.mkdir_p backup_path FileUtils.cp_r "factorio", "#{backup_path}/factorio-#{version}", remove_destination: true $logger.info "Archived current Factorio installation to '#{backup_path}'" end end FactorioTools.start ARGV