@minecraft-docker/mcctl 1.6.13 → 1.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,569 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # backup.sh - GitHub backup for worlds/ directory
4
+ # =============================================================================
5
+ # Backup Minecraft worlds to a private GitHub repository.
6
+ #
7
+ # Usage:
8
+ # ./scripts/backup.sh push [--message "msg"] # Backup to GitHub
9
+ # ./scripts/backup.sh status # Show backup configuration
10
+ # ./scripts/backup.sh history [--json] # Show backup history
11
+ # ./scripts/backup.sh restore <commit> # Restore from specific commit
12
+ # ./scripts/backup.sh diff [commit] # Show differences
13
+ #
14
+ # Environment Variables (in .env):
15
+ # BACKUP_GITHUB_TOKEN - GitHub Personal Access Token
16
+ # BACKUP_GITHUB_REPO - Repository (username/repo-name)
17
+ # BACKUP_GITHUB_BRANCH - Branch name (default: main)
18
+ # BACKUP_AUTO_ON_STOP - Auto backup on server stop (true/false)
19
+ #
20
+ # Exit codes:
21
+ # 0 - Success
22
+ # 1 - Error
23
+ # 2 - Warning (backup skipped, no changes, etc.)
24
+ # =============================================================================
25
+
26
+ set -e
27
+
28
+ # Get script directory and source common functions
29
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
+ PLATFORM_DIR="$(dirname "$SCRIPT_DIR")"
31
+ source "$SCRIPT_DIR/lib/common.sh"
32
+
33
+ # =============================================================================
34
+ # Configuration
35
+ # =============================================================================
36
+
37
+ WORLDS_DIR="$PLATFORM_DIR/worlds"
38
+ BACKUP_CACHE_DIR="$HOME/.minecraft-backup"
39
+ ENV_FILE="$PLATFORM_DIR/.env"
40
+
41
+ # =============================================================================
42
+ # Usage
43
+ # =============================================================================
44
+
45
+ usage() {
46
+ cat <<EOF
47
+ Usage: $(basename "$0") <command> [options]
48
+
49
+ Backup Minecraft worlds to a private GitHub repository.
50
+
51
+ Commands:
52
+ push [--message "msg"] Backup worlds to GitHub
53
+ push --auto Backup with automatic timestamp message
54
+ status Show backup configuration and status
55
+ history [--json] Show recent backup history
56
+ restore <commit> Restore worlds from specific commit
57
+ diff [commit] Show differences with backup
58
+
59
+ Options:
60
+ --json Output in JSON format
61
+ --message, -m "msg" Custom commit message
62
+ --auto Use automatic timestamp message
63
+ -h, --help Show this help message
64
+
65
+ Environment Variables (set in .env):
66
+ BACKUP_GITHUB_TOKEN GitHub Personal Access Token (required)
67
+ BACKUP_GITHUB_REPO Repository as username/repo (required)
68
+ BACKUP_GITHUB_BRANCH Branch name (default: main)
69
+ BACKUP_AUTO_ON_STOP Auto backup on server stop (default: false)
70
+
71
+ Examples:
72
+ $(basename "$0") push -m "Before server upgrade"
73
+ $(basename "$0") push --auto
74
+ $(basename "$0") status
75
+ $(basename "$0") history --json
76
+ $(basename "$0") restore abc1234
77
+ $(basename "$0") diff HEAD~1
78
+ EOF
79
+ }
80
+
81
+ # =============================================================================
82
+ # Helper Functions
83
+ # =============================================================================
84
+
85
+ # Load environment variables from .env file
86
+ load_env() {
87
+ if [[ -f "$ENV_FILE" ]]; then
88
+ # Export variables from .env, ignoring comments and empty lines
89
+ set -a
90
+ source <(grep -v '^\s*#' "$ENV_FILE" | grep -v '^\s*$')
91
+ set +a
92
+ fi
93
+ }
94
+
95
+ # Check if backup is configured
96
+ check_backup_config() {
97
+ local missing=()
98
+
99
+ if [[ -z "${BACKUP_GITHUB_TOKEN:-}" ]]; then
100
+ missing+=("BACKUP_GITHUB_TOKEN")
101
+ fi
102
+
103
+ if [[ -z "${BACKUP_GITHUB_REPO:-}" ]]; then
104
+ missing+=("BACKUP_GITHUB_REPO")
105
+ fi
106
+
107
+ if [[ ${#missing[@]} -gt 0 ]]; then
108
+ error "Missing required environment variables: ${missing[*]}"
109
+ error "Please configure them in $ENV_FILE"
110
+ return 1
111
+ fi
112
+
113
+ return 0
114
+ }
115
+
116
+ # Get the backup repository URL with authentication
117
+ get_repo_url() {
118
+ echo "https://${BACKUP_GITHUB_TOKEN}@github.com/${BACKUP_GITHUB_REPO}.git"
119
+ }
120
+
121
+ # Get the backup repository URL for display (masked token)
122
+ get_repo_url_display() {
123
+ echo "https://***@github.com/${BACKUP_GITHUB_REPO}.git"
124
+ }
125
+
126
+ # Initialize or update the backup cache directory
127
+ init_backup_cache() {
128
+ local branch="${BACKUP_GITHUB_BRANCH:-main}"
129
+
130
+ if [[ ! -d "$BACKUP_CACHE_DIR/.git" ]]; then
131
+ info "Initializing backup cache..."
132
+ mkdir -p "$BACKUP_CACHE_DIR"
133
+
134
+ # Try to clone existing repo
135
+ if git clone --depth 50 --branch "$branch" "$(get_repo_url)" "$BACKUP_CACHE_DIR" 2>/dev/null; then
136
+ info "Cloned existing backup repository"
137
+ else
138
+ # Initialize new repo if clone fails
139
+ info "Creating new backup repository..."
140
+ cd "$BACKUP_CACHE_DIR"
141
+ git init
142
+ git checkout -b "$branch"
143
+ echo "# Minecraft Worlds Backup" > README.md
144
+ echo "" >> README.md
145
+ echo "This repository contains automated backups of Minecraft world data." >> README.md
146
+ echo "" >> README.md
147
+ echo "**Do not edit manually.**" >> README.md
148
+ git add README.md
149
+ git commit -m "Initial backup repository setup"
150
+ git remote add origin "$(get_repo_url)"
151
+ fi
152
+ else
153
+ # Update existing cache
154
+ cd "$BACKUP_CACHE_DIR"
155
+ git remote set-url origin "$(get_repo_url)" 2>/dev/null || git remote add origin "$(get_repo_url)"
156
+ git fetch origin "$branch" 2>/dev/null || true
157
+ git checkout "$branch" 2>/dev/null || git checkout -b "$branch"
158
+ git pull origin "$branch" --rebase 2>/dev/null || true
159
+ fi
160
+
161
+ # Configure git user for backup commits (local to this repo)
162
+ cd "$BACKUP_CACHE_DIR"
163
+ git config user.email "minecraft-backup@localhost"
164
+ git config user.name "Minecraft Backup"
165
+
166
+ cd "$PLATFORM_DIR"
167
+ }
168
+
169
+ # Sync worlds to backup cache
170
+ sync_worlds_to_cache() {
171
+ if [[ ! -d "$WORLDS_DIR" ]]; then
172
+ error "Worlds directory not found: $WORLDS_DIR"
173
+ return 1
174
+ fi
175
+
176
+ # Sync worlds, excluding .locks directory
177
+ rsync -av --delete \
178
+ --exclude='.locks' \
179
+ --exclude='.locks/' \
180
+ "$WORLDS_DIR/" "$BACKUP_CACHE_DIR/worlds/"
181
+
182
+ return 0
183
+ }
184
+
185
+ # Check if there are changes to backup
186
+ has_changes() {
187
+ cd "$BACKUP_CACHE_DIR"
188
+ git add -A
189
+ if git diff --cached --quiet; then
190
+ return 1 # No changes
191
+ fi
192
+ return 0 # Has changes
193
+ }
194
+
195
+ # =============================================================================
196
+ # Commands
197
+ # =============================================================================
198
+
199
+ # Push command - backup worlds to GitHub
200
+ cmd_push() {
201
+ local message=""
202
+ local auto_message=false
203
+
204
+ while [[ $# -gt 0 ]]; do
205
+ case "$1" in
206
+ --message|-m)
207
+ message="$2"
208
+ shift 2
209
+ ;;
210
+ --auto)
211
+ auto_message=true
212
+ shift
213
+ ;;
214
+ -*)
215
+ error "Unknown option: $1"
216
+ return 1
217
+ ;;
218
+ *)
219
+ error "Unexpected argument: $1"
220
+ return 1
221
+ ;;
222
+ esac
223
+ done
224
+
225
+ check_backup_config || return 1
226
+
227
+ # Generate auto message if requested
228
+ if $auto_message || [[ -z "$message" ]]; then
229
+ message="Backup: $(date '+%Y-%m-%d %H:%M:%S')"
230
+ fi
231
+
232
+ info "Starting backup to $(get_repo_url_display)..."
233
+
234
+ # Initialize/update cache
235
+ init_backup_cache || return 1
236
+
237
+ # Sync worlds
238
+ info "Syncing worlds directory..."
239
+ sync_worlds_to_cache || return 1
240
+
241
+ # Check for changes
242
+ cd "$BACKUP_CACHE_DIR"
243
+
244
+ if ! has_changes; then
245
+ warn "No changes to backup"
246
+ return 2
247
+ fi
248
+
249
+ # Commit and push
250
+ info "Committing changes..."
251
+ git commit -m "$message"
252
+
253
+ info "Pushing to GitHub..."
254
+ local branch="${BACKUP_GITHUB_BRANCH:-main}"
255
+ git push -u origin "$branch"
256
+
257
+ local commit_hash
258
+ commit_hash=$(git rev-parse --short HEAD)
259
+
260
+ info "Backup complete: $commit_hash"
261
+ echo ""
262
+ echo -e "${GREEN}✓${NC} Backup successful"
263
+ echo " Commit: $commit_hash"
264
+ echo " Message: $message"
265
+ echo " Repository: https://github.com/${BACKUP_GITHUB_REPO}"
266
+
267
+ cd "$PLATFORM_DIR"
268
+ }
269
+
270
+ # Status command - show backup configuration
271
+ cmd_status() {
272
+ local json_output=false
273
+
274
+ while [[ $# -gt 0 ]]; do
275
+ case "$1" in
276
+ --json)
277
+ json_output=true
278
+ JSON_OUTPUT=true
279
+ setup_colors
280
+ shift
281
+ ;;
282
+ *)
283
+ error "Unknown option: $1"
284
+ return 1
285
+ ;;
286
+ esac
287
+ done
288
+
289
+ local configured=false
290
+ local cache_exists=false
291
+ local last_commit=""
292
+ local last_date=""
293
+ local repo="${BACKUP_GITHUB_REPO:-not set}"
294
+ local branch="${BACKUP_GITHUB_BRANCH:-main}"
295
+ local auto_stop="${BACKUP_AUTO_ON_STOP:-false}"
296
+
297
+ # Check if configured
298
+ if [[ -n "${BACKUP_GITHUB_TOKEN:-}" && -n "${BACKUP_GITHUB_REPO:-}" ]]; then
299
+ configured=true
300
+ fi
301
+
302
+ # Check cache
303
+ if [[ -d "$BACKUP_CACHE_DIR/.git" ]]; then
304
+ cache_exists=true
305
+ cd "$BACKUP_CACHE_DIR"
306
+ last_commit=$(git rev-parse --short HEAD 2>/dev/null || echo "none")
307
+ last_date=$(git log -1 --format="%ci" 2>/dev/null || echo "none")
308
+ cd "$PLATFORM_DIR"
309
+ fi
310
+
311
+ if $json_output; then
312
+ cat <<EOF
313
+ {
314
+ "configured": $configured,
315
+ "repository": "$repo",
316
+ "branch": "$branch",
317
+ "auto_on_stop": $auto_stop,
318
+ "cache_exists": $cache_exists,
319
+ "last_commit": "$last_commit",
320
+ "last_date": "$last_date",
321
+ "worlds_dir": "$WORLDS_DIR",
322
+ "cache_dir": "$BACKUP_CACHE_DIR"
323
+ }
324
+ EOF
325
+ else
326
+ echo -e "${BOLD}=== Backup Status ===${NC}"
327
+ echo ""
328
+
329
+ if $configured; then
330
+ echo -e "Configuration: ${GREEN}Configured${NC}"
331
+ else
332
+ echo -e "Configuration: ${RED}Not configured${NC}"
333
+ fi
334
+
335
+ echo "Repository: $repo"
336
+ echo "Branch: $branch"
337
+ echo "Auto on stop: $auto_stop"
338
+ echo ""
339
+
340
+ if $cache_exists; then
341
+ echo -e "Cache: ${GREEN}Exists${NC}"
342
+ echo "Last commit: $last_commit"
343
+ echo "Last date: $last_date"
344
+ else
345
+ echo -e "Cache: ${YELLOW}Not initialized${NC}"
346
+ fi
347
+
348
+ echo ""
349
+ echo "Worlds dir: $WORLDS_DIR"
350
+ echo "Cache dir: $BACKUP_CACHE_DIR"
351
+
352
+ if ! $configured; then
353
+ echo ""
354
+ echo -e "${YELLOW}To configure backup, add to $ENV_FILE:${NC}"
355
+ echo " BACKUP_GITHUB_TOKEN=ghp_your_token"
356
+ echo " BACKUP_GITHUB_REPO=username/repo-name"
357
+ echo " BACKUP_AUTO_ON_STOP=true"
358
+ fi
359
+ fi
360
+ }
361
+
362
+ # History command - show backup history
363
+ cmd_history() {
364
+ local json_output=false
365
+ local limit=10
366
+
367
+ while [[ $# -gt 0 ]]; do
368
+ case "$1" in
369
+ --json)
370
+ json_output=true
371
+ JSON_OUTPUT=true
372
+ setup_colors
373
+ shift
374
+ ;;
375
+ -n|--limit)
376
+ limit="$2"
377
+ shift 2
378
+ ;;
379
+ *)
380
+ error "Unknown option: $1"
381
+ return 1
382
+ ;;
383
+ esac
384
+ done
385
+
386
+ if [[ ! -d "$BACKUP_CACHE_DIR/.git" ]]; then
387
+ error "Backup cache not initialized. Run 'backup.sh push' first."
388
+ return 1
389
+ fi
390
+
391
+ cd "$BACKUP_CACHE_DIR"
392
+
393
+ if $json_output; then
394
+ echo "{"
395
+ echo ' "history": ['
396
+ local first=true
397
+ while IFS='|' read -r hash date message; do
398
+ if [[ "$first" != "true" ]]; then
399
+ echo ","
400
+ fi
401
+ first=false
402
+ printf ' {"commit": "%s", "date": "%s", "message": "%s"}' \
403
+ "$hash" "$date" "$(echo "$message" | sed 's/"/\\"/g')"
404
+ done < <(git log -n "$limit" --format="%h|%ci|%s")
405
+ echo ""
406
+ echo " ]"
407
+ echo "}"
408
+ else
409
+ echo -e "${BOLD}=== Backup History ===${NC}"
410
+ echo ""
411
+ printf "%-10s %-20s %s\n" "COMMIT" "DATE" "MESSAGE"
412
+ printf "%-10s %-20s %s\n" "------" "----" "-------"
413
+
414
+ git log -n "$limit" --format="%h|%ci|%s" | while IFS='|' read -r hash date message; do
415
+ printf "%-10s %-20s %s\n" "$hash" "${date%% *}" "$message"
416
+ done
417
+ fi
418
+
419
+ cd "$PLATFORM_DIR"
420
+ }
421
+
422
+ # Restore command - restore from specific commit
423
+ cmd_restore() {
424
+ local commit="$1"
425
+
426
+ if [[ -z "$commit" ]]; then
427
+ error "Usage: restore <commit>"
428
+ return 1
429
+ fi
430
+
431
+ if [[ ! -d "$BACKUP_CACHE_DIR/.git" ]]; then
432
+ error "Backup cache not initialized. Run 'backup.sh push' first."
433
+ return 1
434
+ fi
435
+
436
+ cd "$BACKUP_CACHE_DIR"
437
+
438
+ # Verify commit exists
439
+ if ! git rev-parse "$commit" &>/dev/null; then
440
+ error "Commit not found: $commit"
441
+ return 1
442
+ fi
443
+
444
+ local commit_full
445
+ commit_full=$(git rev-parse "$commit")
446
+ local commit_short="${commit_full:0:7}"
447
+ local commit_date
448
+ commit_date=$(git log -1 --format="%ci" "$commit")
449
+
450
+ warn "This will restore worlds from commit $commit_short ($commit_date)"
451
+ warn "Current worlds will be backed up to $WORLDS_DIR.bak"
452
+ echo ""
453
+ read -p "Are you sure? (yes/no): " confirm
454
+
455
+ if [[ "$confirm" != "yes" ]]; then
456
+ info "Restore cancelled"
457
+ return 2
458
+ fi
459
+
460
+ # Backup current worlds
461
+ if [[ -d "$WORLDS_DIR" ]]; then
462
+ local backup_name="$WORLDS_DIR.bak.$(date +%Y%m%d%H%M%S)"
463
+ info "Backing up current worlds to $backup_name"
464
+ cp -r "$WORLDS_DIR" "$backup_name"
465
+ fi
466
+
467
+ # Checkout specific commit
468
+ info "Checking out commit $commit_short..."
469
+ git checkout "$commit" -- worlds/ 2>/dev/null || {
470
+ error "Failed to checkout. The commit may not contain worlds directory."
471
+ return 1
472
+ }
473
+
474
+ # Sync to worlds directory
475
+ info "Restoring worlds..."
476
+ mkdir -p "$WORLDS_DIR"
477
+ rsync -av --delete "$BACKUP_CACHE_DIR/worlds/" "$WORLDS_DIR/"
478
+
479
+ # Restore .locks directory if missing
480
+ mkdir -p "$WORLDS_DIR/.locks"
481
+
482
+ # Return to branch
483
+ local branch="${BACKUP_GITHUB_BRANCH:-main}"
484
+ git checkout "$branch" 2>/dev/null
485
+
486
+ cd "$PLATFORM_DIR"
487
+
488
+ info "Restore complete from $commit_short"
489
+ echo ""
490
+ echo -e "${GREEN}✓${NC} Worlds restored from commit $commit_short"
491
+ echo " Previous worlds backed up to: ${backup_name:-none}"
492
+ }
493
+
494
+ # Diff command - show differences
495
+ cmd_diff() {
496
+ local commit="${1:-HEAD}"
497
+
498
+ if [[ ! -d "$BACKUP_CACHE_DIR/.git" ]]; then
499
+ error "Backup cache not initialized. Run 'backup.sh push' first."
500
+ return 1
501
+ fi
502
+
503
+ # Sync current state to cache first
504
+ sync_worlds_to_cache || return 1
505
+
506
+ cd "$BACKUP_CACHE_DIR"
507
+ git add -A
508
+
509
+ echo -e "${BOLD}=== Differences from backup ($commit) ===${NC}"
510
+ echo ""
511
+
512
+ if git diff --cached --stat "$commit" 2>/dev/null; then
513
+ echo ""
514
+ git diff --cached --name-status "$commit" 2>/dev/null
515
+ else
516
+ warn "Cannot diff with $commit"
517
+ fi
518
+
519
+ cd "$PLATFORM_DIR"
520
+ }
521
+
522
+ # =============================================================================
523
+ # Main
524
+ # =============================================================================
525
+
526
+ main() {
527
+ # Load environment variables
528
+ load_env
529
+
530
+ local command="${1:-}"
531
+ shift || true
532
+
533
+ case "$command" in
534
+ push)
535
+ cmd_push "$@"
536
+ ;;
537
+ status)
538
+ cmd_status "$@"
539
+ ;;
540
+ history)
541
+ cmd_history "$@"
542
+ ;;
543
+ restore)
544
+ cmd_restore "$@"
545
+ ;;
546
+ diff)
547
+ cmd_diff "$@"
548
+ ;;
549
+ -h|--help|help)
550
+ usage
551
+ exit 0
552
+ ;;
553
+ "")
554
+ error "No command specified"
555
+ usage
556
+ exit 1
557
+ ;;
558
+ *)
559
+ error "Unknown command: $command"
560
+ usage
561
+ exit 1
562
+ ;;
563
+ esac
564
+ }
565
+
566
+ # Run main if script is executed directly
567
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
568
+ main "$@"
569
+ fi