`opc NAME=VALUE` sets a field directly; `opc NAME` or `opc NAME=` prompts with hidden input, and an empty answer changes nothing. The old `opc NAME VALUE` form still works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
5.4 KiB
Bash
141 lines
5.4 KiB
Bash
# Managed by dotfiles/zsh/install.sh — edits made directly to ~/.zshrc will be
|
|
# overwritten on the next bootstrap run. Edit zsh/zshrc in the dotfiles repo
|
|
# instead.
|
|
|
|
# Userland tools installed by dotfiles/bin/install.sh live here.
|
|
case ":$PATH:" in
|
|
*":$HOME/.local/bin:"*) ;;
|
|
*) export PATH="$HOME/.local/bin:$PATH" ;;
|
|
esac
|
|
|
|
export ZSH="$HOME/.oh-my-zsh"
|
|
|
|
ZSH_THEME="robbyrussell"
|
|
|
|
plugins=(git)
|
|
|
|
source "$ZSH/oh-my-zsh.sh"
|
|
|
|
HISTSIZE=10000
|
|
SAVEHIST=10000
|
|
setopt SHARE_HISTORY
|
|
setopt HIST_IGNORE_DUPS
|
|
|
|
# Delete remote branches without fighting tab-completion.
|
|
#
|
|
# `git push origin --delete <TAB>` completes to `origin/foo`, but `origin/foo`
|
|
# is a LOCAL remote-tracking name — the ref on the server is just `foo`. So the
|
|
# command zsh completes for you is the one git rejects, with "remote ref does
|
|
# not exist", which reads like the branch is missing rather than misnamed.
|
|
#
|
|
# grmb takes either form. It fetches with --prune first so branches already
|
|
# deleted on the server stop being offered at all (that is the other half of
|
|
# this annoyance, and `fetch.prune = true` from dotfiles/git/install.sh makes it
|
|
# automatic everywhere else).
|
|
#
|
|
# Named grmb, not gbd: oh-my-zsh's git plugin already binds gbd to
|
|
# `git branch --delete`, aliases resolve before functions, and the collision
|
|
# fails in the worst possible way — `gbd origin/foo` would quietly try to
|
|
# delete a LOCAL branch instead of the remote one. Check with `type <name>`
|
|
# before adding any g* helper here.
|
|
#
|
|
# grmb origin/my-branch # completed form, works
|
|
# grmb my-branch other # bare form, several at once
|
|
grmb() {
|
|
emulate -L zsh
|
|
local remote=origin b rc=0
|
|
if [ "$#" -eq 0 ]; then
|
|
print -u2 "grmb: usage: grmb <branch>... (origin/ prefix optional)"
|
|
return 2
|
|
fi
|
|
git fetch --prune --quiet "$remote" || return 1
|
|
for b in "$@"; do
|
|
b=${b#${remote}/}
|
|
if git push "$remote" --delete "$b"; then
|
|
print "deleted ${remote}/${b}"
|
|
else
|
|
print -u2 "grmb: could not delete ${b} (already gone? check: git ls-remote --heads ${remote})"
|
|
rc=1
|
|
fi
|
|
done
|
|
return $rc
|
|
}
|
|
|
|
# Secrets from this user's 1Password vault, written by dotfiles/secrets/install.sh.
|
|
# Reading a local 0600 file keeps shell startup off the network: nothing here
|
|
# calls op. opc/opd refresh the file themselves; after editing the vault
|
|
# anywhere else (the 1Password app, another box), run `ops`.
|
|
_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, 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)
|
|
# ops re-fetch the vault into secrets.env and reload this shell
|
|
#
|
|
# The service-account token lives in op.env and is loaded only inside these
|
|
# subshells, so it never lands in the environment of everything you run.
|
|
_dotfiles_op() {
|
|
[ -f "$_DOTFILES_CONF/op.env" ] || { print -u2 "no $_DOTFILES_CONF/op.env; run: ops"; return 1; }
|
|
( set -a; source "$_DOTFILES_CONF/op.env"; set +a; "$@" )
|
|
}
|
|
_dotfiles_env_name() {
|
|
[[ "$1" =~ '^[A-Za-z_][A-Za-z0-9_]*$' ]] && return 0
|
|
print -u2 "$2: '$1' is not a valid variable name"; return 2
|
|
}
|
|
opr() {
|
|
_dotfiles_env_name "$1" opr || return
|
|
_dotfiles_op sh -c 'op read "op://$OP_VAULT/$OP_ITEM/$1"' opr "$1"
|
|
}
|
|
opc() {
|
|
emulate -L zsh
|
|
local name=$1 value
|
|
if [[ $1 == *=* ]]; then
|
|
# NAME=VALUE: split on the first '=' so values may contain '='
|
|
name=${1%%=*} value=${1#*=}
|
|
elif (( $# >= 2 )); then
|
|
value=$2
|
|
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
|
|
}
|
|
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" \
|
|
&& unset "$1" && ops
|
|
}
|
|
opl() {
|
|
_dotfiles_op sh -c '
|
|
vault=${1:-$OP_VAULT} item=${2:-$OP_ITEM}
|
|
printf "op://%s/%s\n" "$vault" "$item" >&2
|
|
op item get "$item" --vault "$vault" --reveal --format json' opl "$@" \
|
|
| jq -r '
|
|
[.fields[] | select(.purpose != "NOTES")] as $f
|
|
| ([$f[] | .label // "" | length] | max // 0) as $w
|
|
| $f[]
|
|
| ((.label // "") + " " * ($w - ((.label // "") | length))) as $name
|
|
| ((.value // "") | split("\n")) as $lines
|
|
| "\($name) \($lines[0] // "")",
|
|
($lines[1:][] | (" " * ($w + 2)) + .)'
|
|
}
|
|
ops() {
|
|
"${DOTFILES_DIR:-$HOME/.dotfiles}/secrets/install.sh" \
|
|
&& source "$_DOTFILES_CONF/secrets.env"
|
|
}
|
|
|
|
# Per-machine customizations (secrets, keys, local overrides) live in
|
|
# ~/.zshrc.local, which bootstrap never touches. See zsh/zshrc.local.example
|
|
# for the variables modules look for. Loaded last so it can override anything.
|
|
[ -f "$HOME/.zshrc.local" ] && source "$HOME/.zshrc.local"
|