diff --git a/.github/workflows/release-pypi.yaml b/.github/workflows/release-pypi.yaml deleted file mode 100644 index 16111e2..0000000 --- a/.github/workflows/release-pypi.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: Release Krayt -on: - workflow_dispatch: - push: - paths: - - "krayt/**" - - "pyproject.toml" -permissions: - contents: write - pull-requests: write - issues: read - packages: none - id-token: write -jobs: - pypi-release-krayt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: waylonwalker/hatch-action@v4 - with: - before-command: "lint-format" - env: - # required for gh release - GH_TOKEN: ${{ github.token }} - - run: sudo rm -rf dist - - name: Install just - run: | - curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin - shell: bash - - name: Install uv - run: | - curl -LsSf https://astral.sh/uv/0.6.16/install.sh | sh - shell: bash - - name: Install hatch - run: | - uv tool install hatch - shell: bash - - name: Configure Git - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" - shell: bash - - name: GitHub Release (just release) - run: just create-release - env: - GH_TOKEN: ${{ github.token }} - shell: bash diff --git a/.gitignore b/.gitignore index 6f4e31a..6c311bf 100644 --- a/.gitignore +++ b/.gitignore @@ -962,4 +962,4 @@ FodyWeavers.xsd # Additional files built by Visual Studio # End of https://www.toptal.com/developers/gitignore/api/vim,node,data,emacs,python,pycharm,executable,sublimetext,visualstudio,visualstudiocode -*.null-ls* +krayt diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ffba6..4f04f65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,3 @@ -## 0.4.3 - -- working out binary release process - -## 0.4.2 - -- working out binary release process - -## 0.4.1 - -- Automated release for both pypi and github - -## 0.4.0 - -- create now has --apply to apply the generated manifest to the cluster -- generic templates endpoint for cli -- better motd for volume mounts - -## 0.3.0 - -- created pypi release -- updated releases to use pyapp -- all new package -- port forward support -- additional_packages support - ## 0.2.0 ### Added diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 38ea521..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021-present Waylon S. Walker - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index b554981..fc9dd26 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Krayt - The Kubernetes Volume Inspector -![krayt hero image](./krayt.webp "A dark, cartoon-style wide-format illustration featuring a heroic explorer standing in a twilight desert beside a cracked-open dragon skull. The explorer holds a glowing pearl that reveals floating icons representing data and technology. The hero wears utility gear and a sword, with terminal and file icons on their belt. The desert backdrop includes jagged rocks, two moons in a starry sky, and moody blue and purple tones. At the top, the word “KRAYT” is displayed in bold, tech-inspired fantasy lettering.") - 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. diff --git a/justfile b/justfile index f62f5c9..938d946 100644 --- a/justfile +++ b/justfile @@ -3,7 +3,7 @@ delete-tag: set -euo pipefail # Get the version - VERSION=$(hatch version) + VERSION=$(cat version) # Delete the tag git tag -d "v$VERSION" @@ -14,65 +14,59 @@ delete-release: set -euo pipefail # Get the version - VERSION=$(hatch version) + VERSION=$(cat version) # Delete the release gh release delete "v$VERSION" create-tag: #!/usr/bin/env bash - VERSION=$(hatch version) + VERSION=$(cat version) git tag -a "v$VERSION" -m "Release v$VERSION" git push origin "v$VERSION" create-archives: #!/usr/bin/env bash - VERSION=$(hatch version) + VERSION=$(cat version) rm -rf dist build - hatch build -t binary - - krayt_bin=dist/binary/krayt-${VERSION} + mkdir -p dist # Create the binary for each platform for platform in "x86_64-unknown-linux-gnu" "aarch64-unknown-linux-gnu"; do - outbin="krayt-${VERSION}-${platform}" + outdir="krayt-${VERSION}-${platform}" + mkdir -p "dist/${outdir}" + # Copy the Python script and update version - cp ${krayt_bin} "dist/binary/${outbin}" + 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 + ./scripts/generate_install_script.py "$VERSION" + chmod +x dist/install.sh create-release: create-tag create-archives #!/usr/bin/env bash - VERSION=$(hatch version) + VERSION=$(cat version) ./scripts/get_release_notes.py "$VERSION" > release_notes.tmp - - # Check if release already exists - if gh release view "v$VERSION" &>/dev/null; then - echo "Release v$VERSION already exists. Uploading binaries..." - # Upload binaries to existing release - gh release upload "v$VERSION" \ - dist/binary/krayt-${VERSION} \ - dist/binary/krayt-${VERSION}-aarch64-unknown-linux-gnu \ - dist/binary/krayt-${VERSION}-x86_64-unknown-linux-gnu || true - else - echo "Creating new release v$VERSION" - # Create new release with binaries - gh release create "v$VERSION" \ - --title "v$VERSION" \ - --notes-file release_notes.tmp \ - dist/binary/krayt-${VERSION} \ - dist/binary/krayt-${VERSION}-aarch64-unknown-linux-gnu \ - dist/binary/krayt-${VERSION}-x86_64-unknown-linux-gnu - fi + 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=$(hatch version) + VERSION=$(cat version) ./scripts/get_release_notes.py "$VERSION" | less -R release: create-release - diff --git a/krayt1.py b/krayt.py similarity index 100% rename from krayt1.py rename to krayt.py diff --git a/krayt/__about__.py b/krayt/__about__.py deleted file mode 100644 index f6b7e26..0000000 --- a/krayt/__about__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.4.3" diff --git a/krayt/__init__.py b/krayt/__init__.py deleted file mode 100644 index 236e53a..0000000 --- a/krayt/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from krayt.__about__ import __version__ - -__all__ = [ - "__version__", -] diff --git a/krayt/bundles.py b/krayt/bundles.py deleted file mode 100644 index cecc8d9..0000000 --- a/krayt/bundles.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Bundles of packages available in most package managers. -""" - -basics = [ - "curl", - "wget", - "jq", - "yq", - "bash", - "coreutils", -] -bundles = { - "basics": [ - *basics, - ], - "pretty": [ - *basics, - "starship", - "atuin", - "bash", - "zsh", - "fish", - "bat", - "eza", - ], - "networking": [ - *basics, - "mtr", - "bind-tools", - "aws-cli", - "curl", - "wget", - "iperf3", - "nmap", - "traceroute", - "netcat-openbsd", - ], - "database": [ - *basics, - "sqlite", - "sqlite-dev", - "sqlite-libs", - "postgresql", - "mysql", - "mariadb", - "redis", - "mongodb", - ], - "storage": [ - *basics, - "ncdu", - "dust", - "file", - "hexyl", - "ripgrep", - "fd", - "fzf", - "difftastic", - ], - "search": [ - *basics, - "ripgrep", - "fd", - "fzf", - "difftastic", - ], - "monitoring": [ - *basics, - "htop", - "bottom", - "mtr", - ], -} - -bundles["all"] = list( - set( - [ - *bundles["basics"], - *bundles["pretty"], - *bundles["networking"], - *bundles["database"], - *bundles["storage"], - *bundles["search"], - *bundles["monitoring"], - ] - ) -) diff --git a/krayt/cli/__init__.py b/krayt/cli/__init__.py deleted file mode 100644 index 035a6de..0000000 --- a/krayt/cli/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from krayt import __version__ -from krayt.cli.bundles import app as bundles_app -from krayt.cli.pod import app as pod_app, create, exec, logs, clean -from krayt.cli.templates import app as templates_app -from typer import Typer - -app = Typer() - -app.add_typer(templates_app, name="template", no_args_is_help=True) -app.add_typer(pod_app, name="pod", no_args_is_help=True) -app.command(name="create")(create) -app.command(name="c")(create) -app.command(name="clean")(clean) -app.command(name="exec")(exec) -app.command(name="logs")(logs) -app.add_typer(bundles_app, name="bundles", no_args_is_help=True) - - -@app.command() -def version(): - print(__version__) - - -def main(): - app() diff --git a/krayt/cli/bundles.py b/krayt/cli/bundles.py deleted file mode 100644 index be30fe8..0000000 --- a/krayt/cli/bundles.py +++ /dev/null @@ -1,25 +0,0 @@ -from krayt import bundles -import typer - -app = typer.Typer() - - -@app.command() -def list( - verbose: bool = typer.Option( - False, - "--verbose", - "-v", - help="Verbose output", - ), -): - """List available bundles""" - typer.echo("Available bundles:") - # get all variables from bundles - for bundle in bundles.__dict__.keys(): - if bundle.startswith("__"): - continue - typer.echo(bundle) - if verbose: - for package in bundles.__dict__[bundle]: - typer.echo(f" - {package}") diff --git a/krayt/cli/pod.py b/krayt/cli/pod.py deleted file mode 100644 index d8ac87a..0000000 --- a/krayt/cli/pod.py +++ /dev/null @@ -1,925 +0,0 @@ -import iterfzf -from krayt.templates import env -from kubernetes.stream import stream -from kubernetes import client, config -import logging -import os -import time -import typer -from typing import Any, List, Optional -import yaml -from krayt.__about__ import __version__ -import sys -import tty -import termios -import select -import signal -import json - - -logging.basicConfig(level=logging.WARNING) - -app = typer.Typer() - - -def clean_dict(d: dict[str, Any]) -> dict[str, Any]: - """Remove None values and empty dicts from a dictionary recursively.""" - if not isinstance(d, dict): - return d - return { - k: clean_dict(v) - for k, v in d.items() - if v is not None and v != {} and not (isinstance(v, dict) and not clean_dict(v)) - } - - -def format_volume_mount(vm: client.V1VolumeMount) -> dict[str, Any]: - """Format volume mount with only relevant fields.""" - # Skip Kubernetes service account mounts - if vm.mount_path.startswith("/var/run/secrets/kubernetes.io/"): - return None - - return clean_dict( - { - "name": vm.name, - "mount_path": vm.mount_path, - "read_only": vm.read_only if vm.read_only else None, - } - ) - - -def format_volume(v: client.V1Volume) -> dict[str, Any]: - """Format volume into a dictionary, return None if it should be skipped""" - # Skip Kubernetes service account volumes - if v.name.startswith("kube-api-access-"): - return None - - volume_source = None - if v.persistent_volume_claim: - volume_source = { - "persistentVolumeClaim": {"claimName": v.persistent_volume_claim.claim_name} - } - elif v.config_map: - volume_source = {"configMap": {"name": v.config_map.name}} - elif v.secret: - volume_source = {"secret": {"secretName": v.secret.secret_name}} - elif v.host_path: # Add support for hostPath volumes (used for device mounts) - volume_source = { - "hostPath": { - "path": v.host_path.path, - "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, - } - } - - if not volume_source: - return None - - return clean_dict({"name": v.name, **volume_source}) - - -def fuzzy_select(items): - """Use fzf to select from a list of (name, namespace) tuples""" - if not items: - return None, None - - # If there's only one item, return it without prompting - if len(items) == 1: - return items[0] - - # Format items for display - formatted_items = [f"{name} ({namespace})" for name, namespace in items] - - # Use fzf for selection - try: - # selected = inquirer.fuzzy( - # message="Select a pod to clone:", choices=formatted_items - # ).execute() - - selected = iterfzf.iterfzf( - formatted_items, - prompt="Select a pod to clone:", - # preview='''kubectl describe pod "$(echo {} | awk -F'[(|)]' '{gsub(/\x1b\[[0-9;]*m/, "", $1); print $1}' | xargs)" -n "$(echo {} | awk -F'[(|)]' '{gsub(/\x1b\[[0-9;]*m/, "", $2); print $2}' | xargs)"''', - ) - if not selected: - return None, None - - # Parse selection back into name and namespace - # Example: "pod-name (namespace)" -> ("pod-name", "namespace") - name = selected.split(" (")[0] - namespace = selected.split(" (")[1][:-1] - return name, namespace - - except Exception as e: - typer.echo(f"Error during selection: {e}") - return None, None - - -def get_pods( - namespace=None, - label_selector: str = "app=krayt", -): - """Get list of pods in the specified namespace or all namespaces""" - try: - config.load_kube_config() - api = client.CoreV1Api() - if namespace: - pods = api.list_namespaced_pod( - namespace=namespace, - label_selector=label_selector, - ) - else: - pods = api.list_pod_for_all_namespaces( - label_selector=label_selector, - ) - - # Convert to list of (name, namespace) tuples - pod_list = [] - for pod in pods.items: - if pod.metadata.namespace not in PROTECTED_NAMESPACES: - pod_list.append((pod.metadata.name, pod.metadata.namespace)) - return pod_list - - except client.rest.ApiException as e: - typer.echo(f"Error listing pods: {e}") - raise typer.Exit(1) - - -def get_namespaces( - namespace=None, - label_selector: str = "app=krayt", -): - config.load_kube_config() - api = client.CoreV1Api() - - all_namespaces = [n.metadata.name for n in api.list_namespace().items] - return all_namespaces - - -def get_pod_spec(pod_name, namespace): - config.load_kube_config() - v1 = client.CoreV1Api() - return v1.read_namespaced_pod(pod_name, namespace) - - -def get_pod_volumes_and_mounts(pod_spec): - """Extract all volumes and mounts from a pod spec""" - volume_mounts = [] - for container in pod_spec.spec.containers: - if container.volume_mounts: - volume_mounts.extend(container.volume_mounts) - - # Filter out None values from volume mounts - volume_mounts = [vm for vm in volume_mounts if format_volume_mount(vm)] - - # Get all volumes, including device mounts - volumes = [] - if pod_spec.spec.volumes: - 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"), - ) - ) - elif v.name in ["coral-device"]: - 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" - ), - ) - ) - else: - volumes.append(v) - - # Filter out None values from volumes - volumes = [v for v in volumes if format_volume(v)] - - return volume_mounts, volumes - - -def get_env_vars_and_secret_volumes(api, namespace: str): - """Get environment variables and secret volumes for the inspector pod""" - env_vars = [] - volumes = [] - - # 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]}) - - # 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 create_inspector_job( - api, - namespace: str, - pod_name: str, - volume_mounts: list, - volumes: list, - image: str = "alpine:latest", - imagepullsecret: Optional[str] = None, - additional_packages: Optional[List[str]] = None, - pre_init_scripts: Optional[List[str]] = None, - post_init_scripts: Optional[List[str]] = None, - pre_init_hooks: Optional[List[str]] = None, - post_init_hooks: Optional[List[str]] = None, -): - timestamp = int(time.time()) - job_name = f"{pod_name}-krayt-{timestamp}" - - env_vars, secret_volumes = get_env_vars_and_secret_volumes(api, namespace) - volumes.extend(secret_volumes) - - secret_mounts = [ - client.V1VolumeMount( - name=vol.name, - mount_path=f"/mnt/secrets/{vol.secret.secret_name}", - read_only=True, - ) - for vol in secret_volumes - ] - - formatted_mounts = [format_volume_mount(vm) for vm in volume_mounts] - formatted_mounts = [client.V1VolumeMount(**vm) for vm in formatted_mounts if vm] - formatted_mounts.extend(secret_mounts) - - pvc_info = [ - f"{v.name}:{v.persistent_volume_claim.claim_name}" - for v in volumes - if hasattr(v, "persistent_volume_claim") and v.persistent_volume_claim - ] - - template = env.get_template("base.sh") - command = template.render( - volumes=volumes, - pvcs=None, - additional_packages=additional_packages, - pre_init_scripts=None, - post_init_scripts=None, - pre_init_hooks=None, - post_init_hooks=None, - ) - - container = client.V1Container( - name="inspector", - image=image, - command=["sh", "-c", command], - env=env_vars, - volume_mounts=formatted_mounts, - ) - - spec = client.V1PodSpec( - containers=[container], - volumes=[format_volume(v) for v in volumes if format_volume(v)], - restart_policy="Never", - image_pull_secrets=[client.V1LocalObjectReference(name=imagepullsecret)] - if imagepullsecret - else None, - ) - - template = client.V1PodTemplateSpec( - metadata=client.V1ObjectMeta(labels={"app": "krayt"}), spec=spec - ) - - job_spec = client.V1JobSpec( - template=template, - ttl_seconds_after_finished=600, - ) - - job = client.V1Job( - api_version="batch/v1", - kind="Job", - metadata=client.V1ObjectMeta( - name=job_name, - namespace=namespace, - labels={"app": "krayt"}, - annotations={"pvcs": ",".join(pvc_info) if pvc_info else "none"}, - ), - spec=job_spec, - ) - - return job - - -PROTECTED_NAMESPACES = { - "kube-system", - "kube-public", - "kube-node-lease", - "argo-events", - "argo-rollouts", - "argo-workflows", - "argocd", - "cert-manager", - "ingress-nginx", - "monitoring", - "prometheus", - "istio-system", - "linkerd", -} - - -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: {__version__}") - raise typer.Exit() - - -def get_pod(namespace: Optional[str] = None): - config.load_kube_config() - batch_api = client.BatchV1Api() - - try: - if namespace: - logging.debug(f"Listing jobs in namespace {namespace}") - jobs = batch_api.list_namespaced_job( - 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=krayt") - - running_inspectors = [] - for job in jobs.items: - # Get the pod for this job - v1 = client.CoreV1Api() - pods = v1.list_namespaced_pod( - namespace=job.metadata.namespace, - 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) - ) - - if not running_inspectors: - typer.echo("No running inspector pods found.") - raise typer.Exit(1) - - if len(running_inspectors) == 1: - pod_name, pod_namespace = running_inspectors[0] - else: - pod_name, pod_namespace = fuzzy_select(running_inspectors) - if not pod_name: - typer.echo("No inspector selected.") - raise typer.Exit(1) - - except client.exceptions.ApiException as e: - logging.error(f"Failed to list jobs: {e}") - typer.echo(f"Failed to list jobs: {e}", err=True) - raise typer.Exit(1) - - return pod_name, pod_namespace - - -def interactive_exec(pod_name: str, namespace: str): - # Load kubeconfig from local context (or use load_incluster_config if running inside the cluster) - print(f"Connecting to pod {pod_name} in namespace {namespace}...") - try: - config.load_kube_config() - except Exception as e: - print(f"Error loading kubeconfig: {e}", file=sys.stderr) - return - - core_v1 = client.CoreV1Api() - command = ["/bin/bash", "-l"] - resp = None - - # Save the current terminal settings - oldtty = termios.tcgetattr(sys.stdin) - - # Function to handle window resize events - def handle_resize(signum, frame): - if resp and resp.is_open(): - # Get the current terminal size - cols, rows = os.get_terminal_size() - # Send terminal resize command via websocket - # Format matches kubectl's resize message format - resize_msg = json.dumps({"Width": cols, "Height": rows}) - resp.write_channel(4, resize_msg) - - # Function to handle exit signals - def handle_exit(signum, frame): - if resp and resp.is_open(): - # Send Ctrl+C to the remote process - resp.write_stdin("\x03") - - try: - # Put terminal into raw mode but don't handle local echo ourselves - # Let the remote terminal handle echoing and control characters - tty.setraw(sys.stdin.fileno()) - - # Set up signal handlers - signal.signal(signal.SIGWINCH, handle_resize) # Window resize - signal.signal(signal.SIGINT, handle_exit) # Ctrl+C - - # Create a TTY-enabled exec connection to the pod - try: - resp = stream( - core_v1.connect_get_namespaced_pod_exec, - pod_name, - namespace, - command=command, - stderr=True, - stdin=True, - stdout=True, - tty=True, - _preload_content=False, - ) - print(f"Connected to {pod_name}") - except Exception as e: - print(f"\nError connecting to pod: {e}", file=sys.stderr) - return - - # Wait for the connection to be ready - time.sleep(0.2) - - # Send initial terminal size - cols, rows = os.get_terminal_size() - resize_msg = json.dumps({"Width": cols, "Height": rows}) - resp.write_channel(4, resize_msg) - - # Make sure the size is set by sending a resize event - handle_resize(None, None) - - # Set up a simple select-based event loop to handle I/O - try: - while resp and resp.is_open(): - # Update the websocket connection - resp.update(timeout=0.1) - - # Handle output from the pod - if resp.peek_stdout(): - sys.stdout.write(resp.read_stdout()) - sys.stdout.flush() - if resp.peek_stderr(): - sys.stderr.write(resp.read_stderr()) - sys.stderr.flush() - - # Check for input from the user - rlist, _, _ = select.select([sys.stdin], [], [], 0.01) - if sys.stdin in rlist: - # Read input and forward it to the pod without local echo - data = os.read(sys.stdin.fileno(), 1024) - if not data: # EOF (e.g., user pressed Ctrl+D) - break - resp.write_stdin(data.decode()) - except Exception as e: - print(f"\nConnection error: {e}", file=sys.stderr) - - except KeyboardInterrupt: - # Handle Ctrl+C gracefully - print("\nSession terminated by user", file=sys.stderr) - except Exception as e: - print(f"\nError in interactive session: {e}", file=sys.stderr) - finally: - # Reset signal handlers - signal.signal(signal.SIGWINCH, signal.SIG_DFL) - signal.signal(signal.SIGINT, signal.SIG_DFL) - - # Close the connection if it's still open - if resp and resp.is_open(): - try: - resp.close() - except Exception: - pass - - # Always restore terminal settings - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty) - print("\nConnection closed", file=sys.stderr) - - -@app.command() -def exec( - namespace: Optional[str] = typer.Option( - None, - help="Kubernetes namespace. If not specified, will search for inspectors across all namespaces.", - ), - shell: Optional[str] = typer.Option( - "/bin/bash", - "--shell", - "-s", - help="Shell to use for the inspector pod", - ), -): - """ - 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() # or config.load_incluster_config() if running inside a pod - client.CoreV1Api() - - pod_name, pod_namespace = get_pod(namespace) - - try: - pod_name, pod_namespace = get_pod(namespace) - exec_command = [ - "kubectl", - "exec", - "-it", - "-n", - pod_namespace, - pod_name, - "--", - shell, - "-l", - ] - - os.execvp("kubectl", exec_command) - except Exception as e: - print(f"Error executing command with kubectl trying python api: {e}") - - interactive_exec(pod_name, pod_namespace) - - -@app.command() -def port_forward( - namespace: Optional[str] = typer.Option( - None, - help="Kubernetes namespace. If not specified, will search for inspectors across all namespaces.", - ), - port: str = typer.Option( - "8080:8080", - "--port", - "-p", - help="Port to forward to the inspector pod", - ), -): - """ - 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. - """ - if ":" not in port: - # if port does not contain a ":" it should be an int - port = int(port) - port = f"{port}:{port}" - - pod_name, pod_namespace = get_pod(namespace) - port_forward_command = [ - "kubectl", - "port-forward", - "-n", - pod_namespace, - pod_name, - port, - ] - - os.execvp("kubectl", port_forward_command) - - -@app.command() -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", - help="Skip confirmation prompt.", - ), -): - """ - 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() - - try: - if namespace: - if namespace in PROTECTED_NAMESPACES: - typer.echo(f"Error: Cannot cleanup in protected namespace {namespace}") - raise typer.Exit(1) - logging.debug(f"Listing jobs in namespace {namespace}") - jobs = batch_api.list_namespaced_job( - 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=krayt") - - # Filter out jobs 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 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 - ) - 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.") - return - - # Delete each job - for job in jobs.items: - try: - logging.debug( - f"Deleting job {job.metadata.namespace}/{job.metadata.name}" - ) - batch_api.delete_namespaced_job( - name=job.metadata.name, - namespace=job.metadata.namespace, - body=client.V1DeleteOptions(propagation_policy="Background"), - ) - typer.echo(f"Deleted job: {job.metadata.namespace}/{job.metadata.name}") - except client.exceptions.ApiException as e: - logging.error( - f"Failed to delete job {job.metadata.namespace}/{job.metadata.name}: {e}" - ) - typer.echo( - f"Failed to delete job {job.metadata.namespace}/{job.metadata.name}: {e}", - err=True, - ) - - except client.exceptions.ApiException as e: - logging.error(f"Failed to list jobs: {e}") - typer.echo(f"Failed to list jobs: {e}", err=True) - raise typer.Exit(1) - - -@app.command() -def create( - namespace: Optional[str] = typer.Option( - None, - "--namespace", - "-n", - help="Kubernetes namespace. If not specified, will search for pods across all namespaces.", - ), - clone: Optional[str] = typer.Option( - None, - "--clone", - "-c", - help="Clone an existing pod", - ), - image: str = typer.Option( - "alpine:latest", - "--image", - "-i", - help="Container image to use for the inspector pod", - ), - imagepullsecret: Optional[str] = typer.Option( - None, - "--imagepullsecret", - help="Name of the image pull secret to use for pulling private images", - ), - additional_packages: Optional[List[str]] = typer.Option( - None, - "--additional-packages", - "-ap", - help="additional packages to install in the inspector pod", - ), - additional_package_bundles: Optional[List[str]] = typer.Option( - None, - "--additional-package-bundles", - "-ab", - help="additional packages to install in the inspector pod", - ), - pre_init_scripts: Optional[List[str]] = typer.Option( - None, - "--pre-init-scripts", - help="additional scripts to execute at the end of container initialization", - ), - post_init_scripts: Optional[List[str]] = typer.Option( - None, - "--post-init-scripts", - "--init-scripts", - help="additional scripts to execute at the start of container initialization", - ), - pre_init_hooks: Optional[List[str]] = typer.Option( - None, - "--pre-init-hooks", - help="additional hooks to execute at the end of container initialization", - ), - post_init_hooks: Optional[List[str]] = typer.Option( - None, - "--post-init-hooks", - help="additional hooks to execute at the start of container initialization", - ), - apply: bool = typer.Option( - False, - "--apply", - help="Automatically apply the changes instead of just echoing them.", - ), -): - """ - 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. - """ - # For create, we want to list all pods, not just Krayt pods - selected_namespace = namespace - selected_pod = clone - - if namespace is None and clone is not None and "/" in clone: - selected_namespace, selected_pod = clone.split("/", 1) - - get_namespaces(namespace) - pods = get_pods(namespace, label_selector="app!=krayt") - - if not pods: - typer.echo("No pods found.") - raise typer.Exit(1) - - if selected_pod not in (p[0] for p in pods) or selected_pod is None: - if selected_pod is not None: - pods = [p for p in pods if selected_pod in p[0]] - if len(pods) == 1: - selected_pod, selected_namespace = pods[0] - else: - selected_pod, selected_namespace = fuzzy_select(pods) - if not selected_pod: - typer.echo("No pod selected.") - raise typer.Exit(1) - - pod_spec = get_pod_spec(selected_pod, selected_namespace) - volume_mounts, volumes = get_pod_volumes_and_mounts(pod_spec) - - inspector_job = create_inspector_job( - client.CoreV1Api(), - selected_namespace, - selected_pod, - volume_mounts, - volumes, - image=image, - imagepullsecret=imagepullsecret, - additional_packages=additional_packages, - pre_init_scripts=pre_init_scripts, - post_init_scripts=post_init_scripts, - pre_init_hooks=pre_init_hooks, - post_init_hooks=post_init_hooks, - ) - - # Output the job manifest - api_client = client.ApiClient() - job_dict = api_client.sanitize_for_serialization(inspector_job) - job_yaml = yaml.dump(job_dict, sort_keys=False) - - if apply: - batch_api = client.BatchV1Api() - job = batch_api.create_namespaced_job( - namespace=selected_namespace, - body=inspector_job, - ) - print(f"Job {job.metadata.name} created.") - return job - else: - # Just echo the YAML - typer.echo(job_yaml) - - -@app.command() -def version(): - """Show the version of Krayt.""" - typer.echo(f"Version: {__version__}") - - -@app.command() -def logs( - namespace: Optional[str] = typer.Option( - None, - help="Kubernetes namespace. If not specified, will search for inspectors across all namespaces.", - ), - follow: bool = typer.Option( - False, - "--follow", - "-f", - help="Follow the logs in real-time", - ), -): - """ - View logs from a Krayt inspector pod. - If multiple inspectors are found, you'll get to choose which one to explore. - """ - pods = get_pods(namespace) - if not pods: - typer.echo("No pods found.") - raise typer.Exit(1) - - selected_pod, selected_namespace = fuzzy_select(pods) - if not selected_pod: - typer.echo("No pod selected.") - raise typer.Exit(1) - - try: - config.load_kube_config() - api = client.CoreV1Api() - logs = api.read_namespaced_pod_log( - name=selected_pod, - namespace=selected_namespace, - follow=follow, - _preload_content=False, - ) - - if follow: - for line in logs: - typer.echo(line.decode("utf-8").strip()) - else: - typer.echo(logs.read().decode("utf-8")) - - except client.rest.ApiException as e: - typer.echo(f"Error reading logs: {e}") - raise typer.Exit(1) - - -@app.command("list") -def list_pods(): - pods = get_pods() - if not pods: - typer.echo("No pods found.") - raise typer.Exit(1) - - for pod, namespace in pods: - typer.echo(f"{pod} ({namespace})") - - -# def main(): -# setup_environment() -# app() -# -# -# if __name__ == "__main__": -# main() diff --git a/krayt/cli/templates.py b/krayt/cli/templates.py deleted file mode 100644 index 7416db6..0000000 --- a/krayt/cli/templates.py +++ /dev/null @@ -1,98 +0,0 @@ -from krayt.templates import env -import typer -from typing import List, Optional - -app = typer.Typer() - - -@app.command() -def list(): - typer.echo("Available templates:") - for template in env.list_templates(): - typer.echo(template) - - -@app.command() -def render( - template_name: Optional[str] = typer.Option("base.sh", "--template-name", "-t"), - volumes: Optional[List[str]] = typer.Option( - None, - "--volume", - ), - pvcs: Optional[List[str]] = typer.Option( - None, - "--pvc", - ), - additional_packages: Optional[List[str]] = typer.Option( - None, "--additional-packages", "-ap" - ), - pre_init_scripts: Optional[List[str]] = typer.Option( - None, - "--pre-init-scripts", - help="additional scripts to execute at the end of container initialization", - ), - post_init_scripts: Optional[List[str]] = typer.Option( - None, - "--post-init-scripts", - "--init-scripts", - help="additional scripts to execute at the start of container initialization", - ), - pre_init_hooks: Optional[List[str]] = typer.Option( - None, - "--pre-init-hooks", - help="additional hooks to execute at the end of container initialization", - ), - post_init_hooks: Optional[List[str]] = typer.Option( - None, - "--post-init-hooks", - "--init-hooks", - help="additional hooks to execute at the start of container initialization", - ), -): - template = env.get_template(template_name) - rendered = template.render( - volumes=volumes, - pvcs=pvcs, - additional_packages=additional_packages, - pre_init_scripts=pre_init_scripts, - post_init_scripts=post_init_scripts, - pre_init_hooks=pre_init_hooks, - post_init_hooks=post_init_hooks, - ) - print(rendered) - - -# @app.command() -# def install( -# additional_packages: Optional[List[str]] = typer.Option( -# ..., "--additional-packages", "-ap" -# ), -# ): -# template_name = "install.sh" -# template = env.get_template(template_name) -# rendered = template.render(additional_packages=additional_packages) -# print(rendered) -# -# -# @app.command() -# def motd( -# volumes: Optional[List[str]] = typer.Option( -# None, -# "--volume", -# ), -# pvcs: Optional[List[str]] = typer.Option( -# None, -# "--pvc", -# ), -# additional_packages: Optional[List[str]] = typer.Option( -# ..., "--additional-packages", "-ap" -# ), -# ): -# template_name = "motd.sh" -# template = env.get_template(template_name) -# rendered = template.render( -# volumes=volumes, -# pvcs=pvcs, -# additional_packages=additional_packages, -# ) -# print(rendered) diff --git a/krayt/package.py b/krayt/package.py deleted file mode 100644 index 907a412..0000000 --- a/krayt/package.py +++ /dev/null @@ -1,191 +0,0 @@ -from krayt.bundles import bundles -from more_itertools import unique_everseen -from pydantic import BaseModel, BeforeValidator -from typing import Annotated, List, Literal, Optional, Union - - -SUPPORTED_KINDS = { - "system", - "uv", - "installer", - "i", - "curlbash", - "curlsh", - "cargo", - "pipx", - "npm", - "go", - "gh", - "group", - "bundle", -} - -DEPENDENCIES = { - "uv": [ - "curl", - "curlsh:https://astral.sh/uv/install.sh", - ], - "installer": [ - "curl", - ], - "i": ["curl"], - "curlbash": ["curl"], - "curlsh": ["curl"], - "cargo": ["cargo"], - "pipx": ["pipx"], - "npm": ["npm"], - "go": ["go"], - "gh": ["gh"], -} - - -def validate_kind(v): - if v not in SUPPORTED_KINDS: - raise ValueError( - f"Unknown installer kind: {v}\n Supported kinds: {SUPPORTED_KINDS}\n " - ) - return v - - -class Package(BaseModel): - """ - Represents a package to be installed, either via system package manager - or an alternative installer like uv, installer.sh, etc. - """ - - kind: Annotated[ - Literal[*SUPPORTED_KINDS], - BeforeValidator(validate_kind), - ] = "system" - value: str - # dependencies: Optional[List["Package"]] = None - pre_install_hook: Optional[str] = None - post_install_hook: Optional[str] = None - - @classmethod - def from_raw(cls, raw: str) -> "Package": - """ - Parse a raw input string like 'uv:copier' into a Package(kind='uv', value='copier') - """ - if ":" in raw: - prefix, value = raw.split(":", 1) - return cls(kind=prefix.strip(), value=value.strip()) - else: - return cls(kind="system", value=raw.strip()) - - # @model_validator(mode="after") - # def validate_dependencies(self) -> Self: - # if self.dependencies: - # return self - # dependencies = [] - # - # if self.kind in ["uv", "i", "installer", "curlbash", "curlsh", "gh"]: - # dependencies.append(Package.from_raw("curl")) - # dependencies.append( - # Package.from_raw("curlsh:https://astral.sh/uv/install.sh") - # ) - # if self.kind == "cargo": - # dependencies.append(Package.from_raw("cargo")) - # if self.kind == "pipx": - # dependencies.append(Package.from_raw("pipx")) - # if self.kind == "npm": - # dependencies.append(Package.from_raw("npm")) - # if self.kind == "go": - # dependencies.append(Package.from_raw("go")) - # - # self.dependencies = dependencies - # return self - # - def is_system(self) -> bool: - return self.kind == "system" - - def install_command(self) -> str: - """ - Generate the bash install command snippet for this package. - """ - cmd = "" - if self.kind in ["bundle", "group"]: - cmd = "" - elif self.kind == "system": - cmd = f"detect_package_manager_and_install {self.value}" - elif self.kind == "uv": - cmd = f"uv tool install {self.value}" - elif self.kind in ["i", "installer", "gh"]: - cmd = f"installer {self.value}" - elif self.kind == "curlsh": - cmd = f"curl -fsSL {self.value} | sh" - elif self.kind == "curlbash": - cmd = f"curl -fsSL {self.value} | bash" - elif self.kind == "cargo": - cmd = f"cargo install {self.value}" - elif self.kind == "pipx": - cmd = f"pipx install {self.value}" - elif self.kind == "npm": - cmd = f"npm install -g {self.value}" - elif self.kind == "go": - cmd = f"go install {self.value}@latest" - else: - raise ValueError(f"Unknown install method for kind={self.kind}") - - # Add pre-install hook if necessary - if self.pre_install_hook: - return f"{self.pre_install_hook} {cmd}" - else: - return cmd - - -def get_install_script(packages: Union[str, List[str]]) -> str: - if packages is None: - return [] - if isinstance(packages, str): - packages = [packages] - bundled_packages = [] - for package in packages: - if package.startswith("bundle:") or package.startswith("group:"): - _package = package.split(":")[1].strip() - bundled_packages.extend(bundles.get(_package, [])) - packages = list(unique_everseen([*bundled_packages, *packages])) - - packages = [Package.from_raw(raw) for raw in packages] - kinds_used = [package.kind for package in packages] - dependencies = [] - for kind in kinds_used: - dependencies.extend(DEPENDENCIES.get(kind, [])) - dependencies = list( - unique_everseen( - [Package.from_raw(raw).install_command() for raw in dependencies] - ) - ) - # for package in packages: - # if package.dependencies: - # dependencies.extend( - # [dependency.install_command() for dependency in package.dependencies] - # ) - installs = [package.install_command() for package in packages] - post_hooks = [] - for package in packages: - if package.post_install_hook: - post_hooks.append(package.post_install_hook.strip()) - pre_hooks = [] - for package in packages: - if package.pre_install_hook: - pre_hooks.append(package.pre_install_hook.strip()) - - # Final full script - full_script = list( - unique_everseen([*pre_hooks, *dependencies, *installs, *post_hooks]) - ) - return "\n".join(full_script) if full_script else full_script - - -if __name__ == "__main__": - raw_inputs = [ - "bundle:storage", - "wget", - "uv:copier", - "i:sharkdp/fd", - "curlsh:https://example.com/install.sh", - ] - full_script = get_install_script(raw_inputs) - - print("\n".join(full_script)) diff --git a/krayt/templates.py b/krayt/templates.py deleted file mode 100644 index 7b3ba95..0000000 --- a/krayt/templates.py +++ /dev/null @@ -1,13 +0,0 @@ -from jinja2 import Environment, FileSystemLoader -from krayt.package import get_install_script -from pathlib import Path - -# Get the two template directories -template_dirs = [ - Path(__file__).resolve().parents[0] / "templates", - Path.home() / ".config" / "krayt" / "templates", -] - -# Create the Jinja environment -env = Environment(loader=FileSystemLoader([str(path) for path in template_dirs])) -env.globals["get_install_script"] = get_install_script diff --git a/krayt/templates/.kraytrc b/krayt/templates/.kraytrc deleted file mode 100644 index 591b80e..0000000 --- a/krayt/templates/.kraytrc +++ /dev/null @@ -1,3 +0,0 @@ -if [ -t 1 ] && [ -f /etc/motd ]; then - cat /etc/motd -fi diff --git a/krayt/templates/base.sh b/krayt/templates/base.sh deleted file mode 100644 index c248460..0000000 --- a/krayt/templates/base.sh +++ /dev/null @@ -1,25 +0,0 @@ -mkdir -p /etc/krayt -cat <<'KRAYT_INIT_SH_EOF' >/etc/krayt/init.sh -{%- if pre_init_hooks %} -{% for hook in pre_init_hooks %}{{ hook }}{% endfor %} -{% endif -%} -{%- if pre_init_scripts %} -{% for script in pre_init_scripts %}{{ script }}{% endfor %} -{% endif -%} -{% include 'install.sh' %} -{% include 'motd.sh' %} -{% include 'kraytrc.sh' %} -{%- if post_init_scripts %} -{% for script in post_init_scripts %}{{ script }}{% endfor %} -{% endif %} -{%- if post_init_hooks %} -{% for hook in post_init_hooks %}{{ hook }}{% endfor %} -{% endif %} -echo "Krayt environment ready. Sleeping forever..." -trap "echo 'Received SIGTERM. Exiting...'; exit 0" TERM -tail -f /dev/null & -wait -KRAYT_INIT_SH_EOF - -chmod +x /etc/krayt/init.sh -/etc/krayt/init.sh diff --git a/krayt/templates/install.sh b/krayt/templates/install.sh deleted file mode 100644 index de056ad..0000000 --- a/krayt/templates/install.sh +++ /dev/null @@ -1,88 +0,0 @@ -{% if additional_packages %} -# Detect package manager -if command -v apt >/dev/null 2>&1; then - PKG_MANAGER="apt" - UPDATE_CMD="apt update" - INSTALL_CMD="apt install -y" -elif command -v dnf >/dev/null 2>&1; then - PKG_MANAGER="dnf" - UPDATE_CMD="" - INSTALL_CMD="dnf install -y" -elif command -v yum >/dev/null 2>&1; then - PKG_MANAGER="yum" - UPDATE_CMD="" - INSTALL_CMD="yum install -y" -elif command -v pacman >/dev/null 2>&1; then - PKG_MANAGER="pacman" - UPDATE_CMD="" - INSTALL_CMD="pacman -Sy --noconfirm" -elif command -v zypper >/dev/null 2>&1; then - PKG_MANAGER="zypper" - UPDATE_CMD="" - INSTALL_CMD="zypper install -y" -elif command -v apk >/dev/null 2>&1; then - PKG_MANAGER="apk" - UPDATE_CMD="" - INSTALL_CMD="apk add" -else - echo "No supported package manager found." - exit 2 -fi - -echo "Using package manager: $PKG_MANAGER" - -# Run update once if needed -if [ -n "$UPDATE_CMD" ]; then - echo "Running package manager update..." - eval "$UPDATE_CMD" -fi - -detect_package_manager_and_install() { - if [ $# -eq 0 ]; then - echo "Usage: detect_package_manager_and_install [package2] [...]" - return 1 - fi - - FAILED_PKGS="" - - for pkg in "$@"; do - echo "Installing package: $pkg" - if ! $INSTALL_CMD $pkg; then - echo "⚠️ Warning: Failed to install package: $pkg" - FAILED_PKGS="$FAILED_PKGS $pkg" - fi - done - {% raw %} - if [ -n "$FAILED_PKGS" ]; then - echo "⚠️ The following packages failed to install:" - for failed_pkg in $FAILED_PKGS; do - echo " - $failed_pkg" - done - else - echo "✅ All requested packages installed successfully." - fi - {% endraw %} -} - -installer() { - if [ $# -eq 0 ]; then - echo "Usage: installer [package2] [...]" - return 1 - fi - - for pkg in "$@"; do - echo "Installing package with installer: $pkg" - ( - orig_dir="$(pwd)" - cd /usr/local/bin || exit 1 - curl -fsSL https://i.jpillora.com/${pkg} | sh - cd "$orig_dir" || exit 1 - ) - done -} -{% endif %} - -{% if additional_packages %} -{{ get_install_script(additional_packages) | safe }} -{% endif %} - diff --git a/krayt/templates/kraytrc.sh b/krayt/templates/kraytrc.sh deleted file mode 100644 index dd4fe85..0000000 --- a/krayt/templates/kraytrc.sh +++ /dev/null @@ -1,116 +0,0 @@ -KRAYT_MARKER_START="# >>> Added by krayt-inject <<<" -KRAYT_MARKER_END='# <<< End krayt-inject >>>' -KRAYT_BLOCK=' -if [ -t 1 ] && [ -f /etc/motd ] && [ -z "$MOTD_SHOWN" ]; then - cat /etc/motd - export MOTD_SHOWN=1 -fi - -# fix $SHELL, not set in some distros like alpine -if [ -n "$BASH_VERSION" ]; then - export SHELL=/bin/bash -elif [ -n "$ZSH_VERSION" ]; then - export SHELL=/bin/zsh -else - export SHELL=/bin/sh -fi - -# krayt ENVIRONMENT - -{%- if pvcs %} -export KRAYT_PVCS="{{ pvcs | join(' ') }}" -{% endif -%} -{%- if volumes %} -export KRAYT_VOLUMES="{{ volumes | join(' ') }}" -{% endif -%} -{%- if secrets %} -export KRAYT_SECRETS="{{ secrets | join(' ') }}" -{% endif -%} -{%- if additional_packages %} -export KRAYT_ADDITIONAL_PACKAGES="{{ additional_packages | join(' ') }}" -{% endif -%} - -# Universal shell initializers - -# Prompt -if command -v starship >/dev/null 2>&1; then - eval "$(starship init "$(basename "$SHELL")")" -fi - -# Smarter cd -if command -v zoxide >/dev/null 2>&1; then - eval "$(zoxide init "$(basename "$SHELL")")" -fi - -# Smarter shell history -if command -v atuin >/dev/null 2>&1; then - eval "$(atuin init "$(basename "$SHELL")")" -fi - -if command -v mcfly >/dev/null 2>&1; then - eval "$(mcfly init "$(basename "$SHELL")")" -fi - -# Directory-based environment -if command -v direnv >/dev/null 2>&1; then - eval "$(direnv hook "$(basename "$SHELL")")" -fi - -if command -v fzf >/dev/null 2>&1; then - case "$(basename "$SHELL")" in - bash|zsh|fish) - eval "$(fzf --$(basename "$SHELL"))" - ;; - *) - # shell not supported for fzf init - ;; - esac -fi -# "Did you mean...?" for mistyped commands -if command -v thefuck >/dev/null 2>&1; then - eval "$(thefuck --alias)" -fi -' -cat </etc/.kraytrc -$KRAYT_MARKER_START -$KRAYT_BLOCK -$KRAYT_MARKER_END -EOF - -KRAYT_RC_SOURCE=' -if [ -f /etc/.kraytrc ]; then - . /etc/.kraytrc -fi -' - -# List of common rc/profile files to patch -RC_FILES=" -/etc/profile -/etc/bash.bashrc -/etc/bash/bashrc -/etc/bashrc -/etc/ashrc -/etc/zsh/zshrc -/etc/zsh/zprofile -/etc/shinit -/etc/fish/config.fish -" - -echo "Searching for rc files..." - -for rc_file in $RC_FILES; do - if [ -f "$rc_file" ]; then - echo "* Found $rc_file" - - # Check if already patched - if grep -q "$KRAYT_MARKER_START" "$rc_file"; then - echo "- $rc_file already has krayt block. Skipping." - else - echo "+ Patching $rc_file" - echo "" >>"$rc_file" - echo "$KRAYT_MARKER_START" >>"$rc_file" - echo "$KRAYT_RC_SOURCE" >>"$rc_file" - echo "$KRAYT_MARKER_END" >>"$rc_file" - fi - fi -done diff --git a/krayt/templates/motd.sh b/krayt/templates/motd.sh deleted file mode 100644 index 5838161..0000000 --- a/krayt/templates/motd.sh +++ /dev/null @@ -1,40 +0,0 @@ -cat </etc/motd -┌───────────────────────────────────┐ -│Krayt Dragon's Lair │ -│A safe haven for volume inspection │ -└───────────────────────────────────┘ - -"Inside every volume lies a pearl of wisdom waiting to be discovered." -{%- if mounts %} - -Mounted Volumes: -{%- for mount in mounts %} -- {{ mount.name }}:{{ mount.mount_path }} -{%- endfor %} -{%- endif %} - -{%- if pvcs %} - -Persistent Volume Claims: -{%- for pvc in pvcs %} -- {{ pvc }} -{%- endfor %} -{%- endif %} - -{%- if secrets %} - -Mounted Secrets: -{%- for secret in secrets %} -- {{ secret }} -{%- endfor %} -{%- endif %} - -{%- if additional_packages %} - -Additional Packages: -{%- for package in additional_packages %} -- {{ package }} -{%- endfor %} -{%- endif %} - -EOF diff --git a/krayt2.py b/krayt2.py deleted file mode 100755 index e79d280..0000000 --- a/krayt2.py +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/env -S uv run --quiet --script -# /// script -# requires-python = ">=3.12" -# dependencies = [ -# "typer", -# "kubernetes", -# "InquirerPy", -# ] -# /// - -from InquirerPy import inquirer -from kubernetes import client, config -import os -import typer -from typing import List, Optional - -app = typer.Typer(name="krayt") - -VERSION = "0.1.0" - -# Default values -container_image_default = "ubuntu:22.04" -container_name_default = "krayt-container" - -KNOWN_PACKAGE_MANAGERS = { - "apk": "apk add", - "dnf": "dnf install -y", - "yum": "yum install -y", - "apt-get": "apt-get update && apt-get install -y", - "apt": "apt update && apt install -y", - "zypper": "zypper install -y", - "pacman": "pacman -Sy --noconfirm", -} - - -def load_kube_config(): - try: - config.load_kube_config() - except Exception as e: - typer.secho(f"Failed to load kubeconfig: {e}", fg=typer.colors.RED) - raise typer.Exit(1) - - -def detect_package_manager_command() -> str: - checks = [ - f"which {pm} >/dev/null 2>&1 && echo {cmd}" - for pm, cmd in KNOWN_PACKAGE_MANAGERS.items() - ] - return " || ".join(checks) - - -def get_proxy_env_vars() -> List[client.V1EnvVar]: - proxy_vars = [ - "HTTP_PROXY", - "http_proxy", - "HTTPS_PROXY", - "https_proxy", - "NO_PROXY", - "no_proxy", - ] - env_vars = [] - for var in proxy_vars: - value = os.environ.get(var) - if value: - env_vars.append(client.V1EnvVar(name=var, value=value)) - return env_vars - - -def fuzzy_pick_pod(namespace: Optional[str] = None) -> str: - load_kube_config() - core_v1 = client.CoreV1Api() - if namespace is None: - pods = core_v1.list_pod_for_all_namespaces() - else: - pods = core_v1.list_namespaced_pod(namespace=namespace) - pods = {pod.metadata.name: pod for pod in pods.items} - if not pods: - typer.secho("No pods found to clone.", fg=typer.colors.RED) - raise typer.Exit(1) - choice = inquirer.fuzzy( - message="Select a pod to clone:", choices=pods.keys() - ).execute() - return pods[choice] - - -def clone_pod(core_v1, namespace: str, source_pod_name: str): - source_pod = core_v1.read_namespaced_pod(name=source_pod_name, namespace=namespace) - container = source_pod.spec.containers[0] - breakpoint() - return ( - container.image, - container.volume_mounts, - source_pod.spec.volumes, - container.env, - source_pod.spec.image_pull_secrets, - ) - - -@app.command() -def create( - image: str = typer.Option( - container_image_default, "--image", "-i", help="Image to use for the container" - ), - name: str = typer.Option( - container_name_default, "--name", "-n", help="Name for the krayt container" - ), - yes: bool = typer.Option( - False, "--yes", "-Y", help="Non-interactive, pull images without asking" - ), - fuzzy_clone: bool = typer.Option( - False, - "--fuzzy-clone", - "-f", - help="Clone an existing pod", - ), - clone: Optional[str] = typer.Option( - None, "--clone", "-c", help="Clone an existing krayt container" - ), - volume: List[str] = typer.Option( - [], - "--volume", - help="Additional volumes to add to the container (pvc-name:/mount/path)", - ), - additional_flags: List[str] = typer.Option( - [], - "--additional-flags", - "-a", - help="Additional flags to pass to the container manager command", - ), - additional_packages: List[str] = typer.Option( - [], - "--additional-packages", - "-ap", - help="Additional packages to install during setup", - ), - init_hooks: List[str] = typer.Option( - [], "--init-hooks", help="Commands to execute at the end of initialization" - ), - pre_init_hooks: List[str] = typer.Option( - [], - "--pre-init-hooks", - help="Commands to execute at the start of initialization", - ), - namespace: str = typer.Option(None, help="Kubernetes namespace"), - dry_run: bool = typer.Option( - False, "--dry-run", "-d", help="Only print the generated Kubernetes manifest" - ), - verbose: bool = typer.Option(False, "--verbose", "-v", help="Show more verbosity"), - image_pull_secret: Optional[str] = typer.Option( - None, - "--image-pull-secret", - help="Name of the Kubernetes secret for pulling the image", - ), -): - """Create a new Kubernetes pod inspired by distrobox.""" - load_kube_config() - core_v1 = client.CoreV1Api() - - if fuzzy_clone: - namespace, clone = fuzzy_pick_pod(namespace) - - if clone is not None: - image, volume_mounts, volumes, env_vars, image_pull_secrets = clone_pod( - core_v1, namespace, clone - ) - else: - volume_mounts = [] - volumes = [] - env_vars = get_proxy_env_vars() - for idx, pvc_entry in enumerate(volume): - try: - pvc_name, mount_path = pvc_entry.split(":", 1) - except ValueError: - typer.secho( - f"Invalid volume format: {pvc_entry}. Use pvc-name:/mount/path", - fg=typer.colors.RED, - ) - raise typer.Exit(1) - - volumes.append( - client.V1Volume( - name=f"volume-{idx}", - persistent_volume_claim=client.V1PersistentVolumeClaimVolumeSource( - claim_name=pvc_name - ), - ) - ) - volume_mounts.append( - client.V1VolumeMount( - name=f"volume-{idx}", - mount_path=mount_path, - ) - ) - - package_manager_detection = detect_package_manager_command() - package_manager_detection = """ -detect_package_manager_and_install_command() { - if [ $# -eq 0 ]; then - echo "Usage: detect_package_manager_and_install_command [package2] [...]" - return 1 - fi - - if command -v apt >/dev/null 2>&1; then - PKG_MANAGER="apt" - UPDATE_CMD="apt update &&" - INSTALL_CMD="apt install -y" - elif command -v dnf >/dev/null 2>&1; then - PKG_MANAGER="dnf" - UPDATE_CMD="" - INSTALL_CMD="dnf install -y" - elif command -v yum >/dev/null 2>&1; then - PKG_MANAGER="yum" - UPDATE_CMD="" - INSTALL_CMD="yum install -y" - elif command -v pacman >/dev/null 2>&1; then - PKG_MANAGER="pacman" - UPDATE_CMD="" - INSTALL_CMD="pacman -Sy --noconfirm" - elif command -v zypper >/dev/null 2>&1; then - PKG_MANAGER="zypper" - UPDATE_CMD="" - INSTALL_CMD="zypper install -y" - elif command -v apk >/dev/null 2>&1; then - PKG_MANAGER="apk" - UPDATE_CMD="" - INSTALL_CMD="apk add" - else - echo "No supported package manager found." - return 2 - fi - - PACKAGES="$*" - - if [ -n "$UPDATE_CMD" ]; then - echo "$UPDATE_CMD - echo $INSTALL_CMD $PACKAGES" - $UPDATE_CMD - $INSTALL_CMD $PACKAGES - - else - echo "$INSTALL_CMD $PACKAGES" - $INSTALL_CMD $PACKAGES - fi -} -""" - - pre_hooks_command = " && ".join(pre_init_hooks) if pre_init_hooks else "" - install_packages_command = "" - if additional_packages: - install_packages_command = f"{package_manager_detection}\n detect_package_manager_and_install_command {' '.join(additional_packages)}" - # install_packages_command = ( - # f"$({{package_manager_detection}} {' '.join(additional_packages)})" - # ) - post_hooks_command = " && ".join(init_hooks) if init_hooks else "" - - combined_command_parts = [ - cmd - for cmd in [pre_hooks_command, install_packages_command, post_hooks_command] - if cmd - ] - command = None - - if combined_command_parts: - combined_command = " && ".join(combined_command_parts) - command = ["/bin/sh", "-c", f"{combined_command} && tail -f /dev/null"] - - pod_spec = client.V1PodSpec( - containers=[ - client.V1Container( - name=name, - image=image, - command=command, - volume_mounts=volume_mounts if volume_mounts else None, - env=env_vars if env_vars else None, - ) - ], - volumes=volumes if volumes else None, - restart_policy="Never", - ) - - if image_pull_secret: - pod_spec.image_pull_secrets = [ - client.V1LocalObjectReference(name=image_pull_secret) - ] - elif clone and image_pull_secrets: - pod_spec.image_pull_secrets = image_pull_secrets - - pod = client.V1Pod( - metadata=client.V1ObjectMeta(name=name, namespace=namespace), spec=pod_spec - ) - - if dry_run or verbose: - typer.secho(f"Dry-run/Verbose: Pod definition:\n{pod}", fg=typer.colors.BLUE) - - if dry_run: - typer.secho("Dry run completed.", fg=typer.colors.GREEN) - raise typer.Exit() - - typer.secho( - f"Creating pod '{name}' in namespace '{namespace}'...", fg=typer.colors.GREEN - ) - core_v1.create_namespaced_pod(namespace=namespace, body=pod) - typer.secho("Pod created successfully.", fg=typer.colors.GREEN) - - -@app.command("fuzzy-pick-pod") -def cli_fuzzy_pick_pod( - namespace: str = typer.Option(None, help="Kubernetes namespace"), -): - load_kube_config() - pod = fuzzy_pick_pod(namespace) - - if not pod: - typer.secho("No pod selected.", fg=typer.colors.RED) - raise typer.Exit(1) - - typer.secho("Selected pod", fg=typer.colors.GREEN) - typer.secho(f"Name: {pod.metadata.name}", fg=typer.colors.GREEN) - typer.secho(f"Namespace: {pod.metadata.namespace}", fg=typer.colors.GREEN) - typer.secho(f"Image: {pod.spec.containers[0].image}", fg=typer.colors.GREEN) - typer.secho(f"Command: {pod.spec.containers[0].command}", fg=typer.colors.GREEN) - typer.secho(f"Volume mounts: {pod.spec.volumes}", fg=typer.colors.GREEN) - typer.secho( - f"Environment variables: {pod.spec.containers[0].env}", fg=typer.colors.GREEN - ) - - return pod - - -@app.command() -def version(show: bool = typer.Option(False, "--version", "-V", help="Show version")): - if show: - typer.echo(f"krayt version {VERSION}") - - -if __name__ == "__main__": - app() diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 5ef96cd..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,70 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] - -[tool.hatch.build.targets.sdist] -exclude = ["/.github"] - -[tool.hatch.build.targets.binary] - -[project] -name = "krayt" -dynamic = ["version"] -description = 'kubernetes volume explorer' -readme = "README.md" -requires-python = ">=3.8" -keywords = [] -classifiers = [ - "Development Status :: 4 - Beta", - "Programming Language :: Python", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", -] -dependencies = [ - "typer", - "kubernetes", - "inquirerPy", - "inquirer", - "jinja2", - "iterfzf", - "pydantic", - "more-itertools", -] - -[[project.authors]] -name = "Waylon Walker" -email = "waylon@waylonwalker.com" - -[project.license] -file = "LICENSE" - -[project.urls] -Homepage = "https://github.com/waylonwalker/krayt#readme" -Documentation = "https://github.com/waylonwalker/krayt#readme" -Changelog = "https://github.com/waylonwalker/krayt#changelog" -Issues = "https://github.com/waylonwalker/krayt/issues" -Source = "https://github.com/waylonwalker/krayt" - -[tool.hatch.version] -path = "krayt/__about__.py" - -[project.scripts] -krayt = "krayt.cli:app" - -[tool.hatch.envs.default] -dependencies = [ - "ruff", - "pyinstrument", -] - -[tool.hatch.envs.default.scripts] -lint = "ruff check krayt" -format = "ruff format krayt" -lint-format = ['lint', 'format'] diff --git a/scripts/get_release_notes.py b/scripts/get_release_notes.py index f564136..d782a15 100755 --- a/scripts/get_release_notes.py +++ b/scripts/get_release_notes.py @@ -21,11 +21,7 @@ def get_release_notes(version): You can install krayt using one of these methods: -## pypi - -``` bash -pip install krayt -``` +> !krayt requires [uv](https://docs.astral.sh/uv/getting-started/installation/) to be installed ### Using i.jpillora.com (recommended) @@ -41,8 +37,8 @@ curl -fsSL https://github.com/waylonwalker/krayt/releases/download/v{version}/in ### 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) -- [aarch64-unknown-linux-gnu](https://github.com/waylonwalker/krayt/releases/download/v{version}/krayt-{version}-aarch64-unknown-linux-gnu)""" +- [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: @@ -50,23 +46,17 @@ You can also manually download the archive for your platform from the releases p # Get main help output main_help = subprocess.check_output( - ["krayt", "--help"], + ["./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", - "pod", - ] + subcommands = ["create", "exec", "clean", "version"] for cmd in subcommands: cmd_help = subprocess.check_output( - ["krayt", cmd, "--help"], + ["./krayt.py", cmd, "--help"], stderr=subprocess.STDOUT, universal_newlines=True, ) diff --git a/scripts/install.sh.template b/scripts/install.sh.template index 8e930c9..5fc0fb5 100644 --- a/scripts/install.sh.template +++ b/scripts/install.sh.template @@ -148,11 +148,11 @@ function install { FTYPE="" case "${OS}_${ARCH}" in "linux_amd64") - URL="https://github.com/WaylonWalker/krayt/releases/download/v${RELEASE}/krayt-${RELEASE}-x86_64-unknown-linux-gnu.tar.gz" + 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/krayt/releases/download/v${RELEASE}/krayt-${RELEASE}-aarch64-unknown-linux-gnu.tar.gz" + 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}" ;; @@ -193,7 +193,7 @@ function install { unzip -o -qq tmp.zip || fail "unzip failed" rm tmp.zip || fail "cleanup failed" elif [[ $FTYPE = ".bin" ]]; then - bash -c "$GET $URL" >"krayt_${OS}_${ARCH}" || fail "download failed" + bash -c "$GET $URL" >"nvim-manager_${OS}_${ARCH}" || fail "download failed" else fail "unknown file type: $FTYPE" fi diff --git a/test.sh b/test.sh deleted file mode 100644 index 1643cab..0000000 --- a/test.sh +++ /dev/null @@ -1,247 +0,0 @@ -mkdir -p /etc/krayt -cat <<'KRAYT_INIT_SH_EOF' >/etc/krayt/init.sh -detect_package_manager_and_install() { - if [ $# -eq 0 ]; then - echo "Usage: detect_package_manager_and_install [package2] [...]" - return 1 - fi - - if command -v apt >/dev/null 2>&1; then - PKG_MANAGER="apt" - UPDATE_CMD="apt update &&" - INSTALL_CMD="apt install -y" - elif command -v dnf >/dev/null 2>&1; then - PKG_MANAGER="dnf" - UPDATE_CMD="" - INSTALL_CMD="dnf install -y" - elif command -v yum >/dev/null 2>&1; then - PKG_MANAGER="yum" - UPDATE_CMD="" - INSTALL_CMD="yum install -y" - elif command -v pacman >/dev/null 2>&1; then - PKG_MANAGER="pacman" - UPDATE_CMD="" - INSTALL_CMD="pacman -Sy --noconfirm" - elif command -v zypper >/dev/null 2>&1; then - PKG_MANAGER="zypper" - UPDATE_CMD="" - INSTALL_CMD="zypper install -y" - elif command -v apk >/dev/null 2>&1; then - PKG_MANAGER="apk" - UPDATE_CMD="" - INSTALL_CMD="apk add" - else - echo "No supported package manager found." - return 2 - fi - - echo "Using package manager: $PKG_MANAGER" - - if [ -n "$UPDATE_CMD" ]; then - echo "Running package manager update..." - eval "$UPDATE_CMD" - fi - - FAILED_PKGS="" - - for pkg in "$@"; do - echo "Installing package: $pkg" - if ! eval "$INSTALL_CMD $pkg"; then - echo "⚠️ Warning: Failed to install package: $pkg" - FAILED_PKGS="$FAILED_PKGS $pkg" - fi - done - - if [ -n "$FAILED_PKGS" ]; then - echo "⚠️ The following packages failed to install:" - for failed_pkg in $FAILED_PKGS; do - echo " - $failed_pkg" - done - else - echo "✅ All requested packages installed successfully." - fi - -} - -installer() { - if [ $# -eq 0 ]; then - echo "Usage: installer [package2] [...]" - return 1 - fi - - for pkg in "$@"; do - echo "Installing package with installer: $pkg" - ( - orig_dir="$(pwd)" - cd /usr/local/bin || exit 1 - curl -fsSL https://i.jpillora.com/${pkg} | sh - cd "$orig_dir" || exit 1 - ) - done -} - - - -detect_package_manager_and_install eza -detect_package_manager_and_install hexyl -detect_package_manager_and_install mariadb -detect_package_manager_and_install coreutils -detect_package_manager_and_install ncdu -detect_package_manager_and_install postgresql -detect_package_manager_and_install atuin -detect_package_manager_and_install redis -detect_package_manager_and_install file -detect_package_manager_and_install netcat-openbsd -detect_package_manager_and_install traceroute -detect_package_manager_and_install fd -detect_package_manager_and_install iperf3 -detect_package_manager_and_install aws-cli -detect_package_manager_and_install dust -detect_package_manager_and_install sqlite-dev -detect_package_manager_and_install fish -detect_package_manager_and_install bat -detect_package_manager_and_install ripgrep -detect_package_manager_and_install difftastic -detect_package_manager_and_install zsh -detect_package_manager_and_install sqlite-libs -detect_package_manager_and_install bind-tools -detect_package_manager_and_install nmap -detect_package_manager_and_install mysql -detect_package_manager_and_install htop -detect_package_manager_and_install sqlite -detect_package_manager_and_install fzf -detect_package_manager_and_install bottom -detect_package_manager_and_install wget -detect_package_manager_and_install mtr -detect_package_manager_and_install bash -detect_package_manager_and_install curl -detect_package_manager_and_install starship -detect_package_manager_and_install mongodb -detect_package_manager_and_install jq -detect_package_manager_and_install yq - - - -cat </etc/motd -┌───────────────────────────────────┐ -│Krayt Dragon's Lair │ -│A safe haven for volume inspection │ -└───────────────────────────────────┘ - -"Inside every volume lies a pearl of wisdom waiting to be discovered." - -Additional Packages: -- bundle:all - -EOF -KRAYT_MARKER_START="# >>> Added by krayt-inject <<<" -KRAYT_MARKER_END='# <<< End krayt-inject >>>' -KRAYT_BLOCK=' -if [ -t 1 ] && [ -f /etc/motd ] && [ -z "$MOTD_SHOWN" ]; then - cat /etc/motd - export MOTD_SHOWN=1 -fi - -# fix $SHELL, not set in some distros like alpine -if [ -n "$BASH_VERSION" ]; then - export SHELL=/bin/bash -elif [ -n "$ZSH_VERSION" ]; then - export SHELL=/bin/zsh -else - export SHELL=/bin/sh -fi - -# krayt ENVIRONMENT -export KRAYT_ADDITIONAL_PACKAGES="bundle:all" -# Universal shell initializers - -# Prompt -if command -v starship >/dev/null 2>&1; then - eval "$(starship init "$(basename "$SHELL")")" -fi - -# Smarter cd -if command -v zoxide >/dev/null 2>&1; then - eval "$(zoxide init "$(basename "$SHELL")")" -fi - -# Smarter shell history -if command -v atuin >/dev/null 2>&1; then - eval "$(atuin init "$(basename "$SHELL")")" -fi - -if command -v mcfly >/dev/null 2>&1; then - eval "$(mcfly init "$(basename "$SHELL")")" -fi - -# Directory-based environment -if command -v direnv >/dev/null 2>&1; then - eval "$(direnv hook "$(basename "$SHELL")")" -fi - -if command -v fzf >/dev/null 2>&1; then - case "$(basename "$SHELL")" in - bash|zsh|fish) - eval "$(fzf --$(basename "$SHELL"))" - ;; - *) - # shell not supported for fzf init - ;; - esac -fi -# "Did you mean...?" for mistyped commands -if command -v thefuck >/dev/null 2>&1; then - eval "$(thefuck --alias)" -fi -' -cat </etc/.kraytrc -$KRAYT_MARKER_START -$KRAYT_BLOCK -$KRAYT_MARKER_END -EOF - -KRAYT_RC_SOURCE=' -if [ -f /etc/.kraytrc ]; then - . /etc/.kraytrc -fi -' - -# List of common rc/profile files to patch -RC_FILES=" -/etc/profile -/etc/bash.bashrc -/etc/bash/bashrc -/etc/bashrc -/etc/ashrc -/etc/zsh/zshrc -/etc/zsh/zprofile -/etc/shinit -/etc/fish/config.fish -" - -echo "Searching for rc files..." - -for rc_file in $RC_FILES; do - if [ -f "$rc_file" ]; then - echo "* Found $rc_file" - - # Check if already patched - if grep -q "$KRAYT_MARKER_START" "$rc_file"; then - echo "- $rc_file already has krayt block. Skipping." - else - echo "+ Patching $rc_file" - echo "" >>"$rc_file" - echo "$KRAYT_MARKER_START" >>"$rc_file" - echo "$KRAYT_RC_SOURCE" >>"$rc_file" - echo "$KRAYT_MARKER_END" >>"$rc_file" - fi - fi -done -echo "Krayt environment ready. Sleeping forever..." -trap "echo 'Received SIGTERM. Exiting...'; exit 0" TERM -tail -f /dev/null & -wait -KRAYT_INIT_SH_EOF - -chmod +x /etc/krayt/init.sh -/etc/krayt/init.sh diff --git a/test.yaml b/test.yaml deleted file mode 100644 index b2b19a0..0000000 --- a/test.yaml +++ /dev/null @@ -1,206 +0,0 @@ -mkdir -p /etc/krayt -cat <<'KRAYT_INIT_SH_EOF' >/etc/krayt/init.sh -detect_package_manager_and_install() { - if [ $# -eq 0 ]; then - echo "Usage: detect_package_manager_and_install [package2] [...]" - return 1 - fi - - if command -v apt >/dev/null 2>&1; then - PKG_MANAGER="apt" - UPDATE_CMD="apt update &&" - INSTALL_CMD="apt install -y" - elif command -v dnf >/dev/null 2>&1; then - PKG_MANAGER="dnf" - UPDATE_CMD="" - INSTALL_CMD="dnf install -y" - elif command -v yum >/dev/null 2>&1; then - PKG_MANAGER="yum" - UPDATE_CMD="" - INSTALL_CMD="yum install -y" - elif command -v pacman >/dev/null 2>&1; then - PKG_MANAGER="pacman" - UPDATE_CMD="" - INSTALL_CMD="pacman -Sy --noconfirm" - elif command -v zypper >/dev/null 2>&1; then - PKG_MANAGER="zypper" - UPDATE_CMD="" - INSTALL_CMD="zypper install -y" - elif command -v apk >/dev/null 2>&1; then - PKG_MANAGER="apk" - UPDATE_CMD="" - INSTALL_CMD="apk add" - else - echo "No supported package manager found." - return 2 - fi - - echo "Using package manager: $PKG_MANAGER" - - if [ -n "$UPDATE_CMD" ]; then - echo "Running package manager update..." - eval "$UPDATE_CMD" - fi - - FAILED_PKGS=() - - for pkg in "$@"; do - echo "Installing package: $pkg" - if ! eval "$INSTALL_CMD $pkg"; then - echo "⚠️ Warning: Failed to install package: $pkg" - FAILED_PKGS+=("$pkg") - fi - done - - if [ ${#FAILED_PKGS[@]} -ne 0 ]; then - echo "⚠️ The following packages failed to install:" - for failed_pkg in "${FAILED_PKGS[@]}"; do - echo " - $failed_pkg" - done - else - echo "✅ All requested packages installed successfully." - fi - -} - - - - -cat </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: -- hi - -Persistent Volume Claims: -- hi -- hello - -Additional Packages: -- htop -- ripgrep -- uv:copier - -EOF -KRAYT_MARKER_START="# >>> Added by krayt-inject <<<" -KRAYT_MARKER_END='# <<< End krayt-inject >>>' -KRAYT_BLOCK=' -if [ -t 1 ] && [ -f /etc/motd ] && [ -z "$MOTD_SHOWN" ]; then - cat /etc/motd - export MOTD_SHOWN=1 -fi - -# fix $SHELL, not set in some distros like alpine -if [ -n "$BASH_VERSION" ]; then - export SHELL=/bin/bash -elif [ -n "$ZSH_VERSION" ]; then - export SHELL=/bin/zsh -else - export SHELL=/bin/sh -fi - -# krayt ENVIRONMENT -export KRAYT_PVCS="hi hello" - -export KRAYT_VOLUMES="hi" - -export KRAYT_ADDITIONAL_PACKAGES="htop ripgrep uv:copier" -# Universal shell initializers - -# Prompt -if command -v starship >/dev/null 2>&1; then - eval "$(starship init "$(basename "$SHELL")")" -fi - -# Smarter cd -if command -v zoxide >/dev/null 2>&1; then - eval "$(zoxide init "$(basename "$SHELL")")" -fi - -# Smarter shell history -if command -v atuin >/dev/null 2>&1; then - eval "$(atuin init "$(basename "$SHELL")")" -fi - -if command -v mcfly >/dev/null 2>&1; then - eval "$(mcfly init "$(basename "$SHELL")")" -fi - -# Directory-based environment -if command -v direnv >/dev/null 2>&1; then - eval "$(direnv hook "$(basename "$SHELL")")" -fi - -if command -v fzf >/dev/null 2>&1; then - case "$(basename "$SHELL")" in - bash|zsh|fish) - eval "$(fzf --$(basename "$SHELL"))" - ;; - *) - # shell not supported for fzf init - ;; - esac -fi -# "Did you mean...?" for mistyped commands -if command -v thefuck >/dev/null 2>&1; then - eval "$(thefuck --alias)" -fi -' -cat </etc/.kraytrc -$KRAYT_MARKER_START -$KRAYT_BLOCK -$KRAYT_MARKER_END -EOF - -KRAYT_RC_SOURCE=' -if [ -f /etc/.kraytrc ]; then - . /etc/.kraytrc -fi -' - -# List of common rc/profile files to patch -RC_FILES=" -/etc/profile -/etc/bash.bashrc -/etc/bash/bashrc -/etc/bashrc -/etc/ashrc -/etc/zsh/zshrc -/etc/zsh/zprofile -/etc/shinit -/etc/fish/config.fish -" - -echo "Searching for rc files..." - -for rc_file in $RC_FILES; do - if [ -f "$rc_file" ]; then - echo "* Found $rc_file" - - # Check if already patched - if grep -q "$KRAYT_MARKER_START" "$rc_file"; then - echo "- $rc_file already has krayt block. Skipping." - else - echo "+ Patching $rc_file" - echo "" >>"$rc_file" - echo "$KRAYT_MARKER_START" >>"$rc_file" - echo "$KRAYT_RC_SOURCE" >>"$rc_file" - echo "$KRAYT_MARKER_END" >>"$rc_file" - fi - fi -done -touch here.txt - -echo "Krayt environment ready. Sleeping forever..." -trap "echo 'Received SIGTERM. Exiting...'; exit 0" TERM -tail -f /dev/null & -wait -KRAYT_INIT_SH_EOF - -chmod +x /etc/krayt/init.sh -/etc/krayt/init.sh