From 04a0ae6cf9bb20f1f6114107656c64774aa4db5c Mon Sep 17 00:00:00 2001 From: Overlord Date: Fri, 17 Apr 2026 14:24:53 +0200 Subject: [PATCH] init --- README.md | 145 +++++++++++++++++++ init/openrc/gzw-eac-fix.desktop | 7 + init/runit/log/run | 2 + init/runit/run | 2 + init/s6/run | 2 + init/systemd/gzw-eac-fix.path | 8 ++ init/systemd/gzw-eac-fix.service | 10 ++ scripts/fix.sh | 197 +++++++++++++++++++++++++ scripts/watch.sh | 52 +++++++ setup.sh | 237 +++++++++++++++++++++++++++++++ uninstall.sh | 102 +++++++++++++ 11 files changed, 764 insertions(+) create mode 100644 README.md create mode 100644 init/openrc/gzw-eac-fix.desktop create mode 100644 init/runit/log/run create mode 100644 init/runit/run create mode 100644 init/s6/run create mode 100644 init/systemd/gzw-eac-fix.path create mode 100644 init/systemd/gzw-eac-fix.service create mode 100644 scripts/fix.sh create mode 100644 scripts/watch.sh create mode 100644 setup.sh create mode 100644 uninstall.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..14106da --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# gzw-eac-fix + +Automatically applies the EAC cache fix for **Gray Zone Warfare** on Linux after each Steam update. + +After an update, EAC leaves two stale cache files that prevent the game from launching. The fix is to delete them, let Steam verify and restore them, then lock them read-only. This repo automates that via a background watcher registered with your init system. + +## Requirements + +- Steam installed (native or Flatpak) +- Gray Zone Warfare installed +- `inotify-tools` - only required for non-systemd init systems + +## Install + +```bash +git clone https://github.com/youruser/gzw-eac-fix.git +cd gzw-eac-fix +bash setup.sh +``` + +`setup.sh` will: +- Auto-detect your Steam install and GZW location (including Flatpak and custom library paths) +- Substitute all configuration into the installed scripts +- Register a watcher appropriate for your init system +- Run an initial fix immediately so the game is ready to launch right away + +## Expected output (systemd) + +``` +$> bash setup.sh +[gzw-eac-fix] Found manifest: /home/user/.local/share/Steam/steamapps/appmanifest_2479810.acf +[gzw-eac-fix] Installing scripts to /home/user/.local/share/gzw-eac-fix... +[gzw-eac-fix] Installed fix.sh +[gzw-eac-fix] Installed watch.sh +[gzw-eac-fix] Detected init system: systemd +[gzw-eac-fix] Installed gzw-eac-fix.path +[gzw-eac-fix] Installed gzw-eac-fix.service +Created symlink '/home/user/.config/systemd/user/default.target.wants/gzw-eac-fix.path' → '/home/user/.config/systemd/user/gzw-eac-fix.path'. +[gzw-eac-fix] systemd path watcher enabled. +● gzw-eac-fix.path - Watch for Gray Zone Warfare updates + Loaded: loaded (/home/user/.config/systemd/user/gzw-eac-fix.path; enabled; preset: enabled) + Active: active (waiting) since Fri 2026-04-17 14:20:13 CEST; 5ms ago + Triggers: ● gzw-eac-fix.service +[gzw-eac-fix] Logs: journalctl --user -u gzw-eac-fix.service +[gzw-eac-fix] or: /home/user/.local/share/gzw-eac-fix/gzw-eac-fix.log +[gzw-eac-fix] Setup complete. +[gzw-eac-fix] Running initial fix... +[gzw-eac-fix] GZW found at: /home/user/.local/share/Steam/steamapps/common/Gray Zone Warfare/GZW/Content/SKALLA/PrebuildWorldData/World/cache +[gzw-eac-fix] No previous state - running fix and recording baseline. +[gzw-eac-fix] Flushing disk before delete... +[gzw-eac-fix] Removing EAC cache files... +[gzw-eac-fix] Removed: 0xb9af63cee2e43b6c_0x3cb3b3354fb31606.dat +[gzw-eac-fix] Removed: 0xaf497c273f87b6e4_0x7a22fc105639587d.dat +[gzw-eac-fix] Triggering Steam verify integrity (app 2479810)... +[gzw-eac-fix] Waiting for Steam to restore files... +[gzw-eac-fix] Restored: 0xb9af63cee2e43b6c_0x3cb3b3354fb31606.dat +[gzw-eac-fix] Restored: 0xaf497c273f87b6e4_0x7a22fc105639587d.dat +[gzw-eac-fix] Flushing disk after restore... +[gzw-eac-fix] Setting files read-only... +[gzw-eac-fix] chmod 400: 0xb9af63cee2e43b6c_0x3cb3b3354fb31606.dat +[gzw-eac-fix] chmod 400: 0xaf497c273f87b6e4_0x7a22fc105639587d.dat +[gzw-eac-fix] Done. +``` + +After this completes the game is ready to launch. + +## How it works + +1. Steam rewrites `appmanifest_2479810.acf` when an update completes. +2. The watcher detects the change and calls `fix.sh`. +3. `fix.sh` computes a fingerprint from the `buildid` + all `InstalledDepots` manifest IDs. If unchanged since the last run it exits early - prevents spurious runs from non-update manifest writes. +4. If an update is confirmed: `sync`, delete the two EAC cache files, trigger `steam://validate/` to restore them, `sync` again, then `chmod 400` both. + +## Supported init systems + +| Init system | Mechanism | +|-------------|-----------| +| **systemd** | `.path` unit - event-driven, no polling | +| **OpenRC** | XDG autostart + `inotifywait` loop | +| **runit** | User sv service + `inotifywait` loop | +| **s6** | User sv service + `inotifywait` loop | +| **Other** | XDG autostart + `inotifywait` loop | + +## Configuration + +All options live at the top of `setup.sh`. Re-run `setup.sh` to apply any changes. + +| Option | Default | Description | +|--------|---------|-------------| +| `NOTIFY` | `false` | Desktop notifications via `notify-send` | +| `LOG_MAX_LINES` | `200` | Max lines retained in the log file | +| `POLL_INTERVAL` | `3` | Seconds between file-exists checks during Steam verify | +| `POST_RESTORE_WAIT` | `2` | Seconds to wait after files reappear before `chmod` | + +## Files installed + +``` +~/.local/share/gzw-eac-fix/ + fix.sh # fix logic, config baked in at install time + watch.sh # inotifywait loop - non-systemd only + gzw-eac-fix.log # appended on every run + .last_known_state # build fingerprint for update detection + log/ # svlogd log dir - runit only +``` + +## Logs + +```bash +# systemd +journalctl --user -u gzw-eac-fix.service + +# all init systems +tail -f ~/.local/share/gzw-eac-fix/gzw-eac-fix.log +``` + +## Uninstall + +```bash +bash uninstall.sh +``` + +Detects and removes everything that was created by `setup.sh` - unit files, autostart entries, sv directories, and the install directory. + +## Repository structure + +``` +gzw-eac-fix/ + README.md + setup.sh # install - edit this to configure + uninstall.sh # removes everything setup.sh created + scripts/ + fix.sh # EAC fix logic (@@TOKEN@@ placeholders, substituted at install) + watch.sh # inotifywait watcher (non-systemd only) + init/ + systemd/ + gzw-eac-fix.path + gzw-eac-fix.service + openrc/ + gzw-eac-fix.desktop + runit/ + run + log/run + s6/ + run +``` diff --git a/init/openrc/gzw-eac-fix.desktop b/init/openrc/gzw-eac-fix.desktop new file mode 100644 index 0000000..c57af3b --- /dev/null +++ b/init/openrc/gzw-eac-fix.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name=GZW EAC Fix Watcher +Comment=Watches for Gray Zone Warfare updates and applies EAC cache fix +Exec=@@INSTALL_DIR@@/watch.sh +Terminal=false +X-GNOME-Autostart-enabled=true diff --git a/init/runit/log/run b/init/runit/log/run new file mode 100644 index 0000000..7511e73 --- /dev/null +++ b/init/runit/log/run @@ -0,0 +1,2 @@ +#!/bin/sh +exec svlogd -tt @@INSTALL_DIR@@/log diff --git a/init/runit/run b/init/runit/run new file mode 100644 index 0000000..1ad3fad --- /dev/null +++ b/init/runit/run @@ -0,0 +1,2 @@ +#!/bin/sh +exec @@INSTALL_DIR@@/watch.sh diff --git a/init/s6/run b/init/s6/run new file mode 100644 index 0000000..fc5abf9 --- /dev/null +++ b/init/s6/run @@ -0,0 +1,2 @@ +#!/bin/execlineb -P +@@INSTALL_DIR@@/watch.sh diff --git a/init/systemd/gzw-eac-fix.path b/init/systemd/gzw-eac-fix.path new file mode 100644 index 0000000..1cb478c --- /dev/null +++ b/init/systemd/gzw-eac-fix.path @@ -0,0 +1,8 @@ +[Unit] +Description=Watch for Gray Zone Warfare updates + +[Path] +PathChanged=@@MANIFEST_PATH@@ + +[Install] +WantedBy=default.target diff --git a/init/systemd/gzw-eac-fix.service b/init/systemd/gzw-eac-fix.service new file mode 100644 index 0000000..7d70ede --- /dev/null +++ b/init/systemd/gzw-eac-fix.service @@ -0,0 +1,10 @@ +[Unit] +Description=GZW EAC fix - post-update cache patch + +[Service] +Type=oneshot +ExecStart=@@INSTALL_DIR@@/fix.sh +Environment=DISPLAY=:0 +PassEnvironment=DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR +StandardOutput=journal +StandardError=journal diff --git a/scripts/fix.sh b/scripts/fix.sh new file mode 100644 index 0000000..8034346 --- /dev/null +++ b/scripts/fix.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +set -uo pipefail + +# set by setup.sh via @@TOKEN@@ substitution + +STEAM_APP_ID="@@STEAM_APP_ID@@" +SERVICE_NAME="@@SERVICE_NAME@@" +INSTALL_DIR="@@INSTALL_DIR@@" +NOTIFY="@@NOTIFY@@" +LOG_MAX_LINES="@@LOG_MAX_LINES@@" +POLL_INTERVAL="@@POLL_INTERVAL@@" +POST_RESTORE_WAIT="@@POST_RESTORE_WAIT@@" + +GAME_SUBPATH="common/Gray Zone Warfare/GZW/Content/SKALLA/PrebuildWorldData/World/cache" +MANIFEST_NAME="appmanifest_${STEAM_APP_ID}.acf" + +EAC_FILES=( + "0xb9af63cee2e43b6c_0x3cb3b3354fb31606.dat" + "0xaf497c273f87b6e4_0x7a22fc105639587d.dat" +) + +LOG_FILE="$INSTALL_DIR/${SERVICE_NAME}.log" +STATE_FILE="$INSTALL_DIR/.last_known_state" + +# ─── logging ────────────────────────────────────────────────────────────────── + +BLUE='\033[0;34m' +RED='\033[0;31m' +NC='\033[0m' +PREFIX="[${SERVICE_NAME}]" + +mkdir -p "$INSTALL_DIR" + +_ts() { date "+%Y-%m-%d %H:%M:%S"; } + +{ + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " [Gray Zone Warfare EAC Fix] - $(_ts)" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +} >> "$LOG_FILE" + +# Trim log to LOG_MAX_LINES +_lines=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) +if (( _lines > LOG_MAX_LINES )); then + tail -n "$LOG_MAX_LINES" "$LOG_FILE" > "${LOG_FILE}.tmp" && mv "${LOG_FILE}.tmp" "$LOG_FILE" +fi + +log_info() { + local msg="$1" + printf "${BLUE}${PREFIX}${NC} %s\n" "$msg" + echo "[$(_ts)] [INFO] $msg" >> "$LOG_FILE" +} + +log_warn() { + local msg="$1" + printf "${BLUE}${PREFIX}${NC} ⚠ %s\n" "$msg" + echo "[$(_ts)] [WARN] $msg" >> "$LOG_FILE" +} + +log_error() { + local msg="$1" + printf "${RED}${PREFIX} Error: %s${NC}\n" "$msg" >&2 + echo "[$(_ts)] [ERROR] $msg" >> "$LOG_FILE" +} + +die() { log_error "$1"; exit 1; } + +_notify() { + [[ "$NOTIFY" != "true" ]] && return + command -v notify-send &>/dev/null || return + notify-send -a "$SERVICE_NAME" "$1" "$2" 2>/dev/null || true +} + +# ─── auto-detect steam library ──────────────────────────────────────────────── + +find_steam_library() { + local bases=( + "$HOME/.local/share/Steam" + "$HOME/.steam/steam" + "$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam" + ) + + declare -A seen + local all=() + + for base in "${bases[@]}"; do + [[ ! -d "$base/steamapps" ]] && continue + local real; real=$(realpath "$base" 2>/dev/null || echo "$base") + [[ "${seen[$real]+x}" ]] && continue + seen["$real"]=1 + all+=("$base/steamapps") + + local vdf="$base/steamapps/libraryfolders.vdf" + [[ ! -f "$vdf" ]] && continue + while IFS= read -r line; do + local p; p=$(awk -F'"' '/"path"/{print $4}' <<< "$line") + [[ -n "$p" && -d "$p/steamapps" ]] && all+=("$p/steamapps") + done < "$vdf" + done + + [[ ${#all[@]} -eq 0 ]] && return 1 + + for lib in "${all[@]}"; do + [[ -d "$lib/$GAME_SUBPATH" ]] && echo "$lib" && return 0 + done + return 1 +} + +STEAM_APPS=$(find_steam_library) || die "Gray Zone Warfare not found in any Steam library." +CACHE_DIR="$STEAM_APPS/$GAME_SUBPATH" +MANIFEST="$STEAM_APPS/$MANIFEST_NAME" + +log_info "GZW found at: $CACHE_DIR" + +# ─── build state detection ──────────────────────────────────────────────────── +# +# Fingerprint = buildid + all InstalledDepots manifest IDs (sorted). +# Depot manifests rotate on every content update even if the buildid doesn't. +# If fingerprint matches last run, skip the fix — nothing was actually updated. + +read_game_state() { + local acf="$1" + local buildid + buildid=$(grep -m1 '"buildid"' "$acf" | awk -F'"' '{print $4}') + + local depots + depots=$(awk ' + /"InstalledDepots"/ { in_depots=1; depth=0; next } + in_depots && /\{/ { depth++ } + in_depots && /\}/ { depth--; if (depth < 0) in_depots=0 } + in_depots && /"manifest"/ { gsub(/"/, ""); print $2 } + ' "$acf" | sort | paste -sd ':') + + [[ -z "$buildid" || -z "$depots" ]] && return 1 + echo "${buildid}:${depots}" +} + +[[ -f "$MANIFEST" ]] || die "Steam manifest not found: $MANIFEST" + +CURRENT_STATE=$(read_game_state "$MANIFEST") \ + || die "Could not parse build ID or depot manifests from ACF." + +LAST_STATE=$(cat "$STATE_FILE" 2>/dev/null || echo "") + +if [[ "$CURRENT_STATE" == "$LAST_STATE" ]]; then + log_info "No update detected (state unchanged). Skipping." + exit 0 +fi + +if [[ -n "$LAST_STATE" ]]; then + log_info "Update detected." + log_info " Previous: $LAST_STATE" + log_info " Current: $CURRENT_STATE" +else + log_info "No previous state — running fix and recording baseline." +fi + +# ─── fix ────────────────────────────────────────────────────────────────────── + +_notify -i dialog-information "Applying EAC cache fix..." + +log_info "Flushing disk before delete..." +sync + +log_info "Removing EAC cache files..." +for f in "${EAC_FILES[@]}"; do + rm -f "$CACHE_DIR/$f" + log_info " Removed: $f" +done + +log_info "Triggering Steam verify integrity (app $STEAM_APP_ID)..." +steam "steam://validate/$STEAM_APP_ID" + +log_info "Waiting for Steam to restore files..." +for f in "${EAC_FILES[@]}"; do + while [[ ! -f "$CACHE_DIR/$f" ]]; do + sleep "$POLL_INTERVAL" + done + log_info " Restored: $f" +done + +log_info "Flushing disk after restore..." +sync + +sleep "$POST_RESTORE_WAIT" + +log_info "Setting files read-only..." +for f in "${EAC_FILES[@]}"; do + chmod 400 "$CACHE_DIR/$f" + log_info " chmod 400: $f" +done + +echo "$CURRENT_STATE" > "$STATE_FILE" + +_notify -i dialog-information "EAC cache fix applied." +log_info "Done." diff --git a/scripts/watch.sh b/scripts/watch.sh new file mode 100644 index 0000000..6491827 --- /dev/null +++ b/scripts/watch.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -uo pipefail + +# set by setup.sh via @@TOKEN@@ substitution + +SERVICE_NAME="@@SERVICE_NAME@@" +INSTALL_DIR="@@INSTALL_DIR@@" +MANIFEST_PATH="@@MANIFEST_PATH@@" + + +LOG_FILE="$INSTALL_DIR/${SERVICE_NAME}.log" +FIX_SCRIPT="$INSTALL_DIR/fix.sh" + +BLUE='\033[0;34m' +RED='\033[0;31m' +NC='\033[0m' +PREFIX="[${SERVICE_NAME}]" + +mkdir -p "$INSTALL_DIR" + +_ts() { date "+%Y-%m-%d %H:%M:%S"; } + +log_info() { + local msg="$1" + printf "${BLUE}${PREFIX}${NC} %s\n" "$msg" + echo "[$(_ts)] [INFO] $msg" >> "$LOG_FILE" +} + +log_error() { + local msg="$1" + printf "${RED}${PREFIX} Error: %s${NC}\n" "$msg" >&2 + echo "[$(_ts)] [ERROR] $msg" >> "$LOG_FILE" +} + +die() { log_error "$1"; exit 1; } + +command -v inotifywait &>/dev/null \ + || die "inotifywait not found - install inotify-tools" + +[[ -f "$MANIFEST_PATH" ]] \ + || die "Manifest not found: $MANIFEST_PATH - was the game uninstalled or moved? Re-run setup.sh." + +[[ -x "$FIX_SCRIPT" ]] \ + || die "fix.sh not found or not executable at $FIX_SCRIPT - re-run setup.sh." + +log_info "Watching: $MANIFEST_PATH" + +inotifywait -m -e close_write "$MANIFEST_PATH" 2>/dev/null \ + | while read -r _dir _event _file; do + log_info "Manifest changed — invoking fix..." + bash "$FIX_SCRIPT" + done diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..27ecc3f --- /dev/null +++ b/setup.sh @@ -0,0 +1,237 @@ +#!/usr/bin/env bash +set -uo pipefail + +# Configuration: +# Edit these if needed. +# Everything else is derived from these values. + +STEAM_APP_ID="2479810" +SERVICE_NAME="gzw-eac-fix" +INSTALL_DIR="$HOME/.local/share/gzw-eac-fix" + +NOTIFY="false" # set to true to enable desktop notifications via notify-send +LOG_MAX_LINES="200" # max lines kept in the log file +POLL_INTERVAL="3" # seconds between file-exists checks after steam validate +POST_RESTORE_WAIT="2" # seconds to wait after files reappear before chmod + +BLUE='\033[0;34m' +RED='\033[0;31m' +NC='\033[0m' +PREFIX="[${SERVICE_NAME}]" + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOG_FILE="$INSTALL_DIR/${SERVICE_NAME}.log" + +# create install dir early so we can log immediately +mkdir -p "$INSTALL_DIR" + +_ts() { date "+%Y-%m-%d %H:%M:%S"; } + +log_info() { + local msg="$1" + printf "${BLUE}${PREFIX}${NC} %s\n" "$msg" + echo "[$(_ts)] [INFO] $msg" >> "$LOG_FILE" +} + +log_warn() { + local msg="$1" + printf "${BLUE}${PREFIX}${NC} ⚠ %s\n" "$msg" + echo "[$(_ts)] [WARN] $msg" >> "$LOG_FILE" +} + +log_error() { + local msg="$1" + printf "${RED}${PREFIX} Error: %s${NC}\n" "$msg" >&2 + echo "[$(_ts)] [ERROR] $msg" >> "$LOG_FILE" +} + +die() { log_error "$1"; exit 1; } + +{ + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " [Gray Zone Warfare EAC Fix] Setup - $(_ts)" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +} >> "$LOG_FILE" + +# Guard against downloading individual files instead of cloning the repo +for required_dir in scripts init; do + [[ -d "$REPO_DIR/$required_dir" ]] || die \ + "Directory '$required_dir/' not found in $REPO_DIR. " \ + "Please clone the full repository rather than downloading files individually." +done + +command -v steam &>/dev/null || die "'steam' not found in PATH - is Steam installed?" +command -v awk &>/dev/null || die "'awk' not found" +command -v sed &>/dev/null || die "'sed' not found" + +find_manifest() { + local bases=( + "$HOME/.local/share/Steam" + "$HOME/.steam/steam" + "$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam" + ) + + declare -A seen + local all=() + + for base in "${bases[@]}"; do + [[ ! -d "$base/steamapps" ]] && continue + local real; real=$(realpath "$base" 2>/dev/null || echo "$base") + [[ "${seen[$real]+x}" ]] && continue + seen["$real"]=1 + all+=("$base/steamapps") + + local vdf="$base/steamapps/libraryfolders.vdf" + [[ ! -f "$vdf" ]] && continue + while IFS= read -r line; do + local p; p=$(awk -F'"' '/"path"/{print $4}' <<< "$line") + [[ -n "$p" && -d "$p/steamapps" ]] && all+=("$p/steamapps") + done < "$vdf" + done + + [[ ${#all[@]} -eq 0 ]] && return 1 + + for lib in "${all[@]}"; do + local m="$lib/appmanifest_${STEAM_APP_ID}.acf" + [[ -f "$m" ]] && echo "$m" && return 0 + done + return 1 +} + +MANIFEST_PATH=$(find_manifest) || die \ + "appmanifest_${STEAM_APP_ID}.acf not found. " \ + "Is Gray Zone Warfare installed? Try: steam://install/${STEAM_APP_ID}" + +log_info "Found manifest: $MANIFEST_PATH" + +# Replaces all @@TOKEN@@ placeholders in a source file and writes the result +# to a destination. This is the single place all config values are baked in. + +substitute() { + local src="$1" + local dst="$2" + sed \ + -e "s|@@INSTALL_DIR@@|${INSTALL_DIR}|g" \ + -e "s|@@STEAM_APP_ID@@|${STEAM_APP_ID}|g" \ + -e "s|@@MANIFEST_PATH@@|${MANIFEST_PATH}|g" \ + -e "s|@@SERVICE_NAME@@|${SERVICE_NAME}|g" \ + -e "s|@@NOTIFY@@|${NOTIFY}|g" \ + -e "s|@@LOG_MAX_LINES@@|${LOG_MAX_LINES}|g" \ + -e "s|@@POLL_INTERVAL@@|${POLL_INTERVAL}|g" \ + -e "s|@@POST_RESTORE_WAIT@@|${POST_RESTORE_WAIT}|g" \ + "$src" > "$dst" +} + +log_info "Installing scripts to $INSTALL_DIR..." + +for script in fix.sh watch.sh; do + substitute "$REPO_DIR/scripts/$script" "$INSTALL_DIR/$script" + chmod +x "$INSTALL_DIR/$script" + log_info " Installed $script" +done + +detect_init() { + [[ -d /run/systemd/system ]] || systemctl --version &>/dev/null 2>&1 && { echo "systemd"; return; } + command -v rc-service &>/dev/null || [[ -x /sbin/openrc ]] && { echo "openrc"; return; } + command -v runit &>/dev/null || [[ -d /run/runit ]] && { echo "runit"; return; } + command -v s6-svscan &>/dev/null && { echo "s6"; return; } + echo "unknown" +} + +INIT_SYSTEM=$(detect_init) +log_info "Detected init system: $INIT_SYSTEM" + +INIT_SRC="$REPO_DIR/init/$INIT_SYSTEM" + +case "$INIT_SYSTEM" in + + systemd) + SYSTEMD_USER_DIR="$HOME/.config/systemd/user" + mkdir -p "$SYSTEMD_USER_DIR" + + for unit in "${SERVICE_NAME}.path" "${SERVICE_NAME}.service"; do + substitute "$INIT_SRC/$unit" "$SYSTEMD_USER_DIR/$unit" + log_info " Installed $unit" + done + + systemctl --user daemon-reload + systemctl --user enable --now "${SERVICE_NAME}.path" + log_info "systemd path watcher enabled." + echo "" + systemctl --user status "${SERVICE_NAME}.path" --no-pager + echo "" + log_info "Logs: journalctl --user -u ${SERVICE_NAME}.service" + log_info " or: $LOG_FILE" + ;; + + openrc) + command -v inotifywait &>/dev/null || \ + log_warn "inotifywait not found - install inotify-tools" + + XDG_AUTOSTART_DIR="$HOME/.config/autostart" + mkdir -p "$XDG_AUTOSTART_DIR" + substitute "$INIT_SRC/${SERVICE_NAME}.desktop" \ + "$XDG_AUTOSTART_DIR/${SERVICE_NAME}.desktop" + log_info "XDG autostart entry written." + log_warn "Watcher starts on next desktop login." + log_info "Logs: $LOG_FILE" + ;; + + runit) + command -v inotifywait &>/dev/null || \ + log_warn "inotifywait not found - install inotify-tools" + + SV_DIR="$HOME/sv/${SERVICE_NAME}" + mkdir -p "$SV_DIR/log" + substitute "$INIT_SRC/run" "$SV_DIR/run" + substitute "$INIT_SRC/log/run" "$SV_DIR/log/run" + chmod +x "$SV_DIR/run" "$SV_DIR/log/run" + mkdir -p "$INSTALL_DIR/log" + log_info "Runit service written to $SV_DIR" + log_warn "Link it into your supervision tree to activate, e.g.:" + log_warn " ln -s $SV_DIR ~/.local/share/runit/sv/${SERVICE_NAME} (Void Linux)" + log_info "Logs: $INSTALL_DIR/log/" + ;; + + s6) + command -v inotifywait &>/dev/null || \ + log_warn "inotifywait not found - install inotify-tools" + + S6_DIR="$HOME/.config/s6/sv/${SERVICE_NAME}" + mkdir -p "$S6_DIR" + substitute "$INIT_SRC/run" "$S6_DIR/run" + chmod +x "$S6_DIR/run" + log_info "s6 service written to $S6_DIR" + log_warn "Link into your scan directory and reload, e.g.:" + log_warn " ln -s $S6_DIR \$S6_SCAN_DIR/${SERVICE_NAME}" + log_warn " s6-svscanctl -a \$S6_SCAN_DIR" + log_info "Logs: $LOG_FILE" + ;; + + *) + command -v inotifywait &>/dev/null || \ + log_warn "inotifywait not found - install inotify-tools" + + XDG_AUTOSTART_DIR="$HOME/.config/autostart" + mkdir -p "$XDG_AUTOSTART_DIR" + + # No matching init/ template - write a generic desktop entry directly + cat > "$XDG_AUTOSTART_DIR/${SERVICE_NAME}.desktop" << EOF +[Desktop Entry] +Type=Application +Name=GZW EAC Fix Watcher +Exec=${INSTALL_DIR}/watch.sh +Terminal=false +X-GNOME-Autostart-enabled=true +EOF + log_warn "Unrecognized init system - fell back to XDG autostart." + log_warn "Watcher starts on next desktop login." + log_info "Logs: $LOG_FILE" + ;; +esac + +echo "" +log_info "Setup complete." +log_info "Running initial fix..." +bash "$INSTALL_DIR/fix.sh" diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 0000000..5c95bd6 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -uo pipefail + +SERVICE_NAME="gzw-eac-fix" +INSTALL_DIR="$HOME/.local/share/gzw-eac-fix" + +BLUE='\033[0;34m' +RED='\033[0;31m' +NC='\033[0m' +PREFIX="[${SERVICE_NAME}]" + +log_info() { printf "${BLUE}${PREFIX}${NC} %s\n" "$1"; } +log_warn() { printf "${BLUE}${PREFIX}${NC} ⚠ %s\n" "$1"; } +log_error(){ printf "${RED}${PREFIX} Error: %s${NC}\n" "$1" >&2; } +die() { log_error "$1"; exit 1; } + +echo "" +printf "${BLUE}${PREFIX}${NC} This will remove:\n" +printf " %s\n" "$INSTALL_DIR" +printf " systemd units, autostart entries, or sv dirs created by setup.sh\n" +echo "" +read -r -p "Continue? [y/N] " confirm +[[ "${confirm,,}" == "y" ]] || { log_info "Aborted."; exit 0; } +echo "" + +removed=0 + +SYSTEMD_USER_DIR="$HOME/.config/systemd/user" + +if [[ -f "$SYSTEMD_USER_DIR/${SERVICE_NAME}.path" || \ + -f "$SYSTEMD_USER_DIR/${SERVICE_NAME}.service" ]]; then + log_info "Removing systemd units..." + + systemctl --user stop "${SERVICE_NAME}.path" 2>/dev/null \ + && log_info " Stopped ${SERVICE_NAME}.path" || true + systemctl --user stop "${SERVICE_NAME}.service" 2>/dev/null || true + + systemctl --user disable "${SERVICE_NAME}.path" 2>/dev/null \ + && log_info " Disabled ${SERVICE_NAME}.path" || true + + for wants_dir in \ + "$SYSTEMD_USER_DIR/default.target.wants" \ + "$SYSTEMD_USER_DIR/multi-user.target.wants"; do + local_link="$wants_dir/${SERVICE_NAME}.path" + if [[ -L "$local_link" ]]; then + rm -f "$local_link" + log_info " Removed stale symlink: $local_link" + fi + done + + for unit in "${SERVICE_NAME}.path" "${SERVICE_NAME}.service"; do + if [[ -f "$SYSTEMD_USER_DIR/$unit" ]]; then + rm -f "$SYSTEMD_USER_DIR/$unit" + log_info " Removed $SYSTEMD_USER_DIR/$unit" + (( removed++ )) || true + fi + done + + systemctl --user daemon-reload + log_info " Daemon reloaded." +fi + + +DESKTOP_FILE="$HOME/.config/autostart/${SERVICE_NAME}.desktop" +if [[ -f "$DESKTOP_FILE" ]]; then + log_info "Removing XDG autostart entry..." + rm -f "$DESKTOP_FILE" + log_info " Removed $DESKTOP_FILE" + (( removed++ )) || true +fi + +RUNIT_SV_DIR="$HOME/sv/${SERVICE_NAME}" +if [[ -d "$RUNIT_SV_DIR" ]]; then + log_info "Removing runit service..." + sv stop "$SERVICE_NAME" 2>/dev/null || true + rm -rf "$RUNIT_SV_DIR" + log_info " Removed $RUNIT_SV_DIR" + (( removed++ )) || true +fi + +S6_SV_DIR="$HOME/.config/s6/sv/${SERVICE_NAME}" +if [[ -d "$S6_SV_DIR" ]]; then + log_info "Removing s6 service..." + s6-svc -d "$S6_SV_DIR" 2>/dev/null || true + rm -rf "$S6_SV_DIR" + log_info " Removed $S6_SV_DIR" + (( removed++ )) || true +fi + +if [[ -d "$INSTALL_DIR" ]]; then + log_info "Removing install directory..." + rm -rf "$INSTALL_DIR" + log_info " Removed $INSTALL_DIR" + (( removed++ )) || true +fi + +echo "" +if (( removed > 0 )); then + log_info "Uninstall complete." +else + log_warn "Nothing found to remove - was setup.sh ever run?" +fi