Packages

Secure BEAM sandbox runtime for LLM code mode and MCP aggregation. Run concurrent LLM/tool clients safely while agents orchestrate approved tools, call upstream MCP/OpenAPI servers, and transform data.

Current section

Files

Jump to
ptc_runner lib mix tasks ptc.install_babashka.ex
Raw

lib/mix/tasks/ptc.install_babashka.ex

defmodule Mix.Tasks.Ptc.InstallBabashka do
@moduledoc """
Installs Babashka for Clojure validation.
Downloads the appropriate binary for your OS/architecture
and places it in `_build/tools/bb`.
## Usage
mix ptc.install_babashka
mix ptc.install_babashka --force # Reinstall even if present
mix ptc.install_babashka --version 1.4.192 # Specific version
## Supported Platforms
- macOS (Apple Silicon and Intel)
- Linux (x86_64)
## What This Does
1. Detects your OS and architecture
2. Downloads the appropriate Babashka binary from GitHub releases
3. Extracts and places it at `_build/tools/bb`
4. Makes the binary executable
5. Verifies the installation
"""
use Mix.Task
@shortdoc "Install Babashka for Clojure validation"
@default_version "1.4.192"
@download_base "https://github.com/babashka/babashka/releases/download"
@allowed_download_hosts [
"github.com",
"objects.githubusercontent.com",
"release-assets.githubusercontent.com"
]
@version_pattern ~r/^\d+\.\d+\.\d+$/
@install_dir "_build/tools"
@bb_path "_build/tools/bb"
@impl Mix.Task
def run(args) do
{opts, _, _} =
OptionParser.parse(args,
strict: [force: :boolean, version: :string],
aliases: [f: :force, v: :version]
)
force = Keyword.get(opts, :force, false)
version = Keyword.get(opts, :version, @default_version)
validate_version!(version)
# Check if already installed
if File.exists?(@bb_path) and not force do
Mix.shell().info("Babashka already installed at #{@bb_path}")
Mix.shell().info("Use --force to reinstall")
verify_installation()
:ok
else
install_babashka(version)
end
end
defp install_babashka(version) do
Mix.shell().info("Installing Babashka v#{version}...")
# Detect OS and architecture
{os, arch} = detect_platform()
Mix.shell().info("Detected platform: #{os}/#{arch}")
# Build download URL
filename = build_filename(os, arch, version)
url = "#{@download_base}/v#{version}/#{filename}"
checksum_url = "#{url}.sha256"
Mix.shell().info("Downloading from: #{url}")
# Create install directory
File.mkdir_p!(@install_dir)
# Download
archive_path = Path.join(@install_dir, filename)
checksum_path = "#{archive_path}.sha256"
with :ok <- download_file(checksum_url, checksum_path),
:ok <- download_file(url, archive_path),
:ok <- verify_checksum(archive_path, checksum_path) do
Mix.shell().info("Downloaded successfully")
File.rm(checksum_path)
# Extract
extract_archive(archive_path, @install_dir)
File.rm(archive_path)
# Make executable
File.chmod!(@bb_path, 0o755)
Mix.shell().info("Installed Babashka to #{@bb_path}")
verify_installation()
else
{:error, reason} ->
Mix.raise("Failed to download Babashka: #{reason}")
end
end
defp validate_version!(version) do
if Regex.match?(@version_pattern, version) do
:ok
else
Mix.raise("Invalid Babashka version #{inspect(version)}. Expected MAJOR.MINOR.PATCH.")
end
end
defp detect_platform do
os =
case :os.type() do
{:unix, :darwin} -> :macos
{:unix, :linux} -> :linux
{:win32, _} -> :windows
other -> Mix.raise("Unsupported OS: #{inspect(other)}")
end
arch =
case :erlang.system_info(:system_architecture) |> to_string() do
"aarch64" <> _ -> :aarch64
"arm64" <> _ -> :aarch64
"x86_64" <> _ -> :amd64
"amd64" <> _ -> :amd64
other -> Mix.raise("Unsupported architecture: #{other}")
end
{os, arch}
end
defp build_filename(os, arch, version) do
case {os, arch} do
{:macos, :aarch64} ->
"babashka-#{version}-macos-aarch64.tar.gz"
{:macos, :amd64} ->
"babashka-#{version}-macos-amd64.tar.gz"
{:linux, :amd64} ->
"babashka-#{version}-linux-amd64-static.tar.gz"
{:linux, :aarch64} ->
"babashka-#{version}-linux-aarch64-static.tar.gz"
{os, arch} ->
Mix.raise("Unsupported platform: #{os}/#{arch}")
end
end
defp download_file(url, dest_path) do
with :ok <- validate_download_url(url),
{:ok, resolved_url} <- resolve_download_url(url),
:ok <- validate_download_url(resolved_url) do
curl_download(resolved_url, dest_path)
end
end
defp validate_download_url(url) do
case URI.parse(url) do
%URI{scheme: "https", host: host, port: port}
when host in @allowed_download_hosts and port in [nil, 443] ->
:ok
%URI{} ->
{:error, "refusing to download from untrusted URL: #{url}"}
end
end
defp resolve_download_url(url) do
# Resolve GitHub release redirects with HTTPS-only redirect handling before downloading bytes.
args = [
"--head",
"--location",
"--max-redirs",
"5",
"--proto",
"=https",
"--proto-redir",
"=https",
"--fail",
"--silent",
"--show-error",
"--output",
"/dev/null",
"--write-out",
"%{url_effective}",
url
]
case System.cmd("curl", args, stderr_to_stdout: true) do
{resolved_url, 0} ->
{:ok, String.trim(resolved_url)}
{output, code} ->
{:error, "curl failed while resolving redirects (exit #{code}): #{output}"}
end
end
defp curl_download(url, dest_path) do
# Download the already-resolved HTTPS URL without following additional redirects.
case System.cmd("curl", ["-o", dest_path, "-f", "--silent", "--show-error", url],
stderr_to_stdout: true
) do
{_, 0} ->
:ok
{output, code} ->
{:error, "curl failed (exit #{code}): #{output}"}
end
end
defp verify_checksum(archive_path, checksum_path) do
with {:ok, expected} <- read_expected_checksum(checksum_path),
{:ok, actual} <- sha256_file(archive_path) do
if expected == actual do
:ok
else
{:error, "SHA-256 mismatch for #{archive_path}"}
end
end
end
defp read_expected_checksum(checksum_path) do
with {:ok, content} <- File.read(checksum_path),
[checksum] <- Regex.run(~r/\b[0-9a-fA-F]{64}\b/, content) do
{:ok, String.downcase(checksum)}
else
{:error, reason} -> {:error, "failed to read checksum file: #{inspect(reason)}"}
_ -> {:error, "checksum file did not contain a SHA-256 digest"}
end
end
defp sha256_file(path) do
case File.read(path) do
{:ok, bytes} ->
{:ok, :crypto.hash(:sha256, bytes) |> Base.encode16(case: :lower)}
{:error, reason} ->
{:error, "failed to read downloaded archive: #{inspect(reason)}"}
end
end
defp extract_archive(archive_path, dest_dir) do
Mix.shell().info("Extracting archive...")
case System.cmd("tar", ["-xzf", archive_path, "-C", dest_dir]) do
{_, 0} ->
:ok
{output, code} ->
Mix.raise("Failed to extract archive (exit code #{code}): #{output}")
end
end
defp verify_installation do
# Use absolute path for verification
bb_abs_path = Path.join(File.cwd!(), @bb_path)
case System.cmd(bb_abs_path, ["--version"]) do
{version, 0} ->
Mix.shell().info("Verified: #{String.trim(version)}")
:ok
{output, code} ->
Mix.shell().error("Verification failed (exit code #{code}): #{output}")
:error
end
end
end