This document contains a step-by-step plan for preparing a robust development machine for modern AI/LLM and app development on a fresh Ubuntu 24.04.3 LTS installation.
It is designed to be executed by an Agent (or followed manually) on a newly provisioned machine. All steps assume a working network connection and sudo privileges.
Major components installed and configured:
- System prep and base tools; create
~/develop - Git
- Docker (CE) + docker compose plugin
- Python modern setup (uv, pipx, Jupyter, ipykernel, LlamaIndex/LangChain ready)
- Node.js LTS via nvm
- Java via SDKMAN! (Temurin JDK 21 + 17), Gradle, Kotlin
- Android SDK command-line tools, platform tools, build-tools; udev rules
- .NET SDKs (8, 9, 10 if available) + workloads (MAUI Android, wasm-tools for Blazor)
- VS Code + recommended extensions
- JetBrains Toolbox
- Neovim with LSPs and tools (LazyVim full + Mason install automation + AI extras)
- Verification checklist
Key improvements vs previous draft:
- Reordered: Android SDK is now installed before .NET workloads to satisfy MAUI-Android dependencies.
- More robust Java installs via SDKMAN!: use generic
21-temand17.0.16-temfallback when17-temalias is unavailable. - SDKMAN initialization notes to avoid
SDKMAN_CANDIDATES_API: unbound variableerrors. - Android SDK: ensure JAVA_HOME and PATH are set before running
sdkmanager; guidance for accepting licenses non-interactively. - .NET: keep repo SDK 8 via apt, and add side-by-side installs for .NET 9 (STS) and .NET 10 (preview) via Microsoft’s
dotnet-install.shwith PATH persistence; workloads caveats on Linux. - Neovim: recommend user-local prebuilt tarball (>= 0.11) for compatibility with Blink, lazydev.nvim, and lspconfig; ensure Node/npm in PATH for Mason installs.
- Added dev tools: zsh, GitHub CLI (gh), lazydocker (via snap), nmap, btop.
- Docker service explicitly enabled to start on boot.
- Added optional C# Roslyn LSP (
csharp-language-server) alongsideomnisharpin Neovim. - New sections: Google Chrome, GitHub Desktop, LM Studio, Ollama, Fonts (Fira Code, JetBrains Mono, Nerd Fonts), Android Studio.
- Note added about Android cmdline tools URL potentially changing.
set -euo pipefail
# Update/upgrade and base packages
sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install -y \
build-essential curl wget unzip zip tar gnupg ca-certificates \
software-properties-common apt-transport-https lsb-release \
jq tree direnv fzf ripgrep fd-find \
git zsh nmap htop btop
# Ensure fd is available as `fd` (Ubuntu names it `fdfind`)
if ! command -v fd >/dev/null 2>&1 && command -v fdfind >/dev/null 2>&1; then
sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd
fi
# Create development directory
mkdir -p "$HOME/develop"
# Optional subfolders
mkdir -p "$HOME/develop/.venvs" "$HOME/develop/src" "$HOME/develop/tmp"
# Shell integration for direnv (bash and zsh)
grep -q 'direnv hook bash' "$HOME/.bashrc" || echo 'eval "$(direnv hook bash)"' >> "$HOME/.bashrc"
if [ -f "$HOME/.zshrc" ]; then
grep -q 'direnv hook zsh' "$HOME/.zshrc" || echo 'eval "$(direnv hook zsh)"' >> "$HOME/.zshrc"
fiOptional: install GitHub CLI (gh) from the official repo
# GitHub CLI repo and install
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \
sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] \
https://cli.github.com/packages stable main" | \
sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt-get update -y
sudo apt-get install -y gh
# Later: gh auth loginOptional: install lazydocker via Snap (snap is built-in on Ubuntu)
sudo snap install lazydockerVerification:
ls -ld ~/developdirenv --versiongh --version(if installed)lazydocker --version(if installed)
Verification:
ls -ld ~/developdirenv --version
sudo apt-get install -y git
# Optional defaults
git config --global init.defaultBranch main
# Set your identity (EDIT THESE):
# git config --global user.name "Your Name"
# git config --global user.email "[email protected]"Verification:
git --version
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update -y
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Ensure the daemon starts now and on boot
sudo systemctl enable --now docker
# Add current user to docker group (needs re-login or newgrp)
sudo usermod -aG docker "$USER"
# Activate group for current session (starts a subshell)
newgrp docker <<'EOF'
docker --version
docker compose version || true
# Test a simple run (optional)
# docker run --rm hello-world
EOFVerification:
docker --versiondocker compose version- Optional:
docker run --rm hello-world
# System Python utilities
sudo apt-get install -y python3 python3-venv python3-pip pipx
pipx ensurepath
# uv (fast Python package/venv manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Ensure ~/.local/bin in PATH
if ! grep -q 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.bashrc"; then
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc"
fi
if [ -f "$HOME/.zshrc" ] && ! grep -q 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.zshrc"; then
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.zshrc"
fi
# Create a default AI dev venv and seed core packages
uv venv "$HOME/develop/.venvs/ai"
source "$HOME/develop/.venvs/ai/bin/activate"
uv pip install --upgrade pip wheel
uv pip install jupyterlab ipykernel
uv pip install \
llama-index \
langchain langchain-community langgraph \
huggingface_hub datasets tiktoken \
pydantic pydantic-settings
# Register the IPython kernel
python -m ipykernel install --user --name ai --display-name "Python (ai)"
deactivateVerification:
uv --versionpipx --versionjupyter-lab --version- Start Jupyter:
jupyter-lab(then Ctrl+C to exit)
# Microsoft packages repo (for .NET 8 from Ubuntu channel)
wget https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb -O /tmp/packages-microsoft-prod.deb
sudo dpkg -i /tmp/packages-microsoft-prod.deb
rm -f /tmp/packages-microsoft-prod.deb
sudo apt-get update -y
# Install .NET 8 SDK from apt (stable, integrates best with Ubuntu)
sudo apt-get install -y dotnet-sdk-8.0
# Install side-by-side .NET 9 (STS) and .NET 10 (preview) to ~/.dotnet
curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh
chmod +x /tmp/dotnet-install.sh
# 9 (STS)
/tmp/dotnet-install.sh --channel STS --install-dir "$HOME/.dotnet"
# 10 (preview)
/tmp/dotnet-install.sh --channel preview --install-dir "$HOME/.dotnet"
# Persistent PATH and DOTNET_ROOT for user shells (bash/zsh)
if ! grep -q 'DOTNET_ROOT' "$HOME/.bashrc"; then
cat >> "$HOME/.bashrc" <<'EOF'
export DOTNET_ROOT="$HOME/.dotnet"
export PATH="$DOTNET_ROOT:$PATH"
EOF
fi
if [ -f "$HOME/.zshrc" ] && ! grep -q 'DOTNET_ROOT' "$HOME/.zshrc"; then
cat >> "$HOME/.zshrc" <<'EOF'
export DOTNET_ROOT="$HOME/.dotnet"
export PATH="$DOTNET_ROOT:$PATH"
EOF
fi
# Verify across both locations (system and user)
/usr/bin/dotnet --info || true
"$HOME/.dotnet/dotnet" --info || true
# Workloads (Blazor Wasm works cross-platform)
/usr/bin/dotnet workload update || true
/usr/bin/dotnet workload install wasm-tools || true
# Android/MAUI workloads
# Note: On Linux, Android workloads may be unavailable depending on feed/channel.
# If available with your SDKs:
"$HOME/.dotnet/dotnet" workload update || true
"$HOME/.dotnet/dotnet" workload install android || true
"$HOME/.dotnet/dotnet" workload install maui-android || trueNotes:
- If
android/maui-androidworkloads aren’t recognized on Linux, you can still develop Blazor WebAssembly withwasm-tools. For Android MAUI development, ensure Android SDK is installed (Section 6) and consider using Microsoft’s official SDK channels or Windows/macOS for full MAUI tooling. - On this system,
androidandmaui-androidinstalled successfully using the .NET 9 SDK under~/.dotnet.
Verification:
dotnet --list-sdksand$HOME/.dotnet/dotnet --list-sdksdotnet workload list(on both locations)
# Install nvm
export NVM_DIR="$HOME/.nvm"
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
# Activate nvm in current shell
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# Install and set LTS as default
nvm install --lts
nvm alias default 'lts/*'
node --version
npm --versionOptional: auto-use LTS in new shells
# Append to ~/.bashrc to always prefer LTS
if ! grep -q "nvm use --lts" "$HOME/.bashrc"; then
cat >> "$HOME/.bashrc" <<'EOF'
# Prefer Node LTS automatically in new shells
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use --lts >/dev/null 2>&1 || true
EOF
fiVerification:
node -v(LTS)npm -v
# SDKMAN!
curl -s "https://get.sdkman.io" | bash
# Initialize SDKMAN in this shell (avoid unbound var errors)
if [ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]; then
source "$HOME/.sdkman/bin/sdkman-init.sh"
fi
sdk version || true
# JDKs (generic identifiers for robustness)
sdk install java 21-tem <<<"y"
# Some mirrors don’t publish the 17 alias as `17-tem`; fallback to the latest 17.x Temurin explicitly
sdk install java 17-tem || sdk install java 17.0.16-tem <<<"y"
# Use 21 by default
sdk default java 21-tem
# Build tools
sdk install gradle <<<"y"
sdk install kotlin <<<"y"
# Make JAVA_HOME explicit for tools like Android SDK
JAVA_HOME="$HOME/.sdkman/candidates/java/current"
export JAVA_HOMENotes:
- If
sdk versionprintsSDKMAN_CANDIDATES_API: unbound variable, reopen your shell or runsource "$HOME/.sdkman/bin/sdkman-init.sh". - If
sdk install java 17-temfails, usesdk list javato choose a concrete 17.x (e.g.,17.0.16-tem).
Verification:
java -version(Temurin 21)javac -versiongradle -vkotlin -version
Note: The command-line tools download URL is versioned and may change over time. If the link fails, check the "Command line tools only" section on the Android Studio downloads page for the latest URL.
# Android SDK home
export ANDROID_HOME="$HOME/Android/Sdk"
mkdir -p "$ANDROID_HOME"
# Ensure Java is available and JAVA_HOME is set (required by sdkmanager)
if [ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]; then
source "$HOME/.sdkman/bin/sdkman-init.sh"
fi
export JAVA_HOME="${JAVA_HOME:-$HOME/.sdkman/candidates/java/current}"
# Download commandline tools (update URL if needed)
cd /tmp
curl -L -o cmdline-tools.zip "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" || echo "If this fails, update the URL from the Android Studio downloads page."
unzip -q cmdline-tools.zip -d cmdline-tools || true
mkdir -p "$ANDROID_HOME/cmdline-tools/latest"
rsync -a cmdline-tools/cmdline-tools/ "$ANDROID_HOME/cmdline-tools/latest/" || true
rm -rf cmdline-tools cmdline-tools.zip || true
# PATH exports (bash and zsh)
if ! grep -q 'ANDROID_HOME=' "$HOME/.bashrc"; then
cat >> "$HOME/.bashrc" <<'EOF'
export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
export JAVA_HOME="$HOME/.sdkman/candidates/java/current"
EOF
fi
if [ -f "$HOME/.zshrc" ] && ! grep -q 'ANDROID_HOME=' "$HOME/.zshrc"; then
cat >> "$HOME/.zshrc" <<'EOF'
export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
export JAVA_HOME="$HOME/.sdkman/candidates/java/current"
EOF
fi
# Load in current shell
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
# Accept licenses (non-interactive, may still prompt for special cases)
yes | sdkmanager --licenses || true
# Install core packages
sdkmanager \
"platform-tools" \
"platforms;android-34" \
"build-tools;34.0.0" \
"cmdline-tools;latest" \
"emulator"
# udev rules for Android devices
sudo apt-get install -y android-sdk-platform-tools-commonNotes:
- If license prompts persist, run
sdkmanager --licensesin an interactive shell once. Ensure JAVA_HOME is set. - Depending on prior installs, cmdline-tools may be located under
cmdline-tools/latest-2. This is acceptable; bothlatestandlatest-2contain the latest tools.
Verification:
sdkmanager --list | head -n 50adb version
# Microsoft GPG and repo
sudo install -d -m 0755 /etc/apt/keyrings
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/keyrings/packages.microsoft.gpg > /dev/null
sudo sh -c 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list'
sudo apt-get update -y
sudo apt-get install -y code
# Recommended extensions
code --install-extension ms-python.python || true
code --install-extension ms-toolsai.jupyter || true
code --install-extension ms-dotnettools.csharp || true
code --install-extension ms-dotnettools.csdevkit || true
code --install-extension ms-azuretools.vscode-docker || true
code --install-extension eamodio.gitlens || true
code --install-extension esbenp.prettier-vscode || true
code --install-extension dbaeumer.vscode-eslint || true
code --install-extension redhat.java || true
code --install-extension vscjava.vscode-java-pack || true
code --install-extension fwcd.kotlin || true
code --install-extension ms-vscode-remote.remote-containers || trueVerification:
code --version- In VS Code, confirm extensions are installed.
# Download latest via product data service
cd /tmp
curl -L -o jetbrains-toolbox.tar.gz "https://data.services.jetbrains.com/products/download?code=TBA&platform=linux"
tar -xzf jetbrains-toolbox.tar.gz
TB_DIR=$(find . -maxdepth 1 -type d -name 'jetbrains-toolbox-*' | head -n1)
mkdir -p "$HOME/.local/share/JetBrains/Toolbox"
"$TB_DIR/jetbrains-toolbox" --install-dir="$HOME/.local/share/JetBrains/Toolbox" || true
# Optional: create a desktop entry if not auto-created
if [ ! -f "$HOME/.local/share/applications/jetbrains-toolbox.desktop" ]; then
cat > "$HOME/.local/share/applications/jetbrains-toolbox.desktop" <<'EOF'
[Desktop Entry]
Type=Application
Name=JetBrains Toolbox
Exec=%h/.local/share/JetBrains/Toolbox/jetbrains-toolbox
Icon=%h/.local/share/JetBrains/Toolbox/jetbrains-toolbox.svg
Terminal=false
Categories=Development;
EOF
update-desktop-database ~/.local/share/applications || true
fiVerification:
- Launch from applications menu or run:
~/.local/share/JetBrains/Toolbox/jetbrains-toolbox &
# Install latest stable Neovim user-locally (no system changes, no FUSE required)
mkdir -p "$HOME/.local/bin" "$HOME/.local"
cd /tmp
curl -fsSL -o nvim-linux64.tar.gz https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz
rm -rf "$HOME/.local/neovim"
tar -xzf nvim-linux64.tar.gz
mv -f nvim-linux64 "$HOME/.local/neovim"
ln -sf "$HOME/.local/neovim/bin/nvim" "$HOME/.local/bin/nvim"
# Quick verification
nvim --version | head -n 3
# Build/tooling deps used by some plugins/LSPs
sudo apt-get update -y
sudo apt-get install -y \
gettext cmake unzip ninja-build pkg-config libtool libtool-bin autoconf automake \
ripgrep fd-find fzf tree-sitter-cli
# fd symlink if needed
if ! command -v fd >/dev/null 2>&1 && command -v fdfind >/dev/null 2>&1; then
sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd
fi
# IMPORTANT: Migrate from Kickstart to full LazyVim
# Backup your current config (if any)
if [ -d "$HOME/.config/nvim" ]; then
mv "$HOME/.config/nvim" "$HOME/.config/nvim.backup.$(date +%Y%m%d%H%M%S)"
fi
# Install LazyVim starter (full LazyVim)
git clone https://github.com/LazyVim/starter "$HOME/.config/nvim"
# Remove the .git folder so your config isn't a git repo by default
rm -rf "$HOME/.config/nvim/.git"
# Ensure Node/npm is available in this shell for Mason
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# Use LTS Node for tooling
nvm install --lts >/dev/null 2>&1 || true
nvm use --lts >/dev/null 2>&1 || true
# Providers and external tools (recommended)
# - Node provider for Neovim
npm install -g neovim
# - Python provider (choose ONE option)
# A) System package (simple, stable)
sudo apt-get install -y python3-pynvim
# B) Isolated via pipx (newer, set host prog in config)
#pipx ensurepath
#pipx install pynvim
# Then set in your Neovim config (Lua):
# vim.g.python3_host_prog = vim.fn.expand("~/.local/pipx/venvs/pynvim/bin/python")
# - Git TUI integration for LazyVim
sudo snap install lazygit
# - Image rendering & docs in Snacks: ImageMagick, Tectonic (LaTeX), Mermaid CLI
sudo apt-get install -y imagemagick luarocks
sudo snap install tectonic
npm install -g @mermaid-js/mermaid-cli
# - (Optional but recommended) Rust toolchain for Rust LSPs and Mason builds
# This installs rustup and the stable toolchain, then ensures PATH for new shells
sudo apt-get install -y rustup
rustup toolchain install stable
if ! grep -q 'source "$HOME/.cargo/env"' "$HOME/.bashrc"; then
echo 'source "$HOME/.cargo/env"' >> "$HOME/.bashrc"
fi
# Headless: sync LazyVim, then install LSPs, DAPs, linters/formatters via Mason,
# and seed Treesitter parsers (AI + web + dotnet + data)
nvim --headless \
"+Lazy! sync" \
"+MasonInstall lua-language-server pyright ruff-lsp typescript-language-server angular-language-server html-lsp css-lsp tailwindcss-language-server eslint_d emmet-language-server jdtls kotlin-language-server bash-language-server yaml-language-server json-lsp lemminx marksman omnisharp csharp-language-server" \
"+MasonInstall debugpy netcoredbg js-debug-adapter" \
"+MasonInstall black isort ruff prettier prettierd shfmt stylua csharpier" \
"+TSInstallSync lua python javascript typescript tsx html css scss c_sharp java kotlin json yaml xml bash markdown dockerfile" \
"+qa" || trueAI-focused extras for LazyVim (optional but recommended):
- Local LLM assistants (Ollama) and chat/coder UIs inside Neovim
- Inline/ghost suggestions and code actions
- Markdown/Obsidian-friendly tooling for RAG notes
Example plugin specs you can drop into ~/.config/nvim/lua/plugins/ai.lua:
return {
-- Local + cloud AI assistant (chat/completions). Configure to use Ollama by default.
{
"yetone/avante.nvim",
event = "VeryLazy",
dependencies = { "nvim-lua/plenary.nvim", "stevearc/dressing.nvim" },
opts = {
provider = "ollama",
vendors = {
ollama = {
endpoint = "http://127.0.0.1:11434",
-- pick a local model you have via `ollama list`
model = "llama3.1:8b",
},
},
},
},
-- Another great AI workflow tool with tasks/prompts, also supports OpenAI-compatible endpoints
{
"olimorris/codecompanion.nvim",
event = "VeryLazy",
opts = {
strategies = {
chat = { adapter = "openai" },
inline = { adapter = "openai" },
},
adapters = {
openai = function()
return {
-- Point to an OpenAI-compatible endpoint (e.g., litellm proxy or LM Studio)
endpoint = vim.env.OPENAI_BASE_URL or "http://127.0.0.1:11434/v1",
model = vim.env.OPENAI_MODEL or "gpt-4o-mini", -- or your local model name
api_key = vim.env.OPENAI_API_KEY or "dummy", -- required by some clients even for local
}
end,
},
},
},
-- Render Markdown nicely (notes, prompts, scratch buffers)
{ "MeanderingProgrammer/render-markdown.nvim", ft = { "markdown", "rmd" }, opts = {} },
}Secrets note: set credentials/endpoints via environment variables, not inline. Examples (bash):
# Local OpenAI-compatible endpoint (LM Studio or litellm proxy)
export OPENAI_BASE_URL="http://127.0.0.1:11434/v1"
export OPENAI_MODEL="qwen2.5-coder:7b"
# If you use a real OpenAI key, export OPENAI_API_KEY in your shell, but do not commit it.Migration tips from Kickstart -> LazyVim:
- Your previous
~/.config/nvimwas backed up with a timestamp. Pull over snippets (keymaps, autocmds) into~/.config/nvim/lua/config/*.luaorlua/plugins/*.lua. - LazyVim ships with sensible defaults: Telescope, treesitter, gitsigns, which-key, better-escape, etc.
- Enable per-language extras and tooling via Mason and Treesitter (seeded above). Add more in
:MasonUI any time.
Notes:
- Prefer Neovim >= 0.11 to avoid deprecations (lazydev.nvim, blink.cmp, lspconfig).
- For C#, both OmniSharp and Roslyn LSP (csharp-language-server) are installed; try Roslyn first, fall back to OmniSharp.
- Razor/Blazor and XAML language servers are experimental on Neovim. For Razor, consider VS Code for full Razor authoring; for formatting,
csharpierworks well in Neovim. - Snacks.image in Warp: Warp does not support the Kitty graphics protocol; inline image rendering falls back to PNGs in some contexts. Install ImageMagick and Treesitter parsers (latex, markdown) for best docs rendering; Tectonic enables LaTeX math; Mermaid CLI enables diagrams.
- LuaRocks: If you do not use plugins that require LuaRocks, you can disable hererocks or Rocks support in Lazy to silence health warnings. Example (init.lua or config/lazy.lua):
require("lazy").setup({ -- ... your spec ... rocks = { enabled = false }, -- or: hererocks = false })
Verification:
nvim --version(expect 0.11+)- Open
nvimand run:Lazythen:Masonto confirm plugins/tools. - Open a Python/TS/C#/HTML/YAML/Markdown file and confirm LSP attaches (statusline) and formatting on save.
Quick checks to verify everything is wired up:
# System & tools
lsb_release -a || cat /etc/os-release
which git && git --version
which docker && docker --version
which docker && docker compose version
# Python & Jupyter
which python3 && python3 --version
which uv && uv --version
jupyter-lab --version
python3 -c "import jupyter,langchain,llama_index; print('Python AI stack OK')" || true
# .NET
which dotnet && dotnet --info
dotnet new console -o ~/develop/tmp/dotnet-hello
( cd ~/develop/tmp/dotnet-hello && dotnet build )
# Node & npm
which node && node -v
which npm && npm -v
# Java, Gradle, Kotlin
java -version
javac -version
gradle -v
kotlin -version
# Android
sdkmanager --list | head -n 50
adb version
# Editors & IDEs
code --version || true
nvim --version- Configure global
.gitignore, signing, and diff/merge tools. - Install additional VS Code extensions per your workflow.
- In Neovim, add AI assistants or code completion enhancements as desired.
- Consider enabling
ufwfirewall and fail2ban depending on threat model.
# Add Google’s signing key and repo
wget -qO- https://dl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/google-linux.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/google-linux.gpg] http://dl.google.com/linux/chrome/deb/ stable main" | \
sudo tee /etc/apt/sources.list.d/google-chrome.list >/dev/null
sudo apt-get update -y
sudo apt-get install -y google-chrome-stableVerification:
google-chrome --version
Note: GitHub Desktop is not officially released for Ubuntu/Linux. Skip this step for now. If an official Linux release becomes available, add it here.
Verification:
- N/A
AppImage (specific version provided):
mkdir -p "$HOME/Applications" "$HOME/.local/bin"
curl -L -o "$HOME/Applications/lmstudio.AppImage" "https://installers.lmstudio.ai/linux/x64/0.3.26-6/LM-Studio-0.3.26-6-x64.AppImage"
chmod +x "$HOME/Applications/lmstudio.AppImage"Optional CLI wrapper (recommended):
- Prefer Chromium user namespace sandbox by disabling the SUID sandbox helper.
- This aligns with Ubuntu 23.10+ AppArmor policies.
install -m 0755 /dev/stdin "$HOME/.local/bin/lmstudio" <<'EOF'
#!/usr/bin/env bash
exec "$HOME/Applications/lmstudio.AppImage" --disable-setuid-sandbox "$@"
EOFCreate a desktop entry if needed:
mkdir -p "$HOME/.local/share/applications"
cat > "$HOME/.local/share/applications/lmstudio.desktop" <<'EOF'
[Desktop Entry]
Type=Application
Name=LM Studio
Exec=%h/Applications/lmstudio.AppImage
Icon=lmstudio
Terminal=false
Categories=Development;
EOF
update-desktop-database ~/.local/share/applications || trueUbuntu 24.04 sandbox fix (AppArmor) Why this is needed:
- On Ubuntu 23.10+ with AppArmor, processes that create user namespaces may be transitioned to the unprivileged_userns profile, which denies some capabilities (e.g., cap sys_admin) in the user namespace. Chromium-based apps (including LM Studio’s Electron runtime) then fail with: “No usable sandbox!”.
- Kernel setting kernel.unprivileged_userns_clone should be 1 (Ubuntu defaults to 1).
Fix: run LM Studio under an unconfined AppArmor profile (like brave, github-desktop):
# Create profile: /etc/apparmor.d/lm-studio
# Note: this profiles the absolute AppImage path; update if you move/rename it.
sudo tee /etc/apparmor.d/lm-studio >/dev/null <<'EOF'
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile lm-studio /home/user/Applications/lmstudio.AppImage flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/lm-studio>
}
EOF
# Load/reload the profile
sudo apparmor_parser -r -W /etc/apparmor.d/lm-studio
# Verify it’s recognized and unconfined
sudo apparmor_status | grep -i "lm-studio" || trueQuick diagnostics (optional):
# Check kernel userns setting (should be 1)
sysctl kernel.unprivileged_userns_clone
# Inspect recent AppArmor kernel messages indicating userns restrictions
journalctl --no-pager -k -g 'apparmor|userns' -n 100 | sed -n '1,40p'Temporary and alternative workarounds:
- Quick test without policy change: run once with AppArmor unconfined label
aa-exec -p unconfined "$HOME/Applications/lmstudio.AppImage" --disable-setuid-sandbox - SUID sandbox helper path: set up chrome-devel-sandbox and remove --disable-setuid-sandbox (advanced, requires setuid root helper; prefer the AppArmor profile on Ubuntu).
- Last resort only: --no-sandbox (not recommended).
curl -fsSL https://ollama.com/install.sh | sh
# On Ubuntu, this installs a systemd service. Verify version:
ollama --version || trueFira Code and JetBrains Mono via apt:
sudo apt-get update -y
sudo apt-get install -y fonts-firacode fonts-jetbrains-monoNerd Fonts (FiraCode NF, JetBrainsMono NF) user-local install:
mkdir -p "$HOME/.local/share/fonts/NerdFonts"
cd /tmp
# Update version if needed (see Nerd Fonts releases)
curl -L -o JetBrainsMono.zip https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/JetBrainsMono.zip
curl -L -o FiraCode.zip https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/FiraCode.zip
unzip -o JetBrainsMono.zip -d "$HOME/.local/share/fonts/NerdFonts/JetBrainsMono"
unzip -o FiraCode.zip -d "$HOME/.local/share/fonts/NerdFonts/FiraCode"
fc-cache -fvConfigure editors/terminals to use a Nerd Font
- VS Code (settings.json):
mkdir -p "$HOME/.config/Code/User" cat > "$HOME/.config/Code/User/settings.json" <<'EOF' { "editor.fontFamily": "JetBrainsMono Nerd Font", "editor.fontLigatures": true, "terminal.integrated.fontFamily": "JetBrainsMono Nerd Font" } EOF
- GNOME Terminal (optional):
# Find current profile PID=$(gsettings get org.gnome.Terminal.ProfilesList default | tr -d "'") PROF=${PID#/} # Set font (adjust size as preferred) gsettings set org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:$PROF/ use-system-font false gsettings set org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:$PROF/ font "JetBrainsMono Nerd Font 12"
- Warp: set the font in Warp Settings UI to "JetBrainsMono Nerd Font".
Option A: Snap (simple):
sudo snap install android-studio --classicOption B: Manual .deb from Google (use when you need the latest canary):
- Download the current .deb from the Android Studio downloads page.
sudo apt-get install -y ./android-studio-*.deb
Automated install/update (verify before piping to bash):
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bashVerification:
lazydocker --version
This guide appends to ~/.bashrc (and ~/.zshrc if present) to ensure:
~/.local/binis on PATH (foruv,pipx, etc.)- Android SDK (
ANDROID_HOME) tools on PATH direnvhooks are activated
Log out and back in (or source ~/.bashrc) for changes to take effect.
- Linux hosts can develop and build MAUI Android apps after installing the Android SDK and workloads, but cannot build iOS or Windows targets locally.
- For Blazor WebAssembly, ensure the
wasm-toolsworkload is installed (done above).
uvprovides very fast, modern virtualenv management and package installs.pipxis great for installing Python CLI tools isolated from project environments.
- Docker: 28.4.0; Compose v2.39.4
- Python: 3.12.3; uv 0.8.19; venv ai with JupyterLab 4.4.7 and AI libs (LangChain, LlamaIndex, etc.)
- Node (nvm): LTS active by default; Node v22.19.0; npm 10.9.3
- Java: Temurin 21 (default) and 17 installed via SDKMAN; Gradle 9.1.0; Kotlin 2.2.20
- Android SDK: platform-tools 36, build-tools 34.0.0, platforms;android-34, emulator; cmdline-tools at latest-2 (acceptable)
- .NET: 8.0.119 via apt; 9.0.305 via dotnet-install (user local); workloads android and maui-android installed under .NET 9
- VS Code: 1.104.1; Neovim: 0.11.4 (user-local tarball)
- LM Studio: 0.3.26-6 AppImage; Ollama installed (service active)
- Fonts: Fira Code, JetBrains Mono (apt), and Nerd Fonts (JetBrainsMono, FiraCode) installed; VS Code configured to JetBrainsMono Nerd Font
- lazydocker: 0.24.1
- If Docker commands fail with permissions, ensure your user is in the
dockergroup and re-login or runnewgrp docker. - If
sdkmanageris missing, confirm the Android command-line tools are placed under$ANDROID_HOME/cmdline-tools/latestand PATH is updated; ensure JAVA_HOME points at your active JDK. - If
.NET 9/10are not in apt, install viadotnet-install.sh(Section 7) and add$HOME/.dotnetto PATH. - If Android/MAUI workloads are not recognized on Linux, validate your SDK channel/version supports them; otherwise develop with Blazor Wasm on Linux and use Windows/macOS for full MAUI targets.
- Headless Mason installs sometimes need a second run; ensure Node/npm is in PATH (source nvm) and re-run the
nvim --headlessline in Section 10. - If Neovim complains about version requirements (e.g., lazydev.nvim requires >= 0.10, blink.cmp >= 0.10, lspconfig recommends 0.11+), ensure the
nvimfound on PATH is >= 0.11: runcommand -v nvimandnvim --version. Prefer the user-local~/.local/bin/nvimto shadow any older systemnvim.