feat: add windows support + add idempotent installer script

This commit is contained in:
2026-04-27 16:22:00 +03:00
parent 150e367ac2
commit 824bc315fc
12 changed files with 382 additions and 19 deletions

View File

@@ -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

View File

@@ -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
# ==============================================================================

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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
{

View File

@@ -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

View File

@@ -15,6 +15,7 @@ brew
node_modules
pnpm-lock\.yaml
README\.md
install\.sh
\.eslintignore
\.shellcheckrc
\.config/direnv/lib

View File

@@ -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

View File

@@ -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"

View File

@@ -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" ;;

259
install.sh Executable file
View File

@@ -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 "$@"