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 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-21 18:51:34 +01:00
parent 55914b8b3c
commit 1cce649751
2 changed files with 64 additions and 119 deletions

View File

@ -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 <contact@cybermind.fr>

View File

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