Last active
April 5, 2026 15:51
-
-
Save chrisdc/eba88d1216304fd2eac6dfc1e2e0935c to your computer and use it in GitHub Desktop.
Rasperry Pi Setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # ============================================================================= | |
| # Raspberry Pi Setup Script | |
| # Installs: nvm, Node.js LTS, uv, git (with config), SSH key for GitHub, VNC | |
| # ============================================================================= | |
| set -euo pipefail | |
| # --- Flags ------------------------------------------------------------------- | |
| INSTALL_VNC=false | |
| for arg in "$@"; do | |
| case "$arg" in | |
| --vnc) INSTALL_VNC=true ;; | |
| --help|-h) | |
| echo "Usage: $0 [--vnc]" | |
| echo " --vnc Also enable VNC remote desktop" | |
| exit 0 ;; | |
| *) echo "Unknown option: $arg (use --help for usage)" >&2; exit 1 ;; | |
| esac | |
| done | |
| # --- Colours ----------------------------------------------------------------- | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| CYAN='\033[0;36m' | |
| BOLD='\033[1m' | |
| RESET='\033[0m' | |
| # --- Helpers ----------------------------------------------------------------- | |
| info() { echo -e "${CYAN}[INFO]${RESET} $*"; } | |
| success() { echo -e "${GREEN}[OK]${RESET} $*"; } | |
| warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; } | |
| error() { echo -e "${RED}[ERROR]${RESET} $*" >&2; exit 1; } | |
| section() { echo -e "\n${BOLD}${CYAN}==> $*${RESET}"; } | |
| prompt() { | |
| # prompt <var_name> <display_text> [default] | |
| local var="$1" | |
| local msg="$2" | |
| local default="${3:-}" | |
| local input | |
| if [[ -n "$default" ]]; then | |
| read -rp "$(echo -e "${YELLOW}?${RESET} ${msg} [${default}]: ")" input | |
| printf -v "$var" '%s' "${input:-$default}" | |
| else | |
| while true; do | |
| read -rp "$(echo -e "${YELLOW}?${RESET} ${msg}: ")" input | |
| [[ -n "$input" ]] && break | |
| warn "This field is required." | |
| done | |
| printf -v "$var" '%s' "$input" | |
| fi | |
| } | |
| # ============================================================================= | |
| # 0. Banner | |
| # ============================================================================= | |
| echo -e "${BOLD}" | |
| echo " ______ _____ _____ _____ _ " | |
| echo " | ____| __ \_ _| / ____| | | " | |
| echo " | |__ | |__) || | | (___ ___| |_ _ _ _ __ " | |
| echo " | __| | ___/ | | \___ \ / _ \ __| | | | '_ \ " | |
| echo " | | | | _| |_ ____) | __/ |_| |_| | |_) |" | |
| echo " |_| |_| |_____||_____/ \___|\__|\__,_| .__/ " | |
| echo " | | " | |
| echo " |_| " | |
| echo -e "${RESET}" | |
| echo -e " Raspberry Pi First-Boot Setup — $(date '+%Y-%m-%d')\n" | |
| # ============================================================================= | |
| # 1. Collect user input up front | |
| # ============================================================================= | |
| section "Configuration" | |
| # printf -v (used inside prompt) creates variables dynamically, which trips | |
| # set -u before the variable is first read. Disable it for this block. | |
| set +u | |
| prompt GIT_NAME "Git full name (e.g. Jane Smith)" | |
| prompt GIT_EMAIL "Git email address" | |
| prompt SSH_KEY_FILE "SSH key filename" "$HOME/.ssh/id_ed25519" | |
| prompt SSH_KEY_COMMENT "SSH key comment/label" "$GIT_EMAIL" | |
| # Optional passphrase (hidden input) | |
| read -rsp "$(echo -e "${YELLOW}?${RESET} SSH key passphrase (leave blank for none): ")" SSH_PASSPHRASE | |
| echo | |
| set -u | |
| echo | |
| info "Configuration collected. Starting installation…" | |
| # ============================================================================= | |
| # 2. System update & base dependencies | |
| # ============================================================================= | |
| section "System packages" | |
| info "Updating package lists…" | |
| sudo apt-get update -qq | |
| info "Installing base dependencies (curl, git, build-essential)…" | |
| sudo apt-get install -y -qq curl git build-essential ca-certificates | |
| success "Base dependencies ready." | |
| # ============================================================================= | |
| # 3. nvm + Node.js LTS | |
| # ============================================================================= | |
| section "nvm & Node.js LTS" | |
| NVM_DIR="${NVM_DIR:-$HOME/.nvm}" | |
| if [[ -d "$NVM_DIR" ]]; then | |
| warn "nvm already installed at $NVM_DIR — skipping nvm install." | |
| else | |
| info "Installing nvm…" | |
| NVM_INSTALL_URL="https://raw.githubusercontent.com/nvm-sh/nvm/HEAD/install.sh" | |
| curl -fsSL "$NVM_INSTALL_URL" | bash | |
| success "nvm installed." | |
| fi | |
| # Load nvm into the current shell. | |
| # nvm.sh has unset variables that trip set -u, so disable it temporarily. | |
| set +u | |
| export NVM_DIR="$NVM_DIR" | |
| # shellcheck source=/dev/null | |
| [[ -s "$NVM_DIR/nvm.sh" ]] && source "$NVM_DIR/nvm.sh" | |
| [[ -s "$NVM_DIR/bash_completion" ]] && source "$NVM_DIR/bash_completion" | |
| info "Installing latest Node.js LTS…" | |
| nvm install --lts | |
| nvm use --lts | |
| nvm alias default 'lts/*' | |
| NODE_VER=$(node --version) | |
| NPM_VER=$(npm --version) | |
| set -u | |
| success "Node $NODE_VER and npm $NPM_VER active." | |
| # Persist nvm in shell config files that may not yet have it | |
| for RC_FILE in "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do | |
| if [[ -f "$RC_FILE" ]] && ! grep -q 'NVM_DIR' "$RC_FILE"; then | |
| cat >> "$RC_FILE" <<'EOF' | |
| # nvm | |
| export NVM_DIR="$HOME/.nvm" | |
| [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" | |
| [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" | |
| EOF | |
| info "Added nvm init to $RC_FILE" | |
| fi | |
| done | |
| # ============================================================================= | |
| # 4. uv (Python package manager) | |
| # ============================================================================= | |
| section "uv package manager" | |
| if command -v uv &>/dev/null; then | |
| warn "uv is already installed ($(uv --version)) — skipping." | |
| else | |
| info "Installing uv…" | |
| curl -fsSL https://astral.sh/uv/install.sh | sh | |
| # uv installs to ~/.cargo/bin or ~/.local/bin depending on version | |
| # Add both to PATH for this session | |
| export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH" | |
| success "uv $(uv --version) installed." | |
| fi | |
| # Ensure PATH addition is persisted | |
| for RC_FILE in "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do | |
| if [[ -f "$RC_FILE" ]] && ! grep -q 'uv' "$RC_FILE"; then | |
| echo 'export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH"' >> "$RC_FILE" | |
| info "Added uv PATH to $RC_FILE" | |
| fi | |
| done | |
| # Configure uv to use system site-packages by default (required for Pi camera) | |
| info "Configuring uv to use system site-packages (for PiCamera2 / libcamera)…" | |
| UV_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/uv" | |
| mkdir -p "$UV_CONFIG_DIR" | |
| UV_CONFIG_FILE="$UV_CONFIG_DIR/uv.toml" | |
| if [[ ! -f "$UV_CONFIG_FILE" ]]; then | |
| cat > "$UV_CONFIG_FILE" <<'EOF' | |
| [venv] | |
| system-site-packages = true | |
| EOF | |
| success "Created $UV_CONFIG_FILE with system-site-packages = true" | |
| else | |
| if grep -q 'system-site-packages' "$UV_CONFIG_FILE"; then | |
| warn "$UV_CONFIG_FILE already contains system-site-packages — leaving untouched." | |
| else | |
| echo -e '\n[venv]\nsystem-site-packages = true' >> "$UV_CONFIG_FILE" | |
| success "Appended system-site-packages = true to $UV_CONFIG_FILE" | |
| fi | |
| fi | |
| info "Verifying: uv venv --system-site-packages creates correct environment…" | |
| TMPDIR_UV=$(mktemp -d) | |
| uv venv --system-site-packages "$TMPDIR_UV/test_env" &>/dev/null && \ | |
| success "uv venv --system-site-packages works correctly." || \ | |
| warn "Could not verify uv venv — check manually after reboot." | |
| rm -rf "$TMPDIR_UV" | |
| # ============================================================================= | |
| # 5. Git configuration | |
| # ============================================================================= | |
| section "Git configuration" | |
| info "Setting git user.name → $GIT_NAME" | |
| git config --global user.name "$GIT_NAME" | |
| info "Setting git user.email → $GIT_EMAIL" | |
| git config --global user.email "$GIT_EMAIL" | |
| # Sensible defaults | |
| git config --global init.defaultBranch main | |
| git config --global pull.rebase false | |
| git config --global core.autocrlf input | |
| success "Git configured." | |
| git config --global --list | grep -E '^user\.' | while IFS= read -r line; do | |
| info " $line" | |
| done | |
| # ============================================================================= | |
| # 6. SSH key for GitHub | |
| # ============================================================================= | |
| section "SSH key for GitHub" | |
| SSH_DIR="$(dirname "$SSH_KEY_FILE")" | |
| mkdir -p "$SSH_DIR" | |
| chmod 700 "$SSH_DIR" | |
| if [[ -f "$SSH_KEY_FILE" ]]; then | |
| warn "SSH key already exists at $SSH_KEY_FILE — skipping key generation." | |
| else | |
| info "Generating Ed25519 SSH key…" | |
| ssh-keygen -t ed25519 \ | |
| -C "$SSH_KEY_COMMENT" \ | |
| -f "$SSH_KEY_FILE" \ | |
| -N "$SSH_PASSPHRASE" | |
| success "SSH key created: $SSH_KEY_FILE" | |
| fi | |
| # Ensure ssh-agent entry in shell config | |
| for RC_FILE in "$HOME/.bashrc" "$HOME/.bash_profile"; do | |
| if [[ -f "$RC_FILE" ]] && ! grep -q 'ssh-agent' "$RC_FILE"; then | |
| cat >> "$RC_FILE" <<EOF | |
| # Start ssh-agent automatically | |
| if ! pgrep -u "\$USER" ssh-agent >/dev/null 2>&1; then | |
| eval "\$(ssh-agent -s)" >/dev/null | |
| fi | |
| ssh-add "$SSH_KEY_FILE" 2>/dev/null || true | |
| EOF | |
| info "Added ssh-agent auto-start to $RC_FILE" | |
| fi | |
| done | |
| # Start ssh-agent now and add key for this session | |
| eval "$(ssh-agent -s)" >/dev/null 2>&1 || true | |
| ssh-add "$SSH_KEY_FILE" 2>/dev/null || true | |
| # ============================================================================= | |
| # 7. Add public key to GitHub | |
| # ============================================================================= | |
| section "Add SSH key to GitHub" | |
| PUB_KEY_FILE="${SSH_KEY_FILE}.pub" | |
| PUB_KEY_CONTENT=$(cat "$PUB_KEY_FILE") | |
| echo | |
| echo -e "${BOLD}Your public SSH key (add this to GitHub):${RESET}" | |
| echo -e "${YELLOW}────────────────────────────────────────────────────────────${RESET}" | |
| echo "$PUB_KEY_CONTENT" | |
| echo -e "${YELLOW}────────────────────────────────────────────────────────────${RESET}" | |
| echo | |
| echo -e " 1. Go to ${CYAN}https://github.com/settings/ssh/new${RESET}" | |
| echo -e " 2. Paste the key above into the 'Key' field." | |
| echo -e " 3. Give it a memorable title (e.g. 'Raspberry Pi')." | |
| echo -e " 4. Click ${BOLD}Add SSH key${RESET}." | |
| echo | |
| read -rp "$(echo -e "${YELLOW}?${RESET} Press Enter once you have added the key to GitHub… ")" | |
| info "Testing SSH connection to GitHub…" | |
| if ssh -o StrictHostKeyChecking=accept-new -T git@github.com 2>&1 | grep -q 'successfully authenticated'; then | |
| success "GitHub SSH authentication successful!" | |
| else | |
| warn "Could not confirm GitHub auth automatically." | |
| info "Run ${BOLD}ssh -T git@github.com${RESET} after reboot to verify." | |
| fi | |
| # ============================================================================= | |
| # 8. VNC (optional — pass --vnc to enable) | |
| # ============================================================================= | |
| if [[ "$INSTALL_VNC" == true ]]; then | |
| section "VNC remote desktop" | |
| # Install RealVNC server (included in Raspberry Pi OS but may be missing on | |
| # lite images) plus the virtual framebuffer needed for headless operation. | |
| info "Installing RealVNC server and virtual framebuffer…" | |
| sudo apt-get install -y -qq realvnc-vnc-server realvnc-vnc-viewer xvfb || { | |
| warn "RealVNC packages not found in apt — trying tigervnc as fallback…" | |
| sudo apt-get install -y -qq tigervnc-standalone-server tigervnc-common xvfb | |
| } | |
| # Enable and start the vncserver-x11-serviced service (RealVNC) | |
| if systemctl list-unit-files | grep -q 'vncserver-x11-serviced'; then | |
| info "Enabling vncserver-x11-serviced (RealVNC service mode)…" | |
| sudo systemctl enable vncserver-x11-serviced | |
| sudo systemctl start vncserver-x11-serviced | |
| success "RealVNC service enabled and started." | |
| else | |
| warn "vncserver-x11-serviced unit not found — VNC may need manual configuration." | |
| fi | |
| # Enable VNC via raspi-config (works on Raspberry Pi OS) | |
| if command -v raspi-config &>/dev/null; then | |
| info "Enabling VNC via raspi-config…" | |
| sudo raspi-config nonint do_vnc 0 # 0 = enable | |
| success "VNC enabled via raspi-config." | |
| else | |
| warn "raspi-config not found — skipping automated VNC enable." | |
| info "If running Raspberry Pi OS Desktop, you can enable VNC manually:" | |
| info " Preferences → Raspberry Pi Configuration → Interfaces → VNC: Enabled" | |
| fi | |
| # Display connection info | |
| PI_IP=$(hostname -I | awk '{print $1}') | |
| echo | |
| echo -e "${BOLD}VNC connection details:${RESET}" | |
| echo -e " Address : ${CYAN}${PI_IP}:5900${RESET} (or use hostname: ${CYAN}$(hostname).local:5900${RESET})" | |
| echo -e " Client : RealVNC Viewer — ${CYAN}https://www.realvnc.com/en/connect/download/viewer/${RESET}" | |
| echo -e " Login : your Pi username / password" | |
| echo | |
| fi # --vnc | |
| # ============================================================================= | |
| # 9. Summary | |
| # ============================================================================= | |
| section "Setup complete!" | |
| echo -e "${GREEN}${BOLD}" | |
| echo " ✔ nvm $(nvm --version 2>/dev/null || echo '(reload shell)')" | |
| echo " ✔ Node.js $(node --version 2>/dev/null || echo '(reload shell)')" | |
| echo " ✔ npm $(npm --version 2>/dev/null || echo '(reload shell)')" | |
| echo " ✔ uv $(uv --version 2>/dev/null || echo '(reload shell)')" | |
| echo " ✔ git $(git --version)" | |
| echo " ✔ SSH key $SSH_KEY_FILE" | |
| if [[ "$INSTALL_VNC" == true ]]; then | |
| echo " ✔ VNC enabled (port 5900)" | |
| fi | |
| echo -e "${RESET}" | |
| warn "Reload your shell (or run ${BOLD}source ~/.bashrc${RESET} ) to activate all PATH changes." | |
| echo |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
curl -s https://gist.githubusercontent.com/chrisdc/eba88d1216304fd2eac6dfc1e2e0935c/raw/setup.sh | bash