#!/usr/bin/env bash set -euo pipefail unity_cli="${UNITY_CLI:-$HOME/.local/bin/unity}" if [[ ! -x "$unity_cli" ]]; then echo "Set UNITY_CLI to the installed Unity CLI executable." >&2 exit 1 fi # Always use a private bus, including when SSH inherited a desktop bus. if [[ "${1:-}" != --keyring-session ]]; then exec dbus-run-session -- bash "$0" --keyring-session "$@" fi shift unset GNOME_KEYRING_CONTROL # Give Unity its own credential collection. Do not read or modify the user's # desktop login.keyring, which may be locked, damaged, or in use elsewhere. keyring_data_dir="${UNITY_SSH_KEYRING_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/unity-ssh}" umask 077 mkdir -p "$keyring_data_dir" exec 9>"$keyring_data_dir/session.lock" if ! flock -n 9; then echo "A Unity SSH keyring session is already open. Exit it before starting another." >&2 exit 1 fi read -r -s -p "Unity keyring password (choose one on first use): " unity_keyring_password echo if [[ -z "$unity_keyring_password" ]]; then echo "A nonempty password is required for the encrypted keyring." >&2 exit 1 fi runtime_dir="$(mktemp -d "${TMPDIR:-/tmp}/unity-keyring.XXXXXX")" daemon_pid="" cleanup() { if [[ -n "$daemon_pid" ]]; then kill "$daemon_pid" 2>/dev/null || true wait "$daemon_pid" 2>/dev/null || true fi rm -rf -- "$runtime_dir" } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM # Keep the daemon on this bus, wait for it to register, and stop it on exit. # Only the daemon sees the alternate data directory; Unity keeps its normal # configuration and license-file locations. XDG_DATA_HOME="$keyring_data_dir" gnome-keyring-daemon \ --foreground --unlock --components=secrets --control-directory="$runtime_dir" \ < <(printf '%s' "$unity_keyring_password") >"$runtime_dir/daemon.log" 2>&1 & daemon_pid=$! unset unity_keyring_password owner='(false,)' for (( attempt=0; attempt<100; attempt++ )); do owner="$(gdbus call --session --dest org.freedesktop.DBus \ --object-path /org/freedesktop/DBus \ --method org.freedesktop.DBus.NameHasOwner org.freedesktop.secrets)" [[ "$owner" != '(true,)' ]] || break kill -0 "$daemon_pid" 2>/dev/null || break sleep 0.1 done if [[ "$owner" != '(true,)' ]]; then echo "The Unity keyring service did not start." >&2 cat "$runtime_dir/daemon.log" >&2 exit 1 fi locked="$(gdbus call --session --dest org.freedesktop.secrets \ --object-path /org/freedesktop/secrets/collection/login \ --method org.freedesktop.DBus.Properties.Get org.freedesktop.Secret.Collection Locked 2>/dev/null)" || locked='missing' if [[ "$locked" != '(,)' ]]; then echo "Could not unlock the Unity keyring at $keyring_data_dir/keyrings." >&2 echo "Use the password chosen for this helper. No credential files were removed." >&2 exit 1 fi # Check persistence before asking the user to complete a browser sign-in. probe="unity-ssh-$$-$RANDOM" printf '%s' "$probe" | timeout 10 secret-tool store --label='Unity SSH keyring check' unity-ssh-check "$probe" saved="$(timeout 10 secret-tool lookup unity-ssh-check "$probe")" timeout 10 secret-tool clear unity-ssh-check "$probe" if [[ "$saved" != "$probe" ]]; then echo "The Unity keyring could not save and retrieve a test credential." >&2 exit 1 fi echo "Unity keyring storage check passed." export PATH="$(dirname -- "$unity_cli"):$PATH" export UNITY_NO_CLI_INVOKED_TELEMETRY=1 if [[ "${1:-}" == --check ]]; then exit 0 fi if (( $# )); then "$unity_cli" "$@" exit $? fi echo "Unity login shell ready. Run: unity auth login" echo "Open the printed sign-in URL in your own computer's browser." echo "After signing in and reviewing the Personal terms, run:" echo " unity license activate --personal --accept-eula" echo " unity license status" echo "Type exit to close this shell and its keyring session." bash --noprofile --norc -i