refactor: restructure installation to modular architecture

- Remove monolithic bootstrap.sh in favor of modular design
- Add install.sh as new main entry point with improved UX
- Split functionality into focused modules:
  - lib/common.sh: shared utilities and environment detection
  - lib/packages.sh: base system packages
  - lib/neovim.sh: Neovim and LazyVim setup
  - lib/arduino.sh: Arduino CLI and board cores
  - lib/git.sh: Git and Lazygit configuration
  - lib/shell-config.sh: Zsh and shell environment
  - lib/starship.sh: Starship prompt setup
  - lib/zellij.sh: Zellij terminal multiplexer
  - lib/scaffold.sh: project scaffolding utilities
- Update README with new installation instructions and architecture
- Improve error handling and user feedback across modules
- Maintain compatibility with both Arch Linux and Ubuntu/WSL2

This modular approach enables better maintainability, testing,
and allows users to selectively run components as needed.
This commit is contained in:
marvin.dietermann
2026-04-10 11:41:21 +02:00
parent f1db151f08
commit c9fa40b31c
13 changed files with 853 additions and 583 deletions

36
lib/arduino.sh Normal file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Step 7: Arduino CLI + board cores
step "7/9 — Installing Arduino CLI + board cores"
if ! command -v arduino-cli &>/dev/null; then
if $IS_ARCH; then
$PKG_INSTALL arduino-cli
else
curl -kfsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sudo sh
fi
fi
info "Arduino CLI installed: $(arduino-cli version 2>/dev/null | head -1)"
arduino-cli config init 2>/dev/null || true
# Add ESP board manager URLs
arduino-cli config add board_manager.additional_urls \
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json 2>/dev/null || true
arduino-cli config add board_manager.additional_urls \
https://arduino.esp8266.com/stable/package_esp8266com_index.json 2>/dev/null || true
arduino-cli core update-index
info "Installing AVR core..."
arduino-cli core install arduino:avr
info "Installing ESP32 core..."
arduino-cli core install esp32:esp32
info "Installing ESP8266 core..."
arduino-cli core install esp8266:esp8266
# Common libs
arduino-cli lib install "Servo" "ArduinoJson" "Adafruit Unified Sensor" "DHT sensor library" 2>/dev/null || true
info "Board cores & libraries installed"

84
lib/common.sh Normal file
View File

