Skip to content

Instantly share code, notes, and snippets.

@22Pizzas
Created January 3, 2026 03:20
Show Gist options
  • Select an option

  • Save 22Pizzas/b024028868fb936499db00b936bc3b6d to your computer and use it in GitHub Desktop.

Select an option

Save 22Pizzas/b024028868fb936499db00b936bc3b6d to your computer and use it in GitHub Desktop.
Secure LM Studio Linux Installer/Updater – extracts AppImage, fixes chrome-sandbox (SUID), creates desktop entry & symlinks
#!/bin/bash
set -euo pipefail
# -------------------------------
# Configuration
# -------------------------------
readonly INSTALL_DIR="${HOME}/.local/share/lm-studio"
readonly BIN_DIR="${HOME}/.local/bin"
readonly DESKTOP_DIR="${HOME}/.local/share/applications"
readonly EXTRACT_DIR="squashfs-root"
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color
# -------------------------------
# Cleanup handler
# -------------------------------
TEMP_FILES=()
cleanup() {
local exit_code=$?
if [ ${#TEMP_FILES[@]} -gt 0 ]; then
echo "Cleaning up temporary files..." >&2
for file in "${TEMP_FILES[@]}"; do
rm -rf "$file" 2>/dev/null || true
done
fi
if [ $exit_code -ne 0 ]; then
echo -e "${RED}Installation failed. Temporary files have been cleaned up.${NC}" >&2
fi
exit $exit_code
}
trap cleanup EXIT INT TERM
# -------------------------------
# Utility functions
# -------------------------------
log_info() {
echo -e "${GREEN}${NC} $*" >&2
}
log_warn() {
echo -e "${YELLOW}${NC} $*" >&2
}
log_error() {
echo -e "${RED}${NC} $*" >&2
}
log_success() {
echo -e "${GREEN}${NC} $*" >&2
}
# -------------------------------
# Dependencies check
# -------------------------------
check_dependencies() {
log_info "Checking dependencies..."
local missing_deps=()
for cmd in curl file sudo wget; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing_deps+=("$cmd")
fi
done
if [ ${#missing_deps[@]} -gt 0 ]; then
log_error "Missing required dependencies: ${missing_deps[*]}"
echo "Please install them using your package manager." >&2
exit 1
fi
# Check if aria2c is available for faster downloads
if command -v aria2c >/dev/null 2>&1; then
USE_ARIA2=true
log_info "aria2c detected - will use for faster downloads"
else
USE_ARIA2=false
fi
}
# -------------------------------
# Detect architecture
# -------------------------------
detect_architecture() {
local arch
arch=$(uname -m)
case "$arch" in
x86_64)
echo "x64"
;;
aarch64)
echo "arm64"
;;
*)
log_error "Unsupported architecture: $arch"
echo "Only x86_64 and aarch64 are supported." >&2
exit 1
;;
esac
}
# -------------------------------
# Validate version format
# -------------------------------
validate_version() {
local version="$1"
# Version should match pattern like: 0.3.24-6 or 0.3.24
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]]; then
log_error "Invalid version format: $version"
echo "Expected format: X.Y.Z or X.Y.Z-N (e.g., 0.3.24 or 0.3.24-6)" >&2
return 1
fi
# Additional sanitization: ensure no path traversal attempts
if [[ "$version" == *".."* ]] || [[ "$version" == *"/"* ]]; then
log_error "Version contains invalid characters"
return 1
fi
return 0
}
# -------------------------------
# Prompt for version
# -------------------------------
prompt_version() {
echo "" >&2
echo "Enter the LM Studio version you want to install (e.g., 0.3.24-6):" >&2
echo "You can find available versions at: https://github.com/lmstudio-ai" >&2
echo "" >&2
read -r version
if [ -z "$version" ]; then
log_error "No version entered."
exit 1
fi
if ! validate_version "$version"; then
exit 1
fi
echo "$version"
}
# -------------------------------
# Enhanced download validation
# -------------------------------
validate_download() {
local file="$1"
# Check if file exists and is not empty
if [ ! -s "$file" ]; then
log_error "Downloaded file is empty or does not exist"
return 1
fi
# Check if it's an HTML error page
if file "$file" | grep -q "HTML"; then
log_error "Download failed - received HTML instead of AppImage"
log_error "The version may not exist or the URL may be incorrect"
return 1
fi
# Check for ELF magic bytes (AppImages are ELF executables)
local magic
magic=$(head -c 4 "$file" | od -An -tx1 | tr -d ' \n')
if [[ ! "$magic" =~ ^7f454c46 ]]; then
log_error "Downloaded file is not a valid ELF executable"
log_error "Expected AppImage format (ELF), got magic bytes: $magic"
return 1
fi
log_success "Download validation passed"
return 0
}
# -------------------------------
# Display security warning
# -------------------------------
show_security_warning() {
echo "" >&2
log_warn "═══════════════════════════════════════════════════════════════"
log_warn " SECURITY NOTICE"
log_warn "═══════════════════════════════════════════════════════════════"
echo "" >&2
echo "This script will:" >&2
echo " 1. Download LM Studio from installers.lmstudio.ai (unverified)" >&2
echo " 2. Set SUID bit on chrome-sandbox (requires sudo password)" >&2
echo "" >&2
echo "⚠ LM Studio does NOT provide checksums or GPG signatures" >&2
echo "⚠ You are trusting the download source and granting root privileges" >&2
echo "" >&2
echo "The SUID sandbox is required for Electron security isolation." >&2
echo "Without it, LM Studio would run with --no-sandbox (LESS secure)." >&2
echo "" >&2
log_warn "═══════════════════════════════════════════════════════════════"
echo "" >&2
read -p "Do you understand and want to continue? (yes/no): " -r response
if [[ ! "$response" =~ ^[Yy][Ee][Ss]$ ]]; then
log_info "Installation cancelled by user"
exit 0
fi
}
# -------------------------------
# Check existing installation
# -------------------------------
check_existing_installation() {
local version="$1"
local version_file="${INSTALL_DIR}/.installed_version"
if [ -d "$INSTALL_DIR" ]; then
if [ -f "$version_file" ]; then
local installed_version
installed_version=$(cat "$version_file")
if [ "$installed_version" = "$version" ]; then
log_warn "Version $version is already installed."
read -p "Do you want to reinstall it? (y/n): " -r reinstall
if [[ ! "$reinstall" =~ ^[Yy]$ ]]; then
log_info "Installation cancelled"
exit 0
fi
log_info "Reinstalling version $version..."
else
log_info "Upgrading from version $installed_version to $version"
fi
else
log_info "Existing installation found without version info"
fi
log_info "Removing old installation..."
rm -rf "${INSTALL_DIR:?}"
fi
}
# -------------------------------
# Download AppImage
# -------------------------------
download_appimage() {
local version="$1"
local arch="$2"
local appimage_file="LM-Studio-${version}-${arch}.AppImage"
local download_url="https://installers.lmstudio.ai/linux/${arch}/${version}/${appimage_file}"
log_info "Downloading LM Studio v${version} for ${arch}..."
echo "URL: $download_url" >&2
echo "" >&2
# Download to temporary file
local temp_file
temp_file=$(mktemp)
TEMP_FILES+=("$temp_file")
local download_success=false
# Try aria2c first if available
if $USE_ARIA2; then
log_info "Attempting download with aria2c..."
if aria2c -x 8 -s 8 --allow-overwrite=true -d "$(dirname "$temp_file")" -o "$(basename "$temp_file")" "$download_url" >&2; then
download_success=true
log_success "Download completed with aria2c"
else
log_warn "aria2c download failed, falling back to wget..."
# Clean up any partial download
rm -f "$temp_file"
temp_file=$(mktemp)
TEMP_FILES+=("$temp_file")
fi
fi
# Fall back to wget if aria2c failed or wasn't available
if ! $download_success; then
log_info "Downloading with wget..."
if wget --show-progress -O "$temp_file" "$download_url" >&2; then
download_success=true
log_success "Download completed with wget"
else
log_error "Download failed with wget"
return 1
fi
fi
# Validate download
if ! validate_download "$temp_file"; then
return 1
fi
# Move to final location
mv "$temp_file" "$appimage_file"
TEMP_FILES=("${TEMP_FILES[@]/$temp_file}") # Remove from cleanup list
echo "$appimage_file"
}
# -------------------------------
# Extract and install
# -------------------------------
extract_and_install() {
local appimage_file="$1"
local version="$2"
log_info "Extracting AppImage..."
chmod +x "$appimage_file"
TEMP_FILES+=("$appimage_file")
# Extract in current directory
./"$appimage_file" --appimage-extract >/dev/null 2>&1 || {
log_error "Failed to extract AppImage"
return 1
}
TEMP_FILES+=("$EXTRACT_DIR")
# Move to install directory
log_info "Installing to ${INSTALL_DIR}..."
mkdir -p "$(dirname "$INSTALL_DIR")"
mv "$EXTRACT_DIR" "$INSTALL_DIR"
TEMP_FILES=("${TEMP_FILES[@]/$EXTRACT_DIR}") # Remove from cleanup
# Fix chrome-sandbox permissions
log_info "Configuring chrome-sandbox (requires sudo)..."
echo "This sets SUID bit for Electron's security sandbox." >&2
local sandbox="${INSTALL_DIR}/chrome-sandbox"
if [ -f "$sandbox" ]; then
sudo chown root:root "$sandbox" || {
log_error "Failed to set chrome-sandbox ownership"
return 1
}
sudo chmod 4755 "$sandbox" || {
log_error "Failed to set chrome-sandbox permissions"
return 1
}
log_success "Chrome-sandbox configured successfully"
else
log_warn "chrome-sandbox not found - this may cause issues"
fi
# Store installed version
echo "$version" > "${INSTALL_DIR}/.installed_version"
}
# -------------------------------
# Create symlinks
# -------------------------------
create_symlinks() {
log_info "Creating symlinks in ${BIN_DIR}..."
# Ensure ~/.local/bin exists and is in PATH
mkdir -p "$BIN_DIR"
# Create symlink for main executable
ln -sf "${INSTALL_DIR}/lm-studio" "${BIN_DIR}/lm-studio"
log_success "Created: lm-studio"
# Create symlink for CLI if it exists
if [ -f "${INSTALL_DIR}/lms" ]; then
ln -sf "${INSTALL_DIR}/lms" "${BIN_DIR}/lms"
log_success "Created: lms (CLI)"
fi
# Check if ~/.local/bin is in PATH
if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then
log_warn "~/.local/bin is not in your PATH"
echo "" >&2
echo "Add this line to your ~/.bashrc or ~/.zshrc:" >&2
echo " export PATH=\"\$HOME/.local/bin:\$PATH\"" >&2
echo "" >&2
fi
}
# -------------------------------
# Create desktop entry
# -------------------------------
create_desktop_entry() {
log_info "Creating desktop entry..."
mkdir -p "$DESKTOP_DIR"
local desktop_file="${DESKTOP_DIR}/lm-studio.desktop"
# Find the actual icon location (it varies between versions)
local icon_path
if [ -f "${INSTALL_DIR}/lm-studio.png" ]; then
# Icon in root directory (symlink)
icon_path="${INSTALL_DIR}/lm-studio.png"
elif [ -f "${INSTALL_DIR}/usr/share/icons/hicolor/0x0/apps/lm-studio.png" ]; then
# Icon in 0x0 directory
icon_path="${INSTALL_DIR}/usr/share/icons/hicolor/0x0/apps/lm-studio.png"
elif [ -f "${INSTALL_DIR}/usr/share/icons/hicolor/256x256/apps/lm-studio.png" ]; then
# Icon in 256x256 directory
icon_path="${INSTALL_DIR}/usr/share/icons/hicolor/256x256/apps/lm-studio.png"
else
# Fallback: search for any lm-studio icon
icon_path=$(find "${INSTALL_DIR}" -name "lm-studio.png" -type f 2>/dev/null | head -1)
if [ -z "$icon_path" ]; then
log_warn "Icon file not found - using icon name fallback"
icon_path="lm-studio"
else
log_success "Found icon at: $icon_path"
fi
fi
cat > "$desktop_file" <<EOF
[Desktop Entry]
Version=1.0
Type=Application
Name=LM Studio
Comment=Run LLMs locally on your computer
Exec=${BIN_DIR}/lm-studio
Icon=${icon_path}
Terminal=false
Categories=Development;AI;Science;
StartupWMClass=lm-studio
Keywords=AI;LLM;ML;
EOF
chmod +x "$desktop_file"
# Update desktop database if available
if command -v update-desktop-database >/dev/null 2>&1; then
update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
fi
log_success "Desktop entry created"
}
# -------------------------------
# Main installation flow
# -------------------------------
main() {
echo "" >&2
echo "╔════════════════════════════════════════════════════════════╗" >&2
echo "║ LM Studio Linux Installer/Updater (Enhanced) ║" >&2
echo "╚════════════════════════════════════════════════════════════╝" >&2
echo "" >&2
# Check dependencies
check_dependencies
# Detect architecture
local arch
arch=$(detect_architecture)
log_success "Detected architecture: $arch"
# Get version from user
local version
version=$(prompt_version)
log_success "Version: $version"
# Show security warning
show_security_warning
# Check existing installation
check_existing_installation "$version"
# Download AppImage
local appimage_file
appimage_file=$(download_appimage "$version" "$arch")
# Extract and install
extract_and_install "$appimage_file" "$version"
# Create symlinks
create_symlinks
# Create desktop entry
create_desktop_entry
# Success message
echo "" >&2
echo "╔════════════════════════════════════════════════════════════╗" >&2
echo "║ Installation Complete! ║" >&2
echo "╚════════════════════════════════════════════════════════════╝" >&2
echo "" >&2
log_success "LM Studio v${version} installed successfully!"
echo "" >&2
echo "Installation location: ${INSTALL_DIR}" >&2
echo "" >&2
echo "To run LM Studio:" >&2
echo " • Type: lm-studio" >&2
echo " • Or launch from your application menu" >&2
if [ -f "${BIN_DIR}/lms" ]; then
echo " • Use CLI: lms" >&2
fi
echo "" >&2
}
# -------------------------------
# Run main function
# -------------------------------
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment