diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ce8c7f33 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +build diff --git a/ramalama.py b/ramalama.py index 77842b7d..ac3e7c41 100755 --- a/ramalama.py +++ b/ramalama.py @@ -1,408 +1,11 @@ #!/usr/bin/python3 -import os -import glob -import sys -import subprocess -import json -import hashlib -import shutil -import time -import re -import logging -from pathlib import Path - -x = False -funcDict = {} - - -def verify_checksum(filename): - """ - Verifies if the SHA-256 checksum of a file matches the checksum provided in - the filename. - - Args: - filename (str): The filename containing the checksum prefix - (e.g., "sha256:") - - Returns: - bool: True if the checksum matches, False otherwise. - """ - - if not os.path.exists(filename): - return False - - # Check if the filename starts with "sha256:" - fn_base = os.path.basename(filename) - if not fn_base.startswith("sha256:"): - raise ValueError(f"Filename does not start with 'sha256:': {fn_base}") - - # Extract the expected checksum from the filename - expected_checksum = fn_base.split(":")[1] - if len(expected_checksum) != 64: - raise ValueError("Invalid checksum length in filename") - - # Calculate the SHA-256 checksum of the file contents - sha256_hash = hashlib.sha256() - with open(filename, "rb") as f: - for byte_block in iter(lambda: f.read(4096), b""): - sha256_hash.update(byte_block) - - # Compare the checksums - return sha256_hash.hexdigest() == expected_checksum - - -def print_error(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) - - -def run_cmd(args): - if x: - print(*args) - - return subprocess.run(args, check=True, stdout=subprocess.PIPE) - - -def exec_cmd(args): - if x: - print(*args) - - return os.execvp(args[0], args) - - -def run_curl_cmd(args, filename): - if not verify_checksum(filename): - try: - run_cmd(args) - except subprocess.CalledProcessError as e: - if e.returncode == 22: - print_error(filename + " not found") - sys.exit(e.returncode) - - -def pull_ollama_manifest(repos_ollama, manifests, accept, registry_head, model_tag): - os.makedirs(os.path.dirname(manifests), exist_ok=True) - os.makedirs(os.path.join(repos_ollama, "blobs"), exist_ok=True) - curl_cmd = [ - "curl", "-f", "-s", "--header", accept, - "-o", manifests, - f"{registry_head}/manifests/{model_tag}" - ] - run_cmd(curl_cmd) - - -def pull_ollama_config_blob(repos_ollama, accept, registry_head, manifest_data): - cfg_hash = manifest_data["config"]["digest"] - config_blob_path = os.path.join(repos_ollama, "blobs", cfg_hash) - curl_cmd = [ - "curl", "-f", "-s", "-L", "-C", "-", "--header", accept, - "-o", config_blob_path, - f"{registry_head}/blobs/{cfg_hash}" - ] - run_curl_cmd(curl_cmd, config_blob_path) - - -def pull_ollama_blob(repos_ollama, layer_digest, accept, registry_head, ramalama_models, model_name, model_tag, symlink_path): - layer_blob_path = os.path.join(repos_ollama, "blobs", layer_digest) - curl_cmd = ["curl", "-f", "-L", "-C", "-", "--progress-bar", "--header", - accept, "-o", layer_blob_path, - f"{registry_head}/blobs/{layer_digest}"] - run_curl_cmd(curl_cmd, layer_blob_path) - os.makedirs(ramalama_models, exist_ok=True) - relative_target_path = os.path.relpath( - layer_blob_path, start=os.path.dirname(symlink_path)) - try: - run_cmd(["ln", "-sf", relative_target_path, symlink_path]) - except subprocess.CalledProcessError as e: - print_error(e) - sys.exit(e.returncode) - - -def init_pull(repos_ollama, manifests, accept, registry_head, model_name, model_tag, ramalama_models, symlink_path, model): - try: - pull_ollama_manifest(repos_ollama, manifests, - accept, registry_head, model_tag) - with open(manifests, 'r') as f: - manifest_data = json.load(f) - except subprocess.CalledProcessError as e: - if e.returncode == 22: - print_error(f"{model}:{model_tag} not found") - - sys.exit(e.returncode) - - pull_ollama_config_blob(repos_ollama, accept, - registry_head, manifest_data) - for layer in manifest_data["layers"]: - layer_digest = layer["digest"] - if layer["mediaType"] != 'application/vnd.ollama.image.model': - continue - - pull_ollama_blob(repos_ollama, layer_digest, accept, - registry_head, ramalama_models, model_name, model_tag, - symlink_path) - - return symlink_path - - -def huggingface_download(ramalama_store, model, directory, filename): - return run_cmd(["huggingface-cli", "download", directory, filename, "--cache-dir", ramalama_store + "/repos/huggingface/.cache", "--local-dir", ramalama_store + "/repos/huggingface/" + directory]) - - -def try_huggingface_download(ramalama_store, model, directory, filename): - proc = huggingface_download(ramalama_store, model, directory, filename) - return proc.stdout.decode('utf-8') - - -def mkdirs(ramalama_store): - # List of directories to create - directories = [ - 'models/huggingface', - 'repos/huggingface', - 'models/oci', - 'repos/oci', - 'models/ollama', - 'repos/ollama' - ] - - # Create each directory - for directory in directories: - full_path = os.path.join(ramalama_store, directory) - os.makedirs(full_path, exist_ok=True) - - -def human_duration(d): - if d < 1: - return "Less than a second" - elif d == 1: - return "1 second" - elif d < 60: - return f"{d} seconds" - elif d < 120: - return "1 minute" - elif d < 3600: - return f"{d // 60} minutes" - elif d < 7200: - return "1 hour" - elif d < 86400: - return f"{d // 3600} hours" - elif d < 172800: - return "1 day" - elif d < 604800: - return f"{d // 86400} days" - elif d < 1209600: - return "1 week" - elif d < 2419200: - return f"{d // 604800} weeks" - elif d < 4838400: - return "1 month" - elif d < 31536000: - return f"{d // 2419200} months" - elif d < 63072000: - return "1 year" - else: - return f"{d // 31536000} years" - - -def list_files_by_modification(): - return sorted(Path().rglob('*'), key=lambda p: os.path.getmtime(p), - reverse=True) - - -def list_cli(ramalama_store, args, port): - if len(args) > 0: - usage() - print(f"{'NAME':<67} {'MODIFIED':<15} {'SIZE':<6}") - mycwd = os.getcwd() - os.chdir(f"{ramalama_store}/models/") - for path in list_files_by_modification(): - if path.is_symlink(): - name = str(path).replace('/', '://', 1) - file_epoch = path.lstat().st_mtime - diff = int(time.time() - file_epoch) - modified = human_duration(diff) + " ago" - size = subprocess.run(["du", "-h", str(path.resolve())], - capture_output=True, text=True).stdout.split()[0] - print(f"{name:<67} {modified:<15} {size:<6}") - os.chdir(mycwd) - - -funcDict["list"] = list_cli -funcDict["ls"] = list_cli - - -def pull_huggingface(model, ramalama_store): - model = re.sub(r'^huggingface://', '', model) - directory, filename = model.rsplit('/', 1) - gguf_path = try_huggingface_download( - ramalama_store, model, directory, filename) - directory = f"{ramalama_store}/models/huggingface/{directory}" - os.makedirs(directory, exist_ok=True) - symlink_path = f"{directory}/{filename}" - relative_target_path = os.path.relpath( - gguf_path.rstrip(), start=os.path.dirname(symlink_path)) - if os.path.exists(symlink_path) and os.readlink(symlink_path) == relative_target_path: - # Symlink is already correct, no need to update it - return symlink_path - - try: - run_cmd(["ln", "-sf", relative_target_path, symlink_path]) - except subprocess.CalledProcessError as e: - print_error(e) - sys.exit(e.returncode) - - return symlink_path - - -def pull_oci(model, ramalama_store): - target = re.sub(r'^oci://', '', model) - registry, reference = target.split('/', 1) - registry, reference = ("docker.io", - target) if "." not in registry else ( - registry, reference) - reference_dir = reference.replace(":", "/") - outdir = f"{ramalama_store}/repos/oci/{registry}/{reference_dir}" - print(f"Downloading {target}...") - # note: in the current way ramalama is designed, cannot do Helper(OMLMDRegistry()).pull(target, outdir) since cannot use modules/sdk, can use only cli bindings from pip installs - run_cmd(["omlmd", "pull", target, "--output", outdir]) - ggufs = [file for file in os.listdir(outdir) if file.endswith('.gguf')] - if len(ggufs) != 1: - print(f"Error: Unable to identify .gguf file in: {outdir}") - sys.exit(-1) - - directory = f"{ramalama_store}/models/oci/{registry}/{reference_dir}" - os.makedirs(directory, exist_ok=True) - symlink_path = f"{directory}/{ggufs[0]}" - relative_target_path = os.path.relpath( - f"{outdir}/{ggufs[0]}", - start=os.path.dirname(symlink_path) - ) - if os.path.exists(symlink_path) and os.readlink(symlink_path) == relative_target_path: - # Symlink is already correct, no need to update it - return symlink_path - - try: - run_cmd(["ln", "-sf", relative_target_path, symlink_path]) - except subprocess.CalledProcessError as e: - print_error(e) - sys.exit(e.returncode) - - return symlink_path - - -def pull_cli(ramalama_store, args, port): - if len(args) < 1: - usage() - - model = args.pop(0) - matching_files = glob.glob(f"{ramalama_store}/models/*/{model}") - if matching_files: - return matching_files[0] - - if model.startswith("huggingface://"): - return pull_huggingface(model, ramalama_store) - if model.startswith("oci://"): - return pull_oci(model, ramalama_store) - - model = re.sub(r'^ollama://', '', model) - repos_ollama = ramalama_store + "/repos/ollama" - ramalama_models = ramalama_store + "/models/ollama" - registry = "https://registry.ollama.ai" - if '/' in model: - model_full = model - else: - model_full = "library/" + model - - accept = "Accept: application/vnd.docker.distribution.manifest.v2+json" - if ':' in model_full: - model_name, model_tag = model_full.split(':', 1) - else: - model_name = model_full - model_tag = "latest" - - model_base = os.path.basename(model_name) - symlink_path = os.path.join(ramalama_models, f"{model_base}:{model_tag}") - if os.path.exists(symlink_path): - return symlink_path - - manifests = os.path.join(repos_ollama, "manifests", - registry, model_name, model_tag) - registry_head = f"{registry}/v2/{model_name}" - return init_pull(repos_ollama, manifests, accept, registry_head, model_name, model_tag, ramalama_models, symlink_path, model) - - -funcDict["pull"] = pull_cli - - -def run_cli(ramalama_store, args, port): - if len(args) < 1: - usage() - - symlink_path = pull_cli(ramalama_store, args, port) - exec_cmd(["llama-main", "-m", - symlink_path, "--log-disable", "--instruct"]) - - -funcDict["run"] = run_cli - - -def serve_cli(ramalama_store, args, port): - if len(args) < 1: - usage() - - symlink_path = pull_cli(ramalama_store, args, port) - exec_cmd(["llama-server", "--port", port, "-m", symlink_path]) - - -funcDict["serve"] = serve_cli - - -def usage(): - print("Usage:") - print(f" {os.path.basename(__file__)} COMMAND") - print() - print("Commands:") - print(" list List models") - print(" pull MODEL Pull a model") - print(" run MODEL Run a model") - print(" serve MODEL Serve a model") - sys.exit(1) - - -def get_ramalama_store(): - if os.geteuid() == 0: - return "/var/lib/ramalama" - - return os.path.expanduser("~/.local/share/ramalama") - - -def in_container(): - if os.path.exists("/run/.containerenv") or os.path.exists("/.dockerenv") or os.getenv("container"): - return True - - return False - - -def available(cmd): - return shutil.which(cmd) is not None - - -def select_container_manager(): - if sys.platform == "darwin": - return "" - - if available("podman"): - return "podman" - - if available("docker"): - return "docker" - - return "" - +import ramalama +import sys, os def main(args): - conman = select_container_manager() - ramalama_store = get_ramalama_store() - mkdirs(ramalama_store) + conman = ramalama.container_manager() + store = ramalama.create_store() try: dryrun = False @@ -410,11 +13,11 @@ def main(args): if args[0] == "--dryrun": args.pop(0) dryrun = True - elif args[0] in funcDict: + elif args[0] in ramalama.funcDict: break else: print(f"Error: unrecognized command `{args[0]}`\n") - usage() + ramalama.usage() port = "8080" host = os.getenv('RAMALAMA_HOST', port) @@ -423,20 +26,20 @@ def main(args): if conman: home = os.path.expanduser('~') - conman_args = [conman, "run", "--rm", "-it", "--security-opt=label=disable", f"-v{ramalama_store}:/var/lib/ramalama", f"-v{home}:{home}", "-v/tmp:/tmp", + conman_args = [conman, "run", "--rm", "-it", "--security-opt=label=disable", f"-v{store}:/var/lib/ramalama", f"-v{home}:{home}", "-v/tmp:/tmp", f"-v{__file__}:{__file__}", "-e", "RAMALAMA_HOST", "-p", f"{host}:{port}", "quay.io/ramalama/ramalama:latest", __file__] + args if dryrun: return print(*conman_args) - exec_cmd(conman_args) + ramalama.exec_cmd(conman_args) cmd = args.pop(0) - funcDict[cmd](ramalama_store, args, port) + ramalama.funcDict[cmd](store, args, port) except IndexError: - usage() + ramalama.usage() except KeyError: print(cmd + " not valid\n") - usage() + ramalama.usage() if __name__ == "__main__": diff --git a/ramalama/__init__.py b/ramalama/__init__.py new file mode 100644 index 00000000..ad9e626a --- /dev/null +++ b/ramalama/__init__.py @@ -0,0 +1,51 @@ +"""ramalama client module.""" + +import subprocess +import sys, os +import shutil + +assert sys.version_info >= (3, 6), "Python 3.6 or greater is required." + +from ramalama.version import __version__ +from ramalama.ramalama import create_store, funcDict, usage + +__all__ = ['container_manager', 'create_store', 'funcDict', 'usage', '__version__'] + +x = False + +def container_manager(): + if sys.platform != "linux": + raise "ramalama only works on linux" + + if available("podman"): + return "podman" + + if available("docker"): + return "docker" + + raise "ramalama requires either Podman or Docker to be installed" + +def available(cmd): + return shutil.which(cmd) is not None + +def exec_cmd(args): + if x: + print(*args) + + return os.execvp(args[0], args) + +def run_cmd(args): + if x: + print(*args) + + return subprocess.run(args, check=True, stdout=subprocess.PIPE) + + +def run_curl_cmd(args, filename): + if not verify_checksum(filename): + try: + run_cmd(args) + except subprocess.CalledProcessError as e: + if e.returncode == 22: + print_error(filename + " not found") + sys.exit(e.returncode) diff --git a/ramalama/huggingface.py b/ramalama/huggingface.py new file mode 100644 index 00000000..1b914c05 --- /dev/null +++ b/ramalama/huggingface.py @@ -0,0 +1,31 @@ +import os +from ramalama import * + +def download(ramalama_store, model, directory, filename): + return run_cmd(["huggingface-cli", "download", directory, filename, "--cache-dir", ramalama_store + "/repos/huggingface/.cache", "--local-dir", ramalama_store + "/repos/huggingface/" + directory]) + +def try_download(ramalama_store, model, directory, filename): + proc = download(ramalama_store, model, directory, filename) + return proc.stdout.decode('utf-8') + +def pull(model, ramalama_store): + model = re.sub(r'^huggingface://', '', model) + directory, filename = model.rsplit('/', 1) + gguf_path = try_download( + ramalama_store, model, directory, filename) + directory = f"{ramalama_store}/models/huggingface/{directory}" + os.makedirs(directory, exist_ok=True) + symlink_path = f"{directory}/{filename}" + relative_target_path = os.path.relpath( + gguf_path.rstrip(), start=os.path.dirname(symlink_path)) + if os.path.exists(symlink_path) and os.readlink(symlink_path) == relative_target_path: + # Symlink is already correct, no need to update it + return symlink_path + + try: + run_cmd(["ln", "-sf", relative_target_path, symlink_path]) + except subprocess.CalledProcessError as e: + print_error(e) + sys.exit(e.returncode) + + return symlink_path diff --git a/ramalama/oci.py b/ramalama/oci.py new file mode 100644 index 00000000..a786a980 --- /dev/null +++ b/ramalama/oci.py @@ -0,0 +1,35 @@ +def pull_oci(model, ramalama_store): + target = re.sub(r'^oci://', '', model) + registry, reference = target.split('/', 1) + registry, reference = ("docker.io", + target) if "." not in registry else ( + registry, reference) + reference_dir = reference.replace(":", "/") + outdir = f"{ramalama_store}/repos/oci/{registry}/{reference_dir}" + print(f"Downloading {target}...") + # note: in the current way ramalama is designed, cannot do Helper(OMLMDRegistry()).pull(target, outdir) since cannot use modules/sdk, can use only cli bindings from pip installs + run_cmd(["omlmd", "pull", target, "--output", outdir]) + ggufs = [file for file in os.listdir(outdir) if file.endswith('.gguf')] + if len(ggufs) != 1: + print(f"Error: Unable to identify .gguf file in: {outdir}") + sys.exit(-1) + + directory = f"{ramalama_store}/models/oci/{registry}/{reference_dir}" + os.makedirs(directory, exist_ok=True) + symlink_path = f"{directory}/{ggufs[0]}" + relative_target_path = os.path.relpath( + f"{outdir}/{ggufs[0]}", + start=os.path.dirname(symlink_path) + ) + if os.path.exists(symlink_path) and os.readlink(symlink_path) == relative_target_path: + # Symlink is already correct, no need to update it + return symlink_path + + try: + run_cmd(["ln", "-sf", relative_target_path, symlink_path]) + except subprocess.CalledProcessError as e: + print_error(e) + sys.exit(e.returncode) + + return symlink_path + diff --git a/ramalama/ollama.py b/ramalama/ollama.py new file mode 100644 index 00000000..361686fc --- /dev/null +++ b/ramalama/ollama.py @@ -0,0 +1,39 @@ +import os +from ramalama import * + +def pull_manifest(repos, manifests, accept, registry_head, model_tag): + os.makedirs(os.path.dirname(manifests), exyist_ok=True) + os.makedirs(os.path.join(repos, "blobs"), exist_ok=True) + curl_cmd = [ + "curl", "-f", "-s", "--header", accept, + "-o", manifests, + f"{registry_head}/manifests/{model_tag}" + ] + run_cmd(curl_cmd) + + +def pull_config_blob(repos, accept, registry_head, manifest_data): + cfg_hash = manifest_data["config"]["digest"] + config_blob_path = os.path.join(repos, "blobs", cfg_hash) + curl_cmd = [ + "curl", "-f", "-s", "-L", "-C", "-", "--header", accept, + "-o", config_blob_path, + f"{registry_head}/blobs/{cfg_hash}" + ] + run_curl_cmd(curl_cmd, config_blob_path) + + +def pull_blob(repos, layer_digest, accept, registry_head, models, model_name, model_tag, symlink_path): + layer_blob_path = os.path.join(repos, "blobs", layer_digest) + curl_cmd = ["curl", "-f", "-L", "-C", "-", "--progress-bar", "--header", + accept, "-o", layer_blob_path, + f"{registry_head}/blobs/{layer_digest}"] + run_curl_cmd(curl_cmd, layer_blob_path) + os.makedirs(models, exist_ok=True) + relative_target_path = os.path.relpath( + layer_blob_path, start=os.path.dirname(symlink_path)) + try: + run_cmd(["ln", "-sf", relative_target_path, symlink_path]) + except subprocess.CalledProcessError as e: + print_error(e) + sys.exit(e.returncode) diff --git a/ramalama/ramalama.py b/ramalama/ramalama.py new file mode 100644 index 00000000..3521655f --- /dev/null +++ b/ramalama/ramalama.py @@ -0,0 +1,247 @@ +#!/usr/bin/python3 + +import os +import glob +import sys +import subprocess +import json +import hashlib +import time +import re +import logging +from ramalama import * +import ramalama.ollama as ollama +import ramalama.oci as oci +import ramalama.huggingface as huggingface +from pathlib import Path + +def verify_checksum(filename): + """ + Verifies if the SHA-256 checksum of a file matches the checksum provided in + the filename. + + Args: + filename (str): The filename containing the checksum prefix + (e.g., "sha256:") + + Returns: + bool: True if the checksum matches, False otherwise. + """ + + if not os.path.exists(filename): + return False + + # Check if the filename starts with "sha256:" + fn_base = os.path.basename(filename) + if not fn_base.startswith("sha256:"): + raise ValueError(f"Filename does not start with 'sha256:': {fn_base}") + + # Extract the expected checksum from the filename + expected_checksum = fn_base.split(":")[1] + if len(expected_checksum) != 64: + raise ValueError("Invalid checksum length in filename") + + # Calculate the SHA-256 checksum of the file contents + sha256_hash = hashlib.sha256() + with open(filename, "rb") as f: + for byte_block in iter(lambda: f.read(4096), b""): + sha256_hash.update(byte_block) + + # Compare the checksums + return sha256_hash.hexdigest() == expected_checksum + + +def print_error(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + + +def init_pull(repos, manifests, accept, registry_head, model_name, model_tag, models, symlink_path, model): + try: + ollama.pull_ollama_manifest(repos, manifests, + accept, registry_head, model_tag) + with open(manifests, 'r') as f: + manifest_data = json.load(f) + except subprocess.CalledProcessError as e: + if e.returncode == 22: + print_error(f"{model}:{model_tag} not found") + + sys.exit(e.returncode) + + pull_ollama_config_blob(repos, accept, + registry_head, manifest_data) + for layer in manifest_data["layers"]: + layer_digest = layer["digest"] + if layer["mediaType"] != 'application/vnd.ollama.image.model': + continue + + pull_ollama_blob(repos, layer_digest, accept, + registry_head, models, model_name, model_tag, + symlink_path) + + return symlink_path + + +def mkdirs(store): + # List of directories to create + directories = [ + 'models/huggingface', + 'repos/huggingface', + 'models/oci', + 'repos/oci', + 'models/ollama', + 'repos/ollama' + ] + + # Create each directory + for directory in directories: + full_path = os.path.join(store, directory) + os.makedirs(full_path, exist_ok=True) + + +def human_duration(d): + if d < 1: + return "Less than a second" + elif d == 1: + return "1 second" + elif d < 60: + return f"{d} seconds" + elif d < 120: + return "1 minute" + elif d < 3600: + return f"{d // 60} minutes" + elif d < 7200: + return "1 hour" + elif d < 86400: + return f"{d // 3600} hours" + elif d < 172800: + return "1 day" + elif d < 604800: + return f"{d // 86400} days" + elif d < 1209600: + return "1 week" + elif d < 2419200: + return f"{d // 604800} weeks" + elif d < 4838400: + return "1 month" + elif d < 31536000: + return f"{d // 2419200} months" + elif d < 63072000: + return "1 year" + else: + return f"{d // 31536000} years" + + +def list_files_by_modification(): + return sorted(Path().rglob('*'), key=lambda p: os.path.getmtime(p), + reverse=True) + + +def list_cli(store, args, port): + if len(args) > 0: + usage() + print(f"{'NAME':<67} {'MODIFIED':<15} {'SIZE':<6}") + mycwd = os.getcwd() + os.chdir(f"{store}/models/") + for path in list_files_by_modification(): + if path.is_symlink(): + name = str(path).replace('/', '://', 1) + file_epoch = path.lstat().st_mtime + diff = int(time.time() - file_epoch) + modified = human_duration(diff) + " ago" + size = subprocess.run(["du", "-h", str(path.resolve())], + capture_output=True, text=True).stdout.split()[0] + print(f"{name:<67} {modified:<15} {size:<6}") + os.chdir(mycwd) + + +def pull_cli(store, args, port): + if len(args) < 1: + usage() + + model = args.pop(0) + matching_files = glob.glob(f"{store}/models/*/{model}") + if matching_files: + return matching_files[0] + + if model.startswith("huggingface://"): + return huggingface.pull(model, store) + if model.startswith("oci://"): + return oci.pull(model, store) + + model = re.sub(r'^ollama://', '', model) + repos_ollama = store + "/repos/ollama" + models_ollama = store + "/models/ollama" + registry = "https://registry.ollama.ai" + if '/' in model: + model_full = model + else: + model_full = "library/" + model + + accept = "Accept: application/vnd.docker.distribution.manifest.v2+json" + if ':' in model_full: + model_name, model_tag = model_full.split(':', 1) + else: + model_name = model_full + model_tag = "latest" + + model_base = os.path.basename(model_name) + symlink_path = os.path.join(models_ollama, f"{model_base}:{model_tag}") + if os.path.exists(symlink_path): + return symlink_path + + manifests = os.path.join(repos_ollama, "manifests", + registry, model_name, model_tag) + registry_head = f"{registry}/v2/{model_name}" + return init_pull(repos_ollama, manifests, accept, registry_head, model_name, model_tag, models_ollama, symlink_path, model) + + +def run_cli(store, args, port): + if len(args) < 1: + usage() + + symlink_path = pull_cli(store, args, port) + exec_cmd(["llama-main", "-m", + symlink_path, "--log-disable", "--instruct"]) + + +def serve_cli(store, args, port): + if len(args) < 1: + usage() + + symlink_path = pull_cli(store, args, port) + exec_cmd(["llama-server", "--port", port, "-m", symlink_path]) + +def get_store(): + if os.geteuid() == 0: + return "/var/lib/ramalama" + + return os.path.expanduser("~/.local/share/ramalama") + +def create_store(): + store=get_store() + mkdirs(store) + return store + +def in_container(): + if os.path.exists("/run/.containerenv") or os.path.exists("/.dockerenv") or os.getenv("container"): + return True + + return False + +funcDict = {} +funcDict["list"] = list_cli +funcDict["ls"] = list_cli +funcDict["pull"] = pull_cli +funcDict["run"] = run_cli +funcDict["serve"] = serve_cli + +def usage(): + print("Usage:") + print(f" {os.path.basename(__file__)} COMMAND") + print() + print("Commands:") + print(" list List models") + print(" pull MODEL Pull a model") + print(" run MODEL Run a model") + print(" serve MODEL Serve a model") + sys.exit(1) diff --git a/ramalama/version.py b/ramalama/version.py new file mode 100644 index 00000000..b63024ea --- /dev/null +++ b/ramalama/version.py @@ -0,0 +1,3 @@ +"""Version of RamalamaPy.""" + +__version__ = "0.1.0"