@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,580 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # create-server.sh - Create a new Minecraft server from template
4
+ # =============================================================================
5
+ # Usage: ./scripts/create-server.sh <server-name> [options]
6
+ #
7
+ # Arguments:
8
+ # server-name : Name for the new server (e.g., ironwood, myserver)
9
+ # This will be used for:
10
+ # - Directory name: servers/<server-name>/
11
+ # - Service name: mc-<server-name>
12
+ # - Container name: mc-<server-name>
13
+ # - Hostname: <server-name>.local
14
+ #
15
+ # Options:
16
+ # -t, --type TYPE Server type: PAPER (default), VANILLA, FORGE, FABRIC
17
+ # -v, --version VER Minecraft version (e.g., 1.21.1, 1.20.4)
18
+ # -s, --seed NUMBER World seed for new world generation
19
+ # -u, --world-url URL Download world from ZIP URL
20
+ # -w, --world NAME Use existing world from worlds/ directory (creates symlink)
21
+ # --no-start Don't start the server after creation
22
+ # --start Start the server after creation (default)
23
+ #
24
+ # World options are mutually exclusive (only one can be specified).
25
+ #
26
+ # Examples:
27
+ # ./scripts/create-server.sh myserver
28
+ # ./scripts/create-server.sh myserver -t FORGE
29
+ # ./scripts/create-server.sh myserver -t VANILLA -v 1.21.1
30
+ # ./scripts/create-server.sh myserver --seed 12345 --no-start
31
+ # ./scripts/create-server.sh myserver --world-url https://example.com/world.zip
32
+ # ./scripts/create-server.sh myserver --world existing-world --version 1.21.1
33
+ # =============================================================================
34
+
35
+ set -e
36
+
37
+ # Colors for output
38
+ RED='\033[0;31m'
39
+ GREEN='\033[0;32m'
40
+ YELLOW='\033[1;33m'
41
+ BLUE='\033[0;34m'
42
+ NC='\033[0m' # No Color
43
+
44
+ # Get script/platform directories
45
+ # Support both direct execution and npm package execution (mcctl CLI)
46
+ if [[ -n "${MCCTL_ROOT:-}" ]]; then
47
+ # Running via npm package
48
+ PLATFORM_DIR="$MCCTL_ROOT"
49
+ SCRIPT_DIR="${MCCTL_SCRIPTS:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
50
+ TEMPLATE_DIR="${MCCTL_TEMPLATES:-$PLATFORM_DIR/servers/_template}/servers/_template"
51
+ else
52
+ # Running directly (development mode)
53
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
54
+ PLATFORM_DIR="$(dirname "$SCRIPT_DIR")"
55
+ TEMPLATE_DIR="$PLATFORM_DIR/servers/_template"
56
+ fi
57
+
58
+ # Source common functions
59
+ source "$SCRIPT_DIR/lib/common.sh"
60
+
61
+ SERVERS_DIR="$PLATFORM_DIR/servers"
62
+ SERVERS_COMPOSE="$SERVERS_DIR/compose.yml"
63
+ MAIN_COMPOSE="$PLATFORM_DIR/docker-compose.yml"
64
+ ENV_FILE="$PLATFORM_DIR/.env"
65
+ AVAHI_HOSTS="/etc/avahi/hosts"
66
+
67
+ # =============================================================================
68
+ # Helper Functions
69
+ # =============================================================================
70
+
71
+ # Get host IP from .env or auto-detect (returns first IP for avahi)
72
+ get_host_ip() {
73
+ # Try to get from .env file (HOST_IPS first, then HOST_IP)
74
+ if [ -f "$ENV_FILE" ]; then
75
+ local env_ips
76
+ env_ips=$(grep "^HOST_IPS=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'")
77
+ if [ -n "$env_ips" ]; then
78
+ # Return first IP for avahi registration
79
+ echo "$env_ips" | cut -d',' -f1 | tr -d ' '
80
+ return
81
+ fi
82
+
83
+ local env_ip
84
+ env_ip=$(grep "^HOST_IP=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'")
85
+ if [ -n "$env_ip" ]; then
86
+ echo "$env_ip"
87
+ return
88
+ fi
89
+ fi
90
+
91
+ # Auto-detect: get IP of default route interface
92
+ local detected_ip
93
+ detected_ip=$(ip route get 1 2>/dev/null | awk '{print $7; exit}')
94
+ if [ -n "$detected_ip" ]; then
95
+ echo "$detected_ip"
96
+ return
97
+ fi
98
+
99
+ # Fallback: try hostname -I
100
+ detected_ip=$(hostname -I 2>/dev/null | awk '{print $1}')
101
+ if [ -n "$detected_ip" ]; then
102
+ echo "$detected_ip"
103
+ return
104
+ fi
105
+
106
+ echo ""
107
+ }
108
+
109
+ # Get all host IPs from .env (comma-separated)
110
+ # Supports HOST_IPS for multiple IPs (e.g., LAN + VPN mesh)
111
+ get_host_ips() {
112
+ if [ -f "$ENV_FILE" ]; then
113
+ # Try HOST_IPS first (comma-separated list)
114
+ local env_ips
115
+ env_ips=$(grep "^HOST_IPS=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'" | tr -d ' ')
116
+ if [ -n "$env_ips" ]; then
117
+ echo "$env_ips"
118
+ return
119
+ fi
120
+
121
+ # Fallback to single HOST_IP
122
+ local env_ip
123
+ env_ip=$(grep "^HOST_IP=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'")
124
+ if [ -n "$env_ip" ]; then
125
+ echo "$env_ip"
126
+ return
127
+ fi
128
+ fi
129
+
130
+ # Auto-detect if not configured
131
+ get_host_ip
132
+ }
133
+
134
+ # Build nip.io hostnames for all configured IPs
135
+ # Returns: server.local,server.ip1.nip.io,server.ip2.nip.io,...
136
+ build_hostnames() {
137
+ local server_name="$1"
138
+ local host_ips="$2"
139
+
140
+ local hostnames="$server_name.local"
141
+
142
+ if [ -n "$host_ips" ]; then
143
+ # Split by comma and add nip.io for each IP
144
+ IFS=',' read -ra IPS <<< "$host_ips"
145
+ for ip in "${IPS[@]}"; do
146
+ ip=$(echo "$ip" | tr -d ' ') # trim whitespace
147
+ if [ -n "$ip" ]; then
148
+ hostnames="$hostnames,$server_name.$ip.nip.io"
149
+ fi
150
+ done
151
+ fi
152
+
153
+ echo "$hostnames"
154
+ }
155
+
156
+ # Register hostname with avahi-daemon
157
+ register_avahi_hostname() {
158
+ local hostname="$1"
159
+ local ip="$2"
160
+
161
+ if [ -z "$ip" ]; then
162
+ echo -e "${YELLOW} Warning: Could not determine HOST_IP, skipping avahi registration${NC}"
163
+ echo " Set HOST_IP in .env or add manually: sudo nano $AVAHI_HOSTS"
164
+ return 1
165
+ fi
166
+
167
+ # Check if avahi-daemon is available
168
+ if ! command -v avahi-daemon &> /dev/null; then
169
+ echo -e "${YELLOW} Warning: avahi-daemon not found, skipping mDNS registration${NC}"
170
+ return 1
171
+ fi
172
+
173
+ # Check if entry already exists
174
+ if grep -q "^[^#]*$hostname" "$AVAHI_HOSTS" 2>/dev/null; then
175
+ echo -e "${YELLOW} Hostname $hostname already registered in avahi${NC}"
176
+ return 0
177
+ fi
178
+
179
+ # Add entry to /etc/avahi/hosts (requires sudo)
180
+ echo -e " Registering $hostname -> $ip with avahi-daemon..."
181
+ if echo "$ip $hostname" | run_with_sudo_stdin tee -a "$AVAHI_HOSTS" > /dev/null 2>&1; then
182
+ # Restart avahi-daemon to apply changes
183
+ if run_with_sudo systemctl restart avahi-daemon 2>/dev/null; then
184
+ echo -e " ${GREEN}mDNS hostname registered: $hostname -> $ip${NC}"
185
+ return 0
186
+ else
187
+ echo -e "${YELLOW} Warning: Failed to restart avahi-daemon${NC}"
188
+ return 1
189
+ fi
190
+ else
191
+ echo -e "${YELLOW} Warning: Failed to write to $AVAHI_HOSTS (sudo required)${NC}"
192
+ if has_sudo_password; then
193
+ echo " Check if MCCTL_SUDO_PASSWORD is correct"
194
+ else
195
+ echo " Set MCCTL_SUDO_PASSWORD env var or add manually: echo '$ip $hostname' | sudo tee -a $AVAHI_HOSTS"
196
+ fi
197
+ return 1
198
+ fi
199
+ }
200
+
201
+ # Create servers/compose.yml if it doesn't exist
202
+ ensure_servers_compose() {
203
+ if [ ! -f "$SERVERS_COMPOSE" ]; then
204
+ echo -e " Creating servers/compose.yml..."
205
+ cat > "$SERVERS_COMPOSE" << 'EOF'
206
+ # =============================================================================
207
+ # Server Include List (managed automatically by create-server.sh)
208
+ # =============================================================================
209
+ # This file is modified by scripts/create-server.sh and scripts/delete-server.sh
210
+ # Do NOT modify docker-compose.yml - only this file is updated for server changes
211
+ # =============================================================================
212
+
213
+ # Server includes are added below by create-server.sh
214
+ EOF
215
+ echo -e " ${GREEN}Created servers/compose.yml${NC}"
216
+ fi
217
+ }
218
+
219
+ # Default values
220
+ SERVER_TYPE="PAPER"
221
+ MC_VERSION=""
222
+ WORLD_SEED=""
223
+ WORLD_URL=""
224
+ WORLD_NAME=""
225
+ START_SERVER="true"
226
+
227
+ # Show usage
228
+ show_usage() {
229
+ echo "Usage: $0 <server-name> [options]"
230
+ echo ""
231
+ echo "Arguments:"
232
+ echo " server-name : Name for the new server (lowercase, no spaces)"
233
+ echo ""
234
+ echo "Options:"
235
+ echo " -t, --type TYPE Server type: PAPER (default), VANILLA, FORGE, FABRIC"
236
+ echo " -v, --version VER Minecraft version (e.g., 1.21.1, 1.20.4)"
237
+ echo " -s, --seed NUMBER World seed for new world generation"
238
+ echo " -u, --world-url URL Download world from ZIP URL"
239
+ echo " -w, --world NAME Use existing world from worlds/ directory (creates symlink)"
240
+ echo " --no-start Don't start the server after creation"
241
+ echo " --start Start the server after creation (default)"
242
+ echo ""
243
+ echo "World options (--seed, --world-url, --world) are mutually exclusive."
244
+ echo ""
245
+ echo "Examples:"
246
+ echo " $0 myserver"
247
+ echo " $0 myserver -t FORGE"
248
+ echo " $0 myserver -t VANILLA -v 1.21.1"
249
+ echo " $0 myserver --seed 12345"
250
+ echo " $0 myserver --world-url https://example.com/world.zip"
251
+ echo " $0 myserver --world existing-world -v 1.21.1 --no-start"
252
+ }
253
+
254
+ # Check if first argument exists
255
+ if [ -z "$1" ]; then
256
+ echo -e "${RED}Error: Server name is required${NC}"
257
+ echo ""
258
+ show_usage
259
+ exit 1
260
+ fi
261
+
262
+ # First argument is server name
263
+ SERVER_NAME="$1"
264
+ shift
265
+
266
+ # Parse remaining arguments
267
+ while [[ $# -gt 0 ]]; do
268
+ case $1 in
269
+ -t|--type)
270
+ SERVER_TYPE="$2"
271
+ shift 2
272
+ ;;
273
+ -v|--version)
274
+ MC_VERSION="$2"
275
+ shift 2
276
+ ;;
277
+ -s|--seed)
278
+ WORLD_SEED="$2"
279
+ shift 2
280
+ ;;
281
+ -u|--world-url)
282
+ WORLD_URL="$2"
283
+ shift 2
284
+ ;;
285
+ -w|--world)
286
+ WORLD_NAME="$2"
287
+ shift 2
288
+ ;;
289
+ --no-start)
290
+ START_SERVER="false"
291
+ shift
292
+ ;;
293
+ --start)
294
+ START_SERVER="true"
295
+ shift
296
+ ;;
297
+ -h|--help)
298
+ show_usage
299
+ exit 0
300
+ ;;
301
+ *)
302
+ # For backward compatibility: if it looks like a server type, use it
303
+ if [[ "$1" =~ ^(PAPER|VANILLA|FORGE|FABRIC|NEOFORGE|QUILT|SPIGOT)$ ]]; then
304
+ SERVER_TYPE="$1"
305
+ shift
306
+ else
307
+ echo -e "${RED}Error: Unknown option: $1${NC}"
308
+ echo ""
309
+ show_usage
310
+ exit 1
311
+ fi
312
+ ;;
313
+ esac
314
+ done
315
+
316
+ # Validate mutually exclusive world options
317
+ WORLD_OPTIONS_COUNT=0
318
+ [ -n "$WORLD_SEED" ] && WORLD_OPTIONS_COUNT=$((WORLD_OPTIONS_COUNT + 1))
319
+ [ -n "$WORLD_URL" ] && WORLD_OPTIONS_COUNT=$((WORLD_OPTIONS_COUNT + 1))
320
+ [ -n "$WORLD_NAME" ] && WORLD_OPTIONS_COUNT=$((WORLD_OPTIONS_COUNT + 1))
321
+
322
+ if [ "$WORLD_OPTIONS_COUNT" -gt 1 ]; then
323
+ echo -e "${RED}Error: World options (--seed, --world-url, --world) are mutually exclusive${NC}"
324
+ echo "Please specify only one world option."
325
+ exit 1
326
+ fi
327
+
328
+ # Validate server name (lowercase, alphanumeric, hyphens only)
329
+ if [[ ! "$SERVER_NAME" =~ ^[a-z][a-z0-9-]*$ ]]; then
330
+ echo -e "${RED}Error: Server name must start with a letter and contain only lowercase letters, numbers, and hyphens${NC}"
331
+ exit 1
332
+ fi
333
+
334
+ # Check if server already exists
335
+ SERVER_DIR="$SERVERS_DIR/$SERVER_NAME"
336
+ if [ -d "$SERVER_DIR" ]; then
337
+ echo -e "${RED}Error: Server '$SERVER_NAME' already exists at $SERVER_DIR${NC}"
338
+ exit 1
339
+ fi
340
+
341
+ # Check if template exists
342
+ if [ ! -d "$TEMPLATE_DIR" ]; then
343
+ echo -e "${RED}Error: Template directory not found at $TEMPLATE_DIR${NC}"
344
+ exit 1
345
+ fi
346
+
347
+ # Check if main docker-compose.yml exists
348
+ if [ ! -f "$MAIN_COMPOSE" ]; then
349
+ echo -e "${RED}Error: Main docker-compose.yml not found at $MAIN_COMPOSE${NC}"
350
+ exit 1
351
+ fi
352
+
353
+ echo -e "${GREEN}Creating new server: $SERVER_NAME${NC}"
354
+ echo " Type: $SERVER_TYPE"
355
+ [ -n "$MC_VERSION" ] && echo " Version: $MC_VERSION"
356
+ echo " Directory: $SERVER_DIR"
357
+ echo " Auto-start: $START_SERVER"
358
+ echo ""
359
+
360
+ # =============================================================================
361
+ # Step 1: Copy template
362
+ # =============================================================================
363
+ echo -e "${BLUE}[1/6]${NC} Copying template..."
364
+ cp -r "$TEMPLATE_DIR" "$SERVER_DIR"
365
+
366
+ # =============================================================================
367
+ # Step 2: Update server's docker-compose.yml
368
+ # =============================================================================
369
+ echo -e "${BLUE}[2/6]${NC} Updating server docker-compose.yml..."
370
+ COMPOSE_FILE="$SERVER_DIR/docker-compose.yml"
371
+
372
+ # Get all HOST_IPS for nip.io hostnames (supports multiple IPs for VPN mesh)
373
+ HOST_IPS=$(get_host_ips)
374
+ HOSTNAMES=$(build_hostnames "$SERVER_NAME" "$HOST_IPS")
375
+
376
+ # Replace template with server name
377
+ sed -i "s/mc-template/mc-$SERVER_NAME/g" "$COMPOSE_FILE"
378
+
379
+ # Configure mc-router hostnames (supports multiple IPs: .local + nip.io for each IP)
380
+ if [ -n "$HOST_IPS" ]; then
381
+ sed -i "s/template\.local/$HOSTNAMES/g" "$COMPOSE_FILE"
382
+ echo " Hostnames: $HOSTNAMES"
383
+ else
384
+ # Fallback to .local only
385
+ echo -e "${YELLOW} Warning: HOST_IP/HOST_IPS not set, using .local domain only${NC}"
386
+ echo " Set HOST_IP or HOST_IPS in .env for nip.io domain support"
387
+ sed -i "s/template\.local/$SERVER_NAME.local/g" "$COMPOSE_FILE"
388
+ fi
389
+ sed -i "s/# Minecraft Server Configuration Template/# $SERVER_NAME Server/g" "$COMPOSE_FILE"
390
+
391
+ # Update header comments
392
+ sed -i "s|# Usage:|# Hostname: $SERVER_NAME.local|g" "$COMPOSE_FILE"
393
+ sed -i "s|# 1\. Copy this directory.*|# Type: $SERVER_TYPE|g" "$COMPOSE_FILE"
394
+ sed -i "/^# [2-5]\./d" "$COMPOSE_FILE"
395
+
396
+ # =============================================================================
397
+ # Step 3: Update config.env
398
+ # =============================================================================
399
+ echo -e "${BLUE}[3/6]${NC} Updating config.env..."
400
+ CONFIG_FILE="$SERVER_DIR/config.env"
401
+ if [ -f "$CONFIG_FILE" ]; then
402
+ sed -i "s/^TYPE=.*/TYPE=$SERVER_TYPE/" "$CONFIG_FILE"
403
+ sed -i "s/^MOTD=.*/MOTD=Welcome to $SERVER_NAME! Your adventure begins here./" "$CONFIG_FILE"
404
+
405
+ # Set LEVEL to server name by default (world stored in /worlds/<server-name>)
406
+ # This can be overridden by --world option
407
+ sed -i "s/^LEVEL=.*/LEVEL=$SERVER_NAME/" "$CONFIG_FILE"
408
+ echo " World directory: worlds/$SERVER_NAME"
409
+
410
+ # Apply version if specified
411
+ if [ -n "$MC_VERSION" ]; then
412
+ sed -i "s/^VERSION=.*/VERSION=$MC_VERSION/" "$CONFIG_FILE"
413
+ echo " Version: $MC_VERSION"
414
+ fi
415
+
416
+ # Apply world options
417
+ if [ -n "$WORLD_SEED" ]; then
418
+ echo "" >> "$CONFIG_FILE"
419
+ echo "# World Seed" >> "$CONFIG_FILE"
420
+ echo "SEED=$WORLD_SEED" >> "$CONFIG_FILE"
421
+ echo " World seed: $WORLD_SEED"
422
+ fi
423
+
424
+ if [ -n "$WORLD_URL" ]; then
425
+ echo "" >> "$CONFIG_FILE"
426
+ echo "# World Download URL" >> "$CONFIG_FILE"
427
+ echo "WORLD=$WORLD_URL" >> "$CONFIG_FILE"
428
+ echo " World URL: $WORLD_URL"
429
+ fi
430
+
431
+ if [ -n "$WORLD_NAME" ]; then
432
+ # Check if the world exists in shared worlds directory
433
+ WORLD_PATH="$PLATFORM_DIR/worlds/$WORLD_NAME"
434
+ if [ -d "$WORLD_PATH" ]; then
435
+ sed -i "s/^LEVEL=.*/LEVEL=$WORLD_NAME/" "$CONFIG_FILE"
436
+ echo " Using existing world: $WORLD_NAME (from worlds/$WORLD_NAME)"
437
+ else
438
+ echo -e "${YELLOW} Warning: World '$WORLD_NAME' not found in worlds/ directory${NC}"
439
+ echo " LEVEL set to '$WORLD_NAME' - world will be created on first start"
440
+ sed -i "s/^LEVEL=.*/LEVEL=$WORLD_NAME/" "$CONFIG_FILE"
441
+ fi
442
+ fi
443
+ fi
444
+
445
+ # Create data and logs directories
446
+ mkdir -p "$SERVER_DIR/data"
447
+ mkdir -p "$SERVER_DIR/logs"
448
+
449
+ # Ensure worlds directory exists (world will be created here via --world-dir)
450
+ WORLD_LEVEL="${WORLD_NAME:-$SERVER_NAME}"
451
+ mkdir -p "$PLATFORM_DIR/worlds"
452
+
453
+ # =============================================================================
454
+ # Step 4: Update servers/compose.yml
455
+ # =============================================================================
456
+ echo -e "${BLUE}[4/6]${NC} Updating servers/compose.yml..."
457
+
458
+ # Ensure servers/compose.yml exists
459
+ ensure_servers_compose
460
+
461
+ # Backup original
462
+ cp "$SERVERS_COMPOSE" "$SERVERS_COMPOSE.bak"
463
+
464
+ # Check if include section exists
465
+ if grep -q "^include:" "$SERVERS_COMPOSE"; then
466
+ # Add new server to existing include section
467
+ sed -i "/^include:/a\\ - $SERVER_NAME/docker-compose.yml" "$SERVERS_COMPOSE"
468
+ echo " Added to include section"
469
+ else
470
+ # Add include section at the end
471
+ echo "" >> "$SERVERS_COMPOSE"
472
+ echo "include:" >> "$SERVERS_COMPOSE"
473
+ echo " - $SERVER_NAME/docker-compose.yml" >> "$SERVERS_COMPOSE"
474
+ echo " Created include section and added server"
475
+ fi
476
+
477
+ # Verify the changes
478
+ if docker compose -f "$MAIN_COMPOSE" config --quiet 2>/dev/null; then
479
+ echo -e " ${GREEN}Configuration validated successfully${NC}"
480
+ rm -f "$SERVERS_COMPOSE.bak"
481
+ else
482
+ echo -e "${RED} Error: Configuration validation failed!${NC}"
483
+ echo " Restoring backup..."
484
+ mv "$SERVERS_COMPOSE.bak" "$SERVERS_COMPOSE"
485
+ echo " Please check the configuration manually."
486
+ exit 1
487
+ fi
488
+
489
+ # =============================================================================
490
+ # Step 5: Register with avahi-daemon (mDNS)
491
+ # =============================================================================
492
+ echo -e "${BLUE}[5/6]${NC} Registering mDNS hostname..."
493
+ HOST_IP=$(get_host_ip)
494
+ register_avahi_hostname "$SERVER_NAME.local" "$HOST_IP"
495
+
496
+ # =============================================================================
497
+ # Step 6: Start server (optional)
498
+ # =============================================================================
499
+ if [ "$START_SERVER" = "true" ]; then
500
+ echo -e "${BLUE}[6/6]${NC} Starting server..."
501
+ cd "$PLATFORM_DIR"
502
+ docker compose up -d "mc-$SERVER_NAME"
503
+
504
+ # Wait a moment and check status
505
+ sleep 2
506
+ if docker compose ps "mc-$SERVER_NAME" 2>/dev/null | grep -q "Up"; then
507
+ echo -e " ${GREEN}Server mc-$SERVER_NAME started successfully${NC}"
508
+ else
509
+ echo -e " ${YELLOW}Server is starting... (check logs with: docker logs -f mc-$SERVER_NAME)${NC}"
510
+ fi
511
+ else
512
+ echo -e "${BLUE}[6/6]${NC} Skipping server start (--no-start specified)"
513
+ fi
514
+
515
+ # =============================================================================
516
+ # Summary
517
+ # =============================================================================
518
+ echo ""
519
+ echo -e "${GREEN}========================================${NC}"
520
+ echo -e "${GREEN}Server '$SERVER_NAME' created successfully!${NC}"
521
+ echo -e "${GREEN}========================================${NC}"
522
+ echo ""
523
+
524
+ # Show world configuration summary
525
+ echo -e "${GREEN}World configuration:${NC}"
526
+ if [ -n "$WORLD_NAME" ]; then
527
+ echo " - World: worlds/$WORLD_NAME (existing)"
528
+ elif [ -n "$WORLD_URL" ]; then
529
+ echo " - World: worlds/$SERVER_NAME (from URL)"
530
+ echo " - URL: $WORLD_URL"
531
+ else
532
+ echo " - World: worlds/$SERVER_NAME (new)"
533
+ fi
534
+ if [ -n "$WORLD_SEED" ]; then
535
+ echo " - Seed: $WORLD_SEED"
536
+ fi
537
+ echo ""
538
+
539
+ echo -e "${GREEN}Server details:${NC}"
540
+ echo " - Directory: servers/$SERVER_NAME/"
541
+ echo " - Service: mc-$SERVER_NAME"
542
+ if [ -n "$HOST_IPS" ]; then
543
+ echo " - Hostnames: $HOSTNAMES"
544
+ else
545
+ echo " - Hostname: $SERVER_NAME.local"
546
+ fi
547
+ echo " - Type: $SERVER_TYPE"
548
+ [ -n "$MC_VERSION" ] && echo " - Version: $MC_VERSION"
549
+ echo ""
550
+
551
+ echo -e "${GREEN}Connection:${NC}"
552
+ if [ -n "$HOST_IPS" ]; then
553
+ echo " ${GREEN}Recommended (nip.io - no client setup needed):${NC}"
554
+ # Show each IP's nip.io address
555
+ IFS=',' read -ra IPS <<< "$HOST_IPS"
556
+ for ip in "${IPS[@]}"; do
557
+ ip=$(echo "$ip" | tr -d ' ')
558
+ if [ -n "$ip" ]; then
559
+ echo " $SERVER_NAME.$ip.nip.io:25565"
560
+ fi
561
+ done
562
+ echo ""
563
+ echo " Alternative (mDNS - requires avahi/Bonjour on client):"
564
+ echo " $SERVER_NAME.local:25565"
565
+ else
566
+ echo " mDNS: $SERVER_NAME.local:25565"
567
+ echo " (Set HOST_IP or HOST_IPS in .env for nip.io support)"
568
+ fi
569
+ echo ""
570
+
571
+ if [ "$START_SERVER" = "true" ]; then
572
+ echo -e "${GREEN}Commands:${NC}"
573
+ echo " View logs: docker logs -f mc-$SERVER_NAME"
574
+ echo " Stop server: docker compose stop mc-$SERVER_NAME"
575
+ echo " RCON console: docker exec -i mc-$SERVER_NAME rcon-cli"
576
+ else
577
+ echo -e "${YELLOW}To start the server:${NC}"
578
+ echo " cd $PLATFORM_DIR && docker compose up -d mc-$SERVER_NAME"
579
+ fi
580
+ echo ""