Skip to content

Instantly share code, notes, and snippets.

@johnny-official
Last active August 26, 2026 10:07
Show Gist options
  • Select an option

  • Save johnny-official/81031f044d2f5df0198e073aa75a11ce to your computer and use it in GitHub Desktop.

Select an option

Save johnny-official/81031f044d2f5df0198e073aa75a11ce to your computer and use it in GitHub Desktop.
setup-ide.sh
#!/usr/bin/env bash
# Recommended: download first, then execute (safer than curl | bash):
# curl -fL "https://gist.githubusercontent.com/johnny-official/81031f044d2f5df0198e073aa75a11ce/raw/gistfile1.txt?v=$(date +%s)" -o /tmp/johnny-dev-bootstrap.sh \
# && bash /tmp/johnny-dev-bootstrap.sh
set -u
VERSION="2026.08.26"
# Optional controls:
# JOHNNY_INSTALL_SHELL_TOOLS=0 -> skip apt package installation
# JOHNNY_INSTALL_OH_MY_ZSH=0 -> skip Oh My Zsh installation
INSTALL_SHELL_TOOLS="${JOHNNY_INSTALL_SHELL_TOOLS:-1}"
INSTALL_OH_MY_ZSH="${JOHNNY_INSTALL_OH_MY_ZSH:-1}"
# Do not run the entire bootstrap through sudo. The script elevates only the
# package-manager commands that actually require root privileges.
if [ "$(id -u)" -eq 0 ] \n && [ -n "${SUDO_USER:-}" ] \n && [ "${SUDO_USER:-}" != "root" ]
then
printf '[ERROR] Run this bootstrap as your normal user, not with sudo.\n' >&2
printf '[ERROR] The script will use sudo only when packages need installing.\n' >&2
exit 1
fi
HOME_DIR="${HOME:-}"
if [ -z "$HOME_DIR" ]; then
HOME_DIR="$(getent passwd "$(id -u)" 2>/dev/null | cut -d: -f6)"
fi
if [ -z "$HOME_DIR" ] || [ ! -d "$HOME_DIR" ]; then
printf '[ERROR] Cannot determine HOME directory.\n' >&2
exit 1
fi
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME_DIR/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME_DIR/.local/share}"
TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t johnny-dev)"
SETTINGS_TEMPLATE="$TMP_DIR/settings.json"
trap 'rm -rf "$TMP_DIR"' EXIT INT TERM
ok() {
printf '[OK] %s\n' "$*"
}
info() {
printf '[INFO] %s\n' "$*"
}
warn() {
printf '[WARN] %s\n' "$*" >&2
}
printf '\n'
printf '========================================\n'
printf ' Johnny Dev Bootstrap %s\n' "$VERSION"
printf '========================================\n'
printf '\n'
printf 'User : %s\n' "$(id -un 2>/dev/null || printf unknown)"
printf 'Home : %s\n' "$HOME_DIR"
printf 'Shell: %s\n' "${SHELL:-unknown}"
printf '\n'
# =============================================================================
# 1. COMMON EDITOR SETTINGS
# =============================================================================
cat > "$SETTINGS_TEMPLATE" <<'JSON'
{
"php.suggest.basic": false,
"java.errors.incompleteClasspath.severity": "ignore",
"liveServer.settings.donotShowInfoMsg": true,
"editor.mouseWheelZoom": true,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": false,
"editor.minimap.enabled": false,
"editor.stickyScroll.enabled": true,
"editor.smoothScrolling": true,
"editor.cursorSmoothCaretAnimation": "on",
"editor.cursorBlinking": "smooth",
"editor.lineNumbers": "on",
"editor.renderWhitespace": "selection",
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.guides.indentation": true,
"editor.guides.highlightActiveIndentation": true,
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.detectIndentation": true,
"editor.trimAutoWhitespace": true,
"editor.largeFileOptimizations": true,
"editor.linkedEditing": true,
"editor.hover.delay": 300,
"files.autoSave": "off",
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/**": true,
"**/__pycache__/**": true,
"**/.pytest_cache/**": true,
"**/.ruff_cache/**": true,
"**/.mypy_cache/**": true,
"**/.venv/**": true,
"**/venv/**": true,
"**/dist/**": true,
"**/build/**": true,
"**/.next/**": true,
"**/coverage/**": true
},
"search.exclude": {
"**/.git": true,
"**/node_modules": true,
"**/__pycache__": true,
"**/.pytest_cache": true,
"**/.ruff_cache": true,
"**/.mypy_cache": true,
"**/.venv": true,
"**/venv": true,
"**/dist": true,
"**/build": true,
"**/.next": true,
"**/coverage": true
},
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.formatOnPaste": false,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
}
},
"[javascript]": {
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[javascriptreact]": {
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[typescript]": {
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[json]": {
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[html]": {
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[css]": {
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[scss]": {
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[php]": {
"editor.tabSize": 4,
"editor.formatOnSave": true
}
}
JSON
# =============================================================================
# 2. MERGE SETTINGS SAFELY
# =============================================================================
merge_settings() {
target="$1"
mkdir -p "$(dirname "$target")" 2>/dev/null || {
warn "Cannot create: $(dirname "$target")"
return 1
}
# New file
if [ ! -s "$target" ]; then
cp "$SETTINGS_TEMPLATE" "$target" 2>/dev/null || {
warn "Cannot write: $target"
return 1
}
ok "Settings -> $target"
return 0
fi
# Keep one backup
backup="$target.johnny-backup"
if [ ! -e "$backup" ]; then
cp "$target" "$backup" 2>/dev/null || true
fi
# Merge existing JSON if Python is available
if command -v python3 >/dev/null 2>&1; then
if python3 - "$target" "$SETTINGS_TEMPLATE" 2>/dev/null <<'PY'
import json
import sys
from pathlib import Path
old_path = Path(sys.argv[1])
new_path = Path(sys.argv[2])
with old_path.open(encoding="utf-8") as f:
old = json.load(f)
with new_path.open(encoding="utf-8") as f:
new = json.load(f)
def merge(a, b):
for key, value in b.items():
if (
isinstance(value, dict)
and isinstance(a.get(key), dict)
):
merge(a[key], value)
else:
a[key] = value
return a
merged = merge(old, new)
tmp = old_path.with_suffix(
old_path.suffix + ".johnny-tmp"
)
with tmp.open(
"w",
encoding="utf-8"
) as f:
json.dump(
merged,
f,
indent=4,
ensure_ascii=False
)
f.write("\n")
tmp.replace(old_path)
PY
then
ok "Merged settings -> $target"
return 0
fi
warn "Existing settings is JSONC/non-standard:"
warn "$target"
warn "Backup saved -> $backup"
if cp "$SETTINGS_TEMPLATE" "$target" 2>/dev/null; then
ok "Replaced settings -> $target"
return 0
fi
return 1
fi
# No Python available
warn "python3 not found."
warn "Backup saved -> $backup"
if cp "$SETTINGS_TEMPLATE" "$target" 2>/dev/null; then
ok "Settings -> $target"
return 0
fi
return 1
}
# =============================================================================
# 3. APPLY TO COMMON IDE LOCATIONS
# =============================================================================
info "Applying editor settings..."
SETTINGS_PATHS="
$HOME_DIR/.theia/settings.json
$HOME_DIR/.theia-blueprint/settings.json
$XDG_CONFIG_HOME/Code/User/settings.json
$XDG_CONFIG_HOME/Code - Insiders/User/settings.json
$XDG_CONFIG_HOME/VSCodium/User/settings.json
$XDG_DATA_HOME/code-server/User/settings.json
$HOME_DIR/.vscode-remote/data/Machine/settings.json
$HOME_DIR/.openvscode-server/data/Machine/settings.json
"
settings_ok=0
while IFS= read -r target
do
[ -n "$target" ] || continue
if merge_settings "$target"; then
settings_ok=$((settings_ok + 1))
fi
done <<EOF
$SETTINGS_PATHS
EOF
# =============================================================================
# 4. SHELL TOOLING + SAFE PRIVILEGE ESCALATION
# =============================================================================
run_privileged() {
if [ "$(id -u)" -eq 0 ]; then
"$@"
return $?
fi
if command -v sudo >/dev/null 2>&1; then
sudo "$@"
return $?
fi
warn "Root privileges are required for: $*"
warn "sudo was not found; skipping this privileged step."
return 1
}
install_shell_dependencies() {
if [ "$INSTALL_SHELL_TOOLS" != "1" ]; then
info "Shell package installation disabled by JOHNNY_INSTALL_SHELL_TOOLS=$INSTALL_SHELL_TOOLS"
return 0
fi
if ! command -v apt-get >/dev/null 2>&1; then
warn "apt-get not found; skipping automatic installation of zsh/git/curl/bash-completion."
return 0
fi
packages=()
command -v zsh >/dev/null 2>&1 || packages+=(zsh)
command -v git >/dev/null 2>&1 || packages+=(git)
command -v curl >/dev/null 2>&1 || packages+=(curl)
if [ ! -r /usr/share/bash-completion/bash_completion ] \
&& [ ! -r /etc/bash_completion ]
then
packages+=(bash-completion)
fi
if [ "${#packages[@]}" -eq 0 ]; then
ok "Shell dependencies already installed"
return 0
fi
info "Installing shell dependencies: ${packages[*]}"
if ! run_privileged apt-get update; then
warn "apt-get update failed; shell dependency installation was skipped."
return 1
fi
if ! run_privileged env DEBIAN_FRONTEND=noninteractive \
apt-get install -y "${packages[@]}"
then
warn "Could not install one or more shell dependencies."
return 1
fi
ok "Shell dependencies installed"
}
install_oh_my_zsh() {
omz_dir="$HOME_DIR/.oh-my-zsh"
if [ "$INSTALL_OH_MY_ZSH" != "1" ]; then
info "Oh My Zsh installation disabled by JOHNNY_INSTALL_OH_MY_ZSH=$INSTALL_OH_MY_ZSH"
return 0
fi
if [ -r "$omz_dir/oh-my-zsh.sh" ] && [ -d "$omz_dir/.git" ]; then
ok "Oh My Zsh already installed -> $omz_dir"
return 0
fi
if [ -e "$omz_dir" ]; then
warn "Path exists but does not look like a valid Oh My Zsh installation:"
warn "$omz_dir"
warn "Leaving it untouched for safety."
return 1
fi
if ! command -v git >/dev/null 2>&1; then
warn "git is unavailable; cannot install Oh My Zsh."
return 1
fi
info "Installing Oh My Zsh from the official Git repository..."
omz_tmp="$TMP_DIR/oh-my-zsh"
if git clone --depth=1 \
https://github.com/ohmyzsh/ohmyzsh.git \
"$omz_tmp" \
&& mv "$omz_tmp" "$omz_dir"
then
ok "Oh My Zsh -> $omz_dir"
return 0
fi
warn "Oh My Zsh installation failed; no existing config was overwritten."
return 1
}
install_shell_dependencies || true
install_oh_my_zsh || true
# =============================================================================
# 5. MANAGED SHELL CONFIGURATION
# =============================================================================
strip_managed_block() {
rc="$1"
[ -f "$rc" ] || : > "$rc"
backup="$rc.johnny-backup"
if [ -s "$rc" ] && [ ! -e "$backup" ]; then
cp -p "$rc" "$backup" 2>/dev/null \
|| warn "Could not create shell backup: $backup"
fi
begin_count="$(grep -c '^### JOHNNY_DEV_BEGIN$' "$rc" 2>/dev/null || true)"
end_count="$(grep -c '^### JOHNNY_DEV_END$' "$rc" 2>/dev/null || true)"
if [ "$begin_count" -ne "$end_count" ]; then
warn "Managed shell markers are unbalanced in: $rc"
warn "Leaving the file untouched. Backup (if needed): $backup"
return 1
fi
tmp="$TMP_DIR/$(basename "$rc").clean"
if ! awk '
$0 == "### JOHNNY_DEV_BEGIN" {
skip=1
next
}
$0 == "### JOHNNY_DEV_END" {
skip=0
next
}
!skip {
print
}
' "$rc" > "$tmp"
then
warn "Could not parse shell config: $rc"
return 1
fi
if ! cat "$tmp" > "$rc"; then
warn "Could not update shell config: $rc"
return 1
fi
return 0
}
# =============================================================================
# 6. BASH
# =============================================================================
configure_bash() {
rc="$HOME_DIR/.bashrc"
strip_managed_block "$rc" || return 1
cat >> "$rc" <<'BASHRC'
### JOHNNY_DEV_BEGIN
# -----------------------------------------------------------------------------
# PATH
# -----------------------------------------------------------------------------
export PATH="$HOME/.local/bin:$PATH"
# -----------------------------------------------------------------------------
# PROMPT
# Example: [user: project]$
# -----------------------------------------------------------------------------
export PS1="[\[\033[01;32m\]\u\[\033[00m\]: \[\033[01;34m\]\W\[\033[00m\]]\$ "
# -----------------------------------------------------------------------------
# HISTORY
# -----------------------------------------------------------------------------
export HISTSIZE=5000
export HISTFILESIZE=10000
export HISTCONTROL=ignoreboth:erasedups
shopt -s histappend 2>/dev/null || true
# -----------------------------------------------------------------------------
# COMMON ALIASES
# -----------------------------------------------------------------------------
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias ..='cd ..'
alias ...='cd ../..'
alias c='clear'
alias cls='clear'
# -----------------------------------------------------------------------------
# KUBERNETES CLI
# -----------------------------------------------------------------------------
alias k=kubectl
# Load distro-provided bash-completion when available.
if [ -r /usr/share/bash-completion/bash_completion ]; then
. /usr/share/bash-completion/bash_completion
elif [ -r /etc/bash_completion ]; then
. /etc/bash_completion
fi
# kubectl completion is enabled only when kubectl exists.
if command -v kubectl >/dev/null 2>&1; then
source <(kubectl completion bash 2>/dev/null) || true
if declare -F __start_kubectl >/dev/null 2>&1; then
complete -o default -F __start_kubectl k 2>/dev/null || true
fi
fi
### JOHNNY_DEV_END
BASHRC
ok "Bash -> $rc"
}
configure_bash || true
# =============================================================================
# 7. ZSH + OH MY ZSH
# =============================================================================
configure_zsh() {
rc="$HOME_DIR/.zshrc"
strip_managed_block "$rc" || return 1
# Respect an existing user-managed Oh My Zsh source line. If none exists,
# the managed block will source the installation created above.
omz_already_sourced=0
if grep -Eq '^[[:space:]]*(source|\.)[[:space:]].*oh-my-zsh\.sh' "$rc" 2>/dev/null; then
omz_already_sourced=1
fi
cat >> "$rc" <<'ZSHRC'
### JOHNNY_DEV_BEGIN
# -----------------------------------------------------------------------------
# PATH
# -----------------------------------------------------------------------------
export PATH="$HOME/.local/bin:$PATH"
ZSHRC
if [ "$omz_already_sourced" -eq 0 ]; then
cat >> "$rc" <<'ZSHRC'
# -----------------------------------------------------------------------------
# OH MY ZSH
# -----------------------------------------------------------------------------
if [ -r "$HOME/.oh-my-zsh/oh-my-zsh.sh" ]; then
export ZSH="$HOME/.oh-my-zsh"
source "$ZSH/oh-my-zsh.sh"
fi
ZSHRC
fi
cat >> "$rc" <<'ZSHRC'
# -----------------------------------------------------------------------------
# COMPLETION
# -----------------------------------------------------------------------------
# Ensure compdef exists even when Oh My Zsh is not loaded.
if ! whence compdef >/dev/null 2>&1; then
autoload -Uz compinit
compinit -i
fi
# -----------------------------------------------------------------------------
# KUBERNETES CLI
# -----------------------------------------------------------------------------
alias k=kubectl
if command -v kubectl >/dev/null 2>&1; then
source <(kubectl completion zsh 2>/dev/null) || true
if whence compdef >/dev/null 2>&1; then
compdef __start_kubectl k 2>/dev/null || true
fi
fi
# -----------------------------------------------------------------------------
# PROMPT
# Example: [user: project]$
# Keep this after Oh My Zsh so the bootstrap prompt wins over a theme.
# -----------------------------------------------------------------------------
PROMPT='[%F{green}%n%f: %F{blue}%1~%f]$ '
# -----------------------------------------------------------------------------
# HISTORY
# -----------------------------------------------------------------------------
HISTFILE="$HOME/.zsh_history"
HISTSIZE=5000
SAVEHIST=10000
setopt APPEND_HISTORY 2>/dev/null || true
setopt HIST_IGNORE_ALL_DUPS 2>/dev/null || true
# -----------------------------------------------------------------------------
# COMMON ALIASES
# -----------------------------------------------------------------------------
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias ..='cd ..'
alias ...='cd ../..'
alias c='clear'
alias cls='clear'
### JOHNNY_DEV_END
ZSHRC
ok "Zsh -> $rc"
}
if command -v zsh >/dev/null 2>&1; then
configure_zsh || true
else
warn "zsh is unavailable; .zshrc was not changed."
fi
# =============================================================================
# 8. EXTENSIONS
# =============================================================================
EXTENSIONS="
charliermarsh.ruff
oderwat.indent-rainbow
"
extension_ok=0
extension_fail=0
extension_cli_summary="none"
# =============================================================================
# 9. TRY ALL SUPPORTED EXTENSION CLIs
# =============================================================================
try_install_extension() {
ext="$1"
# -------------------------------------------------------------------------
# VS Code / code-server / VSCodium / OpenVSCode Server
# -------------------------------------------------------------------------
for candidate in \
code \
code-server \
codium \
openvscode-server
do
if ! command -v "$candidate" >/dev/null 2>&1; then
continue
fi
cli="$(command -v "$candidate")"
info "Trying $candidate for $ext"
# Already installed?
if "$cli" --list-extensions 2>/dev/null \
| grep -Fxiq "$ext"
then
ok "Extension already installed via $candidate: $ext"
extension_cli_summary="$candidate"
return 0
fi
# Install
if "$cli" \
--install-extension "$ext" \
>/dev/null 2>&1
then
ok "Installed via $candidate: $ext"
extension_cli_summary="$candidate"
return 0
fi
done
# -------------------------------------------------------------------------
# Theia CLI
# -------------------------------------------------------------------------
if command -v theia >/dev/null 2>&1 \
&& theia --help 2>&1 \
| grep -q -- '--install-plugin'
then
cli="$(command -v theia)"
info "Trying Theia for $ext"
if "$cli" \
--install-plugin "$ext" \
>/dev/null 2>&1
then
ok "Installed via Theia: $ext"
extension_cli_summary="theia"
return 0
fi
fi
return 1
}
# =============================================================================
# 10. INSTALL EXTENSIONS
# =============================================================================
installer_found=0
for candidate in \
code \
code-server \
codium \
openvscode-server \
theia
do
if command -v "$candidate" >/dev/null 2>&1; then
installer_found=1
break
fi
done
for ext in $EXTENSIONS
do
if try_install_extension "$ext"; then
extension_ok=$((extension_ok + 1))
else
warn "Could not auto-install extension: $ext"
extension_fail=$((extension_fail + 1))
fi
done
if [ "$installer_found" -eq 0 ]; then
warn "No supported extension CLI found."
warn "Editor settings and terminal were still configured."
warn "Extensions may need to be installed from the IDE Extensions UI."
fi
# =============================================================================
# 11. FINAL REPORT
# =============================================================================
printf '\n'
printf '========================================\n'
printf ' Setup complete\n'
printf '========================================\n'
printf '\n'
printf 'Settings targets : %s written/merged\n' \
"$settings_ok"
printf 'Shell : configured\n'
if [ "$extension_cli_summary" != "none" ]; then
printf 'Extension CLI : %s\n' \
"$extension_cli_summary"
else
printf 'Extension CLI : unavailable/failed\n'
fi
printf 'Extensions : %s OK, %s failed\n' \
"$extension_ok" \
"$extension_fail"
printf '\n'
printf 'Configured editor behavior:\n'
printf ' - Ctrl + Mouse Wheel zoom\n'
printf ' - Ctrl+S saves + formats when formatter exists\n'
printf ' - Ruff format/fix/import on save when Ruff extension exists\n'
printf ' - Minimap disabled\n'
printf ' - Sticky scroll enabled\n'
printf ' - Smooth scrolling enabled\n'
printf ' - Bracket and indentation guides enabled\n'
printf ' - Dependency/cache folders excluded from watchers\n'
printf ' - Dependency/cache folders excluded from search\n'
printf ' - Trailing whitespace removed on save\n'
printf ' - Final newline automatically inserted\n'
printf '\n'
printf 'Terminal:\n'
printf ' [user: current-folder]$\n'
printf '\n'
printf 'If the currently-open browser IDE does not hot-reload settings,\n'
printf 'refresh the browser tab once.\n'
printf '\n'
printf 'Load the updated shell config now with:\n'
printf ' Bash: exec bash\n'
if command -v zsh >/dev/null 2>&1; then
printf ' Zsh : exec zsh\n'
printf '\n'
printf 'The default login shell was NOT changed automatically.\n'
printf 'If you intentionally want Zsh as the default, run once:\n'
printf ' chsh -s "$(command -v zsh)"\n'
fi
printf '\n'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment