/
/
/
My dotfile repo. It sets aliases and installs generic applications for my user.
1#!/bin/bash
2
3export DEBIAN_FRONTEND=noninteractive
4
5home_dir="${HOME:-/home/$(id -un)}"
6temp_dir="${home_dir}/tmp"
7code_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)/.."
8
9local_bin_dir="${home_dir}/.local/bin"
10local_app_dir="${home_dir}/.local/share/applications"
11local_icon_dir="${home_dir}/.local/share/icons/hicolor/apps"
12local_lib_dir="${home_dir}/.local/lib"
13local_opt_dir="${home_dir}/.local/opt"
14font_dir="${home_dir}/.fonts"
15
16mkdir -p "${code_dir}"
17mkdir -p "${local_bin_dir}"
18mkdir -p "${local_app_dir}"
19mkdir -p "${local_icon_dir}"
20mkdir -p "${local_lib_dir}"
21mkdir -p "${temp_dir}"
22mkdir -p "${font_dir}"
23mkdir -p "${local_opt_dir}"
24
25export PATH="${local_bin_dir}:${PATH}"
26
27wget -nc -O "${temp_dir}/nerdfont.zip" \
28 "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/FiraCode.zip"
29
30# Install / reinstall nvim latest stable based on architecture
31arch="$(uname -m)"
32case "$arch" in
33 x86_64)
34 echo "[nvim] Installing latest x86_64 binary..."
35 nvim_asset="nvim-linux-x86_64"
36 ;;
37 aarch64)
38 echo "[nvim] Installing latest ARM64 binary..."
39 nvim_asset="nvim-linux-arm64"
40 ;;
41 *)
42 echo "[nvim] Unsupported architecture: $arch"
43 exit 1
44 ;;
45esac
46
47nvim_tar="${nvim_asset}.tar.gz"
48nvim_url="https://github.com/neovim/neovim/releases/latest/download/${nvim_tar}"
49
50rm -rf "${local_opt_dir}/nvim-linux64"
51rm -rf "${local_opt_dir}/nvim-linux-x86_64"
52rm -rf "${local_opt_dir}/nvim-linux-arm64"
53
54curl -fL -o "${temp_dir}/${nvim_tar}" "${nvim_url}"
55tar xzf "${temp_dir}/${nvim_tar}" -C "${local_opt_dir}/"
56
57ln -sf "${local_opt_dir}/${nvim_asset}/bin/nvim" "${local_bin_dir}/nvim"
58
59"${local_bin_dir}/nvim" --version | head -n 1
60
61unzip -nq "${temp_dir}/nerdfont.zip" -d "${font_dir}"
62fc-cache -f -v
63
64# Install superfile locally, without sudo/password prompts
65arch="$(uname -m)"
66case "$arch" in
67 x86_64) spf_arch="amd64" ;;
68 aarch64) spf_arch="arm64" ;;
69 *)
70 echo "[superfile] Unsupported architecture: $arch"
71 exit 1
72 ;;
73esac
74
75SPF_VERSION="$(
76 curl -fsSL "https://api.github.com/repos/yorukot/superfile/releases/latest" \
77 | grep '"tag_name"' \
78 | head -n1 \
79 | cut -d'"' -f4 \
80 | sed 's/^v//'
81)"
82
83if [ -z "${SPF_VERSION}" ]; then
84 echo "[superfile] Failed to determine latest version"
85 exit 1
86fi
87
88spf_tar="superfile-linux-v${SPF_VERSION}-${spf_arch}.tar.gz"
89spf_url="https://github.com/yorukot/superfile/releases/download/v${SPF_VERSION}/${spf_tar}"
90
91echo "[superfile] downloading ${spf_url}"
92rm -rf "${temp_dir}/superfile"
93mkdir -p "${temp_dir}/superfile"
94curl -fsSL -o "${temp_dir}/${spf_tar}" "${spf_url}"
95tar -xzf "${temp_dir}/${spf_tar}" -C "${temp_dir}/superfile"
96
97spf_bin="$(find "${temp_dir}/superfile" -type f -name spf | head -n 1)"
98if [ -z "${spf_bin}" ]; then
99 echo "[superfile] Could not find extracted spf binary"
100 exit 1
101fi
102
103install -m 0755 "${spf_bin}" "${local_bin_dir}/spf"
104"${local_bin_dir}/spf" --version || true
105
106# Install lazygit locally
107arch="$(uname -m)"
108case "$arch" in
109 x86_64) lazygit_arch="x86_64" ;;
110 aarch64) lazygit_arch="arm64" ;;
111 *)
112 echo "[lazygit] Unsupported architecture: $arch"
113 exit 1
114 ;;
115esac
116
117LAZYGIT_VERSION="$(
118 curl -fsSL "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" \
119 | grep '"tag_name"' \
120 | cut -d'"' -f4 \
121 | sed 's/^v//'
122)"
123
124lazygit_tar="lazygit_${LAZYGIT_VERSION}_Linux_${lazygit_arch}.tar.gz"
125lazygit_url="https://github.com/jesseduffield/lazygit/releases/download/v${LAZYGIT_VERSION}/${lazygit_tar}"
126
127echo "[lazygit] downloading ${lazygit_url}"
128rm -rf "${temp_dir}/lazygit"
129mkdir -p "${temp_dir}/lazygit"
130curl -fsSL -o "${temp_dir}/${lazygit_tar}" "${lazygit_url}"
131tar -xzf "${temp_dir}/${lazygit_tar}" -C "${temp_dir}/lazygit" lazygit
132
133install -m 0755 "${temp_dir}/lazygit/lazygit" "${local_bin_dir}/lazygit"
134"${local_bin_dir}/lazygit" --version
135
136# Clone / refresh source-based tools
137rm -rf "${code_dir}/fzf"
138rm -rf "${code_dir}/powerlevel10k"
139rm -rf "${code_dir}/fri"
140rm -rf "${code_dir}/diff-so-fancy"
141rm -rf "${code_dir}/zoxide"
142rm -rf "${home_dir}/.tmux/plugins/tpm"
143
144git clone --depth=1 -b 0.50.0 https://github.com/junegunn/fzf.git "${code_dir}/fzf"
145git clone --depth=1 -b v1.20.0 https://github.com/romkatv/powerlevel10k.git "${code_dir}/powerlevel10k"
146git clone --depth=1 https://github.com/xuyuan/fri.git "${code_dir}/fri"
147git clone --depth=1 -b v1.4.4 https://github.com/so-fancy/diff-so-fancy.git "${code_dir}/diff-so-fancy"
148git clone --depth=1 -b v0.9.4 https://github.com/ajeetdsouza/zoxide.git "${code_dir}/zoxide"
149git clone --depth=1 https://github.com/tmux-plugins/tpm "${home_dir}/.tmux/plugins/tpm"
150
151# Rust toolchain is only needed for explicit cargo fallback.
152if [ "${ALLOW_CARGO_FALLBACK:-0}" = "1" ] && ! command -v cargo >/dev/null 2>&1; then
153 echo "[setup] Installing Rust toolchain because ALLOW_CARGO_FALLBACK=1 ..."
154 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
155 . "${home_dir}/.cargo/env"
156fi
157
158# Architecture detection for Rust-tool binary downloads
159arch="$(uname -m)"
160case "$arch" in
161 x86_64) rust_arch="x86_64-unknown-linux-gnu" ;;
162 aarch64) rust_arch="aarch64-unknown-linux-gnu" ;;
163 *)
164 echo "[rust-tools] Unsupported architecture: $arch, skipping rust tools installation"
165 rust_arch=""
166 ;;
167esac
168
169if [ -n "${rust_arch}" ]; then
170 echo "[rust-tools] Installing pre-compiled binaries for ${arch} ..."
171
172 # eza
173 echo "[eza] Downloading binary..."
174 rm -rf "${temp_dir}/eza"
175 mkdir -p "${temp_dir}/eza"
176
177 if curl -fsSL "https://github.com/eza-community/eza/releases/download/v0.23.0/eza_${rust_arch}.tar.gz" \
178 | tar xz -C "${temp_dir}/eza" \
179 && [ -f "${temp_dir}/eza/eza" ]; then
180
181 cp "${temp_dir}/eza/eza" "${local_bin_dir}/eza"
182 chmod +x "${local_bin_dir}/eza"
183 echo "[eza] Binary installed successfully"
184 else
185 echo "[eza] Binary download failed"
186 if [ "${ALLOW_CARGO_FALLBACK:-0}" = "1" ] && command -v cargo >/dev/null 2>&1; then
187 cargo install --locked --root "${home_dir}/.local" eza
188 else
189 echo "[eza] Skipping cargo fallback"
190 fi
191 fi
192
193 # bat
194 echo "[bat] Downloading binary..."
195 rm -rf "${temp_dir}/bat"
196 mkdir -p "${temp_dir}/bat"
197
198 if curl -fsSL "https://github.com/sharkdp/bat/releases/download/v0.24.0/bat-v0.24.0-${rust_arch}.tar.gz" \
199 | tar xz --strip-components=1 -C "${temp_dir}/bat" \
200 && [ -f "${temp_dir}/bat/bat" ]; then
201
202 cp "${temp_dir}/bat/bat" "${local_bin_dir}/bat"
203 chmod +x "${local_bin_dir}/bat"
204 echo "[bat] Binary installed successfully"
205 else
206 echo "[bat] Binary download failed"
207 if [ "${ALLOW_CARGO_FALLBACK:-0}" = "1" ] && command -v cargo >/dev/null 2>&1; then
208 cargo install --locked --root "${home_dir}/.local" bat
209 else
210 echo "[bat] Skipping cargo fallback"
211 fi
212 fi
213
214 # ripgrep
215 echo "[ripgrep] Downloading binary..."
216 rm -rf "${temp_dir}/ripgrep"
217 mkdir -p "${temp_dir}/ripgrep"
218
219 if curl -fsSL "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-${rust_arch}.tar.gz" \
220 | tar xz --strip-components=1 -C "${temp_dir}/ripgrep" \
221 && [ -f "${temp_dir}/ripgrep/rg" ]; then
222
223 cp "${temp_dir}/ripgrep/rg" "${local_bin_dir}/rg"
224 chmod +x "${local_bin_dir}/rg"
225 echo "[ripgrep] Binary installed successfully"
226 else
227 echo "[ripgrep] Binary download failed"
228 if [ "${ALLOW_CARGO_FALLBACK:-0}" = "1" ] && command -v cargo >/dev/null 2>&1; then
229 cargo install --locked --root "${home_dir}/.local" ripgrep
230 else
231 echo "[ripgrep] Skipping cargo fallback"
232 fi
233 fi
234
235 # fd
236 echo "[fd] Downloading binary..."
237 rm -rf "${temp_dir}/fd"
238 mkdir -p "${temp_dir}/fd"
239
240 if curl -fsSL "https://github.com/sharkdp/fd/releases/download/v10.2.0/fd-v10.2.0-${rust_arch}.tar.gz" \
241 | tar xz --strip-components=1 -C "${temp_dir}/fd" \
242 && [ -f "${temp_dir}/fd/fd" ]; then
243
244 cp "${temp_dir}/fd/fd" "${local_bin_dir}/fd"
245 chmod +x "${local_bin_dir}/fd"
246 echo "[fd] Binary installed successfully"
247 else
248 echo "[fd] Binary download failed"
249 if [ "${ALLOW_CARGO_FALLBACK:-0}" = "1" ] && command -v cargo >/dev/null 2>&1; then
250 cargo install --locked --root "${home_dir}/.local" fd-find
251 else
252 echo "[fd] Skipping cargo fallback"
253 fi
254 fi
255fi
256
257# Node.js user-local install
258NODE_VERSION="${NODE_VERSION:-v20.17.0}"
259
260arch="$(uname -m)"
261case "$arch" in
262 x86_64) node_arch="x64" ;;
263 aarch64) node_arch="arm64" ;;
264 *)
265 echo "[node] Unsupported architecture: $arch"
266 exit 1
267 ;;
268esac
269
270node_base="node-${NODE_VERSION}-linux-${node_arch}"
271node_tar="${node_base}.tar.xz"
272node_url="https://nodejs.org/dist/${NODE_VERSION}/${node_tar}"
273
274if [ ! -x "${local_opt_dir}/${node_base}/bin/node" ]; then
275 echo "[node] downloading ${node_url}"
276 curl -fsSL -o "${temp_dir}/${node_tar}" "${node_url}"
277 echo "[node] installing to ${local_opt_dir}/${node_base}"
278 tar -xJf "${temp_dir}/${node_tar}" -C "${local_opt_dir}/"
279fi
280
281ln -sf "${local_opt_dir}/${node_base}/bin/node" "${local_bin_dir}/node"
282ln -sf "${local_opt_dir}/${node_base}/bin/npm" "${local_bin_dir}/npm"
283ln -sf "${local_opt_dir}/${node_base}/bin/npx" "${local_bin_dir}/npx"
284
285# Claude Code local install
286rm -f "${home_dir}/.claude"
287ln -s "${code_dir}/dotfiles/.claude" "${home_dir}/.claude"
288
289mkdir -p "${home_dir}/.claude/local"
290"${local_bin_dir}/npm" install --prefix "${home_dir}/.claude/local" @anthropic-ai/claude-code
291
292cat > "${local_bin_dir}/claude" << 'EOF'
293#!/bin/bash
294exec "${HOME}/.local/bin/node" "${HOME}/.claude/local/node_modules/.bin/claude" "$@"
295EOF
296chmod +x "${local_bin_dir}/claude"
297
298# fzf and zoxide
299yes | "${code_dir}/fzf/install" --all
300"${code_dir}/zoxide/install.sh" --bin-dir "${local_bin_dir}"
301
302rm -f "${local_bin_dir}/fri"
303ln -s "${code_dir}/fri/fri" "${local_bin_dir}/fri"
304
305rm -f "${home_dir}/.dircolors"
306ln -s "${code_dir}/dotfiles/.dircolors" "${home_dir}/.dircolors"
307
308# Build diff-so-fancy, handle missing dependency gracefully
309cd "${code_dir}/diff-so-fancy/third_party/build_fatpack"
310if perl build.pl --output "${local_bin_dir}/diff-so-fancy" 2>/dev/null; then
311 echo "[diff-so-fancy] Built successfully"
312else
313 echo "[diff-so-fancy] Build failed - missing App::FatPacker, using direct script"
314 ln -sf "${code_dir}/diff-so-fancy/diff-so-fancy" "${local_bin_dir}/diff-so-fancy"
315 chmod +x "${local_bin_dir}/diff-so-fancy"
316fi
317cd "${home_dir}"
318
319# Dotfile symlinks
320rm -f "${home_dir}/.zshrc"
321ln -s "${code_dir}/dotfiles/.zshrc" "${home_dir}/.zshrc"
322
323rm -f "${home_dir}/.p10k.zsh"
324ln -s "${code_dir}/dotfiles/.p10k.zsh" "${home_dir}/.p10k.zsh"
325
326rm -f "${home_dir}/.powerlevel10k.zsh-theme"
327ln -s "${code_dir}/powerlevel10k/powerlevel10k.zsh-theme" "${home_dir}/.powerlevel10k.zsh-theme"
328
329rm -f "${home_dir}/.aliases"
330ln -s "${code_dir}/dotfiles/.aliases" "${home_dir}/.aliases"
331
332rm -rf "${home_dir}/.config/git"
333mkdir -p "${home_dir}/.config/git"
334ln -s "${code_dir}/dotfiles/.config/git/config" "${home_dir}/.config/git/config"
335
336rm -rf "${home_dir}/.config/tmux"
337ln -s "${code_dir}/dotfiles/.config/tmux" "${home_dir}/.config/"
338
339rm -rf "${home_dir}/.config/nvim"
340ln -s "${code_dir}/dotfiles/.config/nvim" "${home_dir}/.config/nvim"
341
342rm -rf "${home_dir}/.config/lazygit"
343ln -s "${code_dir}/dotfiles/.config/lazygit" "${home_dir}/.config/lazygit"
344
345# Install tmux plugins
346"${home_dir}/.tmux/plugins/tpm/bin/install_plugins"
347
348# Install nvim plugins
349"${local_bin_dir}/nvim" --headless "+Lazy! sync" +qa
350