@minecraft-docker/mcctl 1.13.0 → 1.14.0

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +22 -6
  2. package/README.md +8 -0
  3. package/dist/commands/console/init.d.ts.map +1 -1
  4. package/dist/commands/console/init.js +51 -38
  5. package/dist/commands/console/init.js.map +1 -1
  6. package/dist/commands/console/service.d.ts.map +1 -1
  7. package/dist/commands/console/service.js +82 -19
  8. package/dist/commands/console/service.js.map +1 -1
  9. package/dist/commands/console/user.d.ts.map +1 -1
  10. package/dist/commands/console/user.js +344 -304
  11. package/dist/commands/console/user.js.map +1 -1
  12. package/dist/commands/create.d.ts +3 -0
  13. package/dist/commands/create.d.ts.map +1 -1
  14. package/dist/commands/create.js +16 -1
  15. package/dist/commands/create.js.map +1 -1
  16. package/dist/index.js +8 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/infrastructure/adapters/ClackPromptAdapter.d.ts +4 -1
  19. package/dist/infrastructure/adapters/ClackPromptAdapter.d.ts.map +1 -1
  20. package/dist/infrastructure/adapters/ClackPromptAdapter.js +92 -9
  21. package/dist/infrastructure/adapters/ClackPromptAdapter.js.map +1 -1
  22. package/dist/infrastructure/di/container.d.ts +3 -1
  23. package/dist/infrastructure/di/container.d.ts.map +1 -1
  24. package/dist/infrastructure/di/container.js +9 -1
  25. package/dist/infrastructure/di/container.js.map +1 -1
  26. package/dist/lib/admin-config.js +1 -1
  27. package/dist/lib/console-db.d.ts +64 -0
  28. package/dist/lib/console-db.d.ts.map +1 -0
  29. package/dist/lib/console-db.js +240 -0
  30. package/dist/lib/console-db.js.map +1 -0
  31. package/package.json +5 -3
  32. package/scripts/create-server.sh +63 -3
  33. package/templates/.env.example +1 -1
@@ -74,7 +74,7 @@ export class AdminConfigManager {
74
74
  forceQuotes: false,
75
75
  });
76
76
  // Add header comment
77
- const header = `# mcctl Admin Service Configuration
77
+ const header = `# mcctl Management Console Configuration
78
78
  # Generated by: mcctl admin init
79
79
  # Do not edit this file manually unless you know what you are doing.
80
80
  #
