#!/usr/bin/env bash set -u PLUGIN_ID="local.omarchy-citizen" PLUGIN_VERSION="0.8.1" XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" PLUGIN_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SELF="$(readlink -f "${BASH_SOURCE[0]}")" STATE_DIR="$XDG_STATE_HOME/omarchy-citizen" CACHE_DIR="$XDG_CACHE_HOME/omarchy-citizen" DATA_DIR="$XDG_DATA_HOME/omarchy-citizen" LOG_FILE="$STATE_DIR/omarchy-citizen.log" UPDATER_LOG="$STATE_DIR/updater.log" RSI_LOG="$STATE_DIR/rsi-launcher.log" SANITIZER="$PLUGIN_DIR/support-sanitize.py" BACKEND_EXTERNAL="$HOME/.local/lib/omarchy-citizen/omarchy-citizen-backend" BACKEND_BUNDLED="$PLUGIN_DIR/backend/bin/omarchy-citizen-backend" MANAGED_LUG="$DATA_DIR/vendor/lug-helper/lug-helper.appimage" MANAGED_WINE="$DATA_DIR/vendor/wine/current" mkdir -p "$STATE_DIR" "$CACHE_DIR" ACTION="${1:-status}" rotate_log() { local max=$((4 * 1024 * 1024)) keep=$((2 * 1024 * 1024)) size=0 if [[ -f "$LOG_FILE" ]]; then size="$(stat -c '%s' "$LOG_FILE" 2>/dev/null || echo 0)" if (( size > max )); then tail -c "$keep" "$LOG_FILE" > "$LOG_FILE.tmp" 2>/dev/null || true mv -f "$LOG_FILE.tmp" "$LOG_FILE" 2>/dev/null || true fi fi } log_line() { printf '%s [%s] %s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')" "$$" "$*" >>"$LOG_FILE" 2>/dev/null || true } rotate_log log_line "action=$ACTION version=$PLUGIN_VERSION" notify() { local title="$1" body="$2" if command -v omarchy-notification-send >/dev/null 2>&1; then omarchy-notification-send "$title" "$body" >/dev/null 2>&1 || true elif command -v notify-send >/dev/null 2>&1; then notify-send "$title" "$body" >/dev/null 2>&1 || true fi } notify_error() { local body="$1" log_line "error=$body" if command -v zenity >/dev/null 2>&1; then zenity --error --width=560 --title="Omarchy Citizen" --text="$body" >/dev/null 2>&1 || true else notify "Omarchy Citizen" "$body" printf 'Omarchy Citizen: %s\n' "$body" >&2 fi } find_backend() { if [[ -x "$BACKEND_EXTERNAL" ]]; then printf '%s\n' "$BACKEND_EXTERNAL" elif [[ -x "$BACKEND_BUNDLED" ]]; then printf '%s\n' "$BACKEND_BUNDLED" else return 1 fi } backend_exec() { local backend backend="$(find_backend)" || { printf 'Go-Backend nicht gefunden. Plugin bitte neu installieren.\n' >&2 return 127 } "$backend" "$@" } backend_game_status() { backend_exec game-status; } backend_plugin_status() { backend_exec status; } backend_game_install() { backend_exec game-install; } backend_game_launch() { backend_exec game-launch; } backend_game_repair() { backend_exec game-repair; } autopilot_enable() { backend_exec autopilot enable; } autopilot_maintain() { backend_exec maintain; } wine_doctor() { backend_exec doctor; } kv_get() { local key="$1" sed -n "s/^${key}=//p" | head -n1 } status() { local game="" plugin="" local health="setup" hardware="unknown" hardware_reason="" gpu="" vulkan="unknown" local prefix="missing" launcher="missing" game_state="missing" wine="" dxvk="" lug="" game_autopilot="false" local backend_state="missing" backend_version="" managed="manual" auto_apply="false" auto_maintain="false" local update="unavailable" local_commit="" remote_commit="" maintenance="" if game="$(backend_game_status 2>/dev/null)"; then health="$(printf '%s\n' "$game" | kv_get health)" hardware="$(printf '%s\n' "$game" | kv_get hardware)" hardware_reason="$(printf '%s\n' "$game" | kv_get hardware_reason)" gpu="$(printf '%s\n' "$game" | kv_get gpu)" vulkan="$(printf '%s\n' "$game" | kv_get vulkan)" prefix="$(printf '%s\n' "$game" | kv_get prefix_state)" launcher="$(printf '%s\n' "$game" | kv_get launcher_state)" game_state="$(printf '%s\n' "$game" | kv_get game_state)" wine="$(printf '%s\n' "$game" | kv_get wine_version)" dxvk="$(printf '%s\n' "$game" | kv_get dxvk_version)" lug="$(printf '%s\n' "$game" | kv_get lug_version)" game_autopilot="$(printf '%s\n' "$game" | kv_get autopilot)" fi if plugin="$(backend_plugin_status 2>/dev/null)"; then backend_state="ready" backend_version="$(printf '%s\n' "$plugin" | kv_get backend_version)" managed="$(printf '%s\n' "$plugin" | kv_get managed)" auto_apply="$(printf '%s\n' "$plugin" | kv_get auto_apply)" auto_maintain="$(printf '%s\n' "$plugin" | kv_get auto_maintain)" update="$(printf '%s\n' "$plugin" | kv_get update)" local_commit="$(printf '%s\n' "$plugin" | kv_get local_commit)" remote_commit="$(printf '%s\n' "$plugin" | kv_get remote_commit)" maintenance="$(printf '%s\n' "$plugin" | kv_get maintenance_result)" fi [[ -n "$health" ]] || health="setup" [[ -n "$prefix" ]] || prefix="missing" [[ -n "$launcher" ]] || launcher="missing" [[ -n "$game_state" ]] || game_state="missing" [[ -n "$hardware" ]] || hardware="unknown" [[ -n "$vulkan" ]] || vulkan="unknown" [[ -n "$update" ]] || update="unavailable" printf 'plugin_version=%s\n' "$PLUGIN_VERSION" printf 'health=%s\n' "$health" printf 'deps=ready\n' printf 'deps_missing=\n' printf 'helper=%s\n' "$([[ -n "$lug" ]] && echo ready || echo missing)" printf 'helper_managed=optional\n' printf 'helper_version=%s\n' "$lug" printf 'update=unknown\n' printf 'update_version=\n' printf 'prefix=%s\n' "$prefix" printf 'prefix_detail=%s\n' "$([[ "$prefix" == partial ]] && echo unvollständig || true)" printf 'launcher=%s\n' "$launcher" printf 'game=%s\n' "$game_state" printf 'backend=%s\n' "$backend_state" printf 'backend_version=%s\n' "$backend_version" printf 'plugin_managed=%s\n' "$managed" printf 'plugin_auto=%s\n' "$auto_apply" printf 'plugin_update=%s\n' "$update" printf 'plugin_local_commit=%s\n' "$local_commit" printf 'plugin_remote_commit=%s\n' "$remote_commit" printf 'autopilot=%s\n' "$([[ "$auto_maintain" == true ]] && echo true || echo "$game_autopilot")" printf 'managed_lug_version=%s\n' "$lug" printf 'managed_wine_version=%s\n' "$wine" printf 'managed_dxvk_version=%s\n' "$dxvk" printf 'maintenance_result=%s\n' "$maintenance" printf 'hardware=%s\n' "$hardware" printf 'hardware_reason=%s\n' "$hardware_reason" printf 'hardware_gpu=%s\n' "$gpu" printf 'hardware_vulkan=%s\n' "$vulkan" } hardware_info() { local game reason gpu vulkan game="$(backend_game_status 2>/dev/null || true)" reason="$(printf '%s\n' "$game" | kv_get hardware_reason)" gpu="$(printf '%s\n' "$game" | kv_get gpu)" vulkan="$(printf '%s\n' "$game" | kv_get vulkan)" if command -v zenity >/dev/null 2>&1; then zenity --warning --width=620 --title="Omarchy Citizen – Hardware" \ --text="Star Citizen ist auf diesem System noch nicht spielbereit.\n\nGPU:\n${gpu:-nicht erkannt}\n\nVulkan: ${vulkan:-unbekannt}\n\n${reason:-Hardware-Prüfung fehlgeschlagen.}" >/dev/null 2>&1 || true else printf 'GPU: %s\nVulkan: %s\n%s\n' "${gpu:-nicht erkannt}" "${vulkan:-unbekannt}" "${reason:-Hardware-Prüfung fehlgeschlagen.}" fi } probe_detached_start() { local label="$1"; shift "$@" >>"$LOG_FILE" 2>&1 & local pid=$! rc=0 sleep 0.35 if ! kill -0 "$pid" 2>/dev/null; then wait "$pid" || rc=$? if (( rc != 0 )); then log_line "$label failed rc=$rc" return "$rc" fi else disown "$pid" 2>/dev/null || true fi log_line "$label accepted" } launch_terminal_action() { local action="$1" log_line "launch-terminal action=$action" if command -v uwsm-app >/dev/null 2>&1 && command -v xdg-terminal-exec >/dev/null 2>&1; then probe_detached_start "uwsm-xdg-terminal" setsid uwsm-app -- xdg-terminal-exec \ --app-id=org.omarchy.terminal --title="Omarchy Citizen" -e bash "$SELF" "$action" && return 0 fi if command -v xdg-terminal-exec >/dev/null 2>&1; then probe_detached_start "xdg-terminal" xdg-terminal-exec --title="Omarchy Citizen" -e bash "$SELF" "$action" && return 0 fi if command -v omarchy-launch-floating-terminal-with-presentation >/dev/null 2>&1; then probe_detached_start "omarchy-terminal" omarchy-launch-floating-terminal-with-presentation bash "$SELF" "$action" && return 0 fi notify_error "Das Omarchy-Citizen-Terminal konnte nicht geöffnet werden. Bitte ein Support-Paket erstellen." return 1 } install_stack_terminal() { set -Ee exec > >(tee -a "$LOG_FILE") 2>&1 setup_failed() { local rc=$? trap - ERR echo echo "==================================================" echo " SETUP KONNTE NICHT ABGESCHLOSSEN WERDEN" echo "==================================================" echo "Fehlercode: $rc" echo "Alle Details wurden protokolliert. Im SC-Panel genügt 'Support-Paket erstellen'." notify "Omarchy Citizen" "Setup fehlgeschlagen. Bitte Support-Paket erstellen." if [[ -t 0 ]]; then read -r -p "Drücke Enter zum Schließen … " _ || true; fi exit "$rc" } trap setup_failed ERR echo "==================================================" echo " Omarchy Citizen – Spielbereit machen" echo "==================================================" echo local game health reason game="$(backend_game_status 2>/dev/null || true)" health="$(printf '%s\n' "$game" | kv_get health)" if [[ "$health" == "hardware-blocked" ]]; then reason="$(printf '%s\n' "$game" | kv_get hardware_reason)" echo "Hardware-Prüfung nicht bestanden:" echo "${reason:-Keine Star-Citizen-taugliche Vulkan-GPU erkannt.}" trap - ERR return 2 fi echo "1/4 Autopilot aktivieren und Gaming-Stack synchronisieren …" autopilot_enable echo echo "2/4 Wine-Runner auf diesem Rechner prüfen …" wine_doctor echo echo "3/4 Wine-Prefix, Komponenten und offiziellen RSI Launcher einrichten …" backend_game_install echo echo "4/4 Wartungsstand abschließend synchronisieren …" autopilot_maintain || true trap - ERR echo echo "==================================================" echo " SOFTWARE-STACK BEREIT" echo "==================================================" echo "Der offizielle RSI Launcher ist eingerichtet. Anmeldung und Spieldownload erfolgen dort." notify "Omarchy Citizen" "Software-Stack ist bereit. RSI Launcher kann gestartet werden." } repair_stack_terminal() { set -Ee exec > >(tee -a "$LOG_FILE") 2>&1 repair_failed() { local rc=$? trap - ERR echo echo "REPARATUR FEHLGESCHLAGEN (Code $rc)" echo "Bitte im SC-Panel ein Support-Paket erstellen." notify "Omarchy Citizen" "Reparatur fehlgeschlagen. Bitte Support-Paket erstellen." if [[ -t 0 ]]; then read -r -p "Drücke Enter zum Schließen … " _ || true; fi exit "$rc" } trap repair_failed ERR echo "Omarchy Citizen repariert den verwalteten Gaming-Stack …" autopilot_maintain || true backend_game_repair wine_doctor trap - ERR echo "Reparatur abgeschlossen." notify "Omarchy Citizen" "Gaming-Stack wurde repariert." } update_stack_terminal() { set -Ee exec > >(tee -a "$LOG_FILE") 2>&1 echo "Synchronisiere Wine, DXVK, RSI Launcher und optionale Support-Werkzeuge …" autopilot_maintain echo "Gaming-Stack ist aktuell." } system_update_terminal() { set -Ee exec > >(tee -a "$LOG_FILE") 2>&1 if ! command -v omarchy >/dev/null 2>&1; then echo "Omarchy-Befehl nicht gefunden." >&2 return 1 fi echo "Starte den offiziellen Omarchy-Systemupdate-Pfad …" omarchy update -y } primary_action() { local game health game="$(backend_game_status 2>/dev/null || true)" health="$(printf '%s\n' "$game" | kv_get health)" case "$health" in hardware-blocked) hardware_info ;; ready|install-game) backend_game_launch ;; repair|launcher-repair) launch_terminal_action repair-stack-terminal ;; *) launch_terminal_action install-stack-terminal ;; esac } repair_assistant() { launch_terminal_action repair-stack-terminal } plugin_update_check() { backend_exec check; } plugin_update_now() { backend_exec update; } plugin_auto_enable() { backend_exec auto enable; } plugin_auto_disable() { backend_exec auto disable; } open_file() { local path="$1" [[ -f "$path" ]] || { notify_error "Datei noch nicht vorhanden: $path"; return 1; } xdg-open "$path" >/dev/null 2>&1 & } open_debug_log() { open_file "$LOG_FILE"; } open_updater_log() { open_file "$UPDATER_LOG"; } open_log() { open_file "$RSI_LOG"; } open_wiki() { xdg-open "https://wiki.starcitizen-lug.org" >/dev/null 2>&1 & } open_ngl_page() { xdg-open "https://www.nglproject.com/de#downloads" >/dev/null 2>&1 & } open_helper() { if [[ -x "$MANAGED_LUG" ]]; then nohup env APPIMAGE_EXTRACT_AND_RUN=1 "$MANAGED_LUG" >>"$LOG_FILE" 2>&1 & else open_wiki fi } sanitize_to() { local source="$1" target="$2" [[ -f "$source" ]] || return 0 if command -v python3 >/dev/null 2>&1 && [[ -f "$SANITIZER" ]]; then python3 "$SANITIZER" <"$source" >"$target" else sed -e "s#${HOME//\#/\\#}#~#g" "$source" >"$target" fi } capture_sanitized() { local target="$1"; shift local tmp="$CACHE_DIR/capture-$$-$RANDOM.txt" "$@" >"$tmp" 2>&1 || true sanitize_to "$tmp" "$target" rm -f "$tmp" } support_build() { local stamp support_id work downloads archive game_prefix="" stamp="$(date '+%Y%m%d-%H%M%S')" support_id="OC-${stamp}-$(printf '%s-%s-%s' "$stamp" "$$" "$RANDOM" | sha256sum | cut -c1-6)" downloads="$(command -v xdg-user-dir >/dev/null 2>&1 && xdg-user-dir DOWNLOAD 2>/dev/null || true)" [[ -n "$downloads" && -d "$downloads" ]] || downloads="$HOME/Downloads" mkdir -p "$downloads" work="$CACHE_DIR/support-$support_id" rm -rf "$work" && mkdir -p "$work" cat >"$work/README_SUPPORT.txt" <"$work/citizen-status.txt" 2>&1 || true capture_sanitized "$work/backend-game-status.txt" backend_exec game-status capture_sanitized "$work/backend-status.txt" backend_exec status capture_sanitized "$work/wine-doctor.txt" backend_exec doctor if command -v omarchy >/dev/null 2>&1; then capture_sanitized "$work/plugin-validation.txt" omarchy plugin validate "$PLUGIN_DIR" if omarchy debug --no-sudo --print >"$CACHE_DIR/omarchy-debug-$$.txt" 2>&1; then sanitize_to "$CACHE_DIR/omarchy-debug-$$.txt" "$work/omarchy-debug.txt" else { omarchy version 2>/dev/null || true; omarchy commands --all 2>/dev/null || true; } >"$CACHE_DIR/omarchy-debug-$$.txt" sanitize_to "$CACHE_DIR/omarchy-debug-$$.txt" "$work/omarchy-debug.txt" fi rm -f "$CACHE_DIR/omarchy-debug-$$.txt" fi { echo "=== kernel ==="; uname -rmo 2>/dev/null || true echo "=== memory ==="; free -h 2>/dev/null || true echo "=== filesystems ==="; df -hT / "$HOME" 2>/dev/null || true echo "=== gpu ==="; lspci -nnk 2>/dev/null | grep -A5 -Ei 'VGA|3D|Display' || true echo "=== vulkan ==="; command -v vulkaninfo >/dev/null 2>&1 && vulkaninfo --summary 2>&1 || true echo "=== limits ==="; sysctl vm.max_map_count 2>/dev/null || true; ulimit -Hn 2>/dev/null || true } >"$CACHE_DIR/system-$$.txt" sanitize_to "$CACHE_DIR/system-$$.txt" "$work/system.txt" rm -f "$CACHE_DIR/system-$$.txt" for pair in "$LOG_FILE:omarchy-citizen.log" "$UPDATER_LOG:updater.log" "$RSI_LOG:rsi-launcher.log"; do local src="${pair%%:*}" dst="${pair#*:}" if [[ -f "$src" ]]; then tail -n 1800 "$src" >"$CACHE_DIR/log-$$.txt" sanitize_to "$CACHE_DIR/log-$$.txt" "$work/$dst" rm -f "$CACHE_DIR/log-$$.txt" fi done game_prefix="$(backend_game_status 2>/dev/null | kv_get prefix || true)" if [[ -n "$game_prefix" ]]; then printf 'prefix=%s\n' "$game_prefix" >"$CACHE_DIR/prefix-$$.txt" sanitize_to "$CACHE_DIR/prefix-$$.txt" "$work/prefix.txt" rm -f "$CACHE_DIR/prefix-$$.txt" fi archive="$downloads/Omarchy-Citizen-Support-$support_id.tar.gz" tar -C "$work" -czf "$archive" . rm -rf "$work" notify "Omarchy Citizen" "Support-Paket erstellt: $(basename "$archive")" command -v xdg-open >/dev/null 2>&1 && xdg-open "$downloads" >/dev/null 2>&1 & printf '%s\n' "$archive" } ngl_info() { local msg="NGL ist ein optionaler Community-Mod für den RSI Launcher. Omarchy Citizen installiert ihn nur nach deiner ausdrücklichen Dateiauswahl in den verwalteten Wine-Prefix." if command -v zenity >/dev/null 2>&1; then zenity --info --width=540 --title="Omarchy Citizen – NGL" --text="$msg" >/dev/null 2>&1 || true else printf '%s\n' "$msg" fi } ngl_install() { local game prefix selected exe workdir wine command -v zenity >/dev/null 2>&1 || { notify_error "Für die NGL-Dateiauswahl wird zenity benötigt."; return 1; } game="$(backend_game_status)" prefix="$(printf '%s\n' "$game" | kv_get prefix)" [[ -n "$prefix" && -d "$prefix/drive_c" ]] || { notify_error "Star Citizen muss zuerst eingerichtet werden."; return 1; } wine="$(readlink -f "$MANAGED_WINE")/bin/wine" [[ -x "$wine" ]] || { notify_error "Der verwaltete Wine-Runner fehlt."; return 1; } selected="$(zenity --file-selection --title="NGL Installer auswählen" --filename="$HOME/Downloads/" \ --file-filter="NGL Installer | *.zip *.ZIP *.exe *.EXE" --file-filter="Alle Dateien | *" 2>/dev/null)" || return 0 [[ -f "$selected" ]] || return 1 exe="$selected" if [[ "${selected,,}" == *.zip ]]; then workdir="$CACHE_DIR/ngl-installer" rm -rf "$workdir" && mkdir -p "$workdir" unzip -q "$selected" -d "$workdir" || { notify_error "NGL-ZIP konnte nicht entpackt werden."; return 1; } exe="$(find "$workdir" -type f -iname '*.exe' -print -quit)" fi [[ -n "$exe" && -f "$exe" ]] || { notify_error "Kein Windows-Installer gefunden."; return 1; } zenity --question --width=560 --title="NGL experimentell installieren?" \ --ok-label="Installer starten" --cancel-label="Abbrechen" \ --text="NGL ist Community-Code und läuft im selben Wine-Prefix wie der RSI Launcher. Bitte nur Downloads von nglproject.com verwenden." 2>/dev/null || return 0 nohup env WINEPREFIX="$prefix" WINEDEBUG=-all "$wine" "$exe" >>"$LOG_FILE" 2>&1 & } case "$ACTION" in status) status ;; hardware-info) hardware_info ;; primary) primary_action ;; setup|install-stack) launch_terminal_action install-stack-terminal ;; install-stack-terminal) install_stack_terminal ;; repair|repair-interactive|recover-prefix) repair_assistant ;; repair-stack-terminal|recover-prefix-terminal) repair_stack_terminal ;; support|support-build) support_build ;; debug-log) open_debug_log ;; log) open_log ;; updater-log) open_updater_log ;; plugin-update-check|check-update) plugin_update_check ;; plugin-update-now) plugin_update_now ;; plugin-auto-enable) plugin_auto_enable ;; plugin-auto-disable) plugin_auto_disable ;; autopilot-enable) autopilot_enable ;; autopilot-maintain|update-helper) launch_terminal_action update-stack-terminal ;; update-stack-terminal|update-helper-terminal) update_stack_terminal ;; wine-doctor) launch_terminal_action wine-doctor-terminal ;; wine-doctor-terminal) set -e; wine_doctor ;; system-update) launch_terminal_action system-update-terminal ;; system-update-terminal) system_update_terminal ;; launch) backend_game_launch ;; helper) open_helper ;; preflight) open_helper ;; runners|dxvk) launch_terminal_action update-stack-terminal ;; repair-launch|repair-rsi) launch_terminal_action repair-stack-terminal ;; dirs) ( xdg-open "$DATA_DIR" >/dev/null 2>&1 & ) ;; wiki) open_wiki ;; ngl-page) open_ngl_page ;; ngl-info) ngl_info ;; ngl-install|ngl-install-interactive) ngl_install ;; *) notify_error "Unbekannte Omarchy-Citizen-Aktion: $ACTION"; exit 2 ;; esac