Compare commits
4
Commits
7bbd911365
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41a90a10fb | ||
|
|
5f0dec5270 | ||
|
|
fe8b9a3ec0 | ||
|
|
72d85eb9ec |
@@ -0,0 +1,2 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
@@ -78,6 +78,14 @@ Already have a clone? Just run it directly:
|
||||
`~/.local/bin`. Persists across redeploys; only rebuilt when the venv is
|
||||
missing or broken. Building it may install `python3`/`python3-venv` via
|
||||
the system package manager (the one place this module uses sudo).
|
||||
- `vscode/` — installs Visual Studio Code from upstream's portable tarball into
|
||||
`~/.local/opt/vscode`, symlinks `code` into `~/.local/bin`, and writes a
|
||||
user-level `.desktop` entry so it shows up in the application menu. **No
|
||||
sudo, nothing outside `$HOME`** — the same contract as `bin/`, so it survives
|
||||
a Kasm redeploy. Pinned via `VSCODE_VERSION` in the module; bump it to upgrade.
|
||||
Also sets `password-store: basic` in `~/.vscode/argv.json` so KDE's kwallet
|
||||
does not prompt for an unlock on every launch (see below). **No-ops on
|
||||
headless hosts** — it's a ~400MB GUI app — override with `DOTFILES_VSCODE`.
|
||||
- `docker-rootless-cgroup/` — on systemd-less hosts (e.g. Kasm), hand-delegates
|
||||
cgroup v2 controllers to the rootless Docker daemon so per-container cgroups
|
||||
work (rootless Docker otherwise comes up with
|
||||
@@ -111,6 +119,18 @@ item instead of `~/.zshrc.local`:
|
||||
don't set this by hand: run `mcp-config/install.sh` once and its shell
|
||||
hook exports it from `~/.config/mcp-sync/key`. Set it in `~/.zshrc.local`
|
||||
only on a box without mcp-sync.
|
||||
- `DOTFILES_VSCODE` — controls the `vscode/` module. Unset (default) installs
|
||||
VS Code only when a graphical session is detected; `1` forces the install on
|
||||
a headless host (e.g. for `code tunnel`); `0` skips it everywhere. A plain
|
||||
shell/env var, not a vault secret.
|
||||
|
||||
On the keyring: VS Code's secret storage reaches for the OS keyring through
|
||||
libsecret, which on KDE makes kwalletd prompt for an unlock before the editor
|
||||
is usable — every session, since `kwallet-pam` is not installed. The module
|
||||
sets `"password-store": "basic"` in `~/.vscode/argv.json` to stop that, which
|
||||
means VS Code's stored tokens are obfuscated on disk rather than sealed
|
||||
behind a wallet password. To use the wallet instead, install `kwallet-pam` so
|
||||
it unlocks at login, then change that value to `"kwallet5"`.
|
||||
- `OP_SERVICE_ACCOUNT_TOKEN` — 1Password service-account token. Not a shell
|
||||
variable any more: `secrets/install.sh` asks for it once and stores it in
|
||||
`~/.config/dotfiles/op.env`. Pass it in the env only for an unattended run.
|
||||
|
||||
@@ -124,6 +124,10 @@ else
|
||||
run_module hermes
|
||||
fi
|
||||
run_module ansible
|
||||
# GUI editor, installed from upstream's tarball into ~/.local/opt (no sudo, same
|
||||
# contract as bin/). No ordering constraints — it needs nothing from bin/,
|
||||
# secrets/ or node/ — and no-ops on headless hosts.
|
||||
run_module vscode
|
||||
# Runs last: needs rootless Docker already up (started at boot) so it can
|
||||
# delegate cgroups to it. No-ops where that does not apply.
|
||||
run_module docker-rootless-cgroup
|
||||
|
||||
Binary file not shown.
+30
-2
@@ -8,8 +8,8 @@
|
||||
# reset.
|
||||
#
|
||||
# Source this (callers already run `set -eu`), then use fetch_arch /
|
||||
# fetch_arch_deb, have_version + mark_version, install_tarball_bin, and
|
||||
# install_raw_bin.
|
||||
# fetch_arch_deb, have_version + mark_version, install_tarball_bin,
|
||||
# install_tarball_tree, and install_raw_bin.
|
||||
|
||||
BIN_DIR="${BIN_DIR:-$HOME/.local/bin}"
|
||||
VERSION_DIR="$BIN_DIR/.versions"
|
||||
@@ -93,6 +93,34 @@ install_zip_bin() {
|
||||
fsay "installed $BIN_DIR/$3"
|
||||
}
|
||||
|
||||
# install_tarball_tree <url> <root-within-archive> <dest-dir> — download a
|
||||
# .tar.gz that holds a whole application directory rather than one binary, and
|
||||
# swap it into <dest-dir>. install_tarball_bin cannot be reused here: VS Code
|
||||
# is an Electron bundle of thousands of files that only runs from an intact
|
||||
# tree, not a single binary that can be picked out of the archive.
|
||||
#
|
||||
# The new tree is staged beside the target and moved into place only once the
|
||||
# download and extraction have both succeeded, so an interrupted run leaves the
|
||||
# previously installed version intact instead of a half-extracted directory.
|
||||
install_tarball_tree() {
|
||||
_dest="$3"
|
||||
_tmp="$(mktemp -d)"
|
||||
_fetch_dl "$1" "$_tmp/archive.tar.gz"
|
||||
tar -xzf "$_tmp/archive.tar.gz" -C "$_tmp"
|
||||
if [ ! -d "$_tmp/$2" ]; then
|
||||
fsay "fetch: '$2' not found in archive"; rm -rf "$_tmp"; return 1
|
||||
fi
|
||||
mkdir -p "$(dirname "$_dest")"
|
||||
rm -rf "$_dest.new" "$_dest.old"
|
||||
mv "$_tmp/$2" "$_dest.new"
|
||||
if [ -e "$_dest" ]; then
|
||||
mv "$_dest" "$_dest.old"
|
||||
fi
|
||||
mv "$_dest.new" "$_dest"
|
||||
rm -rf "$_dest.old" "$_tmp"
|
||||
fsay "installed $_dest"
|
||||
}
|
||||
|
||||
# install_raw_bin <url> <dest-name> — download a single raw binary.
|
||||
install_raw_bin() {
|
||||
mkdir -p "$BIN_DIR"
|
||||
|
||||
Executable
+216
@@ -0,0 +1,216 @@
|
||||
#!/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
|
||||
|
||||
# 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
|
||||
|
||||
# A system-wide VS Code (from Microsoft's rpm/deb repo) shadows this one in the
|
||||
# menu and shows a duplicate entry. Flag it rather than touching it -- removing
|
||||
# it needs the root this module deliberately avoids.
|
||||
if [ -e /usr/share/applications/com.microsoft.VSCode.desktop ]; then
|
||||
say ""
|
||||
say "vscode: NOTE: a system-wide VS Code package is also installed."
|
||||
say " You will see two menu entries until it is removed:"
|
||||
say " sudo dnf remove code # or: sudo apt remove code"
|
||||
say ""
|
||||
fi
|
||||
|
||||
say "vscode: ready."
|
||||
@@ -69,9 +69,10 @@ _DOTFILES_CONF="$HOME/.config/dotfiles"
|
||||
[ -f "$_DOTFILES_CONF/secrets.env" ] && source "$_DOTFILES_CONF/secrets.env"
|
||||
|
||||
# opr NAME read one field straight from the vault
|
||||
# opc NAME [VALUE] create or update a field (prompts, hidden, when VALUE is
|
||||
# omitted, which keeps the secret out of shell history),
|
||||
# then refresh and reload this shell
|
||||
# opc NAME=VALUE create or update a field, then refresh and reload this
|
||||
# opc NAME shell; with no VALUE (NAME or NAME=) it prompts, hidden,
|
||||
# which keeps the secret out of shell history.
|
||||
# `opc NAME VALUE` also still works.
|
||||
# opd NAME delete a field, refresh, and unset it in this shell
|
||||
# opl [VAULT [ITEM]] show every field and its current value (default: your
|
||||
# vault and item from op.env)
|
||||
@@ -94,19 +95,47 @@ opr() {
|
||||
opc() {
|
||||
emulate -L zsh
|
||||
local name=$1 value
|
||||
_dotfiles_env_name "$name" opc || return
|
||||
if (( $# >= 2 )); then
|
||||
if [[ $1 == *=* ]]; then
|
||||
# NAME=VALUE: split on the first '=' so values may contain '='
|
||||
name=${1%%=*} value=${1#*=}
|
||||
elif (( $# >= 2 )); then
|
||||
value=$2
|
||||
else
|
||||
fi
|
||||
_dotfiles_env_name "$name" opc || return
|
||||
if [[ -z $value ]]; then
|
||||
read -rs "value?$name: " && print
|
||||
[[ -n $value ]] || { print -u2 "opc: empty value for $name; nothing changed"; return 1; }
|
||||
fi
|
||||
# [concealed] makes it a password field; an existing field of that name is updated.
|
||||
_dotfiles_op sh -c 'op item edit "$OP_ITEM" --vault "$OP_VAULT" "$1[concealed]=$2" >/dev/null' \
|
||||
opc "$name" "$value" && ops
|
||||
# op can exit non-zero with "(404) Not Found" after an edit it did save (seen with
|
||||
# op 2.34.1 and 2.39.0), so its exit code is ignored: the field is read back instead.
|
||||
_dotfiles_op sh -c '
|
||||
clean() { printf "%s" "${1:-(no output)}" | sed "s/^\[ERROR\] [0-9/]* [0-9:]* //"; }
|
||||
ref="op://$OP_VAULT/$OP_ITEM/$1"
|
||||
err=$(op item edit "$OP_ITEM" --vault "$OP_VAULT" "$1[concealed]=$2" 2>&1 >/dev/null)
|
||||
if [ "$(op read "$ref" 2>/dev/null)" = "$2" ]; then
|
||||
printf "opc: saved %s\n" "$ref" >&2
|
||||
else
|
||||
printf "opc: could not save %s\n op said: %s\n check vault, item and service-account write access with: opl\n" \
|
||||
"$ref" "$(clean "$err")" >&2
|
||||
exit 1
|
||||
fi' opc "$name" "$value" && ops
|
||||
}
|
||||
opd() {
|
||||
_dotfiles_env_name "$1" opd || return
|
||||
_dotfiles_op sh -c 'op item edit "$OP_ITEM" --vault "$OP_VAULT" "$1[delete]" >/dev/null' opd "$1" \
|
||||
# As in opc, op's exit code is unreliable here: check the item's field labels instead.
|
||||
_dotfiles_op sh -c '
|
||||
clean() { printf "%s" "${1:-(no output)}" | sed "s/^\[ERROR\] [0-9/]* [0-9:]* //"; }
|
||||
ref="op://$OP_VAULT/$OP_ITEM/$1"
|
||||
err=$(op item edit "$OP_ITEM" --vault "$OP_VAULT" "$1[delete]" 2>&1 >/dev/null)
|
||||
if ! item=$(op item get "$OP_ITEM" --vault "$OP_VAULT" --format json 2>&1); then
|
||||
printf "opd: could not re-read %s to confirm the delete\n op said: %s\n" "$ref" "$(clean "$item")" >&2
|
||||
exit 1
|
||||
elif printf "%s" "$item" | jq -r ".fields[].label" | grep -qxF "$1"; then
|
||||
printf "opd: could not delete %s\n op said: %s\n" "$ref" "$(clean "$err")" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf "opd: deleted %s\n" "$ref" >&2' opd "$1" \
|
||||
&& unset "$1" && ops
|
||||
}
|
||||
opl() {
|
||||
|
||||
Reference in New Issue
Block a user