@@ -0,0 +1,64 @@
1
+ export interface ConsoleUser {
2
+ id: string;
3
+ name: string;
4
+ email: string;
5
+ emailVerified: boolean;
6
+ role: string;
7
+ banned: boolean;
8
+ createdAt: Date;
9
+ updatedAt: Date;
10
+ }
11
+ export interface CreateConsoleUserParams {
12
+ email: string;
13
+ name: string;
14
+ password: string;
15
+ role?: string;
16
+ }
17
+ /**
18
+ * Direct access to the mcctl-console Better Auth SQLite database.
19
+ * Used by CLI to create/manage admin users without requiring
20
+ * the console service to be running.
21
+ */
22
+ export declare class ConsoleDatabase {
23
+ private db;
24
+ constructor(dbPath: string);
25
+ /**
26
+ * Create all Better Auth tables if they don't exist.
27
+ */
28
+ ensureSchema(): void;
29
+ /**
30
+ * Create a new user with credential-based authentication.
31
+ * Inserts into both `users` and `accounts` tables.
32
+ */
33
+ createUser(params: CreateConsoleUserParams): ConsoleUser;
34
+ /**
35
+ * Find a user by email address.
36
+ */
37
+ findUserByEmail(email: string): ConsoleUser | null;
38
+ /**
39
+ * Get all users.
40
+ */
41
+ findAllUsers(): ConsoleUser[];
42
+ /**
43
+ * Delete a user by ID. Cascades to accounts, sessions.
44
+ */
45
+ deleteUser(id: string): void;
46
+ /**
47
+ * Update a user's role.
48
+ */
49
+ updateUserRole(id: string, role: string): void;
50
+ /**
51
+ * Update a user's password in the accounts table.
52
+ */
53
+ updatePassword(userId: string, newPassword: string): void;
54
+ /**
55
+ * Verify a password against the stored hash.
56
+ */
57
+ verifyPassword(userId: string, password: string): boolean;
58
+ /**
59
+ * Close the database connection.
60
+ */
61
+ close(): void;
62
+ private mapRow;
63
+ }
64
+ //# sourceMappingURL=console-db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-db.d.ts","sourceRoot":"","sources":["../../src/lib/console-db.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAiHD;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAoB;gBAElB,MAAM,EAAE,MAAM;IAY1B;;OAEG;IACH,YAAY,IAAI,IAAI;IAIpB;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,uBAAuB,GAAG,WAAW;IAoCxD;;OAEG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAUlD;;OAEG;IACH,YAAY,IAAI,WAAW,EAAE;IAQ7B;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI5B;;OAEG;IACH,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAO9C;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAUzD;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAYzD;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,MAAM;CAYf"}
@@ -0,0 +1,240 @@
1
+ import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ import Database from 'better-sqlite3';
5
+ // ============================================================
6
+ // Password hashing (Better Auth compatible)
7
+ // ============================================================
8
+ // Better Auth uses @noble/hashes/scrypt with:
9
+ // N=16384, r=16, p=1, dkLen=64
10
+ // Format: hex(salt):hex(derivedKey)
11
+ // Password normalized with NFKC
12
+ const SCRYPT_N = 16384;
13
+ const SCRYPT_R = 16;
14
+ const SCRYPT_P = 1;
15
+ const SCRYPT_KEYLEN = 64;
16
+ const SCRYPT_MAXMEM = 128 * SCRYPT_N * SCRYPT_R * 2;
17
+ function hashPassword(password) {
18
+ const salt = randomBytes(16);
19
+ const key = scryptSync(password.normalize('NFKC'), salt, SCRYPT_KEYLEN, {
20
+ N: SCRYPT_N,
21
+ r: SCRYPT_R,
22
+ p: SCRYPT_P,
23
+ maxmem: SCRYPT_MAXMEM,
24
+ });
25
+ return `${salt.toString('hex')}:${key.toString('hex')}`;
26
+ }
27
+ function verifyPasswordHash(hash, password) {
28
+ const [saltHex, keyHex] = hash.split(':');
29
+ if (!saltHex || !keyHex)
30
+ return false;
31
+ const salt = Buffer.from(saltHex, 'hex');
32
+ const expectedKey = Buffer.from(keyHex, 'hex');
33
+ const derivedKey = scryptSync(password.normalize('NFKC'), salt, SCRYPT_KEYLEN, {
34
+ N: SCRYPT_N,
35
+ r: SCRYPT_R,
36
+ p: SCRYPT_P,
37
+ maxmem: SCRYPT_MAXMEM,
38
+ });
39
+ return timingSafeEqual(derivedKey, expectedKey);
40
+ }
41
+ // ============================================================
42
+ // Schema SQL
43
+ // ============================================================
44
+ const SCHEMA_SQL = `
45
+ CREATE TABLE IF NOT EXISTS users (
46
+ id TEXT PRIMARY KEY,
47
+ name TEXT NOT NULL,
48
+ email TEXT NOT NULL UNIQUE,
49
+ email_verified INTEGER NOT NULL DEFAULT 0,
50
+ image TEXT,
51
+ role TEXT NOT NULL DEFAULT 'user',
52
+ banned INTEGER DEFAULT 0,
53
+ ban_reason TEXT,
54
+ ban_expires INTEGER,
55
+ created_at INTEGER NOT NULL,
56
+ updated_at INTEGER NOT NULL
57
+ );
58
+
59
+ CREATE TABLE IF NOT EXISTS accounts (
60
+ id TEXT PRIMARY KEY,
61
+ account_id TEXT NOT NULL,
62
+ provider_id TEXT NOT NULL,
63
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
64
+ access_token TEXT,
65
+ refresh_token TEXT,
66
+ id_token TEXT,
67
+ access_token_expires_at INTEGER,
68
+ refresh_token_expires_at INTEGER,
69
+ scope TEXT,
70
+ password TEXT,
71
+ created_at INTEGER NOT NULL,
72
+ updated_at INTEGER NOT NULL
73
+ );
74
+
75
+ CREATE TABLE IF NOT EXISTS sessions (
76
+ id TEXT PRIMARY KEY,
77
+ expires_at INTEGER NOT NULL,
78
+ token TEXT NOT NULL UNIQUE,
79
+ ip_address TEXT,
80
+ user_agent TEXT,
81
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
82
+ created_at INTEGER NOT NULL,
83
+ updated_at INTEGER NOT NULL
84
+ );
85
+
86
+ CREATE TABLE IF NOT EXISTS verifications (
87
+ id TEXT PRIMARY KEY,
88
+ identifier TEXT NOT NULL,
89
+ value TEXT NOT NULL,
90
+ expires_at INTEGER NOT NULL,
91
+ created_at INTEGER,
92
+ updated_at INTEGER
93
+ );
94
+
95
+ CREATE TABLE IF NOT EXISTS user_servers (
96
+ id TEXT PRIMARY KEY,
97
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
98
+ server_id TEXT NOT NULL,
99
+ permission TEXT NOT NULL DEFAULT 'view',
100
+ created_at INTEGER NOT NULL,
101
+ updated_at INTEGER NOT NULL
102
+ );
103
+ CREATE INDEX IF NOT EXISTS user_server_idx ON user_servers(user_id, server_id);
104
+ `;
105
+ // ============================================================
106
+ // ConsoleDatabase
107
+ // ============================================================
108
+ /**
109
+ * Direct access to the mcctl-console Better Auth SQLite database.
110
+ * Used by CLI to create/manage admin users without requiring
111
+ * the console service to be running.
112
+ */
113
+ export class ConsoleDatabase {
114
+ db;
115
+ constructor(dbPath) {
116
+ // Ensure parent directory exists
117
+ const dir = dirname(dbPath);
118
+ if (!existsSync(dir)) {
119
+ mkdirSync(dir, { recursive: true });
120
+ }
121
+ this.db = new Database(dbPath);
122
+ this.db.pragma('journal_mode = WAL');
123
+ this.db.pragma('foreign_keys = ON');
124
+ }
125
+ /**
126
+ * Create all Better Auth tables if they don't exist.
127
+ */
128
+ ensureSchema() {
129
+ this.db.exec(SCHEMA_SQL);
130
+ }
131
+ /**
132
+ * Create a new user with credential-based authentication.
133
+ * Inserts into both `users` and `accounts` tables.
134
+ */
135
+ createUser(params) {
136
+ const { email, name, password, role = 'user' } = params;
137
+ const now = Math.floor(Date.now() / 1000);
138
+ const userId = crypto.randomUUID().replace(/-/g, '').slice(0, 32);
139
+ const accountId = crypto.randomUUID().replace(/-/g, '').slice(0, 32);
140
+ const passwordHash = hashPassword(password);
141
+ const insertUser = this.db.prepare(`
142
+ INSERT INTO users (id, name, email, email_verified, role, banned, created_at, updated_at)
143
+ VALUES (?, ?, ?, 1, ?, 0, ?, ?)
144
+ `);
145
+ const insertAccount = this.db.prepare(`
146
+ INSERT INTO accounts (id, account_id, provider_id, user_id, password, created_at, updated_at)
147
+ VALUES (?, ?, 'credential', ?, ?, ?, ?)
148
+ `);
149
+ const transaction = this.db.transaction(() => {
150
+ insertUser.run(userId, name, email, role, now, now);
151
+ insertAccount.run(accountId, userId, userId, passwordHash, now, now);
152
+ });
153
+ transaction();
154
+ return {
155
+ id: userId,
156
+ name,
157
+ email,
158
+ emailVerified: true,
159
+ role,
160
+ banned: false,
161
+ createdAt: new Date(now * 1000),
162
+ updatedAt: new Date(now * 1000),
163
+ };
164
+ }
165
+ /**
166
+ * Find a user by email address.
167
+ */
168
+ findUserByEmail(email) {
169
+ const row = this.db
170
+ .prepare('SELECT * FROM users WHERE email = ?')
171
+ .get(email);
172
+ if (!row)
173
+ return null;
174
+ return this.mapRow(row);
175
+ }
176
+ /**
177
+ * Get all users.
178
+ */
179
+ findAllUsers() {
180
+ const rows = this.db
181
+ .prepare('SELECT * FROM users ORDER BY created_at DESC')
182
+ .all();
183
+ return rows.map((row) => this.mapRow(row));
184
+ }
185
+ /**
186
+ * Delete a user by ID. Cascades to accounts, sessions.
187
+ */
188
+ deleteUser(id) {
189
+ this.db.prepare('DELETE FROM users WHERE id = ?').run(id);
190
+ }
191
+ /**
192
+ * Update a user's role.
193
+ */
194
+ updateUserRole(id, role) {
195
+ const now = Math.floor(Date.now() / 1000);
196
+ this.db
197
+ .prepare('UPDATE users SET role = ?, updated_at = ? WHERE id = ?')
198
+ .run(role, now, id);
199
+ }
200
+ /**
201
+ * Update a user's password in the accounts table.
202
+ */
203
+ updatePassword(userId, newPassword) {
204
+ const now = Math.floor(Date.now() / 1000);
205
+ const passwordHash = hashPassword(newPassword);
206
+ this.db
207
+ .prepare("UPDATE accounts SET password = ?, updated_at = ? WHERE user_id = ? AND provider_id = 'credential'")
208
+ .run(passwordHash, now, userId);
209
+ }
210
+ /**
211
+ * Verify a password against the stored hash.
212
+ */
213
+ verifyPassword(userId, password) {
214
+ const row = this.db
215
+ .prepare("SELECT password FROM accounts WHERE user_id = ? AND provider_id = 'credential'")
216
+ .get(userId);
217
+ if (!row?.password)
218
+ return false;
219
+ return verifyPasswordHash(row.password, password);
220
+ }
221
+ /**
222
+ * Close the database connection.
223
+ */
224
+ close() {
225
+ this.db.close();
226
+ }
227
+ mapRow(row) {
228
+ return {
229
+ id: row.id,
230
+ name: row.name,
231
+ email: row.email,
232
+ emailVerified: Boolean(row.email_verified),
233
+ role: row.role,
234
+ banned: Boolean(row.banned),
235
+ createdAt: new Date(row.created_at * 1000),
236
+ updatedAt: new Date(row.updated_at * 1000),
237
+ };
238
+ }
239
+ }
240
+ //# sourceMappingURL=console-db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-db.js","sourceRoot":"","sources":["../../src/lib/console-db.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minecraft-docker/mcctl",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "CLI tool for managing Docker Minecraft servers with mc-router",
5
5
  "type": "module",
6
6
  "bin": {
@@ -55,8 +55,9 @@
55
55
  ],
56
56
  "dependencies": {
57
57
  "@clack/prompts": "^0.8.0",
58
- "@minecraft-docker/mod-source-modrinth": "^1.13.0",
59
- "@minecraft-docker/shared": "^1.13.0",
58
+ "@minecraft-docker/mod-source-modrinth": "^1.14.0",
59
+ "@minecraft-docker/shared": "^1.14.0",
60
+ "better-sqlite3": "^12.6.2",
60
61
  "commander": "^12.0.0",
61
62
  "js-yaml": "^4.1.0",
62
63
  "picocolors": "^1.1.0",
@@ -65,6 +66,7 @@
65
66
  "tar": "^7.5.4"
66
67
  },
67
68
  "devDependencies": {
69
+ "@types/better-sqlite3": "^7.6.13",
68
70
  "@types/js-yaml": "^4.0.9",
69
71
  "@types/node": "^20.10.0",
70
72
  "@types/tar": "^6.1.13",
@@ -13,11 +13,14 @@
13
13
  # - Hostname: <server-name>.local
14
14
  #
15
15
  # Options:
16
- # -t, --type TYPE Server type: PAPER (default), VANILLA, FORGE, FABRIC
16
+ # -t, --type TYPE Server type: PAPER (default), VANILLA, FORGE, FABRIC, MODRINTH, AUTO_CURSEFORGE
17
17
  # -v, --version VER Minecraft version (e.g., 1.21.1, 1.20.4)
18
18
  # -s, --seed NUMBER World seed for new world generation
19
19
  # -u, --world-url URL Download world from ZIP URL
20
20
  # -w, --world NAME Use existing world from worlds/ directory (creates symlink)
21
+ # --modpack SLUG Modpack slug (required for MODRINTH/AUTO_CURSEFORGE)
22
+ # --modpack-version VER Modpack version (optional)
23
+ # --mod-loader LOADER Mod loader: fabric, forge, neoforge, quilt (optional)
21
24
  # --no-start Don't start the server after creation
22
25
  # --start Start the server after creation (default)
23
26
  #
@@ -222,6 +225,9 @@ MC_VERSION=""
222
225
  WORLD_SEED=""
223
226
  WORLD_URL=""
224
227
  WORLD_NAME=""
228
+ MODPACK_SLUG=""
229
+ MODPACK_VERSION=""
230
+ MOD_LOADER=""
225
231
  START_SERVER="true"
226
232
 
227
233
  # Show usage
@@ -232,11 +238,14 @@ show_usage() {
232
238
  echo " server-name : Name for the new server (lowercase, no spaces)"
233
239
  echo ""
234
240
  echo "Options:"
235
- echo " -t, --type TYPE Server type: PAPER (default), VANILLA, FORGE, FABRIC"
241
+ echo " -t, --type TYPE Server type: PAPER (default), VANILLA, FORGE, FABRIC, MODRINTH, AUTO_CURSEFORGE"
236
242
  echo " -v, --version VER Minecraft version (e.g., 1.21.1, 1.20.4)"
237
243
  echo " -s, --seed NUMBER World seed for new world generation"
238
244
  echo " -u, --world-url URL Download world from ZIP URL"
239
245
  echo " -w, --world NAME Use existing world from worlds/ directory (creates symlink)"
246
+ echo " --modpack SLUG Modpack slug (required for MODRINTH/AUTO_CURSEFORGE)"
247
+ echo " --modpack-version VER Modpack version (optional)"
248
+ echo " --mod-loader LOADER Mod loader: fabric, forge, neoforge, quilt (optional)"
240
249
  echo " --no-start Don't start the server after creation"
241
250
  echo " --start Start the server after creation (default)"
242
251
  echo ""
@@ -249,6 +258,7 @@ show_usage() {
249
258
  echo " $0 myserver --seed 12345"
250
259
  echo " $0 myserver --world-url https://example.com/world.zip"
251
260
  echo " $0 myserver --world existing-world -v 1.21.1 --no-start"
261
+ echo " $0 myserver -t MODRINTH --modpack fabric-example --modpack-version 1.0.0"
252
262
  }
253
263
 
254
264
  # Check if first argument exists
@@ -286,6 +296,18 @@ while [[ $# -gt 0 ]]; do
286
296
  WORLD_NAME="$2"
287
297
  shift 2
288
298
  ;;
299
+ --modpack)
300
+ MODPACK_SLUG="$2"
301
+ shift 2
302
+ ;;
303
+ --modpack-version)
304
+ MODPACK_VERSION="$2"
305
+ shift 2
306
+ ;;
307
+ --mod-loader)
308
+ MOD_LOADER="$2"
309
+ shift 2
310
+ ;;
289
311
  --no-start)
290
312
  START_SERVER="false"
291
313
  shift
@@ -300,7 +322,7 @@ while [[ $# -gt 0 ]]; do
300
322
  ;;
301
323
  *)
302
324
  # For backward compatibility: if it looks like a server type, use it
303
- if [[ "$1" =~ ^(PAPER|VANILLA|FORGE|FABRIC|NEOFORGE|QUILT|SPIGOT)$ ]]; then
325
+ if [[ "$1" =~ ^(PAPER|VANILLA|FORGE|FABRIC|NEOFORGE|QUILT|SPIGOT|MODRINTH|AUTO_CURSEFORGE)$ ]]; then
304
326
  SERVER_TYPE="$1"
305
327
  shift
306
328
  else
@@ -325,6 +347,15 @@ if [ "$WORLD_OPTIONS_COUNT" -gt 1 ]; then
325
347
  exit 1
326
348
  fi
327
349
 
350
+ # Validate modpack options for MODRINTH and AUTO_CURSEFORGE types
351
+ if [[ "$SERVER_TYPE" =~ ^(MODRINTH|AUTO_CURSEFORGE)$ ]]; then
352
+ if [ -z "$MODPACK_SLUG" ]; then
353
+ echo -e "${RED}Error: --modpack SLUG is required for $SERVER_TYPE server type${NC}"
354
+ echo "Example: $0 $SERVER_NAME -t $SERVER_TYPE --modpack fabric-example"
355
+ exit 1
356
+ fi
357
+ fi
358
+
328
359
  # Validate server name (lowercase, alphanumeric, hyphens only)
329
360
  if [[ ! "$SERVER_NAME" =~ ^[a-z][a-z0-9-]*$ ]]; then
330
361
  echo -e "${RED}Error: Server name must start with a letter and contain only lowercase letters, numbers, and hyphens${NC}"
@@ -440,6 +471,35 @@ if [ -f "$CONFIG_FILE" ]; then
440
471
  sed -i "s/^LEVEL=.*/LEVEL=$WORLD_NAME/" "$CONFIG_FILE"
441
472
  fi
442
473
  fi
474
+
475
+ # Apply modpack options
476
+ if [ -n "$MODPACK_SLUG" ]; then
477
+ echo "" >> "$CONFIG_FILE"
478
+ echo "# Modpack Configuration" >> "$CONFIG_FILE"
479
+ if [[ "$SERVER_TYPE" == "MODRINTH" ]]; then
480
+ echo "MODRINTH_MODPACK=$MODPACK_SLUG" >> "$CONFIG_FILE"
481
+ echo " Modpack: $MODPACK_SLUG (Modrinth)"
482
+ if [ -n "$MODPACK_VERSION" ]; then
483
+ echo "MODRINTH_VERSION=$MODPACK_VERSION" >> "$CONFIG_FILE"
484
+ echo " Modpack version: $MODPACK_VERSION"
485
+ fi
486
+ if [ -n "$MOD_LOADER" ]; then
487
+ echo "MODRINTH_LOADER=$MOD_LOADER" >> "$CONFIG_FILE"
488
+ echo " Mod loader: $MOD_LOADER"
489
+ fi
490
+ elif [[ "$SERVER_TYPE" == "AUTO_CURSEFORGE" ]]; then
491
+ echo "CF_SLUG=$MODPACK_SLUG" >> "$CONFIG_FILE"
492
+ echo " Modpack: $MODPACK_SLUG (CurseForge)"
493
+ if [ -n "$MODPACK_VERSION" ]; then
494
+ echo "CF_VERSION=$MODPACK_VERSION" >> "$CONFIG_FILE"
495
+ echo " Modpack version: $MODPACK_VERSION"
496
+ fi
497
+ if [ -n "$MOD_LOADER" ]; then
498
+ echo "CF_LOADER=$MOD_LOADER" >> "$CONFIG_FILE"
499
+ echo " Mod loader: $MOD_LOADER"
500
+ fi
501
+ fi
502
+ fi
443
503
  fi
444
504
 
445
505
  # Create data and logs directories
@@ -52,7 +52,7 @@ COMPOSE_PROJECT_NAME=minecraft
52
52
  # BACKUP_AUTO_ON_STOP=true
53
53
 
54
54
  # =============================================================================
55
- # Admin Service Configuration (Optional)
55
+ # Management Console Configuration (Optional)
56
56
  # =============================================================================
57
57
  # Web-based management UI for Minecraft servers.
58
58
  # Start with: mcctl admin service start