From 1cce649751f27732271a38fab737af0493f9d1de Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Wed, 21 Jan 2026 18:51:34 +0100 Subject: [PATCH] feat(localai): Use Docker Registry API for LXC install (no daemon needed) - Download Docker image layers directly via Registry API - No dockerd or podman daemon required anymore - Same approach as mitmproxy and magicmirror2 packages - All backends included (llama-cpp, whisper, etc.) Co-Authored-By: Claude Opus 4.5 --- package/secubox/secubox-app-localai/Makefile | 2 +- .../files/usr/sbin/localaictl | 181 ++++++------------ 2 files changed, 64 insertions(+), 119 deletions(-) diff --git a/package/secubox/secubox-app-localai/Makefile b/package/secubox/secubox-app-localai/Makefile index 4aa400b1..974aee8b 100644 --- a/package/secubox/secubox-app-localai/Makefile +++ b/package/secubox/secubox-app-localai/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=secubox-app-localai -PKG_RELEASE:=11 +PKG_RELEASE:=12 PKG_VERSION:=0.1.0 PKG_ARCH:=all PKG_MAINTAINER:=CyberMind Studio diff --git a/package/secubox/secubox-app-localai/files/usr/sbin/localaictl b/package/secubox/secubox-app-localai/files/usr/sbin/localaictl index 0dbbbb5f..ca379f9c 100644 --- a/package/secubox/secubox-app-localai/files/usr/sbin/localaictl +++ b/package/secubox/secubox-app-localai/files/usr/sbin/localaictl @@ -33,8 +33,8 @@ Service Control: Runtimes: lxc - LXC container with rootfs extracted from Docker image (includes all backends: llama-cpp, whisper, etc.) - Falls back to standalone binary if no docker/podman available - docker - Run Docker container directly + Downloads via Docker Registry API - no daemon required! + docker - Run Docker container directly (requires dockerd) podman - Run Podman container directly (rootless) Configuration: /etc/config/localai @@ -185,21 +185,8 @@ lxc_install() { local rootfs="$lxc_path/$CONTAINER_NAME/rootfs" local config="$lxc_path/$CONTAINER_NAME/config" - # Check if we can extract from Docker image (preferred - includes all backends) - local use_docker_extract=0 - if command -v podman >/dev/null 2>&1 && runtime_is_working podman; then - use_docker_extract=1 - elif command -v docker >/dev/null 2>&1 && runtime_is_working docker; then - use_docker_extract=1 - fi - - if [ "$use_docker_extract" = "1" ]; then - lxc_install_from_docker "$rootfs" || return 1 - else - log_warn "No working Docker/Podman daemon - using standalone binary" - log_warn "For full backend support, start Docker: /etc/init.d/dockerd start" - lxc_install_standalone "$rootfs" || return 1 - fi + # Extract Docker image via Registry API (no daemon needed) + lxc_create_docker_rootfs "$rootfs" || return 1 # Create LXC config lxc_create_config "$config" "$rootfs" @@ -209,121 +196,79 @@ lxc_install() { return 0 } -# Check if container runtime daemon is actually working -runtime_is_working() { - local rt="$1" - case "$rt" in - podman) - # Podman is daemonless, just check command works - podman info >/dev/null 2>&1 - ;; - docker) - # Docker needs daemon running - docker info >/dev/null 2>&1 - ;; - *) - return 1 - ;; - esac -} - -# Extract rootfs from Docker image (includes all backends) -lxc_install_from_docker() { +# Extract Docker image via Registry API (no daemon required!) +lxc_create_docker_rootfs() { local rootfs="$1" - local rt="" + local image="localai/localai" + local tag="${LOCALAI_VERSION}-ffmpeg" + local registry="registry-1.docker.io" + local arch - # Detect available AND WORKING runtime for extraction - if command -v podman >/dev/null 2>&1 && runtime_is_working podman; then - rt="podman" - elif command -v docker >/dev/null 2>&1 && runtime_is_working docker; then - rt="docker" - else - log_error "Need working podman or docker to extract image" - log_error "Docker installed but daemon not running? Start with: /etc/init.d/dockerd start" - return 1 - fi + # Detect architecture for Docker manifest + case "$(uname -m)" in + x86_64) arch="amd64" ;; + aarch64) arch="arm64" ;; + armv7l) arch="arm" ;; + *) arch="amd64" ;; + esac - log_info "Extracting LocalAI rootfs from Docker image..." - log_info "Image: $docker_image" + log_info "Extracting LocalAI Docker image ($arch)..." + log_info "Image: $image:$tag" log_info "This includes ALL backends (llama-cpp, whisper, etc.)" - - # Pull the image - log_info "Pulling image (this may take a while)..." - if ! $rt pull "$docker_image"; then - log_error "Failed to pull image" - return 1 - fi - - # Create temp container to export - local temp_container="localai-extract-$$" - log_info "Creating temporary container..." - $rt create --name "$temp_container" "$docker_image" >/dev/null 2>&1 - - # Export and extract rootfs mkdir -p "$rootfs" - log_info "Exporting rootfs (2-4GB, please wait)..." - if $rt export "$temp_container" | tar -xf - -C "$rootfs" 2>/dev/null; then - log_info "Rootfs extracted successfully" - else - log_error "Failed to extract rootfs" - $rt rm -f "$temp_container" >/dev/null 2>&1 - return 1 - fi + # Get Docker Hub token + log_info "Authenticating with Docker Hub..." + local token=$(wget -q -O - "https://auth.docker.io/token?service=registry.docker.io&scope=repository:$image:pull" | jsonfilter -e '@.token') + [ -z "$token" ] && { log_error "Failed to get Docker Hub token"; return 1; } - # Cleanup temp container - $rt rm -f "$temp_container" >/dev/null 2>&1 + # Get manifest list (multi-arch) + log_info "Fetching manifest..." + local manifest=$(wget -q -O - --header="Authorization: Bearer $token" \ + --header="Accept: application/vnd.docker.distribution.manifest.list.v2+json" \ + "https://$registry/v2/$image/manifests/$tag") - # Optionally remove the Docker image to save space - # $rt rmi "$docker_image" >/dev/null 2>&1 + # Find digest for our architecture + local digest=$(echo "$manifest" | jsonfilter -e "@.manifests[@.platform.architecture='$arch'].digest") + [ -z "$digest" ] && { log_error "No manifest found for $arch"; return 1; } - # Create necessary directories + # Get image manifest with layer digests + local img_manifest=$(wget -q -O - --header="Authorization: Bearer $token" \ + --header="Accept: application/vnd.docker.distribution.manifest.v2+json" \ + "https://$registry/v2/$image/manifests/$digest") + + # Extract layer digests + local layers=$(echo "$img_manifest" | jsonfilter -e '@.layers[*].digest') + local layer_count=$(echo "$layers" | wc -w) + log_info "Downloading $layer_count layers (this will take a while, ~4GB)..." + + local i=0 + for layer_digest in $layers; do + i=$((i + 1)) + log_info " Layer $i/$layer_count: ${layer_digest:7:12}..." + wget -q -O - --header="Authorization: Bearer $token" \ + "https://$registry/v2/$image/blobs/$layer_digest" | \ + tar xzf - -C "$rootfs" 2>&1 | grep -v "Cannot change ownership" || true + done + + # Configure container + echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf" mkdir -p "$rootfs/models" "$rootfs/build" "$rootfs/tmp" - # Setup resolv.conf - echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf" + # Ensure /bin/sh exists + if [ ! -x "$rootfs/bin/sh" ]; then + log_warn "/bin/sh not found, attempting to fix..." + if [ -x "$rootfs/bin/bash" ]; then + ln -sf bash "$rootfs/bin/sh" + elif [ -x "$rootfs/bin/dash" ]; then + ln -sf dash "$rootfs/bin/sh" + fi + fi local rootfs_size=$(du -sh "$rootfs" 2>/dev/null | cut -f1) log_info "Rootfs size: $rootfs_size" - log_info "All LocalAI backends are now available!" - - return 0 -} - -# Fallback: Download standalone binary (limited backends) -lxc_install_standalone() { - local rootfs="$1" - - log_warn "No Docker/Podman available - using standalone binary" - log_warn "Note: Standalone binary has LIMITED backend support" - - # Create directories - mkdir -p "$rootfs/usr/bin" "$rootfs/data" "$rootfs/models" "$rootfs/tmp" "$rootfs/etc" - mkdir -p "$rootfs/bin" "$rootfs/lib" "$rootfs/proc" "$rootfs/sys" "$rootfs/dev" - - # Detect architecture (v3.x format: local-ai-v3.10.0-linux-arm64) - local arch - case "$(uname -m)" in - x86_64) arch="linux-amd64" ;; - aarch64) arch="linux-arm64" ;; - armv7l) arch="linux-arm64" ;; - *) arch="linux-amd64" ;; - esac - - # Download LocalAI binary (v3.x URL format) - local binary_url="https://github.com/mudler/LocalAI/releases/download/${lxc_version}/local-ai-${lxc_version}-${arch}" - log_info "Downloading LocalAI $lxc_version for $arch..." - log_info "URL: $binary_url" - - if ! wget -q --show-progress -O "$rootfs/usr/bin/local-ai" "$binary_url"; then - log_error "Failed to download LocalAI binary" - return 1 - fi - chmod +x "$rootfs/usr/bin/local-ai" - log_info "Binary downloaded: $(ls -sh "$rootfs/usr/bin/local-ai" | cut -d' ' -f1)" - - # Create resolv.conf - echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf" + log_info "LocalAI Docker image extracted successfully" + log_info "All backends available: llama-cpp, whisper, stablediffusion, etc." return 0 }