@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,543 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # mcctl.sh - Minecraft Server Management CLI
4
+ # =============================================================================
5
+ # Main management tool for Minecraft servers with mc-router integration.
6
+ #
7
+ # Usage:
8
+ # mcctl.sh status [--json] Show all server status
9
+ # mcctl.sh logs <server> [lines] View server logs
10
+ # mcctl.sh console <server> Connect to RCON console
11
+ # mcctl.sh world list [--json] List worlds and locks
12
+ # mcctl.sh world assign <world> <srv> Assign world to server
13
+ # mcctl.sh world release <world> Force release world lock
14
+ # mcctl.sh start <server> Start server (bypass auto-scale)
15
+ # mcctl.sh stop <server> Stop server
16
+ #
17
+ # Exit codes:
18
+ # 0 - Success
19
+ # 1 - Error
20
+ # 2 - Warning
21
+ # =============================================================================
22
+
23
+ set -e
24
+
25
+ # Get script directory and source common functions
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ PLATFORM_DIR="$(dirname "$SCRIPT_DIR")"
28
+ source "$SCRIPT_DIR/lib/common.sh"
29
+
30
+ # Load environment variables from .env
31
+ ENV_FILE="$PLATFORM_DIR/.env"
32
+ if [[ -f "$ENV_FILE" ]]; then
33
+ set -a
34
+ source <(grep -v '^\s*#' "$ENV_FILE" | grep -v '^\s*$') 2>/dev/null || true
35
+ set +a
36
+ fi
37
+
38
+ # =============================================================================
39
+ # Usage
40
+ # =============================================================================
41
+
42
+ usage() {
43
+ cat <<EOF
44
+ Usage: $(basename "$0") <command> [options]
45
+
46
+ Server Management:
47
+ status [--json] Show status of all servers and router
48
+ logs <server> [lines] View server logs (default: 50 lines)
49
+ console <server> Connect to RCON console (interactive)
50
+ start <server> Start a specific server
51
+ stop <server> Stop a specific server
52
+
53
+ World Management:
54
+ world list [--json] List worlds and their lock status
55
+ world assign <world> <srv> Lock world and assign to server
56
+ world release <world> Force release world lock
57
+
58
+ Player Lookup:
59
+ player lookup <name> Look up player info (UUID, avatar)
60
+ player uuid <name> Get player's online UUID
61
+ player uuid <name> --offline Get player's offline UUID
62
+
63
+ Backup (requires .env configuration):
64
+ backup push [--message "m"] Backup worlds to GitHub
65
+ backup status Show backup configuration
66
+ backup history [--json] Show backup history
67
+ backup restore <commit> Restore from specific commit
68
+
69
+ Options:
70
+ --json Output in JSON format
71
+ -h, --help Show this help message
72
+
73
+ Examples:
74
+ $(basename "$0") status
75
+ $(basename "$0") status --json
76
+ $(basename "$0") logs ironwood 100
77
+ $(basename "$0") console ironwood
78
+ $(basename "$0") world list
79
+ $(basename "$0") world assign survival mc-ironwood
80
+ $(basename "$0") start ironwood
81
+ $(basename "$0") stop ironwood
82
+ EOF
83
+ }
84
+
85
+ # =============================================================================
86
+ # Status Command
87
+ # =============================================================================
88
+
89
+ cmd_status() {
90
+ local json_output=false
91
+
92
+ while [[ $# -gt 0 ]]; do
93
+ case "$1" in
94
+ --json)
95
+ json_output=true
96
+ JSON_OUTPUT=true
97
+ setup_colors
98
+ shift
99
+ ;;
100
+ *)
101
+ error "Unknown option: $1"
102
+ return 1
103
+ ;;
104
+ esac
105
+ done
106
+
107
+ check_docker || return 1
108
+
109
+ local router_status
110
+ local router_health
111
+ local router_port
112
+
113
+ # Get router status
114
+ router_status=$(get_container_status "mc-router")
115
+ router_health=$(get_container_health "mc-router")
116
+ router_port="25565"
117
+
118
+ # Get avahi-daemon status (system service, not Docker)
119
+ local avahi_status="unknown"
120
+ if systemctl is-active --quiet avahi-daemon 2>/dev/null; then
121
+ avahi_status="running"
122
+ elif rc-service avahi-daemon status &>/dev/null 2>&1; then
123
+ avahi_status="running"
124
+ elif command -v avahi-daemon &>/dev/null; then
125
+ avahi_status="installed (not running)"
126
+ else
127
+ avahi_status="not installed"
128
+ fi
129
+
130
+ if $json_output; then
131
+ # JSON output
132
+ echo "{"
133
+ echo ' "router": {'
134
+ echo " \"name\": \"mc-router\","
135
+ echo " \"status\": \"$router_status\","
136
+ echo " \"health\": \"$router_health\","
137
+ echo " \"port\": $router_port"
138
+ echo ' },'
139
+ echo ' "avahi_daemon": {'
140
+ echo " \"name\": \"avahi-daemon\","
141
+ echo " \"status\": \"$avahi_status\","
142
+ echo " \"type\": \"system\""
143
+ echo ' },'
144
+ echo ' "servers": ['
145
+
146
+ local first=true
147
+ local containers
148
+ containers=$(get_mc_containers)
149
+
150
+ for container in $containers; do
151
+ local status
152
+ local health
153
+ local hostname
154
+ local server_name
155
+
156
+ status=$(get_container_status "$container")
157
+ health=$(get_container_health "$container")
158
+ hostname=$(get_container_hostname "$container")
159
+ server_name="${container#mc-}" # Remove mc- prefix
160
+
161
+ if [[ "$first" != "true" ]]; then
162
+ echo ","
163
+ fi
164
+ first=false
165
+
166
+ printf ' {"name": "%s", "container": "%s", "status": "%s", "health": "%s", "hostname": "%s"}' \
167
+ "$server_name" "$container" "$status" "$health" "$hostname"
168
+ done
169
+
170
+ echo ""
171
+ echo " ]"
172
+ echo "}"
173
+ else
174
+ # Human-readable output
175
+ echo -e "${BOLD}=== Server Status (mc-router Managed) ===${NC}"
176
+ echo ""
177
+
178
+ # Router status
179
+ echo -e "${CYAN}INFRASTRUCTURE${NC}"
180
+ printf "%-20s %-12s %-10s %s\n" "SERVICE" "STATUS" "HEALTH" "PORT/INFO"
181
+ printf "%-20s %-12s %-10s %s\n" "-------" "------" "------" "---------"
182
+
183
+ local router_color="${RED}"
184
+ [[ "$router_status" == "running" ]] && router_color="${GREEN}"
185
+ printf "%-20s ${router_color}%-12s${NC} %-10s %s\n" "mc-router" "$router_status" "$router_health" ":$router_port (hostname routing)"
186
+
187
+ local avahi_color="${RED}"
188
+ [[ "$avahi_status" == "running" ]] && avahi_color="${GREEN}"
189
+ printf "%-20s ${avahi_color}%-12s${NC} %-10s %s\n" "avahi-daemon" "$avahi_status" "(system)" "mDNS broadcast"
190
+
191
+ echo ""
192
+ echo -e "${CYAN}MINECRAFT SERVERS${NC}"
193
+ printf "%-20s %-12s %-10s %s\n" "SERVER" "STATUS" "HEALTH" "HOSTNAME"
194
+ printf "%-20s %-12s %-10s %s\n" "------" "------" "------" "--------"
195
+
196
+ local containers
197
+ containers=$(get_mc_containers)
198
+
199
+ if [[ -z "$containers" ]]; then
200
+ echo " No Minecraft servers configured"
201
+ else
202
+ for container in $containers; do
203
+ local status
204
+ local health
205
+ local hostname
206
+ local server_name
207
+ local status_color="${RED}"
208
+
209
+ status=$(get_container_status "$container")
210
+ health=$(get_container_health "$container")
211
+ hostname=$(get_container_hostname "$container")
212
+ server_name="${container#mc-}"
213
+
214
+ [[ "$status" == "running" ]] && status_color="${GREEN}"
215
+ [[ "$status" == "exited" ]] && status_color="${YELLOW}"
216
+
217
+ printf "%-20s ${status_color}%-12s${NC} %-10s %s\n" "$server_name" "$status" "$health" "$hostname"
218
+ done
219
+ fi
220
+
221
+ echo ""
222
+ fi
223
+ }
224
+
225
+ # =============================================================================
226
+ # Logs Command
227
+ # =============================================================================
228
+
229
+ cmd_logs() {
230
+ local server="$1"
231
+ local lines="${2:-50}"
232
+
233
+ if [[ -z "$server" ]]; then
234
+ error "Usage: logs <server> [lines]"
235
+ return 1
236
+ fi
237
+
238
+ check_docker || return 1
239
+
240
+ local container="mc-$server"
241
+
242
+ if ! container_exists "$container"; then
243
+ error "Server '$server' (container: $container) not found"
244
+ return 1
245
+ fi
246
+
247
+ # Use the logs.sh script if available
248
+ if [[ -x "$SCRIPT_DIR/logs.sh" ]]; then
249
+ "$SCRIPT_DIR/logs.sh" "$server" "$lines"
250
+ else
251
+ docker logs --tail "$lines" "$container"
252
+ fi
253
+ }
254
+
255
+ # =============================================================================
256
+ # Console Command
257
+ # =============================================================================
258
+
259
+ cmd_console() {
260
+ local server="$1"
261
+
262
+ if [[ -z "$server" ]]; then
263
+ error "Usage: console <server>"
264
+ return 1
265
+ fi
266
+
267
+ check_docker || return 1
268
+
269
+ local container="mc-$server"
270
+
271
+ if ! container_exists "$container"; then
272
+ error "Server '$server' (container: $container) not found"
273
+ return 1
274
+ fi
275
+
276
+ local status
277
+ status=$(get_container_status "$container")
278
+
279
+ if [[ "$status" != "running" ]]; then
280
+ error "Server '$server' is not running (status: $status)"
281
+ return 1
282
+ fi
283
+
284
+ info "Connecting to RCON console for '$server'..."
285
+ info "Type 'quit' or press Ctrl+C to exit"
286
+ echo ""
287
+
288
+ docker exec -i "$container" rcon-cli
289
+ }
290
+
291
+ # =============================================================================
292
+ # World Commands
293
+ # =============================================================================
294
+
295
+ cmd_world() {
296
+ local subcmd="${1:-}"
297
+ shift || true
298
+
299
+ case "$subcmd" in
300
+ list)
301
+ cmd_world_list "$@"
302
+ ;;
303
+ assign)
304
+ cmd_world_assign "$@"
305
+ ;;
306
+ release)
307
+ cmd_world_release "$@"
308
+ ;;
309
+ "")
310
+ error "Usage: world <list|assign|release> [options]"
311
+ return 1
312
+ ;;
313
+ *)
314
+ error "Unknown world subcommand: $subcmd"
315
+ return 1
316
+ ;;
317
+ esac
318
+ }
319
+
320
+ cmd_world_list() {
321
+ # Delegate to lock.sh
322
+ if [[ -x "$SCRIPT_DIR/lock.sh" ]]; then
323
+ "$SCRIPT_DIR/lock.sh" list "$@"
324
+ else
325
+ error "lock.sh not found"
326
+ return 1
327
+ fi
328
+ }
329
+
330
+ cmd_world_assign() {
331
+ local world="$1"
332
+ local server="$2"
333
+
334
+ if [[ -z "$world" || -z "$server" ]]; then
335
+ error "Usage: world assign <world> <server>"
336
+ return 1
337
+ fi
338
+
339
+ # Delegate to lock.sh
340
+ if [[ -x "$SCRIPT_DIR/lock.sh" ]]; then
341
+ "$SCRIPT_DIR/lock.sh" lock "$world" "$server"
342
+ else
343
+ error "lock.sh not found"
344
+ return 1
345
+ fi
346
+ }
347
+
348
+ cmd_world_release() {
349
+ local world="$1"
350
+
351
+ if [[ -z "$world" ]]; then
352
+ error "Usage: world release <world>"
353
+ return 1
354
+ fi
355
+
356
+ # Get current holder from lock.sh check
357
+ if [[ -x "$SCRIPT_DIR/lock.sh" ]]; then
358
+ local lock_status
359
+ lock_status=$("$SCRIPT_DIR/lock.sh" check "$world")
360
+
361
+ if [[ "$lock_status" == "unlocked" ]]; then
362
+ warn "World '$world' is not locked"
363
+ return 2
364
+ fi
365
+
366
+ local holder
367
+ holder=$(echo "$lock_status" | cut -d: -f2)
368
+
369
+ "$SCRIPT_DIR/lock.sh" unlock "$world" "$holder" --force
370
+ else
371
+ error "lock.sh not found"
372
+ return 1
373
+ fi
374
+ }
375
+
376
+ # =============================================================================
377
+ # Player Commands
378
+ # =============================================================================
379
+
380
+ cmd_player() {
381
+ # Delegate to player.sh
382
+ if [[ -x "$SCRIPT_DIR/player.sh" ]]; then
383
+ "$SCRIPT_DIR/player.sh" "$@"
384
+ else
385
+ error "player.sh not found"
386
+ return 1
387
+ fi
388
+ }
389
+
390
+ # =============================================================================
391
+ # Start/Stop Commands
392
+ # =============================================================================
393
+
394
+ cmd_start() {
395
+ local server="$1"
396
+
397
+ if [[ -z "$server" ]]; then
398
+ error "Usage: start <server>"
399
+ return 1
400
+ fi
401
+
402
+ check_docker || return 1
403
+
404
+ local container="mc-$server"
405
+ local server_config="$PLATFORM_DIR/servers/$server/docker-compose.yml"
406
+
407
+ # Check if server configuration exists
408
+ if [[ ! -f "$server_config" ]]; then
409
+ error "Server '$server' not found (no config at servers/$server/)"
410
+ return 1
411
+ fi
412
+
413
+ # Check if container already running
414
+ if container_exists "$container"; then
415
+ local status
416
+ status=$(get_container_status "$container")
417
+
418
+ if [[ "$status" == "running" ]]; then
419
+ warn "Server '$server' is already running"
420
+ return 2
421
+ fi
422
+ fi
423
+
424
+ # Use docker compose up to create and start (works even if container doesn't exist)
425
+ info "Starting server '$server'..."
426
+ cd "$PLATFORM_DIR" && docker compose up -d "$container"
427
+ info "Server '$server' started"
428
+ }
429
+
430
+ cmd_stop() {
431
+ local server="$1"
432
+
433
+ if [[ -z "$server" ]]; then
434
+ error "Usage: stop <server>"
435
+ return 1
436
+ fi
437
+
438
+ check_docker || return 1
439
+
440
+ local container="mc-$server"
441
+ local server_config="$PLATFORM_DIR/servers/$server/docker-compose.yml"
442
+
443
+ # Check if server configuration exists
444
+ if [[ ! -f "$server_config" ]]; then
445
+ error "Server '$server' not found (no config at servers/$server/)"
446
+ return 1
447
+ fi
448
+
449
+ # Check if container exists
450
+ if ! container_exists "$container"; then
451
+ warn "Server '$server' is not running (container not created)"
452
+ return 2
453
+ fi
454
+
455
+ local status
456
+ status=$(get_container_status "$container")
457
+
458
+ if [[ "$status" != "running" ]]; then
459
+ warn "Server '$server' is not running (status: $status)"
460
+ return 2
461
+ fi
462
+
463
+ info "Stopping server '$server'..."
464
+ docker stop "$container"
465
+ info "Server '$server' stopped"
466
+
467
+ # Auto-backup on stop (if configured)
468
+ if [[ "${BACKUP_AUTO_ON_STOP:-false}" == "true" ]]; then
469
+ if [[ -n "${BACKUP_GITHUB_TOKEN:-}" && -n "${BACKUP_GITHUB_REPO:-}" ]]; then
470
+ info "Auto-backup triggered..."
471
+ "$SCRIPT_DIR/backup.sh" push --auto || warn "Auto-backup failed (non-critical)"
472
+ fi
473
+ fi
474
+ }
475
+
476
+ # =============================================================================
477
+ # Backup Command
478
+ # =============================================================================
479
+
480
+ cmd_backup() {
481
+ # Delegate to backup.sh
482
+ if [[ -x "$SCRIPT_DIR/backup.sh" ]]; then
483
+ "$SCRIPT_DIR/backup.sh" "$@"
484
+ else
485
+ error "backup.sh not found"
486
+ return 1
487
+ fi
488
+ }
489
+
490
+ # =============================================================================
491
+ # Main
492
+ # =============================================================================
493
+
494
+ main() {
495
+ local command="${1:-}"
496
+ shift || true
497
+
498
+ case "$command" in
499
+ status)
500
+ cmd_status "$@"
501
+ ;;
502
+ logs)
503
+ cmd_logs "$@"
504
+ ;;
505
+ console)
506
+ cmd_console "$@"
507
+ ;;
508
+ world)
509
+ cmd_world "$@"
510
+ ;;
511
+ start)
512
+ cmd_start "$@"
513
+ ;;
514
+ stop)
515
+ cmd_stop "$@"
516
+ ;;
517
+ player)
518
+ cmd_player "$@"
519
+ ;;
520
+ backup)
521
+ cmd_backup "$@"
522
+ ;;
523
+ -h|--help|help)
524
+ usage
525
+ exit 0
526
+ ;;
527
+ "")
528
+ error "No command specified"
529
+ usage
530
+ exit 1
531
+ ;;
532
+ *)
533
+ error "Unknown command: $command"
534
+ usage
535
+ exit 1
536
+ ;;
537
+ esac
538
+ }
539
+
540
+ # Run main if script is executed directly
541
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
542
+ main "$@"
543
+ fi