From 274d1f02dfa0ac7a28f5318fe8c4eb6a28702bbe Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 16 Oct 2026 09:54:42 +0200 Subject: feat(user): userctl reads and writes the user's details One writer for the stores other programs read: AccountsService (and with it the passwd GECOS LibreOffice prefills from), KDE's emaildefaults and the global git config, where the surname goes in as an initial. busctl is asked for JSON because its plain output escapes non-ASCII as octal. Co-Authored-By: Claude Sonnet 5.5 --- desktop/modules/user/userctl | 115 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100755 desktop/modules/user/userctl (limited to 'desktop/modules/user/userctl') diff --git a/desktop/modules/user/userctl b/desktop/modules/user/userctl new file mode 100755 index 0000000..07d3a63 --- /dev/null +++ b/desktop/modules/user/userctl @@ -0,0 +1,115 @@ +#!/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. +prop() { + busctl --system --json=short get-property "$ACC" "$OBJ" "$IFACE" "$1" | jq -r .data +} + +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) + printf 'name=%s\n' "$(prop RealName)" + printf 'email=%s\n' "$(prop Email)" + printf 'avatar=%s\n' "$(prop IconFile)" + printf 'language=%s\n' "$(prop Language)" + printf 'location=%s\n' "$(prop Location)" + 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 = 1; next } + $1 == "fpr" && want == 1 { fpr = $10; want = 2; next } + $1 == "uid" && want == 2 { 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 user.signingkey + fi + ;; + *) usage ;; + esac + ;; + *) usage ;; +esac -- cgit v1.2.3