From 778f7fbd7cab263f2df203150950d8919715ab87 Mon Sep 17 00:00:00 2001 From: "Waylon S. Walker" Date: Mon, 24 Mar 2025 14:53:16 -0500 Subject: [PATCH] release 0.0.0 --- CHANGELOG.md | 3 + README.md | 80 +++++++++++++ justfile | 72 ++++++++++++ pvc_inspector.py => krayt.py | 174 +++++++++++++++++++---------- scripts/generate_install_script.py | 25 +++++ scripts/get_release_notes.py | 89 +++++++++++++++ scripts/install.sh.template | 126 +++++++++++++++++++++ version | 1 + 8 files changed, 513 insertions(+), 57 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 justfile rename pvc_inspector.py => krayt.py (81%) create mode 100755 scripts/generate_install_script.py create mode 100755 scripts/get_release_notes.py create mode 100644 scripts/install.sh.template create mode 100644 version diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2645ca0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.0 + +- Initial release diff --git a/README.md b/README.md new file mode 100644 index 0000000..c05e73c --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# Krayt - The Kubernetes Volume Inspector + +Like cracking open a Krayt dragon pearl, this tool helps you inspect what's inside your Kubernetes volumes. +Hunt down storage issues and explore your persistent data like a true Tatooine dragon hunter. + +## Features + +- ๐Ÿ” Create inspector pods with all the tools you need +- ๐Ÿ“ฆ Access volumes and device mounts from any pod +- ๐Ÿ”Ž Fuzzy search across all namespaces +- ๐Ÿ› ๏ธ Built-in tools for file exploration and analysis +- ๐Ÿงน Automatic cleanup of inspector pods + +## Installation + +### Quick Install (Linux) + +```bash +# Install latest version +curl -sSL https://github.com/waylonwalker/krayt/releases/latest/download/install.sh | sudo bash + +# Install specific version +curl -sSL https://github.com/waylonwalker/krayt/releases/download/v0.1.0/install.sh | sudo bash +``` + +This will install the `krayt` command to `/usr/local/bin`. + +### Manual Installation + +1. Download the latest release for your platform from the [releases page](https://github.com/waylonwalker/krayt/releases) +2. Extract the archive: `tar xzf krayt-*.tar.gz` +3. Move the binary: `sudo mv krayt-*/krayt /usr/local/bin/krayt` +4. Make it executable: `sudo chmod +x /usr/local/bin/krayt` + +## Usage + +```bash +# Create a new inspector and apply it directly +krayt create | kubectl apply -f - + +# Or review the manifest first +krayt create > inspector.yaml +kubectl apply -f inspector.yaml + +# Connect to a running inspector +krayt exec + +# Clean up inspectors +krayt clean + +# Show version +krayt version +``` + +### Available Tools + +Your inspector pod comes equipped with a full arsenal of tools: + +- **File Navigation**: `lf`, `exa`, `fd` +- **Search & Analysis**: `ripgrep`, `bat`, `hexyl` +- **Disk Usage**: `ncdu`, `dust` +- **File Comparison**: `difftastic` +- **System Monitoring**: `bottom`, `htop` +- **JSON/YAML Tools**: `jq`, `yq` +- **Network Tools**: `mtr`, `dig` +- **Cloud & Database**: `aws-cli`, `sqlite3` + +## Quotes from the Field + +> "Inside every volume lies a pearl of wisdom waiting to be discovered." +> +> -- Ancient Tatooine proverb + +> "The path to understanding your storage is through exploration." +> +> -- Krayt dragon hunter's manual + +## May the Force be with your volumes! + +Remember: A Krayt dragon's pearl is valuable not just for what it is, but for what it reveals about the dragon that created it. Similarly, your volumes tell a story about your application's data journey. diff --git a/justfile b/justfile new file mode 100644 index 0000000..938d946 --- /dev/null +++ b/justfile @@ -0,0 +1,72 @@ +delete-tag: + #!/usr/bin/env bash + set -euo pipefail + + # Get the version + VERSION=$(cat version) + + # Delete the tag + git tag -d "v$VERSION" + git push origin ":refs/tags/v$VERSION" + +delete-release: + #!/usr/bin/env bash + set -euo pipefail + + # Get the version + VERSION=$(cat version) + + # Delete the release + gh release delete "v$VERSION" + +create-tag: + #!/usr/bin/env bash + VERSION=$(cat version) + git tag -a "v$VERSION" -m "Release v$VERSION" + git push origin "v$VERSION" + +create-archives: + #!/usr/bin/env bash + VERSION=$(cat version) + rm -rf dist build + mkdir -p dist + + # Create the binary for each platform + for platform in "x86_64-unknown-linux-gnu" "aarch64-unknown-linux-gnu"; do + outdir="krayt-${VERSION}-${platform}" + mkdir -p "dist/${outdir}" + + # Copy the Python script and update version + cp krayt.py "dist/${outdir}/krayt.py" + sed -i "s/NIGHTLY/${VERSION}/" "dist/${outdir}/krayt.py" + + cd dist + tar czf "${outdir}.tar.gz" "${outdir}" + sha256sum "${outdir}.tar.gz" > "${outdir}.tar.gz.sha256" + cd .. + done + + # Generate install.sh + ./scripts/generate_install_script.py "$VERSION" + chmod +x dist/install.sh + +create-release: create-tag create-archives + #!/usr/bin/env bash + VERSION=$(cat version) + ./scripts/get_release_notes.py "$VERSION" > release_notes.tmp + gh release create "v$VERSION" \ + --title "v$VERSION" \ + --notes-file release_notes.tmp \ + dist/krayt-${VERSION}-x86_64-unknown-linux-gnu.tar.gz \ + dist/krayt-${VERSION}-x86_64-unknown-linux-gnu.tar.gz.sha256 \ + dist/krayt-${VERSION}-aarch64-unknown-linux-gnu.tar.gz \ + dist/krayt-${VERSION}-aarch64-unknown-linux-gnu.tar.gz.sha256 \ + dist/install.sh + rm release_notes.tmp + +preview-release-notes: + #!/usr/bin/env bash + VERSION=$(cat version) + ./scripts/get_release_notes.py "$VERSION" | less -R + +release: create-release diff --git a/pvc_inspector.py b/krayt.py similarity index 81% rename from pvc_inspector.py rename to krayt.py index a16517d..566f3e5 100755 --- a/pvc_inspector.py +++ b/krayt.py @@ -1,21 +1,38 @@ #!/usr/bin/env -S uv run --quiet --script # /// script -# requires-python = ">=3.12" +# requires-python = ">=3.10" # dependencies = [ # "typer", # "kubernetes", # "iterfzf" # ] # /// +""" +Krayt - The Kubernetes Volume Inspector + +Like cracking open a Krayt dragon pearl, this tool helps you inspect what's inside your Kubernetes volumes. +Hunt down storage issues and explore your persistent data like a true Tatooine dragon hunter. + +Features: +- Create inspector pods with all the tools you need +- Access volumes and device mounts from any pod +- Fuzzy search across all namespaces +- Built-in tools for file exploration and analysis +- Automatic cleanup of inspector pods + +May the Force be with your volumes! +""" from iterfzf import iterfzf from kubernetes import client, config import logging +import os import time import typer from typing import Any, Optional import yaml -import os + +KRAYT_VERSION = "NIGHTLY" logging.basicConfig(level=logging.WARNING) @@ -57,9 +74,7 @@ def format_volume(v: client.V1Volume) -> dict[str, Any]: volume_source = None if v.persistent_volume_claim: volume_source = { - "persistentVolumeClaim": { - "claimName": v.persistent_volume_claim.claim_name - } + "persistentVolumeClaim": {"claimName": v.persistent_volume_claim.claim_name} } elif v.config_map: volume_source = {"configMap": {"name": v.config_map.name}} @@ -69,14 +84,14 @@ def format_volume(v: client.V1Volume) -> dict[str, Any]: volume_source = { "hostPath": { "path": v.host_path.path, - "type": v.host_path.type if v.host_path.type else None + "type": v.host_path.type if v.host_path.type else None, } } elif v.empty_dir: # Add support for emptyDir volumes (used for /dev/shm) volume_source = { "emptyDir": { "medium": v.empty_dir.medium if v.empty_dir.medium else None, - "sizeLimit": v.empty_dir.size_limit if v.empty_dir.size_limit else None + "sizeLimit": v.empty_dir.size_limit if v.empty_dir.size_limit else None, } } @@ -157,34 +172,36 @@ def get_pod_volumes_and_mounts(pod_spec): for v in pod_spec.spec.volumes: # Handle device mounts if v.name in ["cache-volume"]: - volumes.append(client.V1Volume( - name=v.name, - empty_dir=client.V1EmptyDirVolumeSource( - medium="Memory" + volumes.append( + client.V1Volume( + name=v.name, + empty_dir=client.V1EmptyDirVolumeSource(medium="Memory"), ) - )) + ) elif v.name in ["coral-device"]: - volumes.append(client.V1Volume( - name=v.name, - host_path=client.V1HostPathVolumeSource( - path="/dev/apex_0", - type="CharDevice" + volumes.append( + client.V1Volume( + name=v.name, + host_path=client.V1HostPathVolumeSource( + path="/dev/apex_0", type="CharDevice" + ), ) - )) + ) elif v.name in ["qsv-device"]: - volumes.append(client.V1Volume( - name=v.name, - host_path=client.V1HostPathVolumeSource( - path="/dev/dri", - type="Directory" + volumes.append( + client.V1Volume( + name=v.name, + host_path=client.V1HostPathVolumeSource( + path="/dev/dri", type="Directory" + ), ) - )) + ) else: volumes.append(v) # Filter out None values from volumes volumes = [v for v in volumes if format_volume(v)] - + return volume_mounts, volumes @@ -230,9 +247,12 @@ def get_pod_env_and_secrets(api, namespace, pod_name): return env_vars, secret_volumes -def create_inspector_job(api, namespace, pod_name, volume_mounts, volumes): +def create_inspector_job( + api, namespace: str, pod_name: str, volume_mounts: list, volumes: list +): + """Create a Krayt inspector job with the given mounts""" timestamp = int(time.time()) - job_name = f"{pod_name}-inspector-{timestamp}" + job_name = f"{pod_name}-krayt-{timestamp}" # Get environment variables and secrets from the target pod env_vars, secret_volumes = get_pod_env_and_secrets(api, namespace, pod_name) @@ -258,7 +278,8 @@ def create_inspector_job(api, namespace, pod_name, volume_mounts, volumes): # Format mount and PVC info for MOTD mount_info = [] for vm in formatted_mounts: - mount_info.append(f"{vm['name']}:{vm['mountPath']}") + if vm: + mount_info.append(f"{vm['name']}:{vm['mountPath']}") pvc_info = [] for v in volumes: @@ -271,16 +292,16 @@ def create_inspector_job(api, namespace, pod_name, volume_mounts, volumes): "metadata": { "name": job_name, "namespace": namespace, - "labels": {"app": "pvc-inspector"}, + "labels": {"app": "krayt"}, }, "spec": { "ttlSecondsAfterFinished": 0, # Delete immediately after completion "template": { - "metadata": {"labels": {"app": "pvc-inspector"}}, + "metadata": {"labels": {"app": "krayt"}}, "spec": { "containers": [ { - "name": "inspector", + "name": "krayt", "image": "alpine:latest", # Use Alpine as base for package management "command": [ "sh", @@ -304,8 +325,10 @@ apk add ripgrep exa ncdu dust \ update_motd() { cat << EOF > /etc/motd ==================================== -PVC Inspector Pod +Krayt Dragon's Lair ==================================== +"Inside every volume lies a pearl of wisdom waiting to be discovered." + Mounted Volumes: $(echo "$MOUNTS" | tr ',' '\\n' | sed 's/^/- /') @@ -318,7 +341,7 @@ $(for d in /mnt/secrets/*; do if [ -d "$d" ]; then echo "- $(basename $d)"; fi; Environment Variables: $(env | sort | sed 's/^/- /') -Available Tools: +Your Hunting Tools: File Navigation: - lf: Terminal file manager (run 'lf') - exa: Modern ls (run 'ls', 'll', or 'tree') @@ -377,7 +400,7 @@ alias cat='bat --paging=never' # Function to show detailed tool help tools-help() { - echo "PVC Inspector Tools Guide:" + echo "Krayt Dragon Hunter's Guide:" echo echo "File Navigation:" echo " lf : Navigate with arrow keys, q to quit, h for help" @@ -475,16 +498,37 @@ PROTECTED_NAMESPACES = { "linkerd", } + +def version_callback(value: bool): + if value: + typer.echo(f"Version: {KRAYT_VERSION}") + raise typer.Exit() + + +@app.callback(invoke_without_command=True) +def main( + ctx: typer.Context, + version: bool = typer.Option( + False, "--version", "-v", help="Show version", callback=version_callback + ), +): + """ + Krack open a Krayt dragon! + """ + if ctx.invoked_subcommand is None: + ctx.get_help() + + @app.command() -def exec_inspector( +def exec( namespace: Optional[str] = typer.Option( None, help="Kubernetes namespace. If not specified, will search for inspectors across all namespaces.", ), ): """ - Execute a shell in a running inspector pod. If multiple inspectors are found, - presents a fuzzy finder to select one. + Enter the Krayt dragon's lair! Connect to a running inspector pod. + If multiple inspectors are found, you'll get to choose which one to explore. """ config.load_kube_config() batch_api = client.BatchV1Api() @@ -493,13 +537,11 @@ def exec_inspector( if namespace: logging.debug(f"Listing jobs in namespace {namespace}") jobs = batch_api.list_namespaced_job( - namespace=namespace, label_selector="app=pvc-inspector" + namespace=namespace, label_selector="app=krayt" ) else: logging.debug("Listing jobs in all namespaces") - jobs = batch_api.list_job_for_all_namespaces( - label_selector="app=pvc-inspector" - ) + jobs = batch_api.list_job_for_all_namespaces(label_selector="app=krayt") running_inspectors = [] for job in jobs.items: @@ -507,11 +549,13 @@ def exec_inspector( v1 = client.CoreV1Api() pods = v1.list_namespaced_pod( namespace=job.metadata.namespace, - label_selector=f"job-name={job.metadata.name}" + label_selector=f"job-name={job.metadata.name}", ) for pod in pods.items: if pod.status.phase == "Running": - running_inspectors.append((pod.metadata.name, pod.metadata.namespace)) + running_inspectors.append( + (pod.metadata.name, pod.metadata.namespace) + ) if not running_inspectors: typer.echo("No running inspector pods found.") @@ -527,7 +571,10 @@ def exec_inspector( # Execute the shell typer.echo(f"Connecting to inspector {pod_namespace}/{pod_name}...") - os.execvp("kubectl", ["kubectl", "exec", "-it", "-n", pod_namespace, pod_name, "--", "sh", "-l"]) + os.execvp( + "kubectl", + ["kubectl", "exec", "-it", "-n", pod_namespace, pod_name, "--", "sh", "-l"], + ) except client.exceptions.ApiException as e: logging.error(f"Failed to list jobs: {e}") @@ -536,19 +583,21 @@ def exec_inspector( @app.command() -def cleanup_inspectors( +def clean( namespace: Optional[str] = typer.Option( None, help="Kubernetes namespace. If not specified, will cleanup in all namespaces.", ), yes: bool = typer.Option( False, - "--yes", "-y", + "--yes", + "-y", help="Skip confirmation prompt.", ), ): """ - Delete all PVC inspector jobs in the specified namespace or all namespaces + Clean up after your hunt! Remove all Krayt inspector jobs. + Use --yes/-y to skip confirmation and clean up immediately. """ config.load_kube_config() batch_api = client.BatchV1Api() @@ -560,24 +609,28 @@ def cleanup_inspectors( raise typer.Exit(1) logging.debug(f"Listing jobs in namespace {namespace}") jobs = batch_api.list_namespaced_job( - namespace=namespace, label_selector="app=pvc-inspector" + namespace=namespace, label_selector="app=krayt" ) else: logging.debug("Listing jobs in all namespaces") - jobs = batch_api.list_job_for_all_namespaces( - label_selector="app=pvc-inspector" - ) + jobs = batch_api.list_job_for_all_namespaces(label_selector="app=krayt") # Filter out jobs in protected namespaces - jobs.items = [job for job in jobs.items if job.metadata.namespace not in PROTECTED_NAMESPACES] + jobs.items = [ + job + for job in jobs.items + if job.metadata.namespace not in PROTECTED_NAMESPACES + ] if not jobs.items: - typer.echo("No PVC inspector jobs found.") + typer.echo("No Krayt inspector jobs found.") return # Show confirmation prompt if not yes: - job_list = "\n".join(f" {job.metadata.namespace}/{job.metadata.name}" for job in jobs.items) + job_list = "\n".join( + f" {job.metadata.namespace}/{job.metadata.name}" for job in jobs.items + ) typer.echo(f"The following inspector jobs will be deleted:\n{job_list}") if not typer.confirm("Are you sure you want to continue?"): typer.echo("Operation cancelled.") @@ -611,15 +664,16 @@ def cleanup_inspectors( @app.command() -def create_inspector( +def create( namespace: Optional[str] = typer.Option( None, help="Kubernetes namespace. If not specified, will search for pods across all namespaces.", ), ): """ - Create a PVC inspector job. If namespace is not specified, will search for pods across all namespaces. - The inspector job will be created in the same namespace as the selected pod. + Krack open a Krayt dragon! Create an inspector pod to explore what's inside your volumes. + If namespace is not specified, will search for pods across all namespaces. + The inspector will be created in the same namespace as the selected pod. """ pods = get_pods(namespace) if not pods: @@ -642,5 +696,11 @@ def create_inspector( typer.echo(yaml.dump(clean_dict(inspector_job), sort_keys=False)) +@app.command() +def version(): + """Show the version of Krayt.""" + typer.echo(f"Version: {KRAYT_VERSION}") + + if __name__ == "__main__": app() diff --git a/scripts/generate_install_script.py b/scripts/generate_install_script.py new file mode 100755 index 0000000..62ebcc0 --- /dev/null +++ b/scripts/generate_install_script.py @@ -0,0 +1,25 @@ +#!/usr/bin/env -S uv run --quiet --script +# /// script +# requires-python = ">=3.10" +# /// + +import sys + + +def generate_install_script(version): + with open("scripts/install.sh.template", "r") as f: + template = f.read() + + script = template.replace("{{VERSION}}", version) + + with open("dist/install.sh", "w") as f: + f.write(script) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: generate_install_script.py VERSION", file=sys.stderr) + sys.exit(1) + + version = sys.argv[1] + generate_install_script(version) diff --git a/scripts/get_release_notes.py b/scripts/get_release_notes.py new file mode 100755 index 0000000..d782a15 --- /dev/null +++ b/scripts/get_release_notes.py @@ -0,0 +1,89 @@ +#!/usr/bin/env -S uv run --quiet --script +# /// script +# requires-python = ">=3.10" +# /// + +import subprocess +import sys + + +def get_release_notes(version): + with open("CHANGELOG.md", "r") as f: + content = f.read() + + sections = content.split("\n## ") + # First section won't start with ## since it's split on that + sections = ["## " + s if i > 0 else s for i, s in enumerate(sections)] + + for section in sections: + if section.startswith(f"## {version}"): + install_instructions = f"""## Installation + +You can install krayt using one of these methods: + +> !krayt requires [uv](https://docs.astral.sh/uv/getting-started/installation/) to be installed + +### Using i.jpillora.com (recommended) + +``` bash +curl https://i.jpillora.com/waylonwalker/krayt | bash +``` + +### Direct install script + +``` bash +curl -fsSL https://github.com/waylonwalker/krayt/releases/download/v{version}/install.sh | bash +``` + +### Manual download +You can also manually download the archive for your platform from the releases page: +- [x86_64-unknown-linux-gnu](https://github.com/waylonwalker/krayt/releases/download/v{version}/krayt-{version}-x86_64-unknown-linux-gnu.tar.gz) +- [aarch64-unknown-linux-gnu](https://github.com/waylonwalker/krayt/releases/download/v{version}/krayt-{version}-aarch64-unknown-linux-gnu.tar.gz)""" + + # Get help output for main command and all subcommands + try: + help_outputs = [] + + # Get main help output + main_help = subprocess.check_output( + ["./krayt.py", "--help"], + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + help_outputs.append(("Main Command", main_help)) + + # Get help for each subcommand + subcommands = ["create", "exec", "clean", "version"] + for cmd in subcommands: + cmd_help = subprocess.check_output( + ["./krayt.py", cmd, "--help"], + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + help_outputs.append((f"Subcommand: {cmd}", cmd_help)) + + # Format all help outputs + help_text = "\n\n".join( + f"### {title}\n\n``` bash\n{output}```" + for title, output in help_outputs + ) + + return f"{section.strip()}\n\n{install_instructions.format(version=version)}\n\n## Command Line Usage\n\n{help_text}" + except subprocess.CalledProcessError as e: + return f"{section.strip()}\n\n{install_instructions.format(version=version)}\n\n## Command Line Usage\n\nError getting help: {e.output}" + + return None + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: get_release_notes.py VERSION", file=sys.stderr) + sys.exit(1) + + version = sys.argv[1] + notes = get_release_notes(version) + if notes: + print(notes) + else: + print(f"Error: No release notes found for version {version}", file=sys.stderr) + sys.exit(1) diff --git a/scripts/install.sh.template b/scripts/install.sh.template new file mode 100644 index 0000000..907cc23 --- /dev/null +++ b/scripts/install.sh.template @@ -0,0 +1,126 @@ +#!/bin/bash +if [ "$DEBUG" == "1" ]; then + set -x +fi +TMP_DIR=$(mktemp -d -t krayt-installer-XXXXXXXXXX) +function cleanup { + rm -rf $TMP_DIR >/dev/null +} +function fail { + cleanup + msg=$1 + echo "============" + echo "Error: $msg" 1>&2 + exit 1 +} +function check_deps { + if ! command -v uv &>/dev/null; then + echo " Error: uv is not installed" + echo "krayt requires uv to run. You can install it with:" + echo " curl -LsSf https://astral.sh/uv/install.sh | sh" + echo "" + echo "Or visit: https://github.com/astral/uv for more installation options" + echo "" + fail "uv not found" + fi +} +function install { + #settings + USER="waylonwalker" + PROG="krayt" + ASPROG="krayt" + MOVE="true" + RELEASE="{{VERSION}}" + INSECURE="false" + OUT_DIR="/usr/local/bin" + GH="https://github.com" + #bash check + [ ! "$BASH_VERSION" ] && fail "Please use bash instead" + [ ! -d $OUT_DIR ] && fail "output directory missing: $OUT_DIR" + #dependency check, assume we are a standard POISX machine + which find >/dev/null || fail "find not installed" + which xargs >/dev/null || fail "xargs not installed" + which sort >/dev/null || fail "sort not installed" + which tail >/dev/null || fail "tail not installed" + which cut >/dev/null || fail "cut not installed" + which du >/dev/null || fail "du not installed" + #choose an HTTP client + GET="" + if which curl >/dev/null; then + GET="curl" + if [[ $INSECURE = "true" ]]; then GET="$GET --insecure"; fi + GET="$GET --fail -# -L" + elif which wget >/dev/null; then + GET="wget" + if [[ $INSECURE = "true" ]]; then GET="$GET --no-check-certificate"; fi + GET="$GET -qO-" + else + fail "neither wget/curl are installed" + fi + #find OS + case $(uname -s) in + Darwin) OS="darwin" ;; + Linux) OS="linux" ;; + *) fail "unknown os: $(uname -s)" ;; + esac + #find ARCH + if uname -m | grep -E '(arm|aarch)64' >/dev/null; then + ARCH="aarch64" + elif uname -m | grep 64 >/dev/null; then + ARCH="x86_64" + else + fail "unknown arch: $(uname -m)" + fi + #choose from asset list + URL="" + FTYPE="" + VERSION=${RELEASE#v} + if [[ $VERSION == "" ]]; then + VERSION=$(curl -s https://api.github.com/repos/$USER/$PROG/releases/latest | grep -o '"tag_name": "[^"]*' | cut -d'"' -f4) + fi + if [[ $VERSION == "" ]]; then + fail "cannot find latest version" + fi + VERSION=${VERSION#v} + ASSET_URL="$GH/$USER/$PROG/releases/download/v$VERSION/${PROG}-${VERSION}-${ARCH}-unknown-${OS}-gnu.tar.gz" + echo "Installing $PROG v$VERSION..." + echo "Downloading binary from $ASSET_URL" + #enter tempdir + cd $TMP_DIR + #download and unpack + if [[ $ASSET_URL =~ \.gz$ ]]; then + which tar >/dev/null || fail "tar not installed" + if [[ $GET =~ ^curl ]]; then + curl -s ${ASSET_URL} | tar zx || fail "download failed" + else + wget -qO- ${ASSET_URL} | tar zx || fail "download failed" + fi + else + fail "unknown file type: $ASSET_URL" + fi + #check for error + cd ${PROG}-${VERSION}-${ARCH}-unknown-${OS}-gnu + #move binary + if [[ -f "${PROG}.py" ]]; then + chmod +x "${PROG}.py" + if [[ $MOVE == "true" ]]; then + echo "Moving binary to $OUT_DIR/$ASPROG" + # Create a wrapper script to ensure uv is used + cat > "$OUT_DIR/$ASPROG" << EOF +#!/bin/bash +exec uv run --quiet --script "$OUT_DIR/${ASPROG}.py" "\$@" +EOF + chmod +x "$OUT_DIR/$ASPROG" + mv "${PROG}.py" "$OUT_DIR/${ASPROG}.py" || fail "Cannot move binary to $OUT_DIR" + else + echo "Moving binary to $OUT_DIR/${ASPROG}.py" + mv "${PROG}.py" "$OUT_DIR/${ASPROG}.py" || fail "Cannot move binary to $OUT_DIR" + fi + else + fail "cannot find binary" + fi + echo "Installation complete!" + cleanup +} +check_deps +install diff --git a/version b/version new file mode 100644 index 0000000..77d6f4c --- /dev/null +++ b/version @@ -0,0 +1 @@ +0.0.0