-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathinstall-linux.sh
More file actions
executable file
·584 lines (480 loc) · 20 KB
/
install-linux.sh
File metadata and controls
executable file
·584 lines (480 loc) · 20 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
#!/bin/bash
# Deadlock API Ingest - Linux Installation Script
# This script downloads and installs the deadlock-api-ingest application as a systemd user service
set -euo pipefail
# --- Configuration ---
APP_NAME="deadlock-api-ingest"
GITHUB_REPO="deadlock-api/deadlock-api-ingest"
ASSET_KEYWORD="ubuntu-latest" # Keyword to find in the release asset filename
# Installation paths (User-level, no root required)
INSTALL_DIR="$HOME/.local/share/$APP_NAME"
BIN_DIR="$HOME/.local/bin"
FINAL_EXECUTABLE_NAME=$APP_NAME
# Service and logging
SERVICE_NAME=$APP_NAME
LOG_FILE="/tmp/${APP_NAME}-install.log"
SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
SYSTEMD_SERVICE_FILE="$SYSTEMD_USER_DIR/${SERVICE_NAME}.service"
# --- Colors for output ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# --- Helper Functions ---
# Function to write to log and console
log() {
local level="$1"
local message="$2"
local timestamp color
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
case "$level" in
"ERROR") color="$RED" ;;
"WARN") color="$YELLOW" ;;
"SUCCESS") color="$GREEN" ;;
"INFO") color="$BLUE" ;;
*) color="$NC" ;;
esac
local log_message="[$timestamp] [$level] $message"
# --- FIX APPLIED HERE ---
# All console output is redirected to stderr (>&2) to prevent it from being
# captured by command substitutions like `var=$(...)`.
echo -e "${color}${log_message}${NC}" >&2
# The log file is written to separately and is unaffected.
echo "$log_message" >> "$LOG_FILE"
}
# Function to execute commands quietly while logging details
execute_quietly() {
local cmd=("$@")
# Execute command and capture both stdout and stderr to log file
if "${cmd[@]}" >> "$LOG_FILE" 2>&1; then
return 0
else
local exit_code=$?
log "ERROR" "Command failed: ${cmd[*]}"
return $exit_code
fi
}
# Function to check for required dependencies
check_dependencies() {
local missing_deps=()
for pkg in curl wget jq; do
command -v "$pkg" >/dev/null 2>&1 || missing_deps+=("$pkg")
done
if [[ ${#missing_deps[@]} -eq 0 ]]; then
log "SUCCESS" "All dependencies are already installed."
return 0
fi
log "WARN" "Missing dependencies: ${missing_deps[*]}"
log "INFO" "Please install them using your package manager. For example:"
if command -v apt-get >/dev/null 2>&1; then
log "INFO" " sudo apt-get install ${missing_deps[*]}"
elif command -v dnf >/dev/null 2>&1; then
log "INFO" " sudo dnf install ${missing_deps[*]}"
elif command -v yum >/dev/null 2>&1; then
log "INFO" " sudo yum install ${missing_deps[*]}"
elif command -v pacman >/dev/null 2>&1; then
log "INFO" " sudo pacman -S ${missing_deps[*]}"
elif command -v apk >/dev/null 2>&1; then
log "INFO" " sudo apk add ${missing_deps[*]}"
elif command -v zypper >/dev/null 2>&1; then
log "INFO" " sudo zypper install ${missing_deps[*]}"
else
log "INFO" " Use your system's package manager to install: ${missing_deps[*]}"
fi
log "ERROR" "Cannot proceed without required dependencies."
exit 1
}
# Function to get latest release info from GitHub API
get_latest_release() {
local api_url="https://api.github.com/repos/$GITHUB_REPO/releases/latest"
local response
response=$(curl -s -f --user-agent "Bash-Installer" "$api_url") || {
log "ERROR" "Failed to fetch release information from GitHub API."
exit 1
}
local asset_info
asset_info=$(echo "$response" | jq --arg keyword "$ASSET_KEYWORD" '([.assets[] | select(.name | contains($keyword))])[0]')
if [[ -z "$asset_info" || "$asset_info" == "null" ]]; then
log "ERROR" "Could not find a release asset containing the keyword: '$ASSET_KEYWORD'"
log "INFO" "Available assets are: $(echo "$response" | jq -r '.assets[].name')"
exit 1
fi
local version download_url size
version=$(echo "$response" | jq -r '.tag_name')
download_url=$(echo "$asset_info" | jq -r '.browser_download_url')
size=$(echo "$asset_info" | jq -r '.size')
# This is the only line that should print to stdout, to be captured by the calling variable.
echo "$version|$download_url|$size"
}
# Function to download file with progress
download_file() {
local url="$1"
local output_path="$2"
local expected_size="$3"
mkdir -p "$(dirname "$output_path")"
# Log detailed info to file, show simple progress to user
echo "Downloading from: $url" >> "$LOG_FILE"
echo "Saving to: $output_path" >> "$LOG_FILE"
if ! wget --progress=dot:giga --user-agent="Bash-Installer" -O "$output_path" "$url" 2>> "$LOG_FILE"; then
log "ERROR" "Download failed."
exit 1
fi
log "SUCCESS" "Download complete."
local actual_size
actual_size=$(stat -c%s "$output_path")
if [[ "$actual_size" != "$expected_size" ]]; then
log "ERROR" "File size mismatch! Expected: $expected_size bytes, Got: $actual_size bytes."
exit 1
fi
}
# Function to create desktop shortcut
create_desktop_shortcut() {
local executable_path="$1"
local arguments="${2:-}"
local shortcut_name="${3:-Deadlock API Ingest}"
local comment="${4:-Monitors Steam cache for Deadlock match replays}"
log "INFO" "Creating desktop shortcut: $shortcut_name..."
# Use the current user's local applications directory
local desktop_dir="$HOME/.local/share/applications"
# Create the directory if it doesn't exist
if ! mkdir -p "$desktop_dir" 2>/dev/null; then
log "WARN" "Could not create applications directory. Desktop shortcut will not be created."
return 1
fi
# Create a safe filename from the shortcut name
local safe_name
safe_name=$(echo "$shortcut_name" | tr ' ' '-' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g')
local desktop_file="$desktop_dir/${safe_name}.desktop"
# Build the Exec line with arguments if provided
local exec_line="$executable_path"
if [[ -n "$arguments" ]]; then
exec_line="$executable_path $arguments"
fi
# Create the .desktop file
cat > "$desktop_file" << EOF
[Desktop Entry]
Version=1.0
Type=Application
Name=$shortcut_name
Comment=$comment
Exec=$exec_line
Terminal=false
Keywords=deadlock;api;steam;
EOF
chmod 644 "$desktop_file"
# Try to update desktop database if available
if command -v update-desktop-database >/dev/null 2>&1; then
update-desktop-database "$desktop_dir" 2>> "$LOG_FILE" || true
fi
log "SUCCESS" "Desktop shortcut created at: $desktop_file"
return 0
}
# Function to manage the systemd service
manage_service() {
local action="$1"
case "$action" in
"remove")
if systemctl --user is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
execute_quietly systemctl --user stop "$SERVICE_NAME"
fi
if systemctl --user is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then
execute_quietly systemctl --user disable "$SERVICE_NAME"
fi
if [[ -f "$SYSTEMD_SERVICE_FILE" ]]; then
rm -f "$SYSTEMD_SERVICE_FILE"
fi
systemctl --user daemon-reload 2>> "$LOG_FILE" || true
;;
"create")
local executable_path="$2"
local service_extra_args="${3:-}"
# Create systemd user directory if it doesn't exist
mkdir -p "$SYSTEMD_USER_DIR"
local exec_start="$executable_path"
if [[ -n "$service_extra_args" ]]; then
exec_start="$executable_path $service_extra_args"
fi
cat > "$SYSTEMD_SERVICE_FILE" << EOF
[Unit]
Description=Deadlock API Ingest - Monitors Steam cache for match replays
Documentation=https://github.com/deadlock-api/deadlock-api-ingest
[Service]
Type=simple
ExecStart=$exec_start
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=$SERVICE_NAME
# Security Hardening
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=default.target
EOF
chmod 644 "$SYSTEMD_SERVICE_FILE"
systemctl --user daemon-reload 2>> "$LOG_FILE"
log "SUCCESS" "User service created."
;;
"start")
execute_quietly systemctl --user enable "$SERVICE_NAME"
execute_quietly systemctl --user start "$SERVICE_NAME"
sleep 3
if systemctl --user is-active --quiet "$SERVICE_NAME"; then
log "SUCCESS" "Application service started successfully."
else
log "ERROR" "Service failed to start. Please check the logs."
log "INFO" "To check status: systemctl --user status $SERVICE_NAME"
log "INFO" "To view logs: journalctl --user -u $SERVICE_NAME -n 50"
exit 1
fi
;;
esac
}
# Function to prompt user for auto-start setup
prompt_for_autostart() {
# Check if we're running in an interactive terminal
if [[ ! -t 0 ]] || [[ ! -t 1 ]]; then
log "INFO" "Non-interactive mode detected. Enabling auto-start by default."
return 0
fi
log "INFO" "Auto-start will automatically start the service when the system boots."
log "INFO" "This ensures the application is always running in the background."
echo >&2
local attempts=0
local max_attempts=2
while [[ $attempts -lt $max_attempts ]]; do
echo -n "Would you like to enable auto-start on system boot? (y/n): " >&2
local response
if read -t 10 -r response; then
case "${response,,}" in
y|yes)
return 0
;;
n|no)
return 1
;;
*)
attempts=$((attempts + 1))
if [[ $attempts -lt $max_attempts ]]; then
echo "Invalid response. Please enter 'y' for yes or 'n' for no." >&2
fi
;;
esac
else
log "INFO" "No response received within 10 seconds. Enabling auto-start by default."
return 0
fi
done
log "INFO" "Maximum attempts reached. Enabling auto-start by default."
return 0
}
# Function to prompt user for Statlocker integration
prompt_for_statlocker() {
# Check if we're running in an interactive terminal
if [[ ! -t 0 ]] || [[ ! -t 1 ]]; then
log "INFO" "Non-interactive mode detected. Enabling Statlocker integration by default."
return 0
fi
log "INFO" "Statlocker integration sends match IDs to statlocker.gg after each ingestion."
log "INFO" "This helps track community match statistics. No personal data is sent."
echo >&2
local attempts=0
local max_attempts=2
while [[ $attempts -lt $max_attempts ]]; do
echo -n "Would you like to enable Statlocker integration? (y/n): " >&2
local response
if read -t 10 -r response; then
case "${response,,}" in
y|yes)
return 0
;;
n|no)
return 1
;;
*)
attempts=$((attempts + 1))
if [[ $attempts -lt $max_attempts ]]; then
echo "Invalid response. Please enter 'y' for yes or 'n' for no." >&2
fi
;;
esac
else
log "INFO" "No response received within 10 seconds. Enabling Statlocker integration by default."
return 0
fi
done
log "INFO" "Maximum attempts reached. Enabling Statlocker integration by default."
return 0
}
# --- Main Installation Logic ---
main() {
log "INFO" "Starting Deadlock API Ingest installation..."
log "INFO" "Log file is available at: $LOG_FILE"
check_dependencies
local release_info
release_info=$(get_latest_release)
local version download_url size
IFS='|' read -r version download_url size <<< "$release_info"
if [[ -z "$version" || -z "$download_url" ]]; then
log "ERROR" "Failed to parse release information. Cannot continue."
exit 1
fi
# Try to run uninstall script if it exists (clean uninstall before fresh install)
local existing_uninstall_script="$INSTALL_DIR/uninstall-linux.sh"
if [[ -f "$existing_uninstall_script" ]]; then
log "INFO" "Found existing installation. Running uninstall script..."
if "$existing_uninstall_script" --silent 2>/dev/null; then
log "SUCCESS" "Previous installation uninstalled successfully."
else
log "WARN" "Uninstall script failed, continuing with manual cleanup."
fi
fi
# Remove existing service (in case uninstall script didn't exist or failed)
manage_service "remove"
# Clean up any remaining processes
killall "$APP_NAME" 2>/dev/null || true
# Ensure installation directory exists
mkdir -p "$INSTALL_DIR"
local temp_download_path="$INSTALL_DIR/${APP_NAME}-${version}"
download_file "$download_url" "$temp_download_path" "$size"
local final_executable_path="$INSTALL_DIR/$FINAL_EXECUTABLE_NAME"
mv "$temp_download_path" "$final_executable_path"
chmod +x "$final_executable_path"
# Create bin directory if it doesn't exist
mkdir -p "$BIN_DIR"
local bin_symlink="$BIN_DIR/$FINAL_EXECUTABLE_NAME"
ln -sf "$final_executable_path" "$bin_symlink"
log "SUCCESS" "Application installed successfully."
# Download uninstall script
log "INFO" "Downloading uninstall script..."
local uninstall_script_path="$INSTALL_DIR/uninstall-linux.sh"
local uninstall_script_url="https://raw.githubusercontent.com/deadlock-api/deadlock-api-ingest/master/uninstall-linux.sh"
if curl -fsSL "$uninstall_script_url" -o "$uninstall_script_path"; then
chmod +x "$uninstall_script_path"
log "SUCCESS" "Uninstall script downloaded to: $uninstall_script_path"
else
log "WARN" "Failed to download uninstall script, but continuing installation."
log "INFO" "You can manually download it from: $uninstall_script_url"
fi
# Prompt user for Statlocker integration
local extra_args=""
if ! prompt_for_statlocker; then
extra_args="--no-statlocker"
log "INFO" "Statlocker integration disabled."
else
log "SUCCESS" "Statlocker integration enabled."
fi
# Create the main service (but don't enable/start it yet)
manage_service "create" "$final_executable_path" "$extra_args"
# Prompt user for auto-start setup
if prompt_for_autostart; then
manage_service "start"
log "SUCCESS" "Auto-start enabled. The service will start automatically on user login."
else
log "INFO" "Auto-start disabled. You can start the service manually with: systemctl --user start $SERVICE_NAME"
log "INFO" "To enable auto-start later, run: systemctl --user enable $SERVICE_NAME"
# Offer to create a desktop shortcut instead
echo >&2
log "INFO" "Would you like to create a desktop shortcut instead?"
local create_shortcut=false
local attempts=0
local max_attempts=2
# Check if we're running in an interactive terminal
if [[ ! -t 0 ]] || [[ ! -t 1 ]]; then
log "INFO" "Non-interactive mode detected. Creating desktop shortcut by default."
create_shortcut=true
else
while [[ $attempts -lt $max_attempts ]]; do
echo -n "Create desktop shortcut? (y/n): " >&2
local response
if read -t 10 -r response; then
case "${response,,}" in
y|yes)
create_shortcut=true
break
;;
n|no)
create_shortcut=false
break
;;
*)
attempts=$((attempts + 1))
if [[ $attempts -lt $max_attempts ]]; then
echo "Invalid response. Please enter 'y' for yes or 'n' for no." >&2
fi
;;
esac
else
log "INFO" "No response received within 10 seconds. Creating desktop shortcut by default."
create_shortcut=true
break
fi
done
if [[ $attempts -ge $max_attempts ]]; then
log "INFO" "Maximum attempts reached. Creating desktop shortcut by default."
create_shortcut=true
fi
fi
if [[ "$create_shortcut" == true ]]; then
# Create main shortcut
local main_created=false
local once_created=false
if create_desktop_shortcut "$final_executable_path" "$extra_args" "Deadlock API Ingest" "Monitors Steam cache for Deadlock match replays"; then
main_created=true
fi
# Create "once" shortcut for initial cache ingest only
local once_args="--once"
if [[ -n "$extra_args" ]]; then
once_args="--once $extra_args"
fi
if create_desktop_shortcut "$final_executable_path" "$once_args" "Deadlock API Ingest (Once)" "Scan existing Steam cache once and exit"; then
once_created=true
fi
if [[ "$main_created" == true && "$once_created" == true ]]; then
log "INFO" "Desktop shortcuts created:"
log "INFO" " - Deadlock API Ingest: Start the application with monitoring"
log "INFO" " - Deadlock API Ingest (Once): Run once to ingest existing cache files only"
log "INFO" "You can also start manually: $final_executable_path"
elif [[ "$main_created" == true ]]; then
log "INFO" "Main desktop shortcut created. You can also run: $final_executable_path"
else
log "INFO" "You can start the application by running: $final_executable_path"
fi
else
log "INFO" "You can manually start the application by running: $final_executable_path"
log "INFO" "To run once (ingest existing cache only): $final_executable_path --once"
fi
fi
log "SUCCESS" "🚀 Deadlock API Ingest has been installed successfully!"
# The final messages should also be sent to stderr to not interfere with any potential scripting.
{
echo
echo -e "${GREEN}Installation complete.${NC}"
echo
if systemctl --user is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then
echo -e "${GREEN}[+] Auto-start is enabled${NC} - The service will start automatically on user login."
else
echo -e "${YELLOW}[-] Auto-start is disabled${NC} - The service will not start automatically on user login."
echo -e " To enable auto-start: ${YELLOW}systemctl --user enable $SERVICE_NAME${NC}"
fi
echo
echo -e "You can manage the main service with the following commands:"
echo -e " - Check status: ${YELLOW}systemctl --user status $SERVICE_NAME${NC}"
echo -e " - View logs: ${YELLOW}journalctl --user -u $SERVICE_NAME -f${NC}"
echo -e " - Log files: ${YELLOW}\${XDG_DATA_HOME:-\$HOME/.local/share}/$APP_NAME/logs/${NC}"
echo -e " - Stop service: ${YELLOW}systemctl --user stop $SERVICE_NAME${NC}"
echo -e " - Start service: ${YELLOW}systemctl --user start $SERVICE_NAME${NC}"
if ! systemctl --user is-enabled --quiet "$SERVICE_NAME" 2>/dev/null; then
echo -e " - Enable auto-start: ${YELLOW}systemctl --user enable $SERVICE_NAME${NC}"
fi
echo
echo -e "To uninstall, run: ${YELLOW}$INSTALL_DIR/uninstall-linux.sh${NC}"
echo
} >&2
}
# Graceful error handling
trap 'log "ERROR" "An unexpected error occurred at line $LINENO. Installation failed."' ERR
# Run the main function
main "$@"