still working

This commit is contained in:
Waylon S. Walker 2025-03-24 21:47:05 -05:00
parent 08c70cc18f
commit eb90496cbc
8 changed files with 541 additions and 264 deletions

View file

@ -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."

View file

@ -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

View file

@ -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."

View file

@ -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."

BIN
krayt-squooshed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

BIN
krayt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

467
krayt.py
View file

@ -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"]
return env_vars, secret_volumes
for var in proxy_vars:
if var in os.environ:
env_vars.append({"name": var, "value": os.environ[var]})
# 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()

View file

@ -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"
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
wget -qO- ${ASSET_URL} | tar zx || fail "download failed"
fail "unknown file type: $FTYPE"
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 "unknown file type: $ASSET_URL"
fail "mv failed ($OUT)"
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!"
echo "Downloaded to $DEST"
#done
cleanup
check_uv
}
check_deps
install
setup_config_dir