tamatebako / tebako

Tebako: an executable packager (for Ruby programs)
https://www.tebako.org
43 stars 6 forks source link

Separate tebako runtime and packaged application #154

Open maxirmx opened 3 months ago

maxirmx commented 3 months ago

Use runtime as a base and attach tebako package to it.

ronaldtse commented 2 months ago

Based on the provided implementation of Tebako, here's a proposal for splitting off the Runtime component to allow separate downloading and execution of multiple packaged Runtimes:

  1. Modify the Tebako packaging process:

    • Instead of bundling the Ruby runtime directly into the executable, create a separate package for each supported Ruby version.
    • These runtime packages would contain the Ruby interpreter and necessary libraries.
    • Store these packages in a GitHub releases location (e.g., https://github.com/tamatebako/tebako-runtimes).
  2. Update the tebako.yaml file:

    • Add a runtime section to specify the required Ruby version:

      runtime:
      ruby_version: 3.2.4
  3. Modify the Tebako executable:

    • When the executable starts, it should check for the required runtime in ~/.tebako/ruby/<version>/.
    • If the runtime is not found, download it from the GitHub releases location.
    • Use the downloaded runtime to execute the packaged application.
  4. Implement a runtime manager:

    • Create a new component responsible for managing runtime versions.
    • This manager would handle downloading, installing, and updating runtimes.
    • It could be integrated into the tebako CLI tool.
  5. Update the Tebako loader:

    • Modify the loader to read the required runtime version from the packaged application.
    • Use this information to locate and load the appropriate runtime.
  6. Implement runtime download and caching:

    • When a required runtime is not available locally, download it from the GitHub releases.
    • Store downloaded runtimes in ~/.tebako/ruby/<version>/.
    • Implement version checking to update runtimes when newer versions are available.
  7. Modify the packaging process:

    • Instead of bundling the entire Ruby runtime, include only the necessary application files and dependencies.
    • Add metadata about the required runtime version to the package.
  8. Update the tebako CLI:

    • Add commands to manage runtimes (e.g., tebako runtime install 3.2.4, tebako runtime list).
    • Modify the press command to package applications without bundling the runtime.
  9. Implement runtime isolation:

    • Ensure that different applications can use different Ruby versions without conflicts.
    • This might involve using techniques like environment isolation or containerization.
  10. Update documentation and guides:

    • Explain the new runtime management system to users.
    • Provide instructions for managing runtimes and troubleshooting common issues.

By implementing these changes, Tebako would allow for more flexible runtime management, reduced package sizes, and easier updates to runtime versions. Users could have multiple Ruby versions installed and easily switch between them based on the requirements of each packaged application.

ronaldtse commented 2 months ago

Here's a proposed implementation for the runtime manager in Ruby. This implementation would be part of the Tebako gem and could be invoked via the CLI or used programmatically.

require 'fileutils'
require 'open-uri'
require 'json'
require 'digest'

module Tebako
  class RuntimeManager
    RUNTIME_BASE_URL = "https://github.com/tamatebako/tebako-runtimes/releases/download"
    RUNTIME_MANIFEST_URL = "https://api.github.com/repos/tamatebako/tebako-runtimes/releases/latest"
    RUNTIME_DIR = File.expand_path("~/.tebako/ruby")

    def initialize
      FileUtils.mkdir_p(RUNTIME_DIR)
    end

    def install(version)
      if installed?(version)
        puts "Ruby #{version} is already installed."
        return
      end

      puts "Installing Ruby #{version}..."
      download_runtime(version)
      puts "Ruby #{version} has been installed successfully."
    end

    def uninstall(version)
      if !installed?(version)
        puts "Ruby #{version} is not installed."
        return
      end

      puts "Uninstalling Ruby #{version}..."
      FileUtils.rm_rf(File.join(RUNTIME_DIR, version))
      puts "Ruby #{version} has been uninstalled successfully."
    end

    def list
      installed_versions = Dir.glob(File.join(RUNTIME_DIR, "*")).map { |f| File.basename(f) }
      puts "Installed Ruby versions:"
      installed_versions.each { |v| puts "  - #{v}" }
    end

    def installed?(version)
      Dir.exist?(File.join(RUNTIME_DIR, version))
    end

    def ensure_runtime(version)
      install(version) unless installed?(version)
    end

    private

    def download_runtime(version)
      manifest = fetch_manifest
      runtime_info = manifest['assets'].find { |asset| asset['name'] == "ruby-#{version}.tar.gz" }

      if runtime_info.nil?
        raise "Runtime for Ruby #{version} not found in the manifest."
      end

      download_url = runtime_info['browser_download_url']
      expected_sha256 = runtime_info['sha256']

      target_dir = File.join(RUNTIME_DIR, version)
      FileUtils.mkdir_p(target_dir)

      URI.open(download_url) do |remote_file|
        File.open(File.join(target_dir, "ruby-#{version}.tar.gz"), 'wb') do |local_file|
          local_file.write(remote_file.read)
        end
      end

      verify_integrity(File.join(target_dir, "ruby-#{version}.tar.gz"), expected_sha256)

      system("tar -xzf #{File.join(target_dir, "ruby-#{version}.tar.gz")} -C #{target_dir}")
      FileUtils.rm(File.join(target_dir, "ruby-#{version}.tar.gz"))
    end

    def fetch_manifest
      JSON.parse(URI.open(RUNTIME_MANIFEST_URL).read)
    end

    def verify_integrity(file_path, expected_sha256)
      actual_sha256 = Digest::SHA256.file(file_path).hexdigest
      if actual_sha256 != expected_sha256
        FileUtils.rm(file_path)
        raise "Integrity check failed for downloaded runtime."
      end
    end
  end
end

This RuntimeManager class provides the following functionality:

  1. install(version): Downloads and installs a specific Ruby version.
  2. uninstall(version): Removes a specific Ruby version.
  3. list: Shows all installed Ruby versions.
  4. installed?(version): Checks if a specific Ruby version is installed.
  5. ensure_runtime(version): Installs a runtime if it's not already installed.

The manager downloads runtime packages from a GitHub releases page, verifies their integrity using SHA256 checksums, and extracts them to ~/.tebako/ruby/<version>/.

To integrate this with the Tebako CLI, you could add new commands like this:

module Tebako
  class CLI < Thor
    desc "runtime SUBCOMMAND ...ARGS", "Manage Tebako runtimes"
    subcommand "runtime", Runtime

    class Runtime < Thor
      desc "install VERSION", "Install a specific Ruby runtime"
      def install(version)
        RuntimeManager.new.install(version)
      end

      desc "uninstall VERSION", "Uninstall a specific Ruby runtime"
      def uninstall(version)
        RuntimeManager.new.uninstall(version)
      end

      desc "list", "List installed Ruby runtimes"
      def list
        RuntimeManager.new.list
      end
    end
  end
end

This implementation allows users to manage Tebako runtimes using commands like:

tebako runtime install 3.2.4
tebako runtime uninstall 3.2.4
tebako runtime list

To use this runtime manager in the Tebako packaging and execution process:

  1. During packaging, include the required Ruby version in the package metadata.
  2. When executing a Tebako package, check the required Ruby version, use RuntimeManager#ensure_runtime to install it if necessary, and then use that version to run the packaged application.

This approach provides flexibility in managing multiple Ruby versions and allows for easy updates to runtimes without rebuilding packaged applications.

maxirmx commented 1 month ago

https://github.com/tamatebako/libdwarfs/issues/116