@@ -0,0 +1,84 @@
#!/bin/bash
# Common helpers and environment detection
set -euo pipefail
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
VERBOSE=true
LOG_FILE="/tmp/archduino-install.log"
CURRENT_STEP=0
TOTAL_STEPS=10
info() { echo -e "${GREEN}[✓]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
error() {
echo -e "${RED}[✗]${NC} $1"
exit 1
}
step() { echo -e "\n${GREEN}━━━ $1 ━━━${NC}"; }
# ── Quiet-mode progress bar ────────────────────────────────────
draw_progress() {
local current=$1
local total=$2
local label=$3
local pct=$((current * 100 / total))
local bar_width=40
local filled=$((pct * bar_width / 100))
local empty=$((bar_width - filled))
local bar="" space=""
for ((i = 0; i < filled; i++)); do bar+="█"; done
for ((i = 0; i < empty; i++)); do space+="░"; done
printf "\033[2K\r ${GREEN}${bar}${NC}${space} %3d%% ${BOLD}%s${NC}" "$pct" "$label" >&3
}
begin_quiet_mode() {
$IS_WSL && TOTAL_STEPS=11
: >"$LOG_FILE"
# Save real terminal to fd 3/4, redirect everything else to log
exec 3>&1 4>&2
exec 1>>"$LOG_FILE" 2>&1
# Override output helpers to talk to the terminal via fd 3
step() {
CURRENT_STEP=$((CURRENT_STEP + 1))
draw_progress "$CURRENT_STEP" "$TOTAL_STEPS" "$1"
}
info() { :; }
warn() { printf "\033[2K\r ${YELLOW}[!]${NC} %s\n" "$1" >&3; }
error() {
printf "\033[2K\r ${RED}[✗]${NC} %s\n" "$1" >&3
echo " See log: $LOG_FILE" >&3
exit 1
}
}
end_quiet_mode() {
# Restore stdout/stderr
exec 1>&3 2>&4
exec 3>&- 4>&-
echo ""
}
# ── Detect environment ──────────────────────────────────────────
IS_WSL=false
[[ -f /proc/version ]] && grep -qi microsoft /proc/version && IS_WSL=true
IS_ARCH=false
IS_UBUNTU=false
if command -v pacman &>/dev/null; then
IS_ARCH=true
PKG_INSTALL="sudo pacman -S --needed --noconfirm"
elif command -v apt &>/dev/null; then
IS_UBUNTU=true
PKG_INSTALL="sudo apt install -y"
else
error "Unsupported distro. Need Arch (pacman) or Ubuntu (apt)."
fi

20
lib/git.sh Normal file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Step 8: Git + Lazygit
step "8/9 — Setting up Git + Lazygit"
if ! command -v lazygit &>/dev/null; then
if $IS_ARCH; then
$PKG_INSTALL lazygit
else
LAZYGIT_VERSION=$(curl -ks "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | jq -r '.tag_name' | sed 's/v//')
curl -kLo /tmp/lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/latest/download/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz"
tar xf /tmp/lazygit.tar.gz -C /tmp lazygit
sudo mv /tmp/lazygit /usr/local/bin/
fi
fi
info "Lazygit installed"
mkdir -p ~/.config/lazygit
write_lazygit_config ~/.config/lazygit/config.yml
info "Lazygit configured with $THEME_DISPLAY_NAME"

107
lib/neovim.sh Normal file
View File

@@ -0,0 +1,107 @@
#!/bin/bash
# Steps 5+6: Neovim + NvChad
step "5/9 — Installing Neovim (latest)"
NVIM_NEEDED=true
if command -v nvim &>/dev/null; then
NVIM_VER=$(nvim --version | head -1 | grep -oP '\d+\.\d+\.\d+')
if [[ "$(printf '%s\n' "0.11.2" "$NVIM_VER" | sort -V | head -1)" == "0.11.2" ]]; then
NVIM_NEEDED=false
info "Neovim $NVIM_VER already meets requirements"
fi
fi
if $NVIM_NEEDED; then
curl -kLO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz
sudo rm -rf /opt/nvim-linux-x86_64
sudo tar -xzf nvim-linux-x86_64.tar.gz -C /opt
sudo ln -sf /opt/nvim-linux-x86_64/bin/nvim /usr/local/bin/nvim
rm nvim-linux-x86_64.tar.gz
info "Neovim installed: $(nvim --version | head -1)"
fi
step "6/9 — Setting up NvChad"
if [[ ! -d "$HOME/.config/nvim/lua" ]]; then
for d in ~/.config/nvim ~/.local/share/nvim ~/.local/state/nvim ~/.cache/nvim; do
[[ -d "$d" ]] && mv "$d" "${d}.bak.$(date +%s)"
done
git clone https://github.com/NvChad/starter ~/.config/nvim
rm -rf ~/.config/nvim/.git
info "NvChad starter cloned"
else
info "Neovim config already exists, skipping NvChad clone"
fi
# ── Theme ───────────────────────────────────────────────────────
mkdir -p ~/.config/nvim/lua
cat >~/.config/nvim/lua/chadrc.lua <<EOF
---@type ChadrcConfig
local M = {}
M.base46 = {
theme = "$NVCHAD_THEME",
}
return M
EOF
# ── Arduino plugin ──────────────────────────────────────────────
mkdir -p ~/.config/nvim/lua/plugins
cat >~/.config/nvim/lua/plugins/arduino.lua <<'LUA'
return {
{
"stevearc/vim-arduino",
ft = { "arduino", "cpp", "c" },
config = function()
vim.g.arduino_use_cli = 1
vim.g.arduino_cli_path = "arduino-cli"
end,
},
}
LUA
# ── LSP (clangd) ───────────────────────────────────────────────
mkdir -p ~/.config/nvim/lua/configs
cat >~/.config/nvim/lua/configs/lspconfig.lua <<'LUA'
local configs = require "nvchad.configs.lspconfig"
local on_attach = configs.on_attach
local on_init = configs.on_init
local capabilities = configs.capabilities
local lspconfig = require "lspconfig"
lspconfig.clangd.setup {
on_init = on_init,
on_attach = on_attach,
capabilities = capabilities,
cmd = {
"clangd",
"--background-index",
"--clang-tidy",
"--header-insertion=iwyu",
"--completion-style=detailed",
},
}
LUA
# ── Keymaps ─────────────────────────────────────────────────────
cat >~/.config/nvim/lua/mappings.lua <<'LUA'
require "nvchad.mappings"
local map = vim.keymap.set
map("n", "<leader>ac", "<cmd>ArduinoChooseBoard<cr>", { desc = "Arduino Choose Board" })
map("n", "<leader>ap", "<cmd>ArduinoChoosePort<cr>", { desc = "Arduino Choose Port" })
map("n", "<leader>av", "<cmd>ArduinoVerify<cr>", { desc = "Arduino Verify (compile)" })
map("n", "<leader>au", "<cmd>ArduinoUpload<cr>", { desc = "Arduino Upload" })
map("n", "<leader>as", "<cmd>ArduinoSerial<cr>", { desc = "Arduino Serial Monitor" })
map("n", "<leader>ai", "<cmd>ArduinoInfo<cr>", { desc = "Arduino Board Info" })
LUA
info "NvChad configured ($THEME_DISPLAY_NAME + Arduino + LSP)"

23
lib/packages.sh Normal file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Step 1: Base packages
step "1/9 — Installing base packages"
if $IS_ARCH; then
sudo pacman -Syu --noconfirm
$PKG_INSTALL base-devel git curl unzip wget ripgrep fd bat eza tree jq zsh clang
# tio is in AUR — install yay if needed, then tio
if ! command -v yay &>/dev/null; then
info "Installing yay (AUR helper)..."
git clone https://aur.archlinux.org/yay.git /tmp/yay
(cd /tmp/yay && makepkg -si --noconfirm)
rm -rf /tmp/yay
fi
yay -S --needed --noconfirm tio
else
sudo apt update && sudo apt upgrade -y
$PKG_INSTALL build-essential git curl unzip wget ripgrep bat eza tree jq zsh clang-format tio
# fd is named differently on Ubuntu
sudo apt install -y fd-find 2>/dev/null || true
fi
info "Base packages installed"

138
lib/scaffold.sh Normal file
View File

@@ -0,0 +1,138 @@
#!/bin/bash
# Step 9: Project scaffolding tool + templates
step "9/9 — Creating project scaffolding tool"
mkdir -p ~/.local/bin ~/.config/archduino/templates
cat >~/.local/bin/arduino-new <<'SCAFFOLD'
#!/bin/bash
set -e
TEMPLATE_DIR="$HOME/.config/archduino/templates"
declare -A FQBN_MAP=(
["uno"]="arduino:avr:uno"
["mega"]="arduino:avr:mega"
["nano"]="arduino:avr:nano"
["esp32"]="esp32:esp32:esp32"
["esp32s3"]="esp32:esp32:esp32s3"
["esp8266"]="esp8266:esp8266:nodemcuv2"
)
usage() {
echo "Usage: arduino-new <project-name> <board>"
echo ""
echo "Available boards:"
for board in "${!FQBN_MAP[@]}"; do
printf " %-12s %s\n" "$board" "${FQBN_MAP[$board]}"
done
exit 1
}
[[ $# -lt 2 ]] && usage
PROJECT="$1"
BOARD="$2"
FQBN="${FQBN_MAP[$BOARD]}"
[[ -z "$FQBN" ]] && echo "Unknown board: $BOARD" && usage
mkdir -p "$PROJECT"
cd "$PROJECT"
if [[ -f "$TEMPLATE_DIR/$BOARD.ino" ]]; then
cp "$TEMPLATE_DIR/$BOARD.ino" "$PROJECT.ino"
else
cp "$TEMPLATE_DIR/default.ino" "$PROJECT.ino"
fi
cat > sketch.yaml <<EOF
default_fqbn: $FQBN
default_port: auto
EOF
cat > .clangd <<EOF
CompileFlags:
Add:
- -xc++
- -std=c++17
EOF
cat > .gitignore <<EOF
build/
.cache/
*.elf
*.hex
*.bin
EOF
git init -q
echo "✓ Created '$PROJECT' for $BOARD ($FQBN)"
echo " cd $PROJECT && nvim $PROJECT.ino"
SCAFFOLD
chmod +x ~/.local/bin/arduino-new
# Templates
cat >~/.config/archduino/templates/default.ino <<'INO'
void setup() {
Serial.begin(115200);
while (!Serial) { ; }
Serial.println("Hello from Arduino!");
}
void loop() {
// Your code here
}
INO
cat >~/.config/archduino/templates/esp32.ino <<'INO'
#include <WiFi.h>
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASS";
void setup() {
Serial.begin(115200);
delay(1000);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Connected! IP: ");
Serial.println(WiFi.localIP());
}
void loop() {
delay(1000);
}
INO
cat >~/.config/archduino/templates/esp8266.ino <<'INO'
#include <ESP8266WiFi.h>
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASS";
void setup() {
Serial.begin(115200);
delay(1000);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Connected! IP: ");
Serial.println(WiFi.localIP());
}
void loop() {
delay(1000);
}
INO
info "Scaffolding tool + templates created"

50
lib/shell-config.sh Normal file
View File

@@ -0,0 +1,50 @@
#!/bin/bash
# Shell config (.zshrc) and WSL2 extras
step "Finalizing shell config"
# Append archduino block to .zshrc if not already present
if ! grep -q "ARCHDUINO" ~/.zshrc 2>/dev/null; then
cat >>~/.zshrc <<'ZSHRC'
# ── ARCHDUINO CONFIG ────────────────────────────────────────────
plugins=(git zsh-autosuggestions zsh-syntax-highlighting)
export PATH="$HOME/.local/bin:$PATH"
export EDITOR="nvim"
export VISUAL="nvim"
eval "$(starship init zsh)"
# Auto-start Zellij
if [[ -z "$ZELLIJ" ]]; then
zellij attach --create archduino
fi
# Arduino aliases
alias adev="zellij --layout arduino"
alias ac="arduino-cli"
alias acc="arduino-cli compile"
alias acu="arduino-cli upload"
alias acm="arduino-cli monitor"
alias acb="arduino-cli board list"
alias lg="lazygit"
alias v="nvim"
# ── END ARCHDUINO ───────────────────────────────────────────────
ZSHRC
info "Shell config updated"
else
info "Shell config already contains Archduino block"
fi
# ── WSL2 extras ─────────────────────────────────────────────────
if $IS_WSL; then
step "WSL2 extras"
warn "USB passthrough requires usbipd-win on Windows."
warn "Install it with: winget install usbipd"
warn "Then attach your Arduino: usbipd attach --wsl --busid <BUSID>"
# Add user to dialout for serial access
sudo usermod -aG dialout "$USER" 2>/dev/null || true
sudo usermod -aG uucp "$USER" 2>/dev/null || true
fi

17
lib/starship.sh Normal file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Step 3: Starship prompt
step "3/9 — Installing Starship"
if ! command -v starship &>/dev/null; then
if $IS_ARCH; then
$PKG_INSTALL starship
else
curl -ksS https://starship.rs/install.sh | sh -s -- -y
fi
fi
info "Starship installed"
mkdir -p ~/.config
write_starship_config ~/.config/starship.toml
info "Starship configured with $THEME_DISPLAY_NAME"

43
lib/zellij.sh Normal file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
# Step 4: Zellij terminal multiplexer
step "4/9 — Installing Zellij"
if ! command -v zellij &>/dev/null; then
if $IS_ARCH; then
$PKG_INSTALL zellij
else
curl -kL https://github.com/zellij-org/zellij/releases/latest/download/zellij-x86_64-unknown-linux-musl.tar.gz | tar xz -C /tmp
sudo mv /tmp/zellij /usr/local/bin/
fi
fi
info "Zellij installed"
mkdir -p ~/.config/zellij/layouts
write_zellij_config ~/.config/zellij/config.kdl
cat >~/.config/zellij/layouts/arduino.kdl <<'LAYOUT'
layout {
tab name="code" focus=true {
pane split_direction="vertical" {
pane size="65%" {
command "nvim"
args "."
}
pane split_direction="horizontal" {
pane size="60%" name="terminal"
pane size="40%" name="serial" {
command "bash"
args "-c" "echo '⚡ Serial — run: tio /dev/ttyUSB0 -b 115200'"
}
}
}
}
tab name="git" {
pane {
command "lazygit"
}
}
}
LAYOUT
info "Zellij configured with $THEME_DISPLAY_NAME + Arduino layout"

22
lib/zsh.sh Normal file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Step 2: Zsh + Oh My Zsh + plugins
step "2/9 — Setting up Zsh"
if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
RUNZSH=no KEEP_ZSHRC=yes sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" ||
RUNZSH=no KEEP_ZSHRC=yes sh -c "$(curl -kfsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
info "Oh My Zsh installed"
else
info "Oh My Zsh already present"
fi
ZSH_CUSTOM="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
[[ ! -d "$ZSH_CUSTOM/plugins/zsh-autosuggestions" ]] &&
git clone https://github.com/zsh-users/zsh-autosuggestions "$ZSH_CUSTOM/plugins/zsh-autosuggestions"
[[ ! -d "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting" ]] &&
git clone https://github.com/zsh-users/zsh-syntax-highlighting "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting"
info "Zsh plugins installed"