Skip to content

Instantly share code, notes, and snippets.

@peterblazejewicz
Last active September 21, 2025 17:36
Show Gist options
  • Select an option

  • Save peterblazejewicz/4b87ce15c2bab847e160aed4a8233273 to your computer and use it in GitHub Desktop.

Select an option

Save peterblazejewicz/4b87ce15c2bab847e160aed4a8233273 to your computer and use it in GitHub Desktop.
Setup Ubuntu 24.04.3 tech stack instructions agentic AI

Ubuntu 24.04.3 LTS — AI/LLM Developer Workstation Setup (Revised)

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-tem and 17.0.16-tem fallback when 17-tem alias is unavailable.
  • SDKMAN initialization notes to avoid SDKMAN_CANDIDATES_API: unbound variable errors.
  • 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.sh with 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) alongside omnisharp in 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.

0) Pre-flight and directory layout

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"
fi

Optional: 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 login

Optional: install lazydocker via Snap (snap is built-in on Ubuntu)

sudo snap install lazydocker

Verification:

  • ls -ld ~/develop
  • direnv --version
  • gh --version (if installed)
  • lazydocker --version (if installed)

Verification:

  • ls -ld ~/develop
  • direnv --version

1) Git

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

2) Docker (Engine, CLI, Buildx, Compose)

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
EOF

Verification:

  • docker --version
  • docker compose version
  • Optional: docker run --rm hello-world

3) Python modern setup (uv + pipx + Jupyter)

# 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)"
deactivate

Verification:

  • uv --version
  • pipx --version
  • jupyter-lab --version
  • Start Jupyter: jupyter-lab (then Ctrl+C to exit)

7) .NET SDKs (8 via apt; 9 STS and 10 Preview via dotnet-install) + workloads

# 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 || true

Notes:

  • If android/maui-android workloads aren’t recognized on Linux, you can still develop Blazor WebAssembly with wasm-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, android and maui-android installed successfully using the .NET 9 SDK under ~/.dotnet.

Verification:

  • dotnet --list-sdks and $HOME/.dotnet/dotnet --list-sdks
  • dotnet workload list (on both locations)

4) Node.js LTS via nvm

# 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 --version

Optional: 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
fi

Verification:

  • node -v (LTS)
  • npm -v

5) Java via SDKMAN! (Temurin JDK 21 + 17) + Gradle + Kotlin

# 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_HOME

Notes:

  • If sdk version prints SDKMAN_CANDIDATES_API: unbound variable, reopen your shell or run source "$HOME/.sdkman/bin/sdkman-init.sh".
  • If sdk install java 17-tem fails, use sdk list java to choose a concrete 17.x (e.g., 17.0.16-tem).

Verification:

  • java -version (Temurin 21)
  • javac -version
  • gradle -v
  • kotlin -version

6) Android SDK (CLI tools, platform-tools, build-tools) + udev rules

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-common

Notes:

  • If license prompts persist, run sdkmanager --licenses in 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; both latest and latest-2 contain the latest tools.

Verification:

  • sdkmanager --list | head -n 50
  • adb version

8) Visual Studio Code + extensions

# 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 || true

Verification:

  • code --version
  • In VS Code, confirm extensions are installed.

9) JetBrains Toolbox

# 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
fi

Verification:

  • Launch from applications menu or run: ~/.local/share/JetBrains/Toolbox/jetbrains-toolbox &

10) Neovim (prebuilt tarball) + LazyVim (full) + Mason automation

# 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" || true

AI-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/nvim was backed up with a timestamp. Pull over snippets (keymaps, autocmds) into ~/.config/nvim/lua/config/*.lua or lua/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 :Mason UI 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, csharpier works 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 nvim and run :Lazy then :Mason to confirm plugins/tools.
  • Open a Python/TS/C#/HTML/YAML/Markdown file and confirm LSP attaches (statusline) and formatting on save.

11) Post-install sanity checks

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

12) Optional improvements

  • 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 ufw firewall and fail2ban depending on threat model.

13) Google Chrome

# 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-stable

Verification:

  • google-chrome --version

14) GitHub Desktop

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

15) LM Studio

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 "$@"
EOF

Create 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 || true

Ubuntu 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" || true

Quick 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).

16) Ollama

curl -fsSL https://ollama.com/install.sh | sh
# On Ubuntu, this installs a systemd service. Verify version:
ollama --version || true

17) Fonts (Fira Code, JetBrains Mono, Nerd Fonts)

Fira Code and JetBrains Mono via apt:

sudo apt-get update -y
sudo apt-get install -y fonts-firacode fonts-jetbrains-mono

Nerd 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 -fv

Configure 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".

18) Android Studio

Option A: Snap (simple):

sudo snap install android-studio --classic

Option 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

19) lazydocker

Automated install/update (verify before piping to bash):

curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash

Verification:

  • lazydocker --version

Appendix A: Environment variables and PATH persistence

This guide appends to ~/.bashrc (and ~/.zshrc if present) to ensure:

  • ~/.local/bin is on PATH (for uv, pipx, etc.)
  • Android SDK (ANDROID_HOME) tools on PATH
  • direnv hooks are activated

Log out and back in (or source ~/.bashrc) for changes to take effect.


Appendix B: Notes on .NET MAUI on Linux

  • 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-tools workload is installed (done above).

Appendix C: Why uv + pipx

  • uv provides very fast, modern virtualenv management and package installs.
  • pipx is great for installing Python CLI tools isolated from project environments.

Appendix E: Status snapshot (this machine)

  • 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

Appendix D: Troubleshooting

  • If Docker commands fail with permissions, ensure your user is in the docker group and re-login or run newgrp docker.
  • If sdkmanager is missing, confirm the Android command-line tools are placed under $ANDROID_HOME/cmdline-tools/latest and PATH is updated; ensure JAVA_HOME points at your active JDK.
  • If .NET 9/10 are not in apt, install via dotnet-install.sh (Section 7) and add $HOME/.dotnet to 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 --headless line 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 nvim found on PATH is >= 0.11: run command -v nvim and nvim --version. Prefer the user-local ~/.local/bin/nvim to shadow any older system nvim.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment