@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.
- package/CHANGELOG.md +23 -0
- package/dist/commands/backup.d.ts +1 -0
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +28 -2
- package/dist/commands/backup.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
- package/scripts/backup.sh +569 -0
- package/scripts/create-server.sh +580 -0
- package/scripts/delete-server.sh +266 -0
- package/scripts/init.sh +390 -0
- package/scripts/lib/common.sh +248 -0
- package/scripts/lock.sh +448 -0
- package/scripts/logs.sh +283 -0
- package/scripts/mcctl.sh +543 -0
- package/scripts/migrate-nip-io.sh +258 -0
- package/scripts/player.sh +329 -0
|
@@ -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 ""
|