diff --git a/.config/alacritty/alacritty.toml b/.config/alacritty/alacritty.toml index 9c93aa4d..fbc1a82d 100644 --- a/.config/alacritty/alacritty.toml +++ b/.config/alacritty/alacritty.toml @@ -12,6 +12,9 @@ option_as_alt = "Both" # Spread extra padding evenly around the terminal content dynamic_padding = true +# Windows: decorations = "Full" (no Transparent), remove option_as_alt. +# To launch WSL: [shell] program = "wsl.exe", args = ["-d", "Ubuntu"] + [window.padding] x = 8 y = 0 @@ -114,6 +117,8 @@ mouse = "Left" mods = "Command" action = "None" +# Windows: replace "Command" with "Control" in all bindings below. + # Key bindings # ============================================================================== # WezTerm bindings diff --git a/.config/ghostty/config b/.config/ghostty/config index dbcb6648..e1ec55cb 100644 --- a/.config/ghostty/config +++ b/.config/ghostty/config @@ -23,6 +23,8 @@ window-padding-x = 8 window-padding-y = 0 window-padding-balance = true +# Windows: command = wsl.exe -d Ubuntu (to launch into WSL) + # ============================================================================== # Font # ============================================================================== diff --git a/.config/lazygit/config.yml b/.config/lazygit/config.yml index bf29a322..0d6dd9ce 100644 --- a/.config/lazygit/config.yml +++ b/.config/lazygit/config.yml @@ -241,6 +241,7 @@ keybinding: update: u bulkMenu: b os: + # WSL: use wslview instead of open openLink: open "$(echo {{link}} | sed 's/\[/\%5B/g; s/\]/\%5D/g')" disableStartupPopups: false customCommands: diff --git a/.config/sofmani.yml b/.config/sofmani.yml index 3d7850b0..c4afd0ee 100644 --- a/.config/sofmani.yml +++ b/.config/sofmani.yml @@ -32,7 +32,7 @@ install: tags: config tmux skip_summary: true platforms: - only: ["macos"] + only: ["macos", "linux"] opts: command: | if ! tmux source-file "$HOME/.config/tmux/conf.tmux"; then @@ -108,7 +108,19 @@ install: only: ["linux"] - name: glab - type: brew + type: group + steps: + - name: glab + type: brew + - name: glab + type: github-release + platforms: + only: ["linux"] + opts: + repository: profclems/glab + strategy: tar + destination: ~/.local/bin + download_filename: glab_{{ .Version }}_Linux_{{ .ArchAlias }}.tar.gz - name: git-config type: shell @@ -580,6 +592,8 @@ install: - name: autoraise type: brew + platforms: + only: ["macos"] post_install: brew services start autoraise post_update: brew services restart autoraise opts: @@ -631,6 +645,8 @@ install: - name: nextcloud-talk type: brew + platforms: + only: ["macos"] check_installed: test -d "/Applications/Nextcloud Talk.app" - name: aseprite @@ -684,6 +700,8 @@ install: - name: fastlane type: brew + platforms: + only: ["macos"] machines: only: ["m1"] @@ -787,11 +805,20 @@ install: - name: claude-code bin_name: claude - type: brew - post_install: xattr -rd com.apple.quarantine $(which claude) - post_update: xattr -rd com.apple.quarantine $(which claude) - opts: - cask: true + type: group + steps: + - name: claude-code + bin_name: claude + type: brew + post_install: xattr -rd com.apple.quarantine $(which claude) + post_update: xattr -rd com.apple.quarantine $(which claude) + opts: + cask: true + - name: "@anthropic-ai/claude-code" + bin_name: claude + type: pnpm + platforms: + only: ["linux"] - name: opencode type: brew diff --git a/.config/tmux/conf.tmux b/.config/tmux/conf.tmux index dddec04e..4f467e04 100644 --- a/.config/tmux/conf.tmux +++ b/.config/tmux/conf.tmux @@ -34,7 +34,11 @@ bind -T session-sort r choose-tree -sZ -O time bind -n C-M-w confirm-before kill-session # Clickable close button in status-left — quits the terminal -bind -n MouseUp1StatusLeft run-shell 'osascript -e "tell application \"System Events\" to keystroke \"q\" using command down"' +if-shell "uname -s | grep -qi darwin" { + bind -n MouseUp1StatusLeft run-shell 'osascript -e "tell application \"System Events\" to keystroke \"q\" using command down"' +} { + bind -n MouseUp1StatusLeft kill-server +} # Clear screen and scrollback bind L clear-history \; send-keys C-l diff --git a/.config/wezterm/wezterm.lua b/.config/wezterm/wezterm.lua index ac831f2e..777531cf 100644 --- a/.config/wezterm/wezterm.lua +++ b/.config/wezterm/wezterm.lua @@ -48,6 +48,9 @@ config.window_background_opacity = 0.85 config.macos_window_background_blur = 30 config.enable_tab_bar = false config.window_decorations = "RESIZE" + +-- Windows: default_domain = "WSL:Ubuntu", win32_system_backdrop = "Acrylic" +-- Windows: replace "CMD" with "CTRL" and "OPT" with "ALT" in key bindings config.mouse_bindings = { -- Disable the 'Down' event of Cmd-Click to avoid weird program behaviors { diff --git a/.local/share/zsh/plugins/local/common/os_utils.zsh b/.local/share/zsh/plugins/local/common/os_utils.zsh index 7b219a29..0b8e7ddf 100755 --- a/.local/share/zsh/plugins/local/common/os_utils.zsh +++ b/.local/share/zsh/plugins/local/common/os_utils.zsh @@ -35,6 +35,12 @@ is_linux() { return $? } +# check if running inside WSL +is_wsl() { + [[ -f /proc/version ]] && grep -qi microsoft /proc/version + return $? +} + # runs all scripts in directory $1 in order # same as run-parts from debian, but for osx if is_mac; then diff --git a/.stow-local-ignore b/.stow-local-ignore index e13c6fd3..3aab9a33 100644 --- a/.stow-local-ignore +++ b/.stow-local-ignore @@ -15,6 +15,7 @@ brew node_modules pnpm-lock\.yaml README\.md +install\.sh \.eslintignore \.shellcheckrc \.config/direnv/lib diff --git a/README.md b/README.md index 4d0fea98..50dea4c9 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,17 @@ Some (but not all) of the plugins/modifications are listed here: ## How to install +### Quick install (recommended) + +```bash +git clone git@github.com:chenasraf/dotfiles.git --depth 1 ~/.dotfiles +~/.dotfiles/install.sh +``` + +The script handles everything: installs Homebrew, zsh, stow, and sofmani; symlinks configs; and runs sofmani to set up all tools. It works on macOS, Linux, and WSL, and is safe to run multiple times. + +### Manual install + 1. Install zsh and [GNU Stow](https://www.gnu.org/software/stow/) ```bash diff --git a/aliases.zsh b/aliases.zsh index b7434cbf..de0c0225 100755 --- a/aliases.zsh +++ b/aliases.zsh @@ -17,6 +17,13 @@ addalias() { source "$HOME/.local/share/zsh/plugins/local/common/os_utils.zsh" +# WSL equivalents for macOS commands +if is_wsl 2>/dev/null; then + alias open="wslview" + alias pbcopy="clip.exe" + alias pbpaste="powershell.exe -command 'Get-Clipboard' | tr -d '\r'" +fi + # navigation alias ".."="cd .." alias "..."="cd ../.." @@ -37,7 +44,11 @@ alias vim="nvim" alias lvim="nvim -c':e#<1'" # output pipes -alias -g C="| pbcopy" +if is_wsl 2>/dev/null; then + alias -g C="| clip.exe" +else + alias -g C="| pbcopy" +fi alias -g H="| head" alias -g T="| tail" alias -g G="| grep -i" @@ -55,11 +66,17 @@ alias -g NE="2> /dev/null" alias -g NUL="> /dev/null 2>&1" alias -g P="2>&1| pygmentize" alias -g J="| jq" -alias to-clipboard="pbcopy" +if is_wsl 2>/dev/null; then + alias to-clipboard="clip.exe" +else + alias to-clipboard="pbcopy" +fi -# architecture -alias arm="arch -arm64" -alias x86="arch -x86_64" +# architecture (macOS only) +if is_mac; then + alias arm="arch -arm64" + alias x86="arch -x86_64" +fi # git alias gdiff="git diff" @@ -135,11 +152,19 @@ alias tls="tx ls -s" # network/ip alias ip4="curl -4 simpip.com --max-time 2 --proto-default https --silent | prepend 'ipv4: '" alias ip6="curl -6 simpip.com --max-time 2 --proto-default https --silent | prepend 'ipv6: '" -alias iplocal="ipconfig getifaddr en0 | prepend 'iplocal: '" +if is_wsl 2>/dev/null; then + alias iplocal="hostname -I | awk '{print \$1}' | prepend 'iplocal: '" +else + alias iplocal="ipconfig getifaddr en0 | prepend 'iplocal: '" +fi alias ip="iplocal; ip4; ip6" # package management -alias pkgupdate="brew update; brew upgrade; brew cleanup; pnpm i -g pnpm; pnpm up -g --latest; sudo \$SHELL -c \"gem update; gem cleanup\"" +if is_wsl 2>/dev/null; then + alias pkgupdate="brew update; brew upgrade; brew cleanup; pnpm i -g pnpm; pnpm up -g --latest" +else + alias pkgupdate="brew update; brew upgrade; brew cleanup; pnpm i -g pnpm; pnpm up -g --latest; sudo \$SHELL -c \"gem update; gem cleanup\"" +fi alias pi="platform_install" alias install-wezterm="brew tap homebrew/cask-versions;brew install --cask wezterm@nightly --force" alias update-wezterm="brew upgrade --cask wezterm-nightly --no-quarantine --greedy-latest" @@ -161,7 +186,11 @@ alias ollama-serve="brew services start ollama" alias geminif="gemini -m gemini-2.5-flash" # android emulator -alias emulator="\$HOME/Library/Android/sdk/emulator/emulator" +if is_wsl 2>/dev/null; then + alias emulator="/mnt/c/Users/\$USER/AppData/Local/Android/Sdk/emulator/emulator.exe" +else + alias emulator="\$HOME/Library/Android/sdk/emulator/emulator" +fi alias pixel9="emulator -avd Pixel_9_API_35" alias pixelwatch="emulator -avd Wear_OS_Small_Round_API_34" @@ -182,8 +211,12 @@ alias lsq="lazysql" # general alias serve="open http://localhost:\${PORT:-3001} & http-server -p \${PORT:-3001}" -alias afk="/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend" -alias unq="sudo xattr -rd com.apple.quarantine" +if is_wsl 2>/dev/null; then + alias afk="rundll32.exe user32.dll,LockWorkStation" +else + alias afk="/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend" + alias unq="sudo xattr -rd com.apple.quarantine" +fi alias sf="search-file" alias fnu="find-up" alias lua="luajit" diff --git a/exports.zsh b/exports.zsh index a84601da..de2fe99c 100755 --- a/exports.zsh +++ b/exports.zsh @@ -19,6 +19,8 @@ if [[ -d "$CFG/lazygit" ]]; then export LAZYGIT_HOME="$CFG/lazygit" elif [[ -d "$HOME/Library/ApplicationSupport/lazygit" ]]; then export LAZYGIT_HOME="$HOME/Library/ApplicationSupport/lazygit" +elif [[ -n "$APPDATA" && -d "$APPDATA/lazygit" ]]; then + export LAZYGIT_HOME="$APPDATA/lazygit" fi # Postgres.app @@ -46,12 +48,17 @@ if [[ -d "/opt/homebrew" ]]; then export PATH="$BREW_HOME/opt/flex/bin:$PATH" export PATH="$BREW_HOME/opt/make/libexec/gnubin:$PATH" fi +elif [[ -d "/home/linuxbrew/.linuxbrew" ]]; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" fi if [[ -d "$HOME/Library/Android/sdk" ]]; then export ANDROID_HOME="$HOME/Library/Android/sdk" export ANDROID_SDK_ROOT="$BREW_HOME/bin" export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH" +elif [[ -d "/mnt/c/Users/$USER/AppData/Local/Android/Sdk" ]]; then + export ANDROID_HOME="/mnt/c/Users/$USER/AppData/Local/Android/Sdk" + export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH" fi # yamllint @@ -117,7 +124,11 @@ if [[ -f $(which npm) ]]; then fi # PNPM -export PNPM_HOME="$HOME/Library/pnpm" +if [[ -d "$HOME/Library/pnpm" ]]; then + export PNPM_HOME="$HOME/Library/pnpm" +else + export PNPM_HOME="$HOME/.local/share/pnpm" +fi case ":$PATH:" in *":$PNPM_HOME:"*) ;; *) export PATH="$PNPM_HOME:$PATH" ;; diff --git a/install.sh b/install.sh new file mode 100755 index 00000000..ff6c4003 --- /dev/null +++ b/install.sh @@ -0,0 +1,259 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================== +# dotfiles installer +# Installs stow + sofmani, symlinks configs, and runs sofmani to set up the rest. +# Safe to run multiple times (idempotent). +# ============================================================================== + +DOTFILES_DIR="$HOME/.dotfiles" +DOTFILES_REPO="git@github.com:chenasraf/dotfiles.git" +SOFMANI_VERSION="latest" +LOG_FILE="${TMPDIR:-/tmp}/dotfiles-install-$(date +%Y%m%d-%H%M%S).log" + +# --- Colors & logging -------------------------------------------------------- + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +BOLD='\033[1m' +RESET='\033[0m' + +log() { printf "${BLUE}[info]${RESET} %s\n" "$*" | tee -a "$LOG_FILE"; } +ok() { printf "${GREEN}[ok]${RESET} %s\n" "$*" | tee -a "$LOG_FILE"; } +warn() { printf "${YELLOW}[warn]${RESET} %s\n" "$*" | tee -a "$LOG_FILE"; } +err() { printf "${RED}[error]${RESET} %s\n" "$*" | tee -a "$LOG_FILE" >&2; } + +run() { + log "$ $*" + if "$@" >>"$LOG_FILE" 2>&1; then + return 0 + else + local rc=$? + err "Command failed (exit $rc): $*" + err "See log for details: $LOG_FILE" + return $rc + fi +} + +# --- Platform detection ------------------------------------------------------- + +detect_platform() { + case "$(uname -s)" in + Darwin) PLATFORM="macos" ;; + Linux) + if grep -qi microsoft /proc/version 2>/dev/null; then + PLATFORM="wsl" + else + PLATFORM="linux" + fi + ;; + *) + err "Unsupported OS: $(uname -s)" + exit 1 + ;; + esac + log "Detected platform: $PLATFORM" +} + +# --- Helpers ------------------------------------------------------------------ + +has() { command -v "$1" >/dev/null 2>&1; } + +ensure_homebrew() { + if has brew; then + ok "Homebrew already installed" + return + fi + + log "Installing Homebrew..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + # Add brew to current session PATH + if [[ "$PLATFORM" == "macos" && -d "/opt/homebrew" ]]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + elif [[ -d "/home/linuxbrew/.linuxbrew" ]]; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + fi + ok "Homebrew installed" +} + +ensure_git() { + if has git; then + ok "git already installed" + return + fi + + log "Installing git..." + case "$PLATFORM" in + macos) run xcode-select --install 2>/dev/null || true ;; + linux | wsl) run sudo apt-get update && run sudo apt-get install -y git ;; + esac + ok "git installed" +} + +ensure_stow() { + if has stow; then + ok "GNU Stow already installed" + return + fi + + log "Installing GNU Stow..." + case "$PLATFORM" in + macos) run brew install stow ;; + linux | wsl) run sudo apt-get update && run sudo apt-get install -y stow ;; + esac + ok "GNU Stow installed" +} + +ensure_sofmani() { + if has sofmani; then + ok "sofmani already installed" + return + fi + + log "Installing sofmani..." + case "$PLATFORM" in + macos) + run brew tap chenasraf/tap + run brew install sofmani + ;; + linux | wsl) + local arch + arch="$(uname -m)" + case "$arch" in + x86_64) arch="amd64" ;; + aarch64 | arm64) arch="arm64" ;; + esac + local tmp + tmp="$(mktemp -d)" + local url="https://github.com/chenasraf/sofmani/releases/latest/download/sofmani-linux-${arch}.tar.gz" + log "Downloading sofmani from $url" + curl -fsSL "$url" | tar -xz -C "$tmp" + mkdir -p "$HOME/.local/bin" + mv "$tmp/sofmani" "$HOME/.local/bin/sofmani" + chmod +x "$HOME/.local/bin/sofmani" + rm -rf "$tmp" + export PATH="$HOME/.local/bin:$PATH" + ;; + esac + ok "sofmani installed" +} + +ensure_zsh() { + if has zsh; then + ok "zsh already installed" + return + fi + + log "Installing zsh..." + case "$PLATFORM" in + macos) run brew install zsh ;; + linux | wsl) run sudo apt-get update && run sudo apt-get install -y zsh ;; + esac + ok "zsh installed" +} + +# --- Clone & stow ------------------------------------------------------------- + +clone_dotfiles() { + if [[ -d "$DOTFILES_DIR/.git" ]]; then + ok "Dotfiles repo already cloned at $DOTFILES_DIR" + return + fi + + if [[ -d "$DOTFILES_DIR" ]]; then + warn "$DOTFILES_DIR exists but is not a git repo" + printf " Overwrite? [y/N] " + read -r answer + if [[ "$answer" != [yY]* ]]; then + err "Aborted. Please move/remove $DOTFILES_DIR and re-run." + exit 1 + fi + rm -rf "$DOTFILES_DIR" + fi + + log "Cloning dotfiles..." + run git clone "$DOTFILES_REPO" --depth 1 "$DOTFILES_DIR" + ok "Dotfiles cloned" +} + +stow_dotfiles() { + log "Symlinking configs with stow..." + cd "$DOTFILES_DIR" + if stow -R -t ~ . >>"$LOG_FILE" 2>&1; then + ok "Configs symlinked" + else + warn "Stow had conflicts — attempting adopt + restow..." + run stow --adopt -t ~ . + run stow -R -t ~ . + ok "Configs symlinked (adopted existing files)" + fi +} + +# --- Run sofmani -------------------------------------------------------------- + +run_sofmani() { + log "Running sofmani to install tools..." + printf "\n${BOLD}This will install all configured tools. Continue? [Y/n]${RESET} " + read -r answer + if [[ "$answer" == [nN]* ]]; then + warn "Skipped sofmani. Run 'sofmani' manually when ready." + return + fi + sofmani -U "$DOTFILES_DIR/.config/sofmani.yml" + ok "sofmani finished" +} + +# --- Set default shell -------------------------------------------------------- + +set_default_shell() { + local current_shell + current_shell="$(basename "$SHELL")" + if [[ "$current_shell" == "zsh" ]]; then + ok "Default shell is already zsh" + return + fi + + printf "\n${BOLD}Change default shell to zsh? [Y/n]${RESET} " + read -r answer + if [[ "$answer" == [nN]* ]]; then + warn "Skipped shell change" + return + fi + + local zsh_path + zsh_path="$(which zsh)" + if ! grep -qF "$zsh_path" /etc/shells 2>/dev/null; then + log "Adding $zsh_path to /etc/shells" + echo "$zsh_path" | sudo tee -a /etc/shells >/dev/null + fi + run chsh -s "$zsh_path" + ok "Default shell set to zsh" +} + +# --- Main --------------------------------------------------------------------- + +main() { + printf "${BOLD}dotfiles installer${RESET}\n" + printf "Log: %s\n\n" "$LOG_FILE" + + detect_platform + ensure_git + clone_dotfiles + ensure_homebrew + ensure_zsh + ensure_stow + stow_dotfiles + ensure_sofmani + run_sofmani + set_default_shell + + printf "\n${GREEN}${BOLD}All done!${RESET}\n" + printf "Start a new zsh session to load your config:\n" + printf " ${BOLD}exec zsh -l${RESET}\n" +} + +main "$@"