@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,258 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # migrate-nip-io.sh - Add nip.io domains to existing servers
4
+ # =============================================================================
5
+ # Usage: ./scripts/migrate-nip-io.sh [options]
6
+ #
7
+ # This script updates all existing server docker-compose.yml files to include
8
+ # nip.io magic DNS hostnames alongside the existing .local hostnames.
9
+ #
10
+ # Options:
11
+ # --dry-run Show what would be changed without making changes
12
+ # -h, --help Show this help message
13
+ #
14
+ # Requirements:
15
+ # - HOST_IP must be set in platform/.env
16
+ #
17
+ # Example:
18
+ # ./scripts/migrate-nip-io.sh # Apply changes
19
+ # ./scripts/migrate-nip-io.sh --dry-run # Preview changes only
20
+ # =============================================================================
21
+
22
+ set -e
23
+
24
+ # Colors for output
25
+ RED='\033[0;31m'
26
+ GREEN='\033[0;32m'
27
+ YELLOW='\033[1;33m'
28
+ BLUE='\033[0;34m'
29
+ NC='\033[0m' # No Color
30
+
31
+ # Track current backup for cleanup on error
32
+ CURRENT_BACKUP=""
33
+
34
+ # Cleanup function for trap
35
+ cleanup_on_error() {
36
+ if [ -n "$CURRENT_BACKUP" ] && [ -f "$CURRENT_BACKUP" ]; then
37
+ echo -e "${RED}Error occurred. Restoring backup...${NC}"
38
+ local original_file="${CURRENT_BACKUP%.bak}"
39
+ mv "$CURRENT_BACKUP" "$original_file"
40
+ echo -e "${YELLOW}Restored: $original_file${NC}"
41
+ fi
42
+ }
43
+
44
+ # Set trap for cleanup on error
45
+ trap cleanup_on_error ERR
46
+
47
+ # Get script/platform directories
48
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
49
+ PLATFORM_DIR="$(dirname "$SCRIPT_DIR")"
50
+ SERVERS_DIR="$PLATFORM_DIR/servers"
51
+ ENV_FILE="$PLATFORM_DIR/.env"
52
+
53
+ # Options
54
+ DRY_RUN=false
55
+
56
+ # =============================================================================
57
+ # Helper Functions
58
+ # =============================================================================
59
+
60
+ show_usage() {
61
+ echo "Usage: $0 [options]"
62
+ echo ""
63
+ echo "Add nip.io magic DNS hostnames to existing server configurations."
64
+ echo ""
65
+ echo "Options:"
66
+ echo " --dry-run Show what would be changed without making changes"
67
+ echo " -h, --help Show this help message"
68
+ echo ""
69
+ echo "Requirements:"
70
+ echo " HOST_IP must be set in platform/.env"
71
+ }
72
+
73
+ # Get host IP from .env file or auto-detect
74
+ get_host_ip() {
75
+ # Try .env file first
76
+ if [ -f "$ENV_FILE" ]; then
77
+ local env_ip
78
+ env_ip=$(grep "^HOST_IP=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2 | tr -d '"' | tr -d "'")
79
+ if [ -n "$env_ip" ]; then
80
+ echo "$env_ip"
81
+ return
82
+ fi
83
+ fi
84
+
85
+ # Auto-detect: Try ip route
86
+ local detected_ip
87
+ detected_ip=$(ip route get 1 2>/dev/null | grep -oP 'src \K\S+' | head -1)
88
+ if [ -n "$detected_ip" ]; then
89
+ echo "$detected_ip"
90
+ return
91
+ fi
92
+
93
+ # Auto-detect: Try hostname -I
94
+ detected_ip=$(hostname -I 2>/dev/null | awk '{print $1}')
95
+ if [ -n "$detected_ip" ]; then
96
+ echo "$detected_ip"
97
+ return
98
+ fi
99
+
100
+ echo ""
101
+ }
102
+
103
+ # Extract server name from docker-compose.yml
104
+ get_server_name() {
105
+ local compose_file="$1"
106
+ # Look for mc-router.host label and extract the .local hostname
107
+ local hostname
108
+ hostname=$(grep -E "mc-router\.host.*\.local" "$compose_file" 2>/dev/null | head -1 | grep -oP '[a-z0-9-]+(?=\.local)' | head -1)
109
+ echo "$hostname"
110
+ }
111
+
112
+ # Check if nip.io is already configured
113
+ has_nip_io() {
114
+ local compose_file="$1"
115
+ grep -q "nip\.io" "$compose_file" 2>/dev/null
116
+ }
117
+
118
+ # =============================================================================
119
+ # Parse Arguments
120
+ # =============================================================================
121
+
122
+ while [[ $# -gt 0 ]]; do
123
+ case $1 in
124
+ --dry-run)
125
+ DRY_RUN=true
126
+ shift
127
+ ;;
128
+ -h|--help)
129
+ show_usage
130
+ exit 0
131
+ ;;
132
+ *)
133
+ echo -e "${RED}Error: Unknown option: $1${NC}"
134
+ show_usage
135
+ exit 1
136
+ ;;
137
+ esac
138
+ done
139
+
140
+ # =============================================================================
141
+ # Main Script
142
+ # =============================================================================
143
+
144
+ echo -e "${GREEN}========================================${NC}"
145
+ echo -e "${GREEN}nip.io Migration Script${NC}"
146
+ echo -e "${GREEN}========================================${NC}"
147
+ echo ""
148
+
149
+ # Check HOST_IP
150
+ HOST_IP=$(get_host_ip)
151
+ if [ -z "$HOST_IP" ]; then
152
+ echo -e "${RED}Error: HOST_IP not set in $ENV_FILE${NC}"
153
+ echo "Please set HOST_IP to your server's local IP address."
154
+ echo "Example: HOST_IP=192.168.20.37"
155
+ exit 1
156
+ fi
157
+
158
+ echo "HOST_IP: $HOST_IP"
159
+ echo "Servers directory: $SERVERS_DIR"
160
+ if [ "$DRY_RUN" = true ]; then
161
+ echo -e "${YELLOW}DRY RUN MODE - No changes will be made${NC}"
162
+ fi
163
+ echo ""
164
+
165
+ # Find all server directories (excluding _template)
166
+ SERVERS_FOUND=0
167
+ SERVERS_UPDATED=0
168
+ SERVERS_SKIPPED=0
169
+
170
+ for server_dir in "$SERVERS_DIR"/*/; do
171
+ # Skip _template directory
172
+ if [[ "$(basename "$server_dir")" == "_template" ]]; then
173
+ continue
174
+ fi
175
+
176
+ compose_file="$server_dir/docker-compose.yml"
177
+
178
+ # Check if docker-compose.yml exists
179
+ if [ ! -f "$compose_file" ]; then
180
+ continue
181
+ fi
182
+
183
+ SERVERS_FOUND=$((SERVERS_FOUND + 1))
184
+ server_name=$(basename "$server_dir")
185
+
186
+ echo -e "${BLUE}Processing:${NC} $server_name"
187
+
188
+ # Check if already has nip.io
189
+ if has_nip_io "$compose_file"; then
190
+ echo -e " ${YELLOW}Skipped${NC} - nip.io already configured"
191
+ SERVERS_SKIPPED=$((SERVERS_SKIPPED + 1))
192
+ continue
193
+ fi
194
+
195
+ # Get the current hostname from the file
196
+ current_hostname=$(get_server_name "$compose_file")
197
+ if [ -z "$current_hostname" ]; then
198
+ echo -e " ${YELLOW}Skipped${NC} - Could not find mc-router.host label"
199
+ SERVERS_SKIPPED=$((SERVERS_SKIPPED + 1))
200
+ continue
201
+ fi
202
+
203
+ # Construct new hostname value
204
+ new_hostname="$current_hostname.local,$current_hostname.$HOST_IP.nip.io"
205
+
206
+ echo " Current: $current_hostname.local"
207
+ echo " New: $new_hostname"
208
+
209
+ if [ "$DRY_RUN" = true ]; then
210
+ echo -e " ${YELLOW}Would update${NC} (dry run)"
211
+ SERVERS_UPDATED=$((SERVERS_UPDATED + 1))
212
+ else
213
+ # Create backup (tracked for cleanup on error)
214
+ CURRENT_BACKUP="$compose_file.bak"
215
+ cp "$compose_file" "$CURRENT_BACKUP"
216
+
217
+ # Update the mc-router.host label
218
+ # Handle both YAML format (mc-router.host: "value") and array format (- "mc-router.host=value")
219
+ if grep -q "mc-router\.host:" "$compose_file"; then
220
+ # YAML format
221
+ sed -i "s/mc-router\.host:.*\"$current_hostname\.local\"/mc-router.host: \"$new_hostname\"/" "$compose_file"
222
+ else
223
+ # Array format
224
+ sed -i "s/mc-router\.host=$current_hostname\.local/mc-router.host=$new_hostname/" "$compose_file"
225
+ fi
226
+
227
+ echo -e " ${GREEN}Updated${NC}"
228
+
229
+ # Remove backup on success and clear tracking
230
+ rm -f "$CURRENT_BACKUP"
231
+ CURRENT_BACKUP=""
232
+ SERVERS_UPDATED=$((SERVERS_UPDATED + 1))
233
+ fi
234
+ done
235
+
236
+ echo ""
237
+ echo -e "${GREEN}========================================${NC}"
238
+ echo -e "${GREEN}Migration Summary${NC}"
239
+ echo -e "${GREEN}========================================${NC}"
240
+ echo "Servers found: $SERVERS_FOUND"
241
+ echo "Servers updated: $SERVERS_UPDATED"
242
+ echo "Servers skipped: $SERVERS_SKIPPED"
243
+ echo ""
244
+
245
+ if [ "$DRY_RUN" = true ]; then
246
+ echo -e "${YELLOW}This was a dry run. Run without --dry-run to apply changes.${NC}"
247
+ elif [ "$SERVERS_UPDATED" -gt 0 ]; then
248
+ echo -e "${GREEN}Migration complete!${NC}"
249
+ echo ""
250
+ echo "Next steps:"
251
+ echo " 1. Validate configuration: docker compose -f $PLATFORM_DIR/docker-compose.yml config"
252
+ echo " 2. Restart servers: docker compose down && docker compose up -d"
253
+ echo ""
254
+ echo "Clients can now connect via:"
255
+ echo " <server>.$HOST_IP.nip.io:25565"
256
+ else
257
+ echo "No servers needed migration."
258
+ fi
@@ -0,0 +1,329 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # player.sh - Player UUID lookup using PlayerDB API
4
+ # =============================================================================
5
+ # Look up Minecraft player information including UUID.
6
+ #
7
+ # Usage:
8
+ # ./scripts/player.sh lookup <playerName> # Full player info
9
+ # ./scripts/player.sh lookup <playerName> --json # JSON output
10
+ # ./scripts/player.sh uuid <playerName> # Online UUID only
11
+ # ./scripts/player.sh uuid <playerName> --offline # Offline UUID only
12
+ #
13
+ # Exit codes:
14
+ # 0 - Success
15
+ # 1 - Error (API error, player not found, etc.)
16
+ # 2 - Warning
17
+ # =============================================================================
18
+
19
+ set -e
20
+
21
+ # Get script directory and source common functions
22
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23
+ source "$SCRIPT_DIR/lib/common.sh"
24
+
25
+ # =============================================================================
26
+ # Configuration
27
+ # =============================================================================
28
+
29
+ PLAYERDB_API="https://playerdb.co/api/player/minecraft"
30
+ AVATAR_BASE="https://crafthead.net/avatar"
31
+
32
+ # =============================================================================
33
+ # Usage
34
+ # =============================================================================
35
+
36
+ usage() {
37
+ cat <<EOF
38
+ Usage: $(basename "$0") <command> <playerName> [options]
39
+
40
+ Look up Minecraft player information using PlayerDB API.
41
+
42
+ Commands:
43
+ lookup <playerName> Show full player information
44
+ uuid <playerName> Get player's online UUID
45
+
46
+ Options:
47
+ --json Output in JSON format
48
+ --offline Get offline UUID (for uuid command)
49
+ -h, --help Show this help message
50
+
51
+ Examples:
52
+ $(basename "$0") lookup Notch
53
+ $(basename "$0") lookup Notch --json
54
+ $(basename "$0") uuid Notch
55
+ $(basename "$0") uuid Notch --offline
56
+
57
+ Notes:
58
+ - Online UUID is fetched from Mojang API via PlayerDB
59
+ - Offline UUID is calculated from player name (UUID v3)
60
+ - Requires: curl and jq
61
+ EOF
62
+ }
63
+
64
+ # =============================================================================
65
+ # Helper Functions
66
+ # =============================================================================
67
+
68
+ # Check required commands
69
+ check_requirements() {
70
+ local missing=()
71
+
72
+ if ! command -v curl &> /dev/null; then
73
+ missing+=("curl")
74
+ fi
75
+
76
+ if ! command -v jq &> /dev/null; then
77
+ missing+=("jq")
78
+ fi
79
+
80
+ if [[ ${#missing[@]} -gt 0 ]]; then
81
+ error "Missing required commands: ${missing[*]}"
82
+ error "Please install them and try again"
83
+ return 1
84
+ fi
85
+ }
86
+
87
+ # Calculate offline UUID from player name
88
+ # Offline UUID = UUID v3 based on "OfflinePlayer:<name>"
89
+ calculate_offline_uuid() {
90
+ local player_name="$1"
91
+ local input="OfflinePlayer:$player_name"
92
+
93
+ # Calculate MD5 hash and format as UUID v3
94
+ local md5_hash
95
+ md5_hash=$(echo -n "$input" | md5sum | cut -d' ' -f1)
96
+
97
+ # Format as UUID with version 3 marker
98
+ # UUID format: xxxxxxxx-xxxx-3xxx-yxxx-xxxxxxxxxxxx
99
+ # where 3 is the version and y is 8, 9, a, or b (variant)
100
+ local uuid
101
+ uuid=$(echo "$md5_hash" | sed 's/\(........\)\(....\)\(....\)\(....\)\(............\)/\1-\2-\3-\4-\5/')
102
+
103
+ # Set version to 3 (third section starts with 3)
104
+ uuid=$(echo "$uuid" | sed 's/\(........-....\)-\(.\)/\1-3/')
105
+
106
+ # Set variant (first char of fourth section should be 8, 9, a, or b)
107
+ # We'll use 8 for simplicity
108
+ local fourth_section
109
+ fourth_section=$(echo "$uuid" | cut -d'-' -f4)
110
+ local first_char=${fourth_section:0:1}
111
+ local rest=${fourth_section:1}
112
+
113
+ # Convert first char to variant (8-b range)
114
+ case $first_char in
115
+ 0|1|2|3) first_char="8" ;;
116
+ 4|5|6|7) first_char="9" ;;
117
+ 8|9) ;; # Already valid
118
+ a|b) ;; # Already valid
119
+ c|d|e|f) first_char="a" ;;
120
+ esac
121
+
122
+ # Reconstruct UUID
123
+ uuid=$(echo "$uuid" | sed "s/\(........-....-....-\).\(...-............\)/\1${first_char}\2/")
124
+
125
+ echo "$uuid"
126
+ }
127
+
128
+ # Fetch player info from PlayerDB API
129
+ fetch_player_info() {
130
+ local player_name="$1"
131
+ local response
132
+ local http_code
133
+
134
+ # Make API request with error handling
135
+ response=$(curl -s -w "\n%{http_code}" "${PLAYERDB_API}/${player_name}" 2>/dev/null) || {
136
+ error "Failed to connect to PlayerDB API"
137
+ return 1
138
+ }
139
+
140
+ # Extract HTTP code and body
141
+ http_code=$(echo "$response" | tail -n1)
142
+ local body
143
+ body=$(echo "$response" | sed '$d')
144
+
145
+ # Check HTTP status
146
+ if [[ "$http_code" != "200" ]]; then
147
+ error "API request failed with HTTP $http_code"
148
+ return 1
149
+ fi
150
+
151
+ # Check API success
152
+ local success
153
+ success=$(echo "$body" | jq -r '.success' 2>/dev/null)
154
+
155
+ if [[ "$success" != "true" ]]; then
156
+ local code
157
+ code=$(echo "$body" | jq -r '.code' 2>/dev/null)
158
+ if [[ "$code" == "minecraft.invalid_username" ]]; then
159
+ error "Invalid username format: $player_name"
160
+ elif [[ "$code" == "minecraft.api_failure" ]]; then
161
+ error "Player not found: $player_name"
162
+ else
163
+ error "API error: $(echo "$body" | jq -r '.message' 2>/dev/null)"
164
+ fi
165
+ return 1
166
+ fi
167
+
168
+ echo "$body"
169
+ }
170
+
171
+ # =============================================================================
172
+ # Commands
173
+ # =============================================================================
174
+
175
+ # Lookup command - show full player info
176
+ cmd_lookup() {
177
+ local player_name=""
178
+ local json_output=false
179
+
180
+ while [[ $# -gt 0 ]]; do
181
+ case "$1" in
182
+ --json)
183
+ json_output=true
184
+ JSON_OUTPUT=true
185
+ setup_colors
186
+ shift
187
+ ;;
188
+ -*)
189
+ error "Unknown option: $1"
190
+ return 1
191
+ ;;
192
+ *)
193
+ if [[ -z "$player_name" ]]; then
194
+ player_name="$1"
195
+ else
196
+ error "Unexpected argument: $1"
197
+ return 1
198
+ fi
199
+ shift
200
+ ;;
201
+ esac
202
+ done
203
+
204
+ if [[ -z "$player_name" ]]; then
205
+ error "Player name is required"
206
+ return 1
207
+ fi
208
+
209
+ check_requirements || return 1
210
+
211
+ local response
212
+ response=$(fetch_player_info "$player_name") || return 1
213
+
214
+ # Extract player data
215
+ local username
216
+ local online_uuid
217
+ local raw_id
218
+ local avatar
219
+ local skin_texture
220
+
221
+ username=$(echo "$response" | jq -r '.data.player.username')
222
+ online_uuid=$(echo "$response" | jq -r '.data.player.id')
223
+ raw_id=$(echo "$response" | jq -r '.data.player.raw_id')
224
+ avatar=$(echo "$response" | jq -r '.data.player.avatar')
225
+ skin_texture=$(echo "$response" | jq -r '.data.player.meta.name_history[0].name // empty' 2>/dev/null || echo "")
226
+
227
+ # Calculate offline UUID
228
+ local offline_uuid
229
+ offline_uuid=$(calculate_offline_uuid "$username")
230
+
231
+ if $json_output; then
232
+ cat <<EOF
233
+ {
234
+ "username": "$username",
235
+ "online_uuid": "$online_uuid",
236
+ "offline_uuid": "$offline_uuid",
237
+ "raw_id": "$raw_id",
238
+ "avatar": "$avatar"
239
+ }
240
+ EOF
241
+ else
242
+ echo -e "${BOLD}Player: ${CYAN}$username${NC}"
243
+ echo -e "Online UUID: ${GREEN}$online_uuid${NC}"
244
+ echo -e "Offline UUID: ${YELLOW}$offline_uuid${NC}"
245
+ echo -e "Avatar: $avatar"
246
+ fi
247
+ }
248
+
249
+ # UUID command - get specific UUID
250
+ cmd_uuid() {
251
+ local player_name=""
252
+ local offline_mode=false
253
+
254
+ while [[ $# -gt 0 ]]; do
255
+ case "$1" in
256
+ --offline)
257
+ offline_mode=true
258
+ shift
259
+ ;;
260
+ -*)
261
+ error "Unknown option: $1"
262
+ return 1
263
+ ;;
264
+ *)
265
+ if [[ -z "$player_name" ]]; then
266
+ player_name="$1"
267
+ else
268
+ error "Unexpected argument: $1"
269
+ return 1
270
+ fi
271
+ shift
272
+ ;;
273
+ esac
274
+ done
275
+
276
+ if [[ -z "$player_name" ]]; then
277
+ error "Player name is required"
278
+ return 1
279
+ fi
280
+
281
+ if $offline_mode; then
282
+ # Just calculate offline UUID (no API call needed)
283
+ calculate_offline_uuid "$player_name"
284
+ else
285
+ check_requirements || return 1
286
+
287
+ local response
288
+ response=$(fetch_player_info "$player_name") || return 1
289
+
290
+ echo "$response" | jq -r '.data.player.id'
291
+ fi
292
+ }
293
+
294
+ # =============================================================================
295
+ # Main
296
+ # =============================================================================
297
+
298
+ main() {
299
+ local command="${1:-}"
300
+ shift || true
301
+
302
+ case "$command" in
303
+ lookup)
304
+ cmd_lookup "$@"
305
+ ;;
306
+ uuid)
307
+ cmd_uuid "$@"
308
+ ;;
309
+ -h|--help|help)
310
+ usage
311
+ exit 0
312
+ ;;
313
+ "")
314
+ error "No command specified"
315
+ usage
316
+ exit 1
317
+ ;;
318
+ *)
319
+ error "Unknown command: $command"
320
+ usage
321
+ exit 1
322
+ ;;
323
+ esac
324
+ }
325
+
326
+ # Run main if script is executed directly
327
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
328
+ main "$@"
329
+ fi