@alcedocore/cli 0.0.1-rc.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/LICENSE.md +102 -0
- package/dist/commands/add-endpoint.js +104 -0
- package/dist/commands/add-migration.js +80 -0
- package/dist/commands/add-nav-item.js +72 -0
- package/dist/commands/add-page.js +64 -0
- package/dist/commands/build-frontend.js +126 -0
- package/dist/commands/compile-pages.js +120 -0
- package/dist/commands/connect.js +122 -0
- package/dist/commands/deploy.js +74 -0
- package/dist/commands/dev.js +13 -0
- package/dist/commands/init-core.js +900 -0
- package/dist/commands/init.js +124 -0
- package/dist/commands/init.test.js +84 -0
- package/dist/commands/migrate.js +22 -0
- package/dist/commands/proxy.js +157 -0
- package/dist/commands/publish.js +81 -0
- package/dist/commands/replay.js +142 -0
- package/dist/commands/serve-frontend.js +92 -0
- package/dist/config.js +110 -0
- package/dist/config.test.js +48 -0
- package/dist/index.js +81 -0
- package/dist/integration/cli-api.test.js +116 -0
- package/dist/utils/ejs-renderer.js +46 -0
- package/dist/utils/ejs-renderer.test.js +93 -0
- package/dist/utils/formatting.js +65 -0
- package/dist/utils/generateTimestamp.js +17 -0
- package/dist/utils/logger.js +33 -0
- package/dist/utils/validation.js +15 -0
- package/package.json +41 -0
- package/src/commands/add-endpoint.ts +157 -0
- package/src/commands/add-migration.ts +102 -0
- package/src/commands/add-nav-item.ts +106 -0
- package/src/commands/add-page.ts +100 -0
- package/src/commands/build-frontend.ts +148 -0
- package/src/commands/connect.ts +98 -0
- package/src/commands/deploy.ts +85 -0
- package/src/commands/dev.ts +12 -0
- package/src/commands/init-core.ts +1019 -0
- package/src/commands/init.test.ts +92 -0
- package/src/commands/init.ts +171 -0
- package/src/commands/migrate.ts +20 -0
- package/src/commands/proxy.ts +206 -0
- package/src/commands/publish.ts +106 -0
- package/src/commands/serve-frontend.ts +103 -0
- package/src/config.test.ts +50 -0
- package/src/config.ts +125 -0
- package/src/index.ts +100 -0
- package/src/integration/cli-api.test.ts +143 -0
- package/src/utils/ejs-renderer.ts +55 -0
- package/src/utils/formatting.ts +62 -0
- package/src/utils/generateTimestamp.ts +16 -0
- package/src/utils/logger.ts +27 -0
- package/src/utils/validation.ts +13 -0
- package/templates/endpoint/handler.js.ejs +23 -0
- package/templates/endpoint/handler.py.ejs +23 -0
- package/templates/migration/down.sql.ejs +6 -0
- package/templates/migration/up.sql.ejs +11 -0
- package/templates/page/page.vue.ejs +63 -0
- package/templates/plugin/Dockerfile.ejs +13 -0
- package/templates/plugin/Dockerfile.node.ejs +14 -0
- package/templates/plugin/README.md.ejs +19 -0
- package/templates/plugin/gitignore.ejs +6 -0
- package/templates/plugin/manifest.json.ejs +18 -0
- package/templates/plugin/migrations/.gitkeep +0 -0
- package/templates/plugin/pages/.gitkeep +0 -0
- package/templates/plugin/public/.gitkeep +0 -0
- package/templates/plugin/server.js.ejs +27 -0
- package/templates/plugin/server.py.ejs +32 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.initCoreCommand = void 0;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
42
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
43
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
44
|
+
const node_child_process_1 = require("node:child_process");
|
|
45
|
+
const detect_port_1 = __importDefault(require("detect-port"));
|
|
46
|
+
const check_disk_space_1 = __importDefault(require("check-disk-space"));
|
|
47
|
+
const logger_1 = require("../utils/logger");
|
|
48
|
+
exports.initCoreCommand = new commander_1.Command("init-core")
|
|
49
|
+
.description("Generate configuration for running Alcedo Core (Docker Compose or Kubernetes)")
|
|
50
|
+
.option("-o, --output-dir <path>", "Output directory (default: ./alcedo)")
|
|
51
|
+
.option("-P, --platform <type>", "Platform: docker or k8s (default: docker, prompts if interactive)")
|
|
52
|
+
.option("-e, --admin-email <email>", "Admin email (skips prompt if provided)")
|
|
53
|
+
.option("-p, --admin-password <password>", "Admin password (skips prompt if provided)")
|
|
54
|
+
.option("--port <port>", "Core HTTP port", parseInt)
|
|
55
|
+
.option("--non-interactive", "Skip prompts, use defaults", false)
|
|
56
|
+
.action(async (options) => {
|
|
57
|
+
const targetDir = node_path_1.default.resolve(options.outputDir || "./alcedo");
|
|
58
|
+
if (node_fs_1.default.existsSync(targetDir) &&
|
|
59
|
+
node_fs_1.default.readdirSync(targetDir).length > 0) {
|
|
60
|
+
(0, logger_1.error)(`Directory already exists and is not empty: ${targetDir}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const defaults = {
|
|
64
|
+
platform: options.platform || "docker",
|
|
65
|
+
adminEmail: options.adminEmail || "admin@alcedo.dev",
|
|
66
|
+
adminPassword: options.adminPassword || generatePassword(),
|
|
67
|
+
dbPassword: generatePassword(),
|
|
68
|
+
corePort: options.port || 8080,
|
|
69
|
+
swarmEnabled: false,
|
|
70
|
+
coreImageTag: "alcedocore/core:0.1.0",
|
|
71
|
+
postgresVersion: "16",
|
|
72
|
+
redisPassword: "",
|
|
73
|
+
sessionTtl: 86400,
|
|
74
|
+
rateLimitRequests: 200,
|
|
75
|
+
rateLimitWindow: 60,
|
|
76
|
+
corsOrigins: "*",
|
|
77
|
+
enableCors: true,
|
|
78
|
+
enableRateLimit: true,
|
|
79
|
+
corePublicUrl: "",
|
|
80
|
+
registryPort: 5000,
|
|
81
|
+
systemPluginsUrl: "https://cspm.alcedocore.nl",
|
|
82
|
+
localRegistryUrl: "localhost:5000",
|
|
83
|
+
pluginNetwork: "alcedo_plugins",
|
|
84
|
+
registryEncryptionKey: generateRegistryKey(),
|
|
85
|
+
};
|
|
86
|
+
// Pre-flight checks (Docker only)
|
|
87
|
+
if (defaults.platform === "docker" &&
|
|
88
|
+
!(await preflightCheck(defaults))) {
|
|
89
|
+
(0, logger_1.error)("Pre-flight checks failed. Please fix the issues above and try again.");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const config = options.nonInteractive
|
|
94
|
+
? defaults
|
|
95
|
+
: await prompt(defaults);
|
|
96
|
+
await writeFiles(targetDir, config);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
(0, logger_1.error)(`Failed to generate configuration: ${err.message}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
function generatePassword(length = 24) {
|
|
104
|
+
return node_crypto_1.default.randomBytes(length).toString("base64url").slice(0, length);
|
|
105
|
+
}
|
|
106
|
+
function generateRegistryKey() {
|
|
107
|
+
return node_crypto_1.default.randomBytes(32).toString("base64");
|
|
108
|
+
}
|
|
109
|
+
function checkDocker() {
|
|
110
|
+
try {
|
|
111
|
+
(0, node_child_process_1.execSync)("docker info --format '{{.ServerVersion}}'", {
|
|
112
|
+
stdio: "pipe",
|
|
113
|
+
timeout: 5000,
|
|
114
|
+
});
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function checkPort(port) {
|
|
122
|
+
try {
|
|
123
|
+
const actual = await (0, detect_port_1.default)(port);
|
|
124
|
+
return actual === port; // if detectPort returns same port, it's free
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return true; // on error, assume free
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function checkDiskSpaceFree(minGB = 1) {
|
|
131
|
+
try {
|
|
132
|
+
const disk = await (0, check_disk_space_1.default)("/");
|
|
133
|
+
const freeGB = Math.floor(disk.free / 1024 / 1024 / 1024);
|
|
134
|
+
return { ok: freeGB >= minGB, freeGB };
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return { ok: true, freeGB: 99 };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function preflightCheck(config) {
|
|
141
|
+
let ok = true;
|
|
142
|
+
if (!checkDocker()) {
|
|
143
|
+
(0, logger_1.error)("Docker is not installed or the daemon is not running.");
|
|
144
|
+
(0, logger_1.info)(" Install Docker: https://docs.docker.com/engine/install/");
|
|
145
|
+
ok = false;
|
|
146
|
+
}
|
|
147
|
+
const disk = await checkDiskSpaceFree();
|
|
148
|
+
if (!disk.ok) {
|
|
149
|
+
(0, logger_1.warn)(`Low disk space: only ${disk.freeGB}GB available (minimum 1GB recommended).`);
|
|
150
|
+
}
|
|
151
|
+
if (!(await checkPort(config.corePort))) {
|
|
152
|
+
(0, logger_1.warn)(`Port ${config.corePort} is already in use. The core may fail to start.`);
|
|
153
|
+
}
|
|
154
|
+
if (!(await checkPort(config.registryPort))) {
|
|
155
|
+
(0, logger_1.warn)(`Port ${config.registryPort} is already in use. The registry may fail to start.`);
|
|
156
|
+
}
|
|
157
|
+
return ok;
|
|
158
|
+
}
|
|
159
|
+
function detectSwarmState() {
|
|
160
|
+
try {
|
|
161
|
+
const out = (0, node_child_process_1.execSync)("docker info --format '{{.Swarm.LocalNodeState}}'", { encoding: "utf-8", timeout: 5000 }).trim();
|
|
162
|
+
if (out === "active")
|
|
163
|
+
return "active";
|
|
164
|
+
if (out === "inactive")
|
|
165
|
+
return "inactive";
|
|
166
|
+
return "disabled";
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return "disabled";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async function prompt(config) {
|
|
173
|
+
const inquirer = (await Promise.resolve().then(() => __importStar(require("inquirer")))).default;
|
|
174
|
+
const DEFAULT_PLUGINS_URL = "https://cspm.alcedocore.nl";
|
|
175
|
+
// Platform selection
|
|
176
|
+
const platformAnswer = await inquirer.prompt([
|
|
177
|
+
{
|
|
178
|
+
type: "list",
|
|
179
|
+
name: "platform",
|
|
180
|
+
message: "Which platform?",
|
|
181
|
+
choices: [
|
|
182
|
+
{
|
|
183
|
+
name: "Docker Compose (local dev / single host)",
|
|
184
|
+
value: "docker",
|
|
185
|
+
},
|
|
186
|
+
{ name: "Kubernetes (K8s cluster)", value: "k8s" },
|
|
187
|
+
],
|
|
188
|
+
default: config.platform,
|
|
189
|
+
},
|
|
190
|
+
]);
|
|
191
|
+
config.platform = platformAnswer.platform;
|
|
192
|
+
if (config.platform === "docker") {
|
|
193
|
+
// Detect Swarm state automatically
|
|
194
|
+
const swarmState = detectSwarmState();
|
|
195
|
+
if (swarmState === "active") {
|
|
196
|
+
config.swarmEnabled = true;
|
|
197
|
+
(0, logger_1.info)("Docker Swarm is active — Swarm support enabled.");
|
|
198
|
+
}
|
|
199
|
+
else if (swarmState === "inactive") {
|
|
200
|
+
const initSwarm = await inquirer.prompt([
|
|
201
|
+
{
|
|
202
|
+
type: "confirm",
|
|
203
|
+
name: "confirm",
|
|
204
|
+
message: "Docker Swarm is not initialized. Initialize it now?",
|
|
205
|
+
default: false,
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
if (initSwarm.confirm) {
|
|
209
|
+
try {
|
|
210
|
+
(0, node_child_process_1.execSync)("docker swarm init", {
|
|
211
|
+
stdio: "ignore",
|
|
212
|
+
timeout: 10000,
|
|
213
|
+
});
|
|
214
|
+
config.swarmEnabled = true;
|
|
215
|
+
(0, logger_1.info)("Docker Swarm initialized.");
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
(0, logger_1.info)("Failed to initialize Swarm — proceeding without Swarm support.");
|
|
219
|
+
config.swarmEnabled = false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
config.swarmEnabled = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
(0, logger_1.info)("Docker Swarm is not available on this host — Swarm support disabled.");
|
|
228
|
+
config.swarmEnabled = false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const basic = await inquirer.prompt([
|
|
232
|
+
{
|
|
233
|
+
type: "input",
|
|
234
|
+
name: "adminEmail",
|
|
235
|
+
message: "Admin email:",
|
|
236
|
+
default: config.adminEmail,
|
|
237
|
+
validate: (v) => v.includes("@") || "Please enter a valid email",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
type: "password",
|
|
241
|
+
name: "adminPassword",
|
|
242
|
+
message: "Admin password:",
|
|
243
|
+
default: config.adminPassword,
|
|
244
|
+
validate: (v) => v.length >= 8 || "Password must be at least 8 characters",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
type: "number",
|
|
248
|
+
name: "corePort",
|
|
249
|
+
message: "Core HTTP port:",
|
|
250
|
+
default: config.corePort,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
type: "input",
|
|
254
|
+
name: "corePublicUrl",
|
|
255
|
+
message: "Core public URL (leave empty to use http://localhost:<port>):",
|
|
256
|
+
default: config.corePublicUrl,
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
type: "confirm",
|
|
260
|
+
name: "useCustomPluginsUrl",
|
|
261
|
+
message: "Do you want to use a custom system plugins manifest URL?",
|
|
262
|
+
default: false,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
type: "confirm",
|
|
266
|
+
name: "advanced",
|
|
267
|
+
message: "Configure advanced options?",
|
|
268
|
+
default: false,
|
|
269
|
+
},
|
|
270
|
+
]);
|
|
271
|
+
config.adminEmail = basic.adminEmail;
|
|
272
|
+
config.adminPassword = basic.adminPassword;
|
|
273
|
+
config.corePort = basic.corePort;
|
|
274
|
+
config.corePublicUrl = basic.corePublicUrl;
|
|
275
|
+
if (basic.useCustomPluginsUrl) {
|
|
276
|
+
const custom = await inquirer.prompt([
|
|
277
|
+
{
|
|
278
|
+
type: "input",
|
|
279
|
+
name: "systemPluginsUrl",
|
|
280
|
+
message: "Custom system plugins manifest URL:",
|
|
281
|
+
default: config.systemPluginsUrl,
|
|
282
|
+
},
|
|
283
|
+
]);
|
|
284
|
+
config.systemPluginsUrl = custom.systemPluginsUrl;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
config.systemPluginsUrl = DEFAULT_PLUGINS_URL;
|
|
288
|
+
}
|
|
289
|
+
if (basic.advanced) {
|
|
290
|
+
const advanced = await inquirer.prompt([
|
|
291
|
+
{
|
|
292
|
+
type: "input",
|
|
293
|
+
name: "dbPassword",
|
|
294
|
+
message: "PostgreSQL password:",
|
|
295
|
+
default: config.dbPassword,
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
type: "input",
|
|
299
|
+
name: "redisPassword",
|
|
300
|
+
message: "Redis password (leave empty for none):",
|
|
301
|
+
default: config.redisPassword,
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
type: "input",
|
|
305
|
+
name: "coreImageTag",
|
|
306
|
+
message: "Core Docker image tag:",
|
|
307
|
+
default: config.coreImageTag,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
type: "input",
|
|
311
|
+
name: "postgresVersion",
|
|
312
|
+
message: "PostgreSQL version:",
|
|
313
|
+
default: config.postgresVersion,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
type: "number",
|
|
317
|
+
name: "sessionTtl",
|
|
318
|
+
message: "Session TTL (seconds):",
|
|
319
|
+
default: config.sessionTtl,
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
type: "confirm",
|
|
323
|
+
name: "enableCors",
|
|
324
|
+
message: "Enable CORS?",
|
|
325
|
+
default: config.enableCors,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
type: "input",
|
|
329
|
+
name: "corsOrigins",
|
|
330
|
+
message: "Allowed CORS origins (comma-separated or *):",
|
|
331
|
+
default: config.corsOrigins,
|
|
332
|
+
when: (a) => a.enableCors,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
type: "confirm",
|
|
336
|
+
name: "enableRateLimit",
|
|
337
|
+
message: "Enable rate limiting?",
|
|
338
|
+
default: config.enableRateLimit,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
type: "number",
|
|
342
|
+
name: "rateLimitRequests",
|
|
343
|
+
message: "Max requests per window:",
|
|
344
|
+
default: config.rateLimitRequests,
|
|
345
|
+
when: (a) => a.enableRateLimit,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
type: "number",
|
|
349
|
+
name: "rateLimitWindow",
|
|
350
|
+
message: "Rate limit window (seconds):",
|
|
351
|
+
default: config.rateLimitWindow,
|
|
352
|
+
when: (a) => a.enableRateLimit,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
type: "input",
|
|
356
|
+
name: "pluginNetwork",
|
|
357
|
+
message: "Docker network name for plugin containers:",
|
|
358
|
+
default: config.pluginNetwork,
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
type: "number",
|
|
362
|
+
name: "registryPort",
|
|
363
|
+
message: "Docker registry port:",
|
|
364
|
+
default: config.registryPort,
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
type: "input",
|
|
368
|
+
name: "localRegistryUrl",
|
|
369
|
+
message: "Local registry URL (for plugin image storage):",
|
|
370
|
+
default: config.localRegistryUrl,
|
|
371
|
+
},
|
|
372
|
+
]);
|
|
373
|
+
config.dbPassword = advanced.dbPassword;
|
|
374
|
+
config.redisPassword = advanced.redisPassword;
|
|
375
|
+
config.coreImageTag = advanced.coreImageTag;
|
|
376
|
+
config.postgresVersion = advanced.postgresVersion;
|
|
377
|
+
config.sessionTtl = advanced.sessionTtl;
|
|
378
|
+
config.enableCors = advanced.enableCors;
|
|
379
|
+
config.corsOrigins = advanced.corsOrigins;
|
|
380
|
+
config.enableRateLimit = advanced.enableRateLimit;
|
|
381
|
+
config.rateLimitRequests = advanced.rateLimitRequests;
|
|
382
|
+
config.rateLimitWindow = advanced.rateLimitWindow;
|
|
383
|
+
config.pluginNetwork = advanced.pluginNetwork;
|
|
384
|
+
config.registryPort = advanced.registryPort;
|
|
385
|
+
config.localRegistryUrl = advanced.localRegistryUrl;
|
|
386
|
+
}
|
|
387
|
+
return config;
|
|
388
|
+
}
|
|
389
|
+
function generateCompose(config) {
|
|
390
|
+
const securityVars = `
|
|
391
|
+
SECURITY_COOKIE_SECURE: "false"
|
|
392
|
+
SECURITY_COOKIE_SAMESITE: "lax"
|
|
393
|
+
SECURITY_COOKIE_HTTP_ONLY: "true"
|
|
394
|
+
SECURITY_CORS_ENABLED: "${config.enableCors}"
|
|
395
|
+
SECURITY_CORS_ORIGINS: "${config.corsOrigins}"
|
|
396
|
+
SECURITY_RATE_LIMIT_ENABLED: "${config.enableRateLimit}"
|
|
397
|
+
RATE_LIMIT_AUTH_REQUESTS: "${config.rateLimitRequests}"
|
|
398
|
+
RATE_LIMIT_AUTH_WINDOW: "${config.rateLimitWindow}"
|
|
399
|
+
RATE_LIMIT_API_REQUESTS: "${config.rateLimitRequests}"
|
|
400
|
+
RATE_LIMIT_API_WINDOW: "${config.rateLimitWindow}"
|
|
401
|
+
SECURITY_PASSWORD_MIN_LENGTH: "8"
|
|
402
|
+
SECURITY_SESSION_FIXATION: "true"`;
|
|
403
|
+
return `services:
|
|
404
|
+
nginx:
|
|
405
|
+
image: nginx:alpine
|
|
406
|
+
ports:
|
|
407
|
+
- "${config.corePort}:${config.corePort}"
|
|
408
|
+
volumes:
|
|
409
|
+
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
|
410
|
+
depends_on:
|
|
411
|
+
- core
|
|
412
|
+
core:
|
|
413
|
+
image: ${config.coreImageTag}
|
|
414
|
+
depends_on:
|
|
415
|
+
postgres:
|
|
416
|
+
condition: service_healthy
|
|
417
|
+
redis:
|
|
418
|
+
condition: service_healthy
|
|
419
|
+
environment:
|
|
420
|
+
DATABASE_URL: postgres://postgres:${config.dbPassword}@postgres:5432/alcedo
|
|
421
|
+
REDIS_URL: redis://:${config.redisPassword || ""}@redis:6379
|
|
422
|
+
CORE_PORT: "8080"
|
|
423
|
+
DEV_MODE: "false"
|
|
424
|
+
RUST_LOG: info
|
|
425
|
+
ADMIN_EMAIL: "${config.adminEmail}"
|
|
426
|
+
ADMIN_PASSWORD: "${config.adminPassword}"
|
|
427
|
+
PLUGIN_NETWORK: ${config.pluginNetwork}
|
|
428
|
+
PLUGINS_DIR: /var/lib/plugin-public
|
|
429
|
+
PLUGINS_DIR: /plugins
|
|
430
|
+
CORE_PUBLIC_URL: "${config.corePublicUrl || `http://localhost:${config.corePort}`}"
|
|
431
|
+
SYSTEM_PLUGINS_URL: "${config.systemPluginsUrl}"
|
|
432
|
+
LOCAL_REGISTRY_URL: "${config.localRegistryUrl}"
|
|
433
|
+
SESSION_TTL_SECONDS: "${config.sessionTtl}"
|
|
434
|
+
REGISTRY_ENCRYPTION_KEY: "${config.registryEncryptionKey}"
|
|
435
|
+
COLLECTION_LOG_FLUSH_MS: "100"${securityVars}
|
|
436
|
+
networks:
|
|
437
|
+
- default
|
|
438
|
+
- ${config.pluginNetwork}
|
|
439
|
+
extra_hosts:
|
|
440
|
+
- "host.docker.internal:host-gateway"
|
|
441
|
+
volumes:
|
|
442
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
443
|
+
- plugin-public:/var/lib/plugin-public
|
|
444
|
+
- ./plugins:/plugins
|
|
445
|
+
- ./plugins:/app/plugins
|
|
446
|
+
|
|
447
|
+
registry:
|
|
448
|
+
image: registry:2
|
|
449
|
+
ports:
|
|
450
|
+
- "${config.registryPort}:5000"
|
|
451
|
+
volumes:
|
|
452
|
+
- registry-data:/var/lib/registry
|
|
453
|
+
healthcheck:
|
|
454
|
+
test: ["CMD", "wget", "--spider", "http://localhost:5000/"]
|
|
455
|
+
interval: 10s
|
|
456
|
+
timeout: 5s
|
|
457
|
+
retries: 3
|
|
458
|
+
|
|
459
|
+
postgres:
|
|
460
|
+
image: postgres:${config.postgresVersion}
|
|
461
|
+
environment:
|
|
462
|
+
POSTGRES_USER: postgres
|
|
463
|
+
POSTGRES_PASSWORD: ${config.dbPassword}
|
|
464
|
+
POSTGRES_DB: alcedo
|
|
465
|
+
healthcheck:
|
|
466
|
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
|
467
|
+
interval: 5s
|
|
468
|
+
timeout: 5s
|
|
469
|
+
retries: 5
|
|
470
|
+
volumes:
|
|
471
|
+
- postgres-data:/var/lib/postgresql/data
|
|
472
|
+
|
|
473
|
+
redis:
|
|
474
|
+
image: redis:7-alpine
|
|
475
|
+
restart: unless-stopped
|
|
476
|
+
command: ${config.redisPassword ? `["redis-server", "--requirepass", "${config.redisPassword}"]` : '["redis-server"]'}
|
|
477
|
+
healthcheck:
|
|
478
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
479
|
+
interval: 5s
|
|
480
|
+
timeout: 3s
|
|
481
|
+
retries: 5
|
|
482
|
+
volumes:
|
|
483
|
+
- redis-data:/data
|
|
484
|
+
|
|
485
|
+
networks:
|
|
486
|
+
default:
|
|
487
|
+
${config.pluginNetwork}:
|
|
488
|
+
driver: bridge
|
|
489
|
+
|
|
490
|
+
volumes:
|
|
491
|
+
postgres-data:
|
|
492
|
+
redis-data:
|
|
493
|
+
registry-data:
|
|
494
|
+
plugin-public:
|
|
495
|
+
`;
|
|
496
|
+
}
|
|
497
|
+
function generateNginxConf(config) {
|
|
498
|
+
return `upstream core_backend {
|
|
499
|
+
server core:8080;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
server {
|
|
503
|
+
listen ${config.corePort};
|
|
504
|
+
server_name _;
|
|
505
|
+
absolute_redirect off;
|
|
506
|
+
|
|
507
|
+
location / {
|
|
508
|
+
proxy_pass http://core_backend;
|
|
509
|
+
proxy_set_header Host \$host;
|
|
510
|
+
proxy_set_header X-Real-IP \$remote_addr;
|
|
511
|
+
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
512
|
+
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
513
|
+
proxy_read_timeout 120s;
|
|
514
|
+
proxy_send_timeout 120s;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
`;
|
|
518
|
+
}
|
|
519
|
+
function generateEnv(config) {
|
|
520
|
+
return `# Alcedo Core — Environment Configuration
|
|
521
|
+
# Generated by \`alcedo init-core\`
|
|
522
|
+
|
|
523
|
+
# Admin credentials (used for first-run bootstrap)
|
|
524
|
+
ADMIN_EMAIL=${config.adminEmail}
|
|
525
|
+
ADMIN_PASSWORD=${config.adminPassword}
|
|
526
|
+
|
|
527
|
+
# Database
|
|
528
|
+
POSTGRES_PASSWORD=${config.dbPassword}
|
|
529
|
+
DATABASE_URL=postgres://postgres:${config.dbPassword}@postgres:5432/alcedo
|
|
530
|
+
|
|
531
|
+
# Redis
|
|
532
|
+
${config.redisPassword ? `REDIS_PASSWORD=${config.redisPassword}\nREDIS_URL=redis://:${config.redisPassword}@redis:6379` : "REDIS_URL=redis://redis:6379"}
|
|
533
|
+
|
|
534
|
+
# Core
|
|
535
|
+
CORE_PORT=${config.corePort}
|
|
536
|
+
CORE_PUBLIC_URL=${config.corePublicUrl || `http://localhost:${config.corePort}`}
|
|
537
|
+
SYSTEM_PLUGINS_URL=${config.systemPluginsUrl}
|
|
538
|
+
LOCAL_REGISTRY_URL=${config.localRegistryUrl}
|
|
539
|
+
SESSION_TTL_SECONDS=${config.sessionTtl}
|
|
540
|
+
SECURITY_CORS_ENABLED=${config.enableCors}
|
|
541
|
+
SECURITY_CORS_ORIGINS=${config.corsOrigins}
|
|
542
|
+
SECURITY_RATE_LIMIT_ENABLED=${config.enableRateLimit}
|
|
543
|
+
RATE_LIMIT_AUTH_REQUESTS=${config.rateLimitRequests}
|
|
544
|
+
RATE_LIMIT_AUTH_WINDOW=${config.rateLimitWindow}
|
|
545
|
+
RATE_LIMIT_API_REQUESTS=${config.rateLimitRequests}
|
|
546
|
+
RATE_LIMIT_API_WINDOW=${config.rateLimitWindow}
|
|
547
|
+
|
|
548
|
+
# Docker Registry
|
|
549
|
+
REGISTRY_PORT=${config.registryPort}
|
|
550
|
+
REGISTRY_ENCRYPTION_KEY=${config.registryEncryptionKey}
|
|
551
|
+
`;
|
|
552
|
+
}
|
|
553
|
+
function generateK8sManifests(config) {
|
|
554
|
+
const namespace = config.pluginNetwork || "alcedo-core";
|
|
555
|
+
const dbName = "plugin_core";
|
|
556
|
+
const dbUrlBase = `postgres://postgres:${config.dbPassword}@postgres:5432/${dbName}`;
|
|
557
|
+
return {
|
|
558
|
+
"00-namespace.yaml": `apiVersion: v1
|
|
559
|
+
kind: Namespace
|
|
560
|
+
metadata:
|
|
561
|
+
name: ${namespace}
|
|
562
|
+
`,
|
|
563
|
+
"02-configmap.yaml": `apiVersion: v1
|
|
564
|
+
kind: ConfigMap
|
|
565
|
+
metadata:
|
|
566
|
+
name: core-config
|
|
567
|
+
namespace: ${namespace}
|
|
568
|
+
data:
|
|
569
|
+
CORE_PORT: "${config.corePort}"
|
|
570
|
+
DATABASE_URL: "${dbUrlBase}"
|
|
571
|
+
REDIS_URL: "redis://redis:6379"
|
|
572
|
+
DEV_MODE: "false"
|
|
573
|
+
RUST_LOG: "info"
|
|
574
|
+
PLUGIN_NETWORK: "${namespace}"
|
|
575
|
+
CORE_PUBLIC_URL: "${config.corePublicUrl || `http://localhost:${config.corePort}`}"
|
|
576
|
+
SYSTEM_PLUGINS_URL: "${config.systemPluginsUrl}"
|
|
577
|
+
LOCAL_REGISTRY_URL: "${config.localRegistryUrl}"
|
|
578
|
+
SESSION_TTL_SECONDS: "${config.sessionTtl}"
|
|
579
|
+
COLLECTION_LOG_FLUSH_MS: "100"
|
|
580
|
+
RATE_LIMIT_AUTH_REQUESTS: "${config.rateLimitRequests}"
|
|
581
|
+
RATE_LIMIT_AUTH_WINDOW: "${config.rateLimitWindow}"
|
|
582
|
+
RATE_LIMIT_API_REQUESTS: "${config.rateLimitRequests}"
|
|
583
|
+
RATE_LIMIT_API_WINDOW: "${config.rateLimitWindow}"
|
|
584
|
+
SECURITY_COOKIE_SECURE: "false"
|
|
585
|
+
SECURITY_COOKIE_SAMESITE: "lax"
|
|
586
|
+
SECURITY_COOKIE_HTTP_ONLY: "true"
|
|
587
|
+
SECURITY_SESSION_FIXATION: "true"
|
|
588
|
+
SECURITY_PASSWORD_MIN_LENGTH: "8"
|
|
589
|
+
PLUGINS_DIR: "/plugins"
|
|
590
|
+
PLUGINS_DIR: "/var/lib/plugin-public"
|
|
591
|
+
`,
|
|
592
|
+
"03-secrets.yaml": `apiVersion: v1
|
|
593
|
+
kind: Secret
|
|
594
|
+
metadata:
|
|
595
|
+
name: core-secrets
|
|
596
|
+
namespace: ${namespace}
|
|
597
|
+
type: Opaque
|
|
598
|
+
stringData:
|
|
599
|
+
REGISTRY_ENCRYPTION_KEY: "${config.registryEncryptionKey}"
|
|
600
|
+
POSTGRES_PASSWORD: "${config.dbPassword}"
|
|
601
|
+
ADMIN_EMAIL: "${config.adminEmail}"
|
|
602
|
+
ADMIN_PASSWORD: "${config.adminPassword}"
|
|
603
|
+
`,
|
|
604
|
+
"04-postgres.yaml": `apiVersion: apps/v1
|
|
605
|
+
kind: StatefulSet
|
|
606
|
+
metadata:
|
|
607
|
+
name: postgres
|
|
608
|
+
namespace: ${namespace}
|
|
609
|
+
spec:
|
|
610
|
+
serviceName: postgres
|
|
611
|
+
replicas: 1
|
|
612
|
+
selector:
|
|
613
|
+
matchLabels:
|
|
614
|
+
app: postgres
|
|
615
|
+
volumeClaimTemplates:
|
|
616
|
+
- metadata:
|
|
617
|
+
name: postgres-data
|
|
618
|
+
spec:
|
|
619
|
+
accessModes: ["ReadWriteOnce"]
|
|
620
|
+
resources:
|
|
621
|
+
requests:
|
|
622
|
+
storage: 10Gi
|
|
623
|
+
storageClassName: local-path
|
|
624
|
+
template:
|
|
625
|
+
metadata:
|
|
626
|
+
labels:
|
|
627
|
+
app: postgres
|
|
628
|
+
spec:
|
|
629
|
+
containers:
|
|
630
|
+
- name: postgres
|
|
631
|
+
image: postgres:${config.postgresVersion}
|
|
632
|
+
env:
|
|
633
|
+
- name: POSTGRES_USER
|
|
634
|
+
value: postgres
|
|
635
|
+
- name: POSTGRES_PASSWORD
|
|
636
|
+
valueFrom:
|
|
637
|
+
secretKeyRef:
|
|
638
|
+
name: core-secrets
|
|
639
|
+
key: POSTGRES_PASSWORD
|
|
640
|
+
- name: POSTGRES_DB
|
|
641
|
+
value: ${dbName}
|
|
642
|
+
ports:
|
|
643
|
+
- containerPort: 5432
|
|
644
|
+
volumeMounts:
|
|
645
|
+
- name: postgres-data
|
|
646
|
+
mountPath: /var/lib/postgresql/data
|
|
647
|
+
subPath: postgres
|
|
648
|
+
readinessProbe:
|
|
649
|
+
exec:
|
|
650
|
+
command: ["pg_isready", "-U", "postgres"]
|
|
651
|
+
initialDelaySeconds: 5
|
|
652
|
+
periodSeconds: 5
|
|
653
|
+
---
|
|
654
|
+
apiVersion: v1
|
|
655
|
+
kind: Service
|
|
656
|
+
metadata:
|
|
657
|
+
name: postgres
|
|
658
|
+
namespace: ${namespace}
|
|
659
|
+
spec:
|
|
660
|
+
selector:
|
|
661
|
+
app: postgres
|
|
662
|
+
ports:
|
|
663
|
+
- port: 5432
|
|
664
|
+
targetPort: 5432
|
|
665
|
+
`,
|
|
666
|
+
"05-redis.yaml": `apiVersion: apps/v1
|
|
667
|
+
kind: Deployment
|
|
668
|
+
metadata:
|
|
669
|
+
name: redis
|
|
670
|
+
namespace: ${namespace}
|
|
671
|
+
spec:
|
|
672
|
+
replicas: 1
|
|
673
|
+
selector:
|
|
674
|
+
matchLabels:
|
|
675
|
+
app: redis
|
|
676
|
+
template:
|
|
677
|
+
metadata:
|
|
678
|
+
labels:
|
|
679
|
+
app: redis
|
|
680
|
+
spec:
|
|
681
|
+
containers:
|
|
682
|
+
- name: redis
|
|
683
|
+
image: redis:7-alpine
|
|
684
|
+
ports:
|
|
685
|
+
- containerPort: 6379
|
|
686
|
+
readinessProbe:
|
|
687
|
+
exec:
|
|
688
|
+
command: ["redis-cli", "ping"]
|
|
689
|
+
initialDelaySeconds: 3
|
|
690
|
+
periodSeconds: 5
|
|
691
|
+
---
|
|
692
|
+
apiVersion: v1
|
|
693
|
+
kind: Service
|
|
694
|
+
metadata:
|
|
695
|
+
name: redis
|
|
696
|
+
namespace: ${namespace}
|
|
697
|
+
spec:
|
|
698
|
+
selector:
|
|
699
|
+
app: redis
|
|
700
|
+
ports:
|
|
701
|
+
- port: 6379
|
|
702
|
+
targetPort: 6379
|
|
703
|
+
`,
|
|
704
|
+
"06-core-deployment.yaml": `apiVersion: apps/v1
|
|
705
|
+
kind: Deployment
|
|
706
|
+
metadata:
|
|
707
|
+
name: alcedo-core
|
|
708
|
+
namespace: ${namespace}
|
|
709
|
+
labels:
|
|
710
|
+
app.kubernetes.io/name: alcedo-core
|
|
711
|
+
app.kubernetes.io/managed-by: alcedo-core
|
|
712
|
+
spec:
|
|
713
|
+
replicas: 1
|
|
714
|
+
selector:
|
|
715
|
+
matchLabels:
|
|
716
|
+
app: alcedo-core
|
|
717
|
+
template:
|
|
718
|
+
metadata:
|
|
719
|
+
labels:
|
|
720
|
+
app: alcedo-core
|
|
721
|
+
app.kubernetes.io/name: alcedo-core
|
|
722
|
+
app.kubernetes.io/managed-by: alcedo-core
|
|
723
|
+
spec:
|
|
724
|
+
serviceAccountName: alcedo-core
|
|
725
|
+
securityContext:
|
|
726
|
+
fsGroup: 1001
|
|
727
|
+
initContainers:
|
|
728
|
+
- name: seed-plugins
|
|
729
|
+
image: ${config.coreImageTag}
|
|
730
|
+
imagePullPolicy: Always
|
|
731
|
+
command: ["sh", "-c", "cp -r /plugins/* /data/ 2>/dev/null; chmod -R 755 /data/"]
|
|
732
|
+
securityContext:
|
|
733
|
+
runAsUser: 0
|
|
734
|
+
volumeMounts:
|
|
735
|
+
- name: plugins
|
|
736
|
+
mountPath: /data
|
|
737
|
+
- name: plugin-data
|
|
738
|
+
mountPath: /var/lib/plugin-public
|
|
739
|
+
containers:
|
|
740
|
+
- name: core
|
|
741
|
+
image: ${config.coreImageTag}
|
|
742
|
+
imagePullPolicy: Always
|
|
743
|
+
ports:
|
|
744
|
+
- containerPort: 8080
|
|
745
|
+
name: http
|
|
746
|
+
envFrom:
|
|
747
|
+
- configMapRef:
|
|
748
|
+
name: core-config
|
|
749
|
+
- secretRef:
|
|
750
|
+
name: core-secrets
|
|
751
|
+
env:
|
|
752
|
+
- name: POD_NAMESPACE
|
|
753
|
+
valueFrom:
|
|
754
|
+
fieldRef:
|
|
755
|
+
fieldPath: metadata.namespace
|
|
756
|
+
- name: POD_NAME
|
|
757
|
+
valueFrom:
|
|
758
|
+
fieldRef:
|
|
759
|
+
fieldPath: metadata.name
|
|
760
|
+
volumeMounts:
|
|
761
|
+
- name: plugins
|
|
762
|
+
mountPath: /plugins
|
|
763
|
+
- name: plugin-data
|
|
764
|
+
mountPath: /var/lib/plugin-public
|
|
765
|
+
readinessProbe:
|
|
766
|
+
httpGet:
|
|
767
|
+
path: /health
|
|
768
|
+
port: 8080
|
|
769
|
+
initialDelaySeconds: 5
|
|
770
|
+
periodSeconds: 10
|
|
771
|
+
livenessProbe:
|
|
772
|
+
httpGet:
|
|
773
|
+
path: /health
|
|
774
|
+
port: 8080
|
|
775
|
+
initialDelaySeconds: 15
|
|
776
|
+
periodSeconds: 20
|
|
777
|
+
resources:
|
|
778
|
+
requests:
|
|
779
|
+
cpu: 100m
|
|
780
|
+
memory: 256Mi
|
|
781
|
+
limits:
|
|
782
|
+
cpu: 500m
|
|
783
|
+
memory: 512Mi
|
|
784
|
+
volumes:
|
|
785
|
+
- name: plugins
|
|
786
|
+
emptyDir: {}
|
|
787
|
+
- name: plugin-data
|
|
788
|
+
persistentVolumeClaim:
|
|
789
|
+
claimName: plugin-data
|
|
790
|
+
`,
|
|
791
|
+
"07-core-service.yaml": `apiVersion: v1
|
|
792
|
+
kind: Service
|
|
793
|
+
metadata:
|
|
794
|
+
name: alcedo-core
|
|
795
|
+
namespace: ${namespace}
|
|
796
|
+
labels:
|
|
797
|
+
app.kubernetes.io/name: alcedo-core
|
|
798
|
+
spec:
|
|
799
|
+
selector:
|
|
800
|
+
app: alcedo-core
|
|
801
|
+
ports:
|
|
802
|
+
- port: 8080
|
|
803
|
+
targetPort: 8080
|
|
804
|
+
name: http
|
|
805
|
+
type: ClusterIP
|
|
806
|
+
`,
|
|
807
|
+
"08-plugin-data-pvc.yaml": `apiVersion: v1
|
|
808
|
+
kind: PersistentVolumeClaim
|
|
809
|
+
metadata:
|
|
810
|
+
name: plugin-data
|
|
811
|
+
namespace: ${namespace}
|
|
812
|
+
labels:
|
|
813
|
+
app.kubernetes.io/name: alcedo-core
|
|
814
|
+
app.kubernetes.io/managed-by: alcedo-core
|
|
815
|
+
spec:
|
|
816
|
+
accessModes:
|
|
817
|
+
- ReadWriteOnce
|
|
818
|
+
resources:
|
|
819
|
+
requests:
|
|
820
|
+
storage: 1Gi
|
|
821
|
+
`,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
async function writeFiles(targetDir, config) {
|
|
825
|
+
const spinner = (0, logger_1.createSpinner)("Generating Alcedo Core configuration");
|
|
826
|
+
try {
|
|
827
|
+
node_fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
828
|
+
if (config.platform === "k8s") {
|
|
829
|
+
const k8sDir = node_path_1.default.join(targetDir, "k8s");
|
|
830
|
+
node_fs_1.default.mkdirSync(k8sDir, { recursive: true });
|
|
831
|
+
const manifests = generateK8sManifests(config);
|
|
832
|
+
for (const [filename, content] of Object.entries(manifests)) {
|
|
833
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(k8sDir, filename), content, "utf-8");
|
|
834
|
+
}
|
|
835
|
+
// Create plugins directory
|
|
836
|
+
const pluginsDir = node_path_1.default.join(targetDir, "plugins");
|
|
837
|
+
node_fs_1.default.mkdirSync(pluginsDir, { recursive: true });
|
|
838
|
+
spinner.succeed();
|
|
839
|
+
(0, logger_1.success)(`Alcedo Core K8s configuration generated: ${targetDir}`);
|
|
840
|
+
(0, logger_1.info)("");
|
|
841
|
+
(0, logger_1.info)("To deploy Alcedo Core on Kubernetes:");
|
|
842
|
+
(0, logger_1.info)(` cd ${node_path_1.default.relative(process.cwd(), targetDir)}`);
|
|
843
|
+
(0, logger_1.info)(" kubectl apply -f k8s/");
|
|
844
|
+
(0, logger_1.info)("");
|
|
845
|
+
(0, logger_1.info)("The admin UI will be available after port-forwarding:");
|
|
846
|
+
(0, logger_1.info)(" kubectl port-forward -n alcedo-core svc/alcedo-core 8080:8080");
|
|
847
|
+
(0, logger_1.info)(` ${config.corePublicUrl || `http://localhost:${config.corePort}`}/admin`);
|
|
848
|
+
(0, logger_1.info)(` Email: ${config.adminEmail}`);
|
|
849
|
+
(0, logger_1.info)("");
|
|
850
|
+
(0, logger_1.info)("Static plugins directory:");
|
|
851
|
+
(0, logger_1.info)(` ${node_path_1.default.join(targetDir, "plugins")}/`);
|
|
852
|
+
(0, logger_1.info)(" Place static plugin folders (with manifest.json) here.");
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
// docker-compose.yml
|
|
856
|
+
const compose = generateCompose(config);
|
|
857
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(targetDir, "docker-compose.yml"), compose, "utf-8");
|
|
858
|
+
// .env
|
|
859
|
+
const env = generateEnv(config);
|
|
860
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(targetDir, ".env"), env, "utf-8");
|
|
861
|
+
const nginx = generateNginxConf(config);
|
|
862
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(targetDir, "nginx.conf"), nginx, "utf-8");
|
|
863
|
+
// Create plugins directory for static system plugins (e.g. admin UI)
|
|
864
|
+
const pluginsDir = node_path_1.default.join(targetDir, "plugins");
|
|
865
|
+
node_fs_1.default.mkdirSync(pluginsDir, { recursive: true });
|
|
866
|
+
// Create a README so users know they can place static plugins here
|
|
867
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(pluginsDir, "README.md"), "# Static Plugins\n\nPlace static plugin folders here (each with a manifest.json).\n" +
|
|
868
|
+
"The core will load them automatically on startup.\n", "utf-8");
|
|
869
|
+
spinner.succeed();
|
|
870
|
+
(0, logger_1.success)(`Alcedo Core configuration generated: ${targetDir}`);
|
|
871
|
+
(0, logger_1.info)("");
|
|
872
|
+
(0, logger_1.info)("To start Alcedo Core:");
|
|
873
|
+
(0, logger_1.info)(` cd ${node_path_1.default.relative(process.cwd(), targetDir)}`);
|
|
874
|
+
(0, logger_1.info)(" docker compose up -d");
|
|
875
|
+
(0, logger_1.info)("");
|
|
876
|
+
(0, logger_1.info)("The admin UI will be available at:");
|
|
877
|
+
(0, logger_1.info)(` ${config.corePublicUrl || `http://localhost:${config.corePort}`}/admin`);
|
|
878
|
+
(0, logger_1.info)(` Email: ${config.adminEmail}`);
|
|
879
|
+
(0, logger_1.info)("");
|
|
880
|
+
(0, logger_1.info)("Local Docker Registry:");
|
|
881
|
+
(0, logger_1.info)(` http://localhost:${config.registryPort}`);
|
|
882
|
+
(0, logger_1.info)("");
|
|
883
|
+
(0, logger_1.info)("Static plugins directory:");
|
|
884
|
+
(0, logger_1.info)(` ${node_path_1.default.join(targetDir, "plugins")}/`);
|
|
885
|
+
(0, logger_1.info)(" Place static plugin folders (with manifest.json) here.");
|
|
886
|
+
(0, logger_1.info)("System plugins will be auto-deployed from:");
|
|
887
|
+
(0, logger_1.info)(` ${config.systemPluginsUrl}`);
|
|
888
|
+
(0, logger_1.info)("");
|
|
889
|
+
(0, logger_1.info)("To deploy a plugin:");
|
|
890
|
+
(0, logger_1.info)(` curl -X POST http://localhost:${config.corePort}/api/plugins/deploy \\`);
|
|
891
|
+
(0, logger_1.info)(' -H "Content-Type: application/json" \\');
|
|
892
|
+
(0, logger_1.info)(' -d \'{"slug": "my-plugin", "image": "localhost:5000/my-plugin:latest"\'}');
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
catch (err) {
|
|
896
|
+
spinner.fail();
|
|
897
|
+
throw err;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
//# sourceMappingURL=init-core.js.map
|