@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.
- package/CHANGELOG.md +22 -6
- package/README.md +8 -0
- package/dist/commands/console/init.d.ts.map +1 -1
- package/dist/commands/console/init.js +51 -38
- package/dist/commands/console/init.js.map +1 -1
- package/dist/commands/console/service.d.ts.map +1 -1
- package/dist/commands/console/service.js +82 -19
- package/dist/commands/console/service.js.map +1 -1
- package/dist/commands/console/user.d.ts.map +1 -1
- package/dist/commands/console/user.js +344 -304
- package/dist/commands/console/user.js.map +1 -1
- package/dist/commands/create.d.ts +3 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +16 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/adapters/ClackPromptAdapter.d.ts +4 -1
- package/dist/infrastructure/adapters/ClackPromptAdapter.d.ts.map +1 -1
- package/dist/infrastructure/adapters/ClackPromptAdapter.js +92 -9
- package/dist/infrastructure/adapters/ClackPromptAdapter.js.map +1 -1
- package/dist/infrastructure/di/container.d.ts +3 -1
- package/dist/infrastructure/di/container.d.ts.map +1 -1
- package/dist/infrastructure/di/container.js +9 -1
- package/dist/infrastructure/di/container.js.map +1 -1
- package/dist/lib/admin-config.js +1 -1
- package/dist/lib/console-db.d.ts +64 -0
- package/dist/lib/console-db.d.ts.map +1 -0
- package/dist/lib/console-db.js +240 -0
- package/dist/lib/console-db.js.map +1 -0
- package/package.json +5 -3
- package/scripts/create-server.sh +63 -3
- package/templates/.env.example +1 -1
package/dist/lib/admin-config.js
CHANGED
|
@@ -74,7 +74,7 @@ export class AdminConfigManager {
|
|
|
74
74
|
forceQuotes: false,
|
|
75
75
|
});
|
|
76
76
|
// Add header comment
|
|
77
|
-
const header = `# mcctl
|
|
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.
|
|
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.
|
|
59
|
-
"@minecraft-docker/shared": "^1.
|
|
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",
|
package/scripts/create-server.sh
CHANGED
|
@@ -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
|
package/templates/.env.example
CHANGED
|
@@ -52,7 +52,7 @@ COMPOSE_PROJECT_NAME=minecraft
|
|
|
52
52
|
# BACKUP_AUTO_ON_STOP=true
|
|
53
53
|
|
|
54
54
|
# =============================================================================
|
|
55
|
-
#
|
|
55
|
+
# Management Console Configuration (Optional)
|
|
56
56
|
# =============================================================================
|
|
57
57
|
# Web-based management UI for Minecraft servers.
|
|
58
58
|
# Start with: mcctl admin service start
|