diff --git a/README.md b/README.md index 19a99db..eb48c65 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ 2. updates system system, and install many useful packages for me +- new-ubuntu-open-claw.sh: + + 1. non-interactively bootstrap a fresh Ubuntu laptop for OpenClaw-oriented use + + 2. installs core packages/tools, OpenClaw, Docker, Chrome, VS Code, CopyQ tweaks, very large bash history, and post-OpenClaw npm CLIs like Codex / Claude Code / Gemini CLI while skipping desktop-session-only steps when no GUI session is active + - install_flutter.sh 1. install flutter, and android-studio, and configure some system things diff --git a/new-ubuntu-open-claw.sh b/new-ubuntu-open-claw.sh new file mode 100755 index 0000000..19ef3ac --- /dev/null +++ b/new-ubuntu-open-claw.sh @@ -0,0 +1,373 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Fast Ubuntu bootstrap for a future OpenClaw laptop. +# It installs packages and configures the local user environment, +# but does not intentionally reboot the machine. + +log() { + printf '\n[%s] %s\n' "$(date '+%F %T')" "$*" +} + +warn() { + printf '\n[WARN] %s\n' "$*" >&2 +} + +require_non_root() { + if [ "${EUID:-$(id -u)}" -eq 0 ]; then + echo "Run this script as the regular desktop user. It uses sudo internally and writes user-scoped config." >&2 + exit 1 + fi +} + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "Missing required command: $1" >&2 + exit 1 + } +} + +apt_pkg_installed() { + dpkg -s "$1" >/dev/null 2>&1 +} + +has_graphical_session() { + [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ] +} + +has_session_bus() { + [ -n "${DBUS_SESSION_BUS_ADDRESS:-}" ] +} + +has_user_systemd_session() { + [ -n "${XDG_RUNTIME_DIR:-}" ] && systemctl --user show-environment >/dev/null 2>&1 +} + +install_apt_packages() { + local missing=() + for pkg in "$@"; do + apt_pkg_installed "$pkg" || missing+=("$pkg") + done + + if ((${#missing[@]} == 0)); then + log "APT packages already installed." + return + fi + + log "Installing APT packages: ${missing[*]}" + sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y "${missing[@]}" +} + +ensure_apt_keyring_dir() { + sudo install -d -m 0755 /etc/apt/keyrings +} + +ensure_block_in_file() { + local marker="$1" + local file="$2" + local content="$3" + touch "$file" + if ! grep -Fq "$marker" "$file" 2>/dev/null; then + printf '\n%s\n' "$content" >> "$file" + fi +} + +configure_vscode_repo() { + if [ -f /etc/apt/sources.list.d/vscode.list ]; then + log "VS Code APT repo already configured." + return + fi + + log "Configuring VS Code APT repository" + ensure_apt_keyring_dir + curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \ + | gpg --dearmor \ + | sudo tee /etc/apt/keyrings/packages.microsoft.gpg >/dev/null + sudo chmod go+r /etc/apt/keyrings/packages.microsoft.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" \ + | sudo tee /etc/apt/sources.list.d/vscode.list >/dev/null +} + +configure_google_chrome_repo() { + if [ -f /etc/apt/sources.list.d/google-chrome.list ]; then + log "Google Chrome APT repo already configured." + return + fi + + log "Configuring Google Chrome APT repository" + ensure_apt_keyring_dir + curl -fsSL https://dl.google.com/linux/linux_signing_key.pub \ + | gpg --dearmor \ + | sudo tee /etc/apt/keyrings/google-chrome.gpg >/dev/null + sudo chmod go+r /etc/apt/keyrings/google-chrome.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/google-chrome.gpg] https://dl.google.com/linux/chrome/deb/ stable main" \ + | sudo tee /etc/apt/sources.list.d/google-chrome.list >/dev/null +} + +configure_docker_repo() { + if [ -f /etc/apt/sources.list.d/docker.list ]; then + log "Docker APT repo already configured." + return + fi + + log "Configuring Docker APT repository" + ensure_apt_keyring_dir + 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 + . /etc/os-release + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${VERSION_CODENAME} stable" \ + | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null +} + +apt_update() { + log "Running apt-get update" + sudo apt-get update +} + +install_base_packages() { + install_apt_packages \ + apt-transport-https \ + apache2-utils \ + ca-certificates \ + copyq \ + curl \ + gdebi \ + gnome-shell-extensions \ + gnupg \ + htop \ + lm-sensors \ + ncdu \ + net-tools \ + nmap \ + p7zip-full \ + python3-pip \ + python3-venv \ + wget \ + whois +} + +install_docker_packages() { + install_apt_packages \ + containerd.io \ + docker-buildx-plugin \ + docker-ce \ + docker-ce-cli \ + docker-compose-plugin +} + +ensure_docker_group_membership() { + if id -nG "$USER" | tr ' ' '\n' | grep -qx docker; then + log "User already in docker group." + else + log "Adding $USER to docker group" + sudo usermod -aG docker "$USER" + warn "Docker group updated. Log out/in or reboot later to use Docker without sudo." + fi +} + +install_openclaw() { + if command -v openclaw >/dev/null 2>&1; then + log "OpenClaw already installed." + return + fi + + log "Installing OpenClaw with the official non-interactive npm flow" + curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh \ + | bash -s -- --install-method npm --no-prompt --no-onboard +} + +refresh_shell_path() { + export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" + + if [ -f "$HOME/.bashrc" ]; then + # shellcheck disable=SC1090 + . "$HOME/.bashrc" || true + fi + + hash -r +} + +ensure_npm_after_openclaw() { + if command -v npm >/dev/null 2>&1; then + return + fi + + warn "npm is still unavailable after the OpenClaw installer. Falling back to Node.js 24 from NodeSource." + curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - + apt_update + install_apt_packages nodejs + refresh_shell_path +} + +configure_npm_global_prefix() { + if ! command -v npm >/dev/null 2>&1; then + warn "npm is still unavailable; skipping npm prefix configuration." + return + fi + + mkdir -p "$HOME/.npm-global/bin" + if [ "$(npm config get prefix 2>/dev/null || true)" != "$HOME/.npm-global" ]; then + log "Configuring a user-writable npm global prefix at $HOME/.npm-global" + npm config set prefix "$HOME/.npm-global" + fi + + refresh_shell_path +} + +install_npm_cli() { + local pkg="$1" + local label="$2" + + if ! command -v npm >/dev/null 2>&1; then + warn "npm not found yet; skipping $label for now. Re-run after OpenClaw install finishes provisioning Node/npm." + return + fi + + if npm list -g --depth=0 "$pkg" >/dev/null 2>&1; then + log "$label already installed globally." + return + fi + + log "Installing $label ($pkg)" + npm install -g "$pkg" +} + +configure_copyq() { + if ! command -v copyq >/dev/null 2>&1; then + warn "CopyQ not installed; skipping configuration." + return + fi + + log "Configuring CopyQ" + if has_graphical_session && has_session_bus; then + copyq config autostart true || true + copyq config maxitems 20000 || true + else + warn "No graphical user session detected; writing CopyQ autostart only." + fi + + mkdir -p "$HOME/.config/autostart" + cat > "$HOME/.config/autostart/copyq.desktop" <<'EOF' +[Desktop Entry] +Type=Application +Name=CopyQ +Exec=copyq +Hidden=false +NoDisplay=false +X-GNOME-Autostart-enabled=true +Comment=Start CopyQ on login +EOF +} + +configure_bash_history() { + local bashrc="$HOME/.bashrc" + ensure_block_in_file '# OPENCLAW_BASH_HISTORY_TUNING' "$bashrc" "$(cat <<'EOF' + +# OPENCLAW_BASH_HISTORY_TUNING +export HISTSIZE=1000000 +export HISTFILESIZE=2000000 +export HISTCONTROL=ignoredups:erasedups +shopt -s histappend +export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" +EOF +)" +} + +configure_no_lid_sleep_and_lock() { + if command -v gsettings >/dev/null 2>&1; then + if has_graphical_session && has_session_bus; then + log "Configuring GNOME to stay awake/unlocked with lid closed" + gsettings set org.gnome.settings-daemon.plugins.power lid-close-ac-action 'nothing' || true + gsettings set org.gnome.settings-daemon.plugins.power lid-close-battery-action 'nothing' || true + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' || true + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 0 || true + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing' || true + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout 0 || true + gsettings set org.gnome.desktop.screensaver lock-enabled false || true + gsettings set org.gnome.desktop.screensaver idle-activation-enabled false || true + gsettings set org.gnome.desktop.screensaver ubuntu-lock-on-suspend false || true + gsettings set org.gnome.desktop.lockdown disable-lock-screen true || true + else + warn "No graphical user session detected; skipping live GNOME gsettings updates." + fi + else + warn "gsettings unavailable; skipping GNOME lid/lock configuration." + fi + + mkdir -p "$HOME/.config/systemd/user" + cat > "$HOME/.config/systemd/user/no-lid-sleep.service" <<'EOF' +[Unit] +Description=Keep laptop running unlocked when lid is closed +After=graphical-session.target + +[Service] +Type=simple +ExecStart=/usr/bin/systemd-inhibit --what=handle-lid-switch:sleep --mode=block --why=Keep_laptop_awake_and_unlocked_with_lid_closed /usr/bin/sleep infinity +Restart=always +RestartSec=2 + +[Install] +WantedBy=default.target +EOF + if has_user_systemd_session; then + systemctl --user daemon-reload || true + systemctl --user enable --now no-lid-sleep.service || true + else + warn "User systemd session unavailable; wrote no-lid-sleep.service but could not enable it automatically." + fi +} + +print_optional_followups() { + cat <<'EOF' + +Optional/manual follow-ups intentionally left out of automatic installation: +- antigravity (package/source intentionally not assumed here) +- any secrets/API key login steps for OpenClaw, Codex, Claude Code, Gemini CLI, Docker registries, etc. +- root-level /etc/systemd/logind.conf.d/ lid-close policy if you want system-wide enforcement beyond the user session + +Recommended post-run checks: +- openclaw --help +- docker --version +- code --version +- google-chrome --version +- copyq config maxitems +EOF +} + +main() { + require_non_root + need_cmd sudo + + log "Preparing repositories and base tooling" + sudo apt-get update + install_apt_packages ca-certificates curl gnupg wget + + configure_vscode_repo + configure_google_chrome_repo + configure_docker_repo + + apt_update + install_base_packages + install_apt_packages code google-chrome-stable + install_docker_packages + ensure_docker_group_membership + + install_openclaw + refresh_shell_path + ensure_npm_after_openclaw + configure_npm_global_prefix + + install_npm_cli @openai/codex "OpenAI Codex CLI" + install_npm_cli @anthropic-ai/claude-code "Anthropic Claude Code" + install_npm_cli @google/gemini-cli "Google Gemini CLI" + + configure_copyq + configure_bash_history + configure_no_lid_sleep_and_lock + + log "Done. Reboot later if you want docker group membership and desktop autostarts to apply cleanly." + print_optional_followups +} + +main "$@"