diff --git a/README.md b/README.md index c05e73c..01ff9fb 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,80 @@ Your inspector pod comes equipped with a full arsenal of tools: - **Network Tools**: `mtr`, `dig` - **Cloud & Database**: `aws-cli`, `sqlite3` +## Customization + +### Init Scripts + +Krayt supports initialization scripts that run in the inspector pod before any packages are installed. These scripts are useful for: +- Setting up proxy configurations +- Installing additional tools +- Configuring custom package repositories +- Setting environment variables + +Place your scripts in `~/.config/krayt/init.d/` with a `.sh` extension. Scripts are executed in alphabetical order, so you can control the execution sequence using numerical prefixes. + +Example init scripts: + +1. Install additional tools (`~/.config/krayt/init.d/10_install_git.sh`): +```bash +#!/bin/sh +echo "Installing additional tools..." + +# Install git for source control +apk add git + +# Configure git +git config --global init.defaultBranch main +git config --global core.editor vi +``` + +2. Set up custom repositories (`~/.config/krayt/init.d/20_custom_repos.sh`): +```bash +#!/bin/sh +echo "Adding custom package repositories..." + +# Add testing repository for newer packages +echo "@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories + +# Update package list +apk update +``` + +### Proxy Configuration + +If your environment requires a proxy, you have two options: + +1. **Environment Variables** (Recommended): + ```bash + # Add to your shell's rc file (e.g., ~/.bashrc, ~/.zshrc) + export HTTP_PROXY="http://proxy.example.com:8080" + export HTTPS_PROXY="http://proxy.example.com:8080" + export NO_PROXY="localhost,127.0.0.1,.internal.example.com" + ``` + +2. **Init Script** (`~/.config/krayt/init.d/00_proxy.sh`): + ```bash + #!/bin/sh + echo "Configuring proxy settings..." + + # Set proxy for Alpine package manager + mkdir -p /etc/apk + cat > /etc/apk/repositories << EOF + http://dl-cdn.alpinelinux.org/alpine/latest-stable/main + http://dl-cdn.alpinelinux.org/alpine/latest-stable/community + + # Configure proxy + proxy=http://proxy.example.com:8080 + EOF + + # Set proxy for other tools + export HTTP_PROXY="http://proxy.example.com:8080" + export HTTPS_PROXY="http://proxy.example.com:8080" + export NO_PROXY="localhost,127.0.0.1,.internal.example.com" + ``` + +The proxy configuration will be applied before any packages are installed, ensuring that all package installations and network operations work correctly through your proxy. + ## Quotes from the Field > "Inside every volume lies a pearl of wisdom waiting to be discovered." diff --git a/examples/init.d/00_proxy.sh b/examples/init.d/00_proxy.sh new file mode 100644 index 0000000..37d99cb --- /dev/null +++ b/examples/init.d/00_proxy.sh @@ -0,0 +1,25 @@ +#!/bin/sh +echo "Configuring proxy settings..." + +# Set proxy for Alpine package manager +mkdir -p /etc/apk +cat > /etc/apk/repositories << EOF +http://dl-cdn.alpinelinux.org/alpine/latest-stable/main +http://dl-cdn.alpinelinux.org/alpine/latest-stable/community + +# Configure proxy +proxy=http://proxy.example.com:8080 +EOF + +# Set proxy for other tools +export HTTP_PROXY="http://proxy.example.com:8080" +export HTTPS_PROXY="http://proxy.example.com:8080" +export NO_PROXY="localhost,127.0.0.1,.internal.example.com" + +# Test proxy configuration +echo "Testing proxy configuration..." +if curl -s -m 5 https://www.google.com > /dev/null; then + echo "Proxy configuration successful!" +else + echo "Warning: Proxy test failed. Check your proxy settings." +fi diff --git a/examples/init.d/10_install_git.sh b/examples/init.d/10_install_git.sh new file mode 100644 index 0000000..1646dbf --- /dev/null +++ b/examples/init.d/10_install_git.sh @@ -0,0 +1,20 @@ +#!/bin/sh +echo "Installing additional development tools..." + +# Install git and related tools +apk add git git-lfs + +# Configure git defaults +git config --global init.defaultBranch main +git config --global core.editor vi +git config --global pull.rebase false + +# Add some helpful git aliases +git config --global alias.st status +git config --global alias.co checkout +git config --global alias.br branch +git config --global alias.ci commit +git config --global alias.unstage 'reset HEAD --' +git config --global alias.last 'log -1 HEAD' + +echo "Git configuration complete." diff --git a/examples/init.d/20_custom_repos.sh b/examples/init.d/20_custom_repos.sh new file mode 100644 index 0000000..164f496 --- /dev/null +++ b/examples/init.d/20_custom_repos.sh @@ -0,0 +1,19 @@ +#!/bin/sh +echo "Setting up additional package repositories..." + +# Add testing repository for newer packages +echo "@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories + +# Add community repository +echo "@community http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories + +# Update package list +apk update + +# Install some useful tools from testing/community +apk add \ + @testing golang \ + @community rust \ + @community cargo + +echo "Additional repositories configured and packages installed." diff --git a/krayt-squooshed.png b/krayt-squooshed.png new file mode 100644 index 0000000..c3f6c5c Binary files /dev/null and b/krayt-squooshed.png differ diff --git a/krayt.png b/krayt.png new file mode 100644 index 0000000..eb181a0 Binary files /dev/null and b/krayt.png differ diff --git a/krayt.py b/krayt.py index 566f3e5..f9b0c67 100755 --- a/krayt.py +++ b/krayt.py @@ -13,20 +13,15 @@ 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! """ +import os +import glob +from pathlib import Path from iterfzf import iterfzf from kubernetes import client, config import logging -import os import time import typer from typing import Any, Optional @@ -205,46 +200,110 @@ def get_pod_volumes_and_mounts(pod_spec): return volume_mounts, volumes -def get_pod_env_and_secrets(api, namespace, pod_name): - pod = api.read_namespaced_pod(pod_name, namespace) - - # Get environment variables from the pod +def get_env_vars_and_secret_volumes(api, namespace: str): + """Get environment variables and secret volumes for the inspector pod""" env_vars = [] - for container in pod.spec.containers: - if container.env: - for env in container.env: - env_dict = {"name": env.name} - if env.value: - env_dict["value"] = env.value - elif env.value_from: - if env.value_from.config_map_key_ref: - env_dict["valueFrom"] = { - "configMapKeyRef": { - "name": env.value_from.config_map_key_ref.name, - "key": env.value_from.config_map_key_ref.key, - } - } - elif env.value_from.secret_key_ref: - env_dict["valueFrom"] = { - "secretKeyRef": { - "name": env.value_from.secret_key_ref.name, - "key": env.value_from.secret_key_ref.key, - } - } - elif env.value_from.field_ref: - env_dict["valueFrom"] = { - "fieldRef": { - "fieldPath": env.value_from.field_ref.field_path - } - } - env_vars.append(env_dict) + volumes = [] - # Get all volume mounts that are secrets - secret_volumes = [] - if pod.spec.volumes: - secret_volumes = [v for v in pod.spec.volumes if v.secret] + # Add proxy environment variables if they exist in the host environment + proxy_vars = ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", + "http_proxy", "https_proxy", "no_proxy"] + + for var in proxy_vars: + if var in os.environ: + env_vars.append({"name": var, "value": os.environ[var]}) - return env_vars, secret_volumes + # Look for secret volumes in the namespace + try: + secrets = api.list_namespaced_secret(namespace) + for secret in secrets.items: + # Skip service account tokens and other system secrets + if ( + secret.type != "Opaque" + or secret.metadata.name.startswith("default-token-") + ): + continue + + # Mount each secret as a volume + volume_name = f"secret-{secret.metadata.name}" + volumes.append( + client.V1Volume( + name=volume_name, + secret=client.V1SecretVolumeSource( + secret_name=secret.metadata.name + ), + ) + ) + + except client.exceptions.ApiException as e: + if e.status != 404: # Ignore if no secrets found + logging.warning(f"Failed to list secrets in namespace {namespace}: {e}") + + return env_vars, volumes + + +def get_init_scripts(): + """Get the contents of init scripts to be run in the pod""" + init_dir = Path.home() / ".config" / "krayt" / "init.d" + if not init_dir.exists(): + return "" + + # Sort scripts to ensure consistent execution order + scripts = sorted(init_dir.glob("*.sh")) + if not scripts: + return "" + + # Create a combined script that will run all init scripts + init_script = "#!/bin/sh\n\n" + init_script += "echo 'Running initialization scripts...'\n\n" + + for script in scripts: + try: + with open(script, 'r') as f: + script_content = f.read() + if script_content: + init_script += f"echo '=== Running {script.name} ==='\n" + # Write each script to a separate file + init_script += f"cat > /tmp/{script.name} << 'EOFSCRIPT'\n" + init_script += script_content + if not script_content.endswith('\n'): + init_script += '\n' + init_script += "EOFSCRIPT\n\n" + # Make it executable and run it + init_script += f"chmod +x /tmp/{script.name}\n" + init_script += f"/tmp/{script.name} 2>&1 | tee -a /tmp/init.log\n" + init_script += f"echo '=== Finished {script.name} ===\n\n'" + except Exception as e: + logging.error(f"Failed to load init script {script}: {e}") + + init_script += "echo 'Initialization scripts complete.'\n" + return init_script + + +def get_motd_script(mount_info, pvc_info): + """Generate the MOTD script with proper escaping""" + return f""" +# Create MOTD +cat << 'EOF' > /etc/motd +==================================== +Krayt Dragon's Lair +A safe haven for volume inspection +==================================== + +"Inside every volume lies a pearl of wisdom waiting to be discovered." + +Mounted Volumes: +$(echo "{','.join(mount_info)}" | tr ',' '\\n' | sed 's/^/- /') + +Persistent Volume Claims: +$(echo "{','.join(pvc_info)}" | tr ',' '\\n' | sed 's/^/- /') + +Mounted Secrets: +$(for d in /mnt/secrets/*; do if [ -d "$d" ]; then echo "- $(basename $d)"; fi; done) + +Init Script Status: +$(if [ -f /tmp/init.log ]; then echo "View initialization log at /tmp/init.log"; fi) +EOF""" def create_inspector_job( @@ -254,8 +313,8 @@ def create_inspector_job( timestamp = int(time.time()) 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) + # Get environment variables and secret volumes from the target pod + env_vars, secret_volumes = get_env_vars_and_secret_volumes(api, namespace) # Add secret volumes to our volumes list volumes.extend(secret_volumes) @@ -286,6 +345,89 @@ def create_inspector_job( if hasattr(v, "persistent_volume_claim") and v.persistent_volume_claim: pvc_info.append(f"{v.name}:{v.persistent_volume_claim.claim_name}") + init_scripts = get_init_scripts() + + # Build the command script + command_parts = [] + + # Configure apk proxy settings BEFORE any package installation + command_parts.extend([ + "# Configure apk proxy settings", + "mkdir -p /etc/apk", + "cat > /etc/apk/repositories << 'EOF'", + "https://dl-cdn.alpinelinux.org/alpine/latest-stable/main", + "https://dl-cdn.alpinelinux.org/alpine/latest-stable/community", + "EOF", + "", + "if [ ! -z \"$HTTP_PROXY\" ]; then", + " echo \"Setting up apk proxy configuration...\"", + " mkdir -p /etc/apk/", + " cat > /etc/apk/repositories << EOF", + "#/media/cdrom/apks", + "http://dl-cdn.alpinelinux.org/alpine/latest-stable/main", + "http://dl-cdn.alpinelinux.org/alpine/latest-stable/community", + "", + "# Configure proxy", + "proxy=$HTTP_PROXY", + "EOF", + "fi", + "" + ]) + + # Add init scripts if present + if init_scripts: + command_parts.extend([ + "# Write and run init scripts", + "mkdir -p /tmp/init.d", + "cat > /tmp/init.sh << 'EOFSCRIPT'", + init_scripts, + "EOFSCRIPT", + "", + "# Make init script executable and run it", + "chmod +x /tmp/init.sh", + "/tmp/init.sh 2>&1 | tee /tmp/init.log", + "echo 'Init script log available at /tmp/init.log'", + "" + ]) + + # Add base installation commands AFTER proxy configuration + command_parts.extend([ + "# Install basic tools first", + "apk update", + "apk add curl", + "", + "# Install additional tools", + "apk add ripgrep exa ncdu dust file hexyl jq yq bat fd fzf htop bottom difftastic mtr bind-tools aws-cli sqlite sqlite-dev sqlite-libs", + "", + "# Create .ashrc with MOTD", + "cat > /root/.ashrc << 'EOF'", + "# Display MOTD on login", + "[ -f /etc/motd ] && cat /etc/motd", + "EOF", + "", + "# Set up shell environment", + "export EDITOR=vi", + "export PAGER=less", + "", + "# Set up environment to always source our RC file", + "echo 'export ENV=/root/.ashrc' > /etc/profile", + "echo 'export ENV=/root/.ashrc' > /etc/environment", + "", + "# Make RC file available to all shells", + "mkdir -p /etc/profile.d", + "cp /root/.ashrc /etc/profile.d/motd.sh", + "ln -sf /root/.ashrc /root/.profile", + "ln -sf /root/.ashrc /root/.bashrc", + "ln -sf /root/.ashrc /root/.mkshrc", + "ln -sf /root/.ashrc /etc/shinit", + "", + "# Update MOTD", + get_motd_script(mount_info, pvc_info), + "", + "# Keep container running", + "tail -f /dev/null" + ]) + inspector_job = { "apiVersion": "batch/v1", "kind": "Job", @@ -293,11 +435,15 @@ def create_inspector_job( "name": job_name, "namespace": namespace, "labels": {"app": "krayt"}, + "annotations": { + "pvcs": ",".join(pvc_info) if pvc_info else "none" + } }, "spec": { - "ttlSecondsAfterFinished": 0, # Delete immediately after completion "template": { - "metadata": {"labels": {"app": "krayt"}}, + "metadata": { + "labels": {"app": "krayt"} + }, "spec": { "containers": [ { @@ -306,170 +452,9 @@ def create_inspector_job( "command": [ "sh", "-c", - """ -# Install basic tools first -apk update -apk add curl - -# Install lf (terminal file manager) -curl -L https://github.com/gokcehan/lf/releases/download/r31/lf-linux-amd64.tar.gz | tar xzf - -C /usr/local/bin - -# Install the rest of the tools -apk add ripgrep exa ncdu dust \ - file hexyl jq yq bat fd fzf \ - htop bottom difftastic \ - mtr bind-tools \ - aws-cli sqlite sqlite-dev sqlite-libs - -# Function to update MOTD -update_motd() { - cat << EOF > /etc/motd -==================================== -Krayt Dragon's Lair -==================================== -"Inside every volume lies a pearl of wisdom waiting to be discovered." - -Mounted Volumes: -$(echo "$MOUNTS" | tr ',' '\\n' | sed 's/^/- /') - -Persistent Volume Claims: -$(echo "$PVCS" | tr ',' '\\n' | sed 's/^/- /') - -Mounted Secrets: -$(for d in /mnt/secrets/*; do if [ -d "$d" ]; then echo "- $(basename $d)"; fi; done) - -Environment Variables: -$(env | sort | sed 's/^/- /') - -Your Hunting Tools: -File Navigation: -- lf: Terminal file manager (run 'lf') -- exa: Modern ls (run 'ls', 'll', or 'tree') -- fd: Modern find (run 'fd pattern') - -Search & Analysis: -- rg (ripgrep): Fast search (run 'rg pattern') -- bat: Better cat with syntax highlighting -- hexyl: Hex viewer (run 'hexyl file') -- file: File type detection - -Disk Usage: -- ncdu: Interactive disk usage analyzer -- dust: Disk usage analyzer -- du: Standard disk usage tool - -File Comparison: -- difft: Modern diff tool (alias 'diff') - -System Monitoring: -- btm: Modern system monitor (alias 'top') -- htop: Interactive process viewer - -JSON/YAML Tools: -- jq: JSON processor -- yq: YAML processor - -Network Tools: -- dig: DNS lookup -- mtr: Network diagnostics - -Cloud & Database: -- aws: AWS CLI -- sqlite3: SQLite database tool - -Type 'tools-help' for detailed usage information -==================================== -EOF -} - -# Create helpful aliases and functions -cat << 'EOF' > /root/.ashrc -if [ "$PS1" ]; then - cat /etc/motd -fi - -# Aliases for better file navigation -alias ls='exa' -alias ll='exa -l' -alias la='exa -la' -alias tree='exa --tree' -alias find='fd' -alias top='btm' -alias diff='difft' -alias cat='bat --paging=never' - -# Function to show detailed tool help -tools-help() { - echo "Krayt Dragon Hunter's Guide:" - echo - echo "File Navigation:" - echo " lf : Navigate with arrow keys, q to quit, h for help" - echo " ls, ll, la : List files (exa with different options)" - echo " tree : Show directory structure" - echo " fd pattern : Find files matching pattern" - echo - echo "Search & Analysis:" - echo " rg pattern : Search file contents" - echo " bat file : View file with syntax highlighting" - echo " hexyl file : View file in hex format" - echo " file path : Determine file type" - echo - echo "Disk Usage:" - echo " ncdu : Interactive disk usage analyzer (navigate with arrows)" - echo " dust path : Tree-based disk usage" - echo " du -sh * : Summarize disk usage" - echo - echo "File Comparison:" - echo " diff file1 file2 : Compare files with syntax highlighting" - echo - echo "System Monitoring:" - echo " top (btm) : Modern system monitor" - echo " htop : Process viewer" - echo - echo "JSON/YAML Tools:" - echo " jq . file.json : Format and query JSON" - echo " yq . file.yaml : Format and query YAML" - echo - echo "Network Tools:" - echo " dig domain : DNS lookup" - echo " mtr host : Network diagnostics" - echo - echo "Cloud & Database:" - echo " aws : AWS CLI tool" - echo " sqlite3 : SQLite database tool" - echo - echo "Secrets:" - echo " ls /mnt/secrets : List mounted secrets" -} - -# Set some helpful environment variables -export EDITOR=vi -export PAGER=less -EOF - -# Set up environment to always source our RC file -echo "export ENV=/root/.ashrc" > /etc/profile -echo "export ENV=/root/.ashrc" > /etc/environment - -# Make RC file available to all shells -cp /root/.ashrc /etc/profile.d/motd.sh -ln -sf /root/.ashrc /root/.profile -ln -sf /root/.ashrc /root/.bashrc -ln -sf /root/.ashrc /root/.mkshrc -ln -sf /root/.ashrc /etc/shinit - -# Create initial MOTD -update_motd - -sleep 3600 - """, - ], - "env": env_vars - + [ - {"name": "MOUNTS", "value": ",".join(mount_info)}, - {"name": "PVCS", "value": ",".join(pvc_info)}, - {"name": "ENV", "value": "/root/.ashrc"}, + "\n".join(command_parts) ], + "env": env_vars, "volumeMounts": formatted_mounts, } ], @@ -499,6 +484,37 @@ PROTECTED_NAMESPACES = { } +def load_init_scripts(): + """Load and execute initialization scripts from ~/.config/krayt/scripts/""" + init_dir = Path.home() / ".config" / "krayt" / "scripts" + if not init_dir.exists(): + return + + # Sort scripts to ensure consistent execution order + scripts = sorted(init_dir.glob("*.py")) + + for script in scripts: + try: + with open(script, 'r') as f: + exec(f.read(), globals()) + logging.debug(f"Loaded init script: {script}") + except Exception as e: + logging.error(f"Failed to load init script {script}: {e}") + + +def setup_environment(): + """Set up the environment with proxy settings and other configurations""" + # Load environment variables for proxies + proxy_vars = ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", + "http_proxy", "https_proxy", "no_proxy"] + + for var in proxy_vars: + if var in os.environ: + # Make both upper and lower case versions available + os.environ[var.upper()] = os.environ[var] + os.environ[var.lower()] = os.environ[var] + + def version_callback(value: bool): if value: typer.echo(f"Version: {KRAYT_VERSION}") @@ -569,12 +585,21 @@ def exec( typer.echo("No inspector selected.") raise typer.Exit(1) - # Execute the shell - typer.echo(f"Connecting to inspector {pod_namespace}/{pod_name}...") - os.execvp( + # Execute into the pod with a login shell to source .ashrc and show MOTD + exec_command = [ "kubectl", - ["kubectl", "exec", "-it", "-n", pod_namespace, pod_name, "--", "sh", "-l"], - ) + "-n", + pod_namespace, + "exec", + "-it", + pod_name, + "--", + "/bin/sh", + "-c", + "cat /etc/motd; exec /bin/ash -l" + ] + + os.execvp("kubectl", exec_command) except client.exceptions.ApiException as e: logging.error(f"Failed to list jobs: {e}") @@ -702,5 +727,11 @@ def version(): typer.echo(f"Version: {KRAYT_VERSION}") -if __name__ == "__main__": +def main(): + setup_environment() + load_init_scripts() app() + + +if __name__ == "__main__": + main() diff --git a/scripts/install.sh.template b/scripts/install.sh.template index 907cc23..5fc0fb5 100644 --- a/scripts/install.sh.template +++ b/scripts/install.sh.template @@ -13,7 +13,7 @@ function fail { echo "Error: $msg" 1>&2 exit 1 } -function check_deps { +function check_uv { if ! command -v uv &>/dev/null; then echo " Error: uv is not installed" echo "krayt requires uv to run. You can install it with:" @@ -24,15 +24,67 @@ function check_deps { fail "uv not found" fi } + +function setup_config_dir { + # Create config directory + CONFIG_DIR="${HOME}/.config/krayt" + mkdir -p "${CONFIG_DIR}/init.d" + + # Create example init script if it doesn't exist + EXAMPLE_SCRIPT="${CONFIG_DIR}/init.d/00_proxy.sh.example" + if [ ! -f "$EXAMPLE_SCRIPT" ]; then + cat > "$EXAMPLE_SCRIPT" << 'EOF' +#!/bin/sh +# Example initialization script for Krayt inspector pods +# This script runs before any packages are installed +# To use this script, rename it to remove the .example extension + +# Example: Set up proxy configuration +setup_proxy() { + # Uncomment and modify these lines to set up your proxy + # export HTTP_PROXY="http://proxy.example.com:8080" + # export HTTPS_PROXY="http://proxy.example.com:8080" + # export NO_PROXY="localhost,127.0.0.1,.example.com" + + # Set up proxy for apk if needed + if [ ! -z "$HTTP_PROXY" ]; then + echo "proxy = $HTTP_PROXY" >> /etc/apk/repositories + fi +} + +# Example: Add custom Alpine repositories +setup_repos() { + # Uncomment to add custom repos + # echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories + : +} + +# Run the setup functions +setup_proxy +setup_repos + +# Log the configuration +echo "Krayt inspector pod initialization complete" +echo "Proxy settings:" +echo "HTTP_PROXY=$HTTP_PROXY" +echo "HTTPS_PROXY=$HTTPS_PROXY" +echo "NO_PROXY=$NO_PROXY" +EOF + fi + + echo "Created config directory at ${CONFIG_DIR}" + echo "Example init script created at ${EXAMPLE_SCRIPT}" +} + function install { #settings USER="waylonwalker" PROG="krayt" ASPROG="krayt" - MOVE="true" + MOVE="false" RELEASE="{{VERSION}}" INSECURE="false" - OUT_DIR="/usr/local/bin" + OUT_DIR="$(pwd)" GH="https://github.com" #bash check [ ! "$BASH_VERSION" ] && fail "Please use bash instead" @@ -57,70 +109,126 @@ function install { else fail "neither wget/curl are installed" fi - #find OS + #debug HTTP + if [ "$DEBUG" == "1" ]; then + GET="$GET -v" + fi + #optional auth to install from private repos + #NOTE: this also needs to be set on your instance of installer + AUTH="${GITHUB_TOKEN}" + if [ ! -z "$AUTH" ]; then + GET="$GET -H 'Authorization: $AUTH'" + fi + #find OS #TODO BSDs and other posixs 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" + if uname -m | grep -E '(arm|arch)64' >/dev/null; then + ARCH="arm64" + + # no m1 assets. if on mac arm64, rosetta allows fallback to amd64 + if [[ $OS = "darwin" ]]; then + ARCH="amd64" + fi + elif uname -m | grep 64 >/dev/null; then - ARCH="x86_64" + ARCH="amd64" + elif uname -m | grep arm >/dev/null; then + ARCH="arm" #TODO armv6/v7 + elif uname -m | grep 386 >/dev/null; then + ARCH="386" 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) + case "${OS}_${ARCH}" in + "linux_amd64") + URL="https://github.com/WaylonWalker/nvim-manager/releases/download/v${RELEASE}/nvim-manager-${RELEASE}-x86_64-unknown-linux-gnu.tar.gz" + FTYPE=".tar.gz" + ;; + "linux_arm64") + URL="https://github.com/WaylonWalker/nvim-manager/releases/download/v${RELEASE}/nvim-manager-${RELEASE}-aarch64-unknown-linux-gnu.tar.gz" + FTYPE=".tar.gz" + ;; + *) fail "No asset for platform ${OS}-${ARCH}" ;; + esac + #got URL! download it... + echo -n "Downloading" + echo -n " $USER/$PROG" + if [ ! -z "$RELEASE" ]; then + echo -n " $RELEASE" fi - if [[ $VERSION == "" ]]; then - fail "cannot find latest version" + if [ ! -z "$ASPROG" ]; then + echo -n " as $ASPROG" 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" + echo -n " (${OS}/${ARCH})" + + echo "....." + #enter tempdir + mkdir -p $TMP_DIR 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 + if [[ $FTYPE = ".gz" ]]; then + which gzip >/dev/null || fail "gzip is not installed" + bash -c "$GET $URL" | gzip -d - >$PROG || fail "download failed" + elif [[ $FTYPE = ".bz2" ]]; then + which bzip2 >/dev/null || fail "bzip2 is not installed" + bash -c "$GET $URL" | bzip2 -d - >$PROG || fail "download failed" + elif [[ $FTYPE = ".tar.bz" ]] || [[ $FTYPE = ".tar.bz2" ]]; then + which tar >/dev/null || fail "tar is not installed" + which bzip2 >/dev/null || fail "bzip2 is not installed" + bash -c "$GET $URL" | tar jxf - || fail "download failed" + elif [[ $FTYPE = ".tar.gz" ]] || [[ $FTYPE = ".tgz" ]]; then + which tar >/dev/null || fail "tar is not installed" + which gzip >/dev/null || fail "gzip is not installed" + bash -c "$GET $URL" | tar zxf - || fail "download failed" + elif [[ $FTYPE = ".zip" ]]; then + which unzip >/dev/null || fail "unzip is not installed" + bash -c "$GET $URL" >tmp.zip || fail "download failed" + unzip -o -qq tmp.zip || fail "unzip failed" + rm tmp.zip || fail "cleanup failed" + elif [[ $FTYPE = ".bin" ]]; then + bash -c "$GET $URL" >"nvim-manager_${OS}_${ARCH}" || fail "download failed" else - fail "unknown file type: $ASSET_URL" + fail "unknown file type: $FTYPE" 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 + #search subtree largest file (bin) + TMP_BIN=$(find . -type f | xargs du | sort -n | tail -n 1 | cut -f 2) + if [ ! -f "$TMP_BIN" ]; then + fail "could not find find binary (largest file)" + fi + #ensure its larger than 1MB + #TODO linux=elf/darwin=macho file detection? + if [[ $(du -m $TMP_BIN | cut -f1) -lt 1 ]]; then + fail "no binary found ($TMP_BIN is not larger than 1MB)" + fi + #move into PATH or cwd + chmod +x $TMP_BIN || fail "chmod +x failed" + DEST="$OUT_DIR/$PROG" + if [ ! -z "$ASPROG" ]; then + DEST="$OUT_DIR/$ASPROG" + fi + #move without sudo + OUT=$(mv $TMP_BIN $DEST 2>&1) + STATUS=$? + # failed and string contains "Permission denied" + if [ $STATUS -ne 0 ]; then + if [[ $OUT =~ "Permission denied" ]]; then + echo "mv with sudo..." + sudo mv $TMP_BIN $DEST || fail "sudo mv failed" else - fail "cannot find binary" + fail "mv failed ($OUT)" + fi fi - echo "Installation complete!" + echo "Downloaded to $DEST" + #done cleanup + check_uv } -check_deps install +setup_config_dir