#!/bin/bash # # Copyright (C) 2026 Danilo M. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # The user's details, read and written where other programs look for them. # # userctl get key=value: name email avatar language location signingkey # userctl keys fingerprintuid per gpg secret key # userctl set write one field to every store holding it # # Stores: AccountsService (and through SetRealName the passwd GECOS that # LibreOffice reads), KDE's ~/.config/emaildefaults, the global git config. # Polkit lets a user change their own AccountsService entry without a prompt. # # set prints nothing on success. On a failing write it prints the command and # its error on stdout, for the drawer page, and exits 1 without trying the # remaining stores. A new field is one case below and one entry in the # page's field list. set -u # Character-wise ${x:0:1}, so an accented initial is not cut in half. export LC_ALL=C.UTF-8 ACC=org.freedesktop.Accounts OBJ="/org/freedesktop/Accounts/User$(id -u)" IFACE=org.freedesktop.Accounts.User usage() { printf 'usage: %s get | keys | set \n' \ "${0##*/}" >&2 exit 2 } run() { local err if ! err="$("$@" 2>&1 >/dev/null)"; then printf '%s: %s\n' "$1" "${err:-failed}" exit 1 fi } # --json, because plain get-property prints non-ASCII as octal escapes. # Fails with the first line of stderr as its output, so get can report it. prop() { local out out="$(busctl --system --json=short get-property "$ACC" "$OBJ" "$IFACE" "$1" 2>&1)" \ || { head -n1 <<< "${out:-failed}"; return 1; } jq -r .data <<< "$out" } acc() { run busctl --system call -- "$ACC" "$OBJ" "$IFACE" "$1" s "$2"; } kde() { run kwriteconfig6 --file emaildefaults --group PROFILE_Default --key "$1" -- "$2"; } # The surname as an initial, for commits: "Jane Doèr" -> "Jane D.". gitname() { local -a w read -ra w <<< "$1" local n=${#w[@]} if (( n < 2 )); then printf '%s' "${w[*]}"; return; fi printf '%s %s.' "${w[*]:0:n-1}" "${w[n-1]:0:1}" } [[ $# -ge 1 ]] || usage case "$1" in get) out= # Read everything first: one error line and exit 1, never blank fields. for p in RealName:name Email:email IconFile:avatar Language:language Location:location; do val="$(prop "${p%%:*}")" || { printf 'error=%s\n' "$val"; exit 1; } out+="${p#*:}=$val"$'\n' done printf '%s' "$out" printf 'signingkey=%s\n' "$(git config --global --get user.signingkey)" ;; keys) # The first fpr after a sec is the primary key's; the first uid # after that names it. ssb fingerprints come later and are skipped. gpg --list-secret-keys --with-colons 2>/dev/null | awk -F: ' $1 == "sec" { want = ($2 !~ /^[rei]$/ && $12 ~ /S/) ? 1 : 0; next } $1 == "fpr" && want == 1 { fpr = $10; want = 2; next } $1 == "uid" && want == 2 && $2 !~ /^[re]$/ { print fpr "\t" $10; want = 0 }' ;; set) [[ $# -eq 3 ]] || usage v="$3" case "$2" in name) acc SetRealName "$v" kde FullName "$v" run git config --global user.name "$(gitname "$v")" ;; email) acc SetEmail "$v" kde EmailAddress "$v" run git config --global user.email "$v" ;; avatar) acc SetIconFile "$v" ;; language) acc SetLanguage "$v" ;; location) acc SetLocation "$v" ;; signingkey) if [[ -n "$v" ]]; then run git config --global user.signingkey "$v" elif git config --global --get user.signingkey >/dev/null; then run git config --global --unset-all user.signingkey fi ;; *) usage ;; esac ;; *) usage ;; esac