On an Omarchy laptop bootstrap left two of almost everything: VS Code from both pacman and the tarball (two menu entries, one shared ~/.config/Code profile), and kubectl/helm/k9s/kubectx/yq/jq/node/ansible in ~/.local/bin behind mise and pacman copies that always won on PATH. The zsh helpers (op*, grmb, secrets.env) also never reached the bash login shell. - lib/fetch.sh: add `shadowed`, true when another copy of a tool comes earlier on PATH than ~/.local/bin. Always false where zshrc puts ~/.local/bin first, so VM installs are unchanged. - bin/, node/, ansible/: skip tools that are shadowed. - vscode/: defer to any system `code`, and remove a tarball copy this module installed earlier. - bash/: new module; bash port of the zshrc helpers, sourced from ~/.bashrc via a marked block. No-ops without ~/.bashrc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
237 lines
10 KiB
Bash
Executable File
237 lines
10 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
# vscode/install.sh — install Visual Studio Code into ~/.local/opt/vscode.
|
|
#
|
|
# WHY: VS Code is the one GUI app this repo installs, and the obvious way to get
|
|
# it — Microsoft's rpm/deb repo — needs root, writes outside $HOME, and is wiped
|
|
# by a Kasm redeploy, so the editor quietly disappears on every rebuild. The box
|
|
# this was written for had exactly that half-state: /etc/yum.repos.d/vscode.repo
|
|
# present, `code` never installed, and nothing in this repo recording that VS
|
|
# Code was meant to be there at all. Upstream's portable tarball is the same
|
|
# build with none of that: everything lands under $HOME, no sudo, and it
|
|
# survives a redeploy. The only thing given up is dnf/apt-driven updates, which
|
|
# is what the pinned VSCODE_VERSION below replaces.
|
|
#
|
|
# It is not in bin/install.sh because that script installs single self-contained
|
|
# binaries; VS Code is an Electron tree of thousands of files plus a menu entry
|
|
# and an icon. It follows node/ and ansible/ instead: unpack under ~/.local/opt
|
|
# and symlink the CLI into ~/.local/bin.
|
|
#
|
|
# The Chromium sandbox still works from a userland install: the bundled
|
|
# chrome-sandbox helper is not setuid here, so Electron falls back to the
|
|
# unprivileged user-namespace sandbox. That needs user namespaces enabled
|
|
# (user.max_user_namespaces > 0, the default on every distro this repo targets).
|
|
# Verified by launching without --no-sandbox. Nothing is given up for dropping
|
|
# root; if a host ever has userns disabled, VS Code says so on startup.
|
|
#
|
|
# Idempotent and safe to re-run. To upgrade, bump VSCODE_VERSION.
|
|
set -eu
|
|
|
|
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
REPO_ROOT="$(dirname -- "$SCRIPT_DIR")"
|
|
|
|
# shellcheck source=../lib/fetch.sh
|
|
. "$REPO_ROOT/lib/fetch.sh"
|
|
|
|
say() { printf '%s\n' "$*" >&2; }
|
|
|
|
# Pinned deliberately. VS Code auto-updates itself on other platforms; a tarball
|
|
# install does not, which is the point — the editor runs your code and holds
|
|
# your Git and extension credentials, so the version is a supply-chain decision
|
|
# rather than whatever upstream served today. `code --version` prints this
|
|
# string verbatim, so it must match with no leading "v".
|
|
VSCODE_VERSION="1.139.0"
|
|
|
|
PREFIX="$HOME/.local/opt/vscode"
|
|
DESKTOP_DIR="$HOME/.local/share/applications"
|
|
ARGV_JSON="$HOME/.vscode/argv.json"
|
|
|
|
# has_desktop — true when this host has a graphical session to run a GUI editor
|
|
# in. VS Code is ~400MB unpacked and $HOME is the thing that persists across
|
|
# redeploys, so it is not worth carrying on a box that will never display it.
|
|
# Desktop images ship session files under /usr/share/{x,wayland-}sessions.
|
|
has_desktop() {
|
|
[ -n "${XDG_CURRENT_DESKTOP:-}" ] && return 0
|
|
for d in /usr/share/xsessions /usr/share/wayland-sessions; do
|
|
[ -d "$d" ] && [ -n "$(ls -A "$d" 2>/dev/null)" ] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# --- skip on headless hosts --------------------------------------------------
|
|
# DOTFILES_VSCODE=1 forces the install anyway (e.g. for `code tunnel` on a
|
|
# server); DOTFILES_VSCODE=0 skips it even on a desktop.
|
|
case "${DOTFILES_VSCODE:-auto}" in
|
|
0) say "vscode: DOTFILES_VSCODE=0; skipping."; exit 0 ;;
|
|
1) ;;
|
|
*) if ! has_desktop; then
|
|
say "vscode: no graphical session detected; skipping (DOTFILES_VSCODE=1 to force)."
|
|
exit 0
|
|
fi ;;
|
|
esac
|
|
|
|
# --- defer to a system-wide VS Code -------------------------------------------
|
|
# WHY: a distro package (Arch's visual-studio-code-bin on Omarchy, Microsoft's
|
|
# rpm/deb elsewhere) and this tarball both add a "Visual Studio Code" menu entry
|
|
# and both open the same ~/.config/Code profile. Two builds on one profile is
|
|
# the real hazard: the older one opens state the newer one already migrated,
|
|
# and extensions break in ways that look like extension bugs. The system copy
|
|
# also gets updates this pinned one does not, so it wins.
|
|
#
|
|
# PATH order is irrelevant here (unlike `shadowed` in bin/): the menu entries
|
|
# collide wherever the binaries sit. Anything we installed earlier is removed --
|
|
# only ours, recognised by the .desktop Exec pointing into $PREFIX.
|
|
_sys_code=""
|
|
_sh_ifs="$IFS"; IFS=:
|
|
for _d in $PATH; do
|
|
[ "$_d" != "$BIN_DIR" ] && [ -x "$_d/code" ] && { _sys_code="$_d/code"; break; }
|
|
done
|
|
IFS="$_sh_ifs"
|
|
if [ -n "$_sys_code" ]; then
|
|
say "vscode: system VS Code at $_sys_code; skipping the tarball install."
|
|
if grep -qs "^Exec=$PREFIX/" "$DESKTOP_DIR/code.desktop"; then
|
|
rm -f "$DESKTOP_DIR/code.desktop"
|
|
[ "$(readlink "$BIN_DIR/code" 2>/dev/null)" = "$PREFIX/bin/code" ] && rm -f "$BIN_DIR/code"
|
|
rm -f "$VERSION_DIR/code"
|
|
rm -rf "$PREFIX"
|
|
command -v update-desktop-database >/dev/null 2>&1 &&
|
|
update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
|
|
say "vscode: removed the earlier tarball copy ($PREFIX, its menu entry and $BIN_DIR/code)."
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# Upstream keys its download URLs on x64/arm64 rather than amd64/arm64.
|
|
case "$(fetch_arch)" in
|
|
x86_64) VSCODE_ARCH=x64 ;;
|
|
aarch64) VSCODE_ARCH=arm64 ;;
|
|
*) say "vscode: unsupported arch $(fetch_arch)"; exit 1 ;;
|
|
esac
|
|
|
|
# --- install the app tree ----------------------------------------------------
|
|
# Verify it RUNS, not just that the tree exists: a Kasm redeploy can leave a
|
|
# present-but-broken directory behind (the same failure node/ and ansible/
|
|
# guard against).
|
|
#
|
|
# The root fallback is not optional. Run as root, `code --version` refuses --
|
|
# it prints "You are trying to start Visual Studio Code as a super user"
|
|
# instead of the version, and still exits 0 -- so a bare run-check never
|
|
# matches and would re-download 325MB on every single bootstrap, silently.
|
|
# That is the forced-install path (DOTFILES_VSCODE=1 in a container or on a
|
|
# server), so for root only, fall back to the recorded version stamp.
|
|
installed_version="$("$PREFIX/bin/code" --version 2>/dev/null | head -1 || true)"
|
|
if [ "$installed_version" = "$VSCODE_VERSION" ]; then
|
|
say "vscode: $VSCODE_VERSION already present at $PREFIX."
|
|
elif [ "$(id -u)" = "0" ] && have_version code "$VSCODE_VERSION"; then
|
|
say "vscode: $VSCODE_VERSION already present at $PREFIX (stamped; root cannot query --version)."
|
|
else
|
|
say "vscode: installing $VSCODE_VERSION into $PREFIX (this is a ~325MB download)..."
|
|
install_tarball_tree \
|
|
"https://update.code.visualstudio.com/$VSCODE_VERSION/linux-$VSCODE_ARCH/stable" \
|
|
"VSCode-linux-$VSCODE_ARCH" "$PREFIX"
|
|
|
|
# bin/code is upstream's CLI wrapper (`code .`, `code --install-extension`);
|
|
# the Electron binary at the tree root is what the menu entry launches.
|
|
mkdir -p "$BIN_DIR"
|
|
ln -sf "$PREFIX/bin/code" "$BIN_DIR/code"
|
|
mark_version code "$VSCODE_VERSION"
|
|
say "vscode: installed $VSCODE_VERSION and linked $BIN_DIR/code."
|
|
fi
|
|
|
|
# --- menu entry --------------------------------------------------------------
|
|
# Icon= is an absolute path rather than a theme name: the bundled icon is a
|
|
# single 1024x1024 PNG and hicolor tops out at 512x512, so there is no correct
|
|
# themed directory to drop it in. The spec allows an absolute path, and it needs
|
|
# no icon-cache rebuild to take effect.
|
|
mkdir -p "$DESKTOP_DIR"
|
|
cat >"$DESKTOP_DIR/code.desktop" <<DESKTOP
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=Visual Studio Code
|
|
GenericName=Text Editor
|
|
Comment=Code Editing. Redefined.
|
|
Exec=$PREFIX/code %F
|
|
Icon=$PREFIX/resources/app/resources/linux/code.png
|
|
Terminal=false
|
|
StartupNotify=true
|
|
StartupWMClass=Code
|
|
Categories=TextEditor;Development;IDE;
|
|
MimeType=text/plain;inode/directory;
|
|
Keywords=vscode;editor;
|
|
Actions=new-window;
|
|
|
|
[Desktop Action new-window]
|
|
Name=New Empty Window
|
|
Exec=$PREFIX/code --new-window %F
|
|
Icon=$PREFIX/resources/app/resources/linux/code.png
|
|
DESKTOP
|
|
say "vscode: wrote $DESKTOP_DIR/code.desktop"
|
|
|
|
# Refresh the per-user menu caches so the entry appears without a re-login.
|
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
|
update-desktop-database "$DESKTOP_DIR" 2>/dev/null || true
|
|
fi
|
|
if command -v kbuildsycoca5 >/dev/null 2>&1; then
|
|
kbuildsycoca5 --noincremental >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
# --- keyring prompt suppression ----------------------------------------------
|
|
# WHY: on first launch Electron's safeStorage reaches for the OS keyring through
|
|
# libsecret. On a KDE session that wakes kwalletd, which pops an unlock/create
|
|
# dialog before the editor is usable -- and since kwallet-pam is not installed
|
|
# on these hosts, nothing unlocks the wallet at login, so it prompts every
|
|
# session. "basic" keeps VS Code's secrets in its own store and never reaches
|
|
# for the wallet at all. Verified with dbus-monitor: no org.kde.KWallet traffic
|
|
# on launch.
|
|
#
|
|
# TRADE-OFF: basic is weaker than a real keyring. Secrets VS Code stores (Git
|
|
# and GitHub auth tokens, extension credentials) end up obfuscated on disk
|
|
# rather than sealed behind a wallet password. To move to the wallet later,
|
|
# install kwallet-pam so it unlocks at login, then change the value to
|
|
# "kwallet5" and restart VS Code.
|
|
#
|
|
# The path comes from product.json's dataFolderName (".vscode"), NOT the
|
|
# user-data-dir: argv.json lives at ~/.vscode/argv.json and a copy written
|
|
# anywhere else is read by nothing and silently ignored.
|
|
if [ ! -e "$ARGV_JSON" ]; then
|
|
mkdir -p "$(dirname "$ARGV_JSON")"
|
|
cat >"$ARGV_JSON" <<'ARGV'
|
|
// Runtime arguments for VS Code. See dotfiles vscode/install.sh.
|
|
{
|
|
// Keep secrets in VS Code's own store rather than the OS keyring, so KDE's
|
|
// kwallet does not prompt for an unlock on every launch.
|
|
"password-store": "basic"
|
|
}
|
|
ARGV
|
|
say "vscode: wrote $ARGV_JSON (password-store=basic)"
|
|
elif grep -q '"password-store"' "$ARGV_JSON"; then
|
|
say "vscode: $ARGV_JSON already sets password-store; leaving it alone."
|
|
else
|
|
# Merge rather than overwrite: this file is JSON-with-comments that VS Code
|
|
# and the user both own, and it carries a crash-reporter-id that should
|
|
# survive. Insert the key after the opening brace and keep the rest byte for
|
|
# byte.
|
|
cp -p "$ARGV_JSON" "$ARGV_JSON.bak"
|
|
if awk '
|
|
BEGIN { done = 0 }
|
|
{
|
|
print
|
|
if (!done && $0 ~ /^[[:space:]]*\{[[:space:]]*$/) {
|
|
print "\t// Added by dotfiles vscode/install.sh: keep secrets in VS Code'"'"'s"
|
|
print "\t// own store so kwallet does not prompt for an unlock on every launch."
|
|
print "\t\"password-store\": \"basic\","
|
|
done = 1
|
|
}
|
|
}
|
|
END { exit(done ? 0 : 1) }
|
|
' "$ARGV_JSON.bak" >"$ARGV_JSON.new"; then
|
|
mv "$ARGV_JSON.new" "$ARGV_JSON"
|
|
say "vscode: set password-store=basic in $ARGV_JSON (backup: $ARGV_JSON.bak)"
|
|
else
|
|
rm -f "$ARGV_JSON.new"
|
|
say "vscode: WARNING: could not find the object opening in $ARGV_JSON."
|
|
say " Add '\"password-store\": \"basic\"' by hand to stop the keyring prompt."
|
|
fi
|
|
fi
|
|
|
|
say "vscode: ready."
|