Last active
July 18, 2025 14:44
-
-
Save mikestankavich/137742bceb0215265103d947d25f9fa9 to your computer and use it in GitHub Desktop.
Convenience script to bootstrap an ubuntu claude code sandbox
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 | |
| set -euo pipefail | |
| # --- Configurable defaults --- | |
| USER="${1:-ubuntu}" | |
| HOME_DIR=$(getent passwd "$USER" | cut -d: -f6 || true) | |
| GO_VER="go1.24.5.linux-amd64" | |
| NVM_VER="v0.40.3" | |
| SAMBA_CONF="/etc/samba/smb.conf" | |
| if ! id "$USER" >/dev/null 2>&1; then | |
| echo "User $USER does not exist; creating..." >&2 | |
| useradd -m -s /bin/bash -G sudo "$USER" | |
| HOME_DIR=$(getent passwd "$USER" | cut -d: -f6) | |
| # Optionally set a locked password: passwd -l "$USER" | |
| echo "$USER ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/"$USER" | |
| chmod 440 /etc/sudoers.d/"$USER" | |
| fi | |
| if [[ -z "$HOME_DIR" ]] || [[ ! -d "$HOME_DIR" ]]; then | |
| echo "Could not determine home directory for $USER" >&2 | |
| exit 1 | |
| fi | |
| # --- Ensure packages are installed --- | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt-get update -y | |
| apt-get upgrade -y | |
| apt-get install -y \ | |
| avahi-daemon openssh-server curl tree wget samba git build-essential \ | |
| software-properties-common ca-certificates gnupg lsb-release nano direnv \ | |
| python3 python3-pip python3-venv | |
| # --- Configure Samba home share only if needed --- | |
| if ! grep -q "\[${USER}-home\]" "$SAMBA_CONF" 2>/dev/null; then | |
| tee -a "$SAMBA_CONF" > /dev/null <<EOF | |
| [${USER}-home] | |
| path = $HOME_DIR | |
| browseable = yes | |
| writable = yes | |
| valid users = $USER | |
| create mask = 0755 | |
| directory mask = 0755 | |
| EOF | |
| systemctl restart smbd || true | |
| fi | |
| # --- .bashrc patch: only if not already present --- | |
| BASHRC="$HOME_DIR/.bashrc" | |
| if ! grep -q "# direnv hook to auto set env vars" "$BASHRC" 2>/dev/null; then | |
| sudo -u "$USER" tee -a "$BASHRC" > /dev/null <<'EOF' | |
| # direnv hook to auto set env vars on change directory when the destination directory has a .envrc file | |
| eval "$(direnv hook bash)" | |
| # Add user-global NPM path | |
| export PATH=$PATH:~/.npm-global/bin | |
| # Add golang bin path | |
| export PATH=$PATH:/usr/local/go/bin | |
| # Add uv path | |
| export PATH=$PATH:~/.cargo/bin | |
| # First-login Samba password setup | |
| if [ -x "$HOME/.first-login-smbpasswd.sh" ]; then | |
| "$HOME/.first-login-smbpasswd.sh" | |
| fi | |
| EOF | |
| chown "$USER":"$USER" "$BASHRC" | |
| fi | |
| # create samba user | |
| RANDO_PWD=$(openssl rand -base64 20) | |
| (echo "$RANDO_PWD"; echo "$RANDO_PWD") | smbpasswd -a -s $USER | |
| # --- .first-login-smbpasswd.sh creation --- | |
| FIRST_LOGIN_SH="$HOME_DIR/.first-login-smbpasswd.sh" | |
| if [ ! -f "$FIRST_LOGIN_SH" ]; then | |
| sudo -u "$USER" tee "$FIRST_LOGIN_SH" > /dev/null <<'EOS' | |
| #!/usr/bin/env bash | |
| FLAG="$HOME/.smbpasswd_set" | |
| MAX_RETRIES=3 | |
| attempt=1 | |
| if [ ! -f "$FLAG" ]; then | |
| echo | |
| echo "Please choose a Samba password for your account (used for file sharing):" | |
| while [ $attempt -le $MAX_RETRIES ]; do | |
| echo | |
| echo "------------------------------------" | |
| echo "Attempt $attempt of $MAX_RETRIES" | |
| if sudo smbpasswd "$USER"; then | |
| touch "$FLAG" | |
| echo "Samba password set. You won't be prompted again." | |
| exit 0 | |
| else | |
| echo | |
| echo "There was a problem setting your Samba password." | |
| if [ $attempt -lt $MAX_RETRIES ]; then | |
| echo "Would you like to try again? [y/N]" | |
| read -r answer | |
| case "$answer" in | |
| [Yy]* ) : ;; | |
| * ) echo "Skipping Samba password setup. You will be prompted again next time."; exit 1;; | |
| esac | |
| fi | |
| fi | |
| attempt=$((attempt+1)) | |
| done | |
| echo | |
| echo "Maximum attempts reached. Samba password was not set." | |
| echo "You will be prompted to set the password again next time." | |
| fi | |
| EOS | |
| chmod 755 "$FIRST_LOGIN_SH" | |
| chown "$USER":"$USER" "$FIRST_LOGIN_SH" | |
| fi | |
| # --- NPM global directory setup --- | |
| sudo -u "$USER" mkdir -p "$HOME_DIR/.npm-global" | |
| # --- GitHub CLI install (runs as root, standard script installs system-wide) --- | |
| if ! command -v gh >/dev/null 2>&1; then | |
| curl -fsSL https://gist.github.com/mikestankavich/4728909ba36b5142bd59d722210c6f43/raw/install-gh-cli.sh | bash | |
| fi | |
| # --- Node.js (NodeSource) --- | |
| if ! command -v node >/dev/null 2>&1; then | |
| curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - | |
| apt-get install -y nodejs | |
| fi | |
| # --- NVM (Node Version Manager), as user --- | |
| if ! sudo -u "$USER" test -d "$HOME_DIR/.nvm"; then | |
| sudo -u "$USER" bash -c "curl -o- 'https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VER}/install.sh' | bash" | |
| fi | |
| # --- uv (Python), as user to get ~/.cargo/bin available --- | |
| if ! sudo -u "$USER" command -v uv >/dev/null 2>&1; then | |
| sudo -u "$USER" bash -c 'curl -LsSf https://astral.sh/uv/install.sh | sh' | |
| fi | |
| # --- Go install --- | |
| if ! [ -x /usr/local/go/bin/go ] || [[ "$(/usr/local/go/bin/go version 2>/dev/null)" != *"${GO_VER%%.*}"* ]]; then | |
| curl -fsSL "https://go.dev/dl/${GO_VER}.tar.gz" | tar -C /usr/local -xz | |
| fi | |
| # --- Set NPM user-global prefix and install pnpm/claude-code as user --- | |
| sudo -u "$USER" bash -c " | |
| npm config set prefix '$HOME_DIR/.npm-global' | |
| if ! command -v pnpm >/dev/null 2>&1 || ! npm list -g | grep -q '@anthropic-ai/claude-code'; then | |
| npm install -g pnpm @anthropic-ai/claude-code | |
| fi | |
| " | |
| echo "" | |
| echo "Bootstrap complete for user $USER." | |
| echo "You will be prompted for a Samba password on first login." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment