This commit is contained in:
2026-04-17 14:24:53 +02:00
commit 04a0ae6cf9
11 changed files with 764 additions and 0 deletions

145
README.md Normal file
View File

@@ -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
```

View File

@@ -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

2
init/runit/log/run Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
exec svlogd -tt @@INSTALL_DIR@@/log

2
init/runit/run Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
exec @@INSTALL_DIR@@/watch.sh

2
init/s6/run Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/execlineb -P
@@INSTALL_DIR@@/watch.sh

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Watch for Gray Zone Warfare updates
[Path]
PathChanged=@@MANIFEST_PATH@@
[Install]
WantedBy=default.target

View File

@@ -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

197
scripts/fix.sh Normal file
View File

@@ -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."

52
scripts/watch.sh Normal file
View File

@@ -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

237
setup.sh Normal file
View File

@@ -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"

102
uninstall.sh Normal file
View File

@@ -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