-
-
Save Cellane/ab2c422ce981afa2101ba68fa3cd11b0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -Eeuo pipefail | |
| # Make Node.js applications (Bitwarden CLI) trust Arch's system CA bundle, | |
| # including our private CA. | |
| export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt | |
| # --------------------------------------------------------------------------- | |
| # Account configuration | |
| # --------------------------------------------------------------------------- | |
| ACCOUNT="${1:-DEFAULT_SQUEENIX_ACCOUNT_HERE}" | |
| XIV_ACCOUNT_ID="${XIV_ACCOUNT_ID:-${ACCOUNT,,}-True-False}" | |
| BW_ITEM="${BW_ITEM:-secure.square-enix.com/${ACCOUNT}}" | |
| # --------------------------------------------------------------------------- | |
| # Application / KWallet configuration | |
| # --------------------------------------------------------------------------- | |
| XIVLAUNCHER="${XIVLAUNCHER:-xivlauncher-rb}" | |
| KWALLET="${KWALLET:-kdewallet}" | |
| KWALLET_FOLDER="${KWALLET_FOLDER:-Passwords}" | |
| KWALLET_ENTRY="${KWALLET_ENTRY:-xiv-autologin-bitwarden-master}" | |
| OTP_SERVER="http://127.0.0.1:4646" | |
| MAX_WAIT="${MAX_WAIT:-90}" | |
| POLL_INTERVAL="${POLL_INTERVAL:-1}" | |
| # --------------------------------------------------------------------------- | |
| # Logging | |
| # --------------------------------------------------------------------------- | |
| LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/xiv-autologin" | |
| mkdir -p "$LOG_DIR" | |
| exec >>"$LOG_DIR/${ACCOUNT}.log" 2>&1 | |
| printf '\n=== XIV autologin started: %s ===\n' "$(date --iso-8601=seconds)" | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| die() { | |
| printf 'ERROR: %s\n' "$*" >&2 | |
| exit 1 | |
| } | |
| need_command() { | |
| command -v "$1" >/dev/null 2>&1 || | |
| die "Required command not found: $1" | |
| } | |
| need_command "$XIVLAUNCHER" | |
| need_command bw | |
| need_command jq | |
| need_command kwallet-query | |
| need_command curl | |
| need_command setxkbmap | |
| # Session acquired by this script, if any. | |
| BW_SESSION_LOCAL="" | |
| bw_cmd() { | |
| if [[ -n "$BW_SESSION_LOCAL" ]]; then | |
| BW_SESSION="$BW_SESSION_LOCAL" bw "$@" | |
| else | |
| bw "$@" | |
| fi | |
| } | |
| unlock_bitwarden() { | |
| printf '🔐 Unlocking Bitwarden using KWallet...\n' | |
| local bw_password | |
| bw_password="$( | |
| kwallet-query \ | |
| -f "$KWALLET_FOLDER" \ | |
| -r "$KWALLET_ENTRY" \ | |
| "$KWALLET" | |
| )" || die "Could not read Bitwarden password from KWallet." | |
| [[ -n "$bw_password" ]] || | |
| die "KWallet entry '$KWALLET_ENTRY' is empty." | |
| BW_SESSION_LOCAL="$( | |
| BW_PASSWORD="$bw_password" \ | |
| bw unlock \ | |
| --passwordenv BW_PASSWORD \ | |
| --raw \ | |
| --nointeraction | |
| )" || { | |
| unset bw_password | |
| die "Could not unlock Bitwarden." | |
| } | |
| unset bw_password | |
| [[ -n "$BW_SESSION_LOCAL" ]] || | |
| die "Bitwarden returned an empty session key." | |
| printf '✓ Bitwarden unlocked.\n' | |
| } | |
| get_totp() { | |
| local otp | |
| otp="$( | |
| bw_cmd get totp "$BW_ITEM" \ | |
| --raw \ | |
| --nointeraction \ | |
| 2>/dev/null | |
| )" || return 1 | |
| [[ "$otp" =~ ^[0-9]{6}$ ]] || return 1 | |
| printf '%s' "$otp" | |
| } | |
| configure_xwayland_keyboard() { | |
| printf '⌨️ Configuring XWayland keyboard layout...\n' | |
| local attempt | |
| for attempt in {1..20}; do | |
| if setxkbmap \ | |
| -layout custom \ | |
| -variant apple_qwerty \ | |
| >/dev/null 2>&1; then | |
| printf '✓ XWayland keyboard layout configured (attempt %d).\n' "$attempt" | |
| return 0 | |
| fi | |
| sleep 0.25 | |
| done | |
| printf '⚠ Could not configure XWayland keyboard layout.\n' >&2 | |
| printf ' Continuing anyway; Czech input may be QWERTZ.\n' >&2 | |
| return 0 | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Inspect Bitwarden state | |
| # --------------------------------------------------------------------------- | |
| printf '🔎 Checking Bitwarden status...\n' | |
| BW_STATUS_JSON="$(bw status --nointeraction 2>/dev/null)" || | |
| die "Could not query Bitwarden CLI status." | |
| BW_STATUS="$( | |
| jq -r '.status // empty' <<<"$BW_STATUS_JSON" | |
| )" | |
| case "$BW_STATUS" in | |
| unlocked) | |
| printf '✓ Bitwarden vault is already unlocked.\n' | |
| ;; | |
| locked) | |
| printf '• Bitwarden vault is locked.\n' | |
| unlock_bitwarden | |
| ;; | |
| unauthenticated) | |
| die "Bitwarden CLI is not logged in. Run 'bw login' once first." | |
| ;; | |
| *) | |
| die "Unexpected Bitwarden status: '${BW_STATUS:-<empty>}'" | |
| ;; | |
| esac | |
| unset BW_STATUS_JSON | |
| # --------------------------------------------------------------------------- | |
| # Verify vault access | |
| # | |
| # This catches an important edge case: | |
| # | |
| # `bw status` says "unlocked", but this invocation doesn't actually have a | |
| # usable session for `bw get`. | |
| # | |
| # If that happens, just unlock normally through KWallet. | |
| # --------------------------------------------------------------------------- | |
| if ! get_totp >/dev/null; then | |
| if [[ "$BW_STATUS" == "unlocked" ]]; then | |
| printf '⚠ Bitwarden reports unlocked, but vault access failed.\n' | |
| printf ' Falling back to KWallet unlock.\n' | |
| unlock_bitwarden | |
| get_totp >/dev/null || | |
| die "Could not access TOTP item '$BW_ITEM' after unlocking." | |
| else | |
| die "Could not access TOTP item '$BW_ITEM'." | |
| fi | |
| fi | |
| printf '✓ Bitwarden TOTP entry is accessible.\n' | |
| unset BW_STATUS | |
| # --------------------------------------------------------------------------- | |
| # Launch XIVLauncher-RB | |
| # --------------------------------------------------------------------------- | |
| printf '🚀 Launching XIVLauncher-RB\n' | |
| printf ' Account: %s\n' "$XIV_ACCOUNT_ID" | |
| "$XIVLAUNCHER" \ | |
| "--CurrentAccountId=$XIV_ACCOUNT_ID" \ | |
| "--IsAutologin=true" \ | |
| "--IsOtpServer=true" \ | |
| >/dev/null 2>&1 & | |
| XIV_PID=$! | |
| configure_xwayland_keyboard | |
| # --------------------------------------------------------------------------- | |
| # Wait for XIVLauncher to request an OTP | |
| # --------------------------------------------------------------------------- | |
| printf '⏳ Waiting for XIVLauncher OTP server...\n' | |
| DEADLINE=$((SECONDS + MAX_WAIT)) | |
| while ((SECONDS < DEADLINE)); do | |
| # Don't wait forever if XIVLauncher itself crashed. | |
| if ! kill -0 "$XIV_PID" 2>/dev/null; then | |
| unset BW_SESSION_LOCAL | |
| die "XIVLauncher-RB exited before reaching the OTP screen." | |
| fi | |
| # Probe the HTTP listener. It only appears once XIVLauncher reaches | |
| # the OTP-entry phase. | |
| if curl \ | |
| --silent \ | |
| --max-time 1 \ | |
| "$OTP_SERVER/" \ | |
| >/dev/null 2>&1; then | |
| printf '✓ OTP server is ready.\n' | |
| # Generate a FRESH TOTP at the moment we're ready to send it. | |
| OTP="$(get_totp)" || | |
| die "Could not obtain TOTP from Bitwarden item '$BW_ITEM'." | |
| printf '🔑 Supplying TOTP to XIVLauncher...\n' | |
| if curl \ | |
| --silent \ | |
| --fail \ | |
| --max-time 2 \ | |
| "$OTP_SERVER/ffxivlauncher/$OTP" \ | |
| >/dev/null 2>&1; then | |
| unset OTP BW_SESSION_LOCAL | |
| printf '✓ TOTP supplied. Login continuing.\n' | |
| exit 0 | |
| fi | |
| # Listener may have changed state between probe and submission. | |
| # Don't reuse an aging OTP. | |
| unset OTP | |
| fi | |
| sleep "$POLL_INTERVAL" | |
| done | |
| unset BW_SESSION_LOCAL | |
| die "XIVLauncher did not request an OTP within ${MAX_WAIT}s." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment