@aifabrix/builder 2.5.2 → 2.5.3
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/.markdownlint.json +14 -0
- package/lib/infra.js +55 -56
- package/lib/secrets.js +1 -1
- package/package.json +1 -1
- package/templates/infra/compose.yaml.hbs +14 -0
- package/templates/infra/servers.json.hbs +21 -0
package/lib/infra.js
CHANGED
|
@@ -107,24 +107,42 @@ async function ensureAdminSecrets() {
|
|
|
107
107
|
return adminSecretsPath;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Generates pgAdmin4 configuration files (servers.json and pgpass)
|
|
112
|
+
* @param {string} infraDir - Infrastructure directory path
|
|
113
|
+
* @param {string} postgresPassword - PostgreSQL password
|
|
114
|
+
*/
|
|
115
|
+
function generatePgAdminConfig(infraDir, postgresPassword) {
|
|
116
|
+
const serversJsonTemplatePath = path.join(__dirname, '..', 'templates', 'infra', 'servers.json.hbs');
|
|
117
|
+
if (!fs.existsSync(serversJsonTemplatePath)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const serversJsonTemplateContent = fs.readFileSync(serversJsonTemplatePath, 'utf8');
|
|
122
|
+
const serversJsonTemplate = handlebars.compile(serversJsonTemplateContent);
|
|
123
|
+
const serversJsonContent = serversJsonTemplate({ postgresPassword });
|
|
124
|
+
const serversJsonPath = path.join(infraDir, 'servers.json');
|
|
125
|
+
fs.writeFileSync(serversJsonPath, serversJsonContent, { mode: 0o644 });
|
|
126
|
+
|
|
127
|
+
const pgpassContent = `postgres:5432:postgres:pgadmin:${postgresPassword}\n`;
|
|
128
|
+
const pgpassPath = path.join(infraDir, 'pgpass');
|
|
129
|
+
fs.writeFileSync(pgpassPath, pgpassContent, { mode: 0o600 });
|
|
130
|
+
}
|
|
131
|
+
|
|
110
132
|
async function startInfra(developerId = null) {
|
|
111
133
|
await checkDockerAvailability();
|
|
112
134
|
const adminSecretsPath = await ensureAdminSecrets();
|
|
113
135
|
|
|
114
|
-
// Get developer ID from parameter or config
|
|
115
136
|
const devId = developerId || await config.getDeveloperId();
|
|
116
|
-
// Convert to number for getDevPorts (it expects numbers)
|
|
117
137
|
const devIdNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
118
138
|
const ports = devConfig.getDevPorts(devIdNum);
|
|
119
139
|
const idNum = devIdNum;
|
|
120
140
|
|
|
121
|
-
// Load compose template (Handlebars)
|
|
122
141
|
const templatePath = path.join(__dirname, '..', 'templates', 'infra', 'compose.yaml.hbs');
|
|
123
142
|
if (!fs.existsSync(templatePath)) {
|
|
124
143
|
throw new Error(`Compose template not found: ${templatePath}`);
|
|
125
144
|
}
|
|
126
145
|
|
|
127
|
-
// Create infra directory in AIFABRIX_HOME with dev ID
|
|
128
146
|
const aifabrixDir = paths.getAifabrixHome();
|
|
129
147
|
const infraDirName = getInfraDirName(devId);
|
|
130
148
|
const infraDir = path.join(aifabrixDir, infraDirName);
|
|
@@ -132,8 +150,11 @@ async function startInfra(developerId = null) {
|
|
|
132
150
|
fs.mkdirSync(infraDir, { recursive: true });
|
|
133
151
|
}
|
|
134
152
|
|
|
135
|
-
|
|
136
|
-
|
|
153
|
+
const adminSecretsContent = fs.readFileSync(adminSecretsPath, 'utf8');
|
|
154
|
+
const postgresPasswordMatch = adminSecretsContent.match(/^POSTGRES_PASSWORD=(.+)$/m);
|
|
155
|
+
const postgresPassword = postgresPasswordMatch ? postgresPasswordMatch[1] : '';
|
|
156
|
+
generatePgAdminConfig(infraDir, postgresPassword);
|
|
157
|
+
|
|
137
158
|
handlebars.registerHelper('eq', (a, b) => {
|
|
138
159
|
if (a === null || a === undefined) a = '0';
|
|
139
160
|
if (b === null || b === undefined) b = '0';
|
|
@@ -146,19 +167,20 @@ async function startInfra(developerId = null) {
|
|
|
146
167
|
});
|
|
147
168
|
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
148
169
|
const template = handlebars.compile(templateContent);
|
|
149
|
-
// Dev 0: infra-aifabrix-network, Dev > 0: infra-dev{id}-aifabrix-network
|
|
150
170
|
const networkName = idNum === 0 ? 'infra-aifabrix-network' : `infra-dev${devId}-aifabrix-network`;
|
|
151
|
-
|
|
152
|
-
|
|
171
|
+
const serversJsonPath = path.join(infraDir, 'servers.json');
|
|
172
|
+
const pgpassPath = path.join(infraDir, 'pgpass');
|
|
153
173
|
const composeContent = template({
|
|
154
174
|
devId: devId,
|
|
155
175
|
postgresPort: ports.postgres,
|
|
156
176
|
redisPort: ports.redis,
|
|
157
177
|
pgadminPort: ports.pgadmin,
|
|
158
178
|
redisCommanderPort: ports.redisCommander,
|
|
159
|
-
networkName: networkName
|
|
179
|
+
networkName: networkName,
|
|
180
|
+
serversJsonPath: serversJsonPath,
|
|
181
|
+
pgpassPath: pgpassPath,
|
|
182
|
+
infraDir: infraDir
|
|
160
183
|
});
|
|
161
|
-
|
|
162
184
|
const composePath = path.join(infraDir, 'compose.yaml');
|
|
163
185
|
fs.writeFileSync(composePath, composeContent);
|
|
164
186
|
|
|
@@ -170,6 +192,22 @@ async function startInfra(developerId = null) {
|
|
|
170
192
|
await execAsyncWithCwd(`${composeCmd} -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" up -d`, { cwd: infraDir });
|
|
171
193
|
logger.log('Infrastructure services started successfully');
|
|
172
194
|
|
|
195
|
+
// Copy pgAdmin4 config files into container after it starts
|
|
196
|
+
const pgadminContainerName = idNum === 0 ? 'aifabrix-pgadmin' : `aifabrix-dev${devId}-pgadmin`;
|
|
197
|
+
try {
|
|
198
|
+
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait for container to be ready
|
|
199
|
+
if (fs.existsSync(serversJsonPath)) {
|
|
200
|
+
await execAsync(`docker cp "${serversJsonPath}" ${pgadminContainerName}:/pgadmin4/servers.json`);
|
|
201
|
+
}
|
|
202
|
+
if (fs.existsSync(pgpassPath)) {
|
|
203
|
+
await execAsync(`docker cp "${pgpassPath}" ${pgadminContainerName}:/pgpass`);
|
|
204
|
+
await execAsync(`docker exec ${pgadminContainerName} chmod 600 /pgpass`);
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
// Ignore copy errors - files might already be there or container not ready
|
|
208
|
+
logger.log('Note: Could not copy pgAdmin4 config files (this is OK if container was just restarted)');
|
|
209
|
+
}
|
|
210
|
+
|
|
173
211
|
await waitForServices(devId);
|
|
174
212
|
logger.log('All services are healthy and ready');
|
|
175
213
|
} finally {
|
|
@@ -431,64 +469,25 @@ async function getAppStatus() {
|
|
|
431
469
|
const apps = [];
|
|
432
470
|
|
|
433
471
|
try {
|
|
434
|
-
// Find all containers with pattern
|
|
435
|
-
// Dev 0: aifabrix-* (but exclude infrastructure containers)
|
|
436
|
-
// Dev > 0: aifabrix-dev{id}-*
|
|
437
472
|
const filterPattern = devId === 0 ? 'aifabrix-' : `aifabrix-dev${devId}-`;
|
|
438
473
|
const { stdout } = await execAsync(`docker ps --filter "name=${filterPattern}" --format "{{.Names}}\t{{.Ports}}\t{{.Status}}"`);
|
|
439
474
|
const lines = stdout.trim().split('\n').filter(line => line.trim() !== '');
|
|
440
|
-
|
|
441
|
-
// Infrastructure container names to exclude
|
|
442
|
-
// Dev 0: aifabrix-{serviceName}, Dev > 0: aifabrix-dev{id}-{serviceName}
|
|
443
475
|
const infraContainers = devId === 0
|
|
444
|
-
? [
|
|
445
|
-
|
|
446
|
-
'aifabrix-redis',
|
|
447
|
-
'aifabrix-pgadmin',
|
|
448
|
-
'aifabrix-redis-commander'
|
|
449
|
-
]
|
|
450
|
-
: [
|
|
451
|
-
`aifabrix-dev${devId}-postgres`,
|
|
452
|
-
`aifabrix-dev${devId}-redis`,
|
|
453
|
-
`aifabrix-dev${devId}-pgadmin`,
|
|
454
|
-
`aifabrix-dev${devId}-redis-commander`
|
|
455
|
-
];
|
|
456
|
-
|
|
476
|
+
? ['aifabrix-postgres', 'aifabrix-redis', 'aifabrix-pgadmin', 'aifabrix-redis-commander']
|
|
477
|
+
: [`aifabrix-dev${devId}-postgres`, `aifabrix-dev${devId}-redis`, `aifabrix-dev${devId}-pgadmin`, `aifabrix-dev${devId}-redis-commander`];
|
|
457
478
|
for (const line of lines) {
|
|
458
479
|
const [containerName, ports, status] = line.split('\t');
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (infraContainers.includes(containerName)) {
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Extract app name from container name
|
|
466
|
-
// Dev 0: aifabrix-{appName}, Dev > 0: aifabrix-dev{id}-{appName}
|
|
467
|
-
const pattern = devId === 0
|
|
468
|
-
? /^aifabrix-(.+)$/
|
|
469
|
-
: new RegExp(`^aifabrix-dev${devId}-(.+)$`);
|
|
480
|
+
if (infraContainers.includes(containerName)) continue;
|
|
481
|
+
const pattern = devId === 0 ? /^aifabrix-(.+)$/ : new RegExp(`^aifabrix-dev${devId}-(.+)$`);
|
|
470
482
|
const appNameMatch = containerName.match(pattern);
|
|
471
|
-
if (!appNameMatch)
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
|
|
483
|
+
if (!appNameMatch) continue;
|
|
475
484
|
const appName = appNameMatch[1];
|
|
476
|
-
|
|
477
|
-
// Extract host port from ports string (e.g., "0.0.0.0:3100->3000/tcp")
|
|
478
485
|
const portMatch = ports.match(/:(\d+)->\d+\//);
|
|
479
486
|
const hostPort = portMatch ? portMatch[1] : 'unknown';
|
|
480
487
|
const url = hostPort !== 'unknown' ? `http://localhost:${hostPort}` : 'unknown';
|
|
481
|
-
|
|
482
|
-
apps.push({
|
|
483
|
-
name: appName,
|
|
484
|
-
container: containerName,
|
|
485
|
-
port: ports,
|
|
486
|
-
status: status.trim(),
|
|
487
|
-
url: url
|
|
488
|
-
});
|
|
488
|
+
apps.push({ name: appName, container: containerName, port: ports, status: status.trim(), url: url });
|
|
489
489
|
}
|
|
490
490
|
} catch (error) {
|
|
491
|
-
// If no containers found, return empty array
|
|
492
491
|
return [];
|
|
493
492
|
}
|
|
494
493
|
|
package/lib/secrets.js
CHANGED
|
@@ -379,7 +379,7 @@ async function generateAdminSecretsEnv(secretsPath) {
|
|
|
379
379
|
POSTGRES_PASSWORD=${postgresPassword}
|
|
380
380
|
PGADMIN_DEFAULT_EMAIL=admin@aifabrix.ai
|
|
381
381
|
PGADMIN_DEFAULT_PASSWORD=${postgresPassword}
|
|
382
|
-
REDIS_HOST=local:redis:6379
|
|
382
|
+
REDIS_HOST=local:redis:6379:0:
|
|
383
383
|
REDIS_COMMANDER_USER=admin
|
|
384
384
|
REDIS_COMMANDER_PASSWORD=${postgresPassword}
|
|
385
385
|
`;
|
package/package.json
CHANGED
|
@@ -53,10 +53,24 @@ services:
|
|
|
53
53
|
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
|
|
54
54
|
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
|
|
55
55
|
PGADMIN_CONFIG_SERVER_MODE: 'False'
|
|
56
|
+
PGADMIN_SERVER_JSON_FILE: /pgadmin4/servers.json
|
|
57
|
+
PGPASSFILE: /pgpass
|
|
56
58
|
ports:
|
|
57
59
|
- "{{pgadminPort}}:80"
|
|
58
60
|
volumes:
|
|
59
61
|
- {{#if (eq devId 0)}}pgadmin_data{{else}}dev{{devId}}_pgadmin_data{{/if}}:/var/lib/pgadmin
|
|
62
|
+
- {{infraDir}}:/host-config:ro
|
|
63
|
+
command: >
|
|
64
|
+
sh -c "
|
|
65
|
+
if [ -f /host-config/servers.json ]; then
|
|
66
|
+
cp /host-config/servers.json /pgadmin4/servers.json;
|
|
67
|
+
fi &&
|
|
68
|
+
if [ -f /host-config/pgpass ]; then
|
|
69
|
+
cp /host-config/pgpass /pgpass;
|
|
70
|
+
chmod 600 /pgpass;
|
|
71
|
+
fi &&
|
|
72
|
+
/entrypoint.sh
|
|
73
|
+
"
|
|
60
74
|
restart: unless-stopped
|
|
61
75
|
depends_on:
|
|
62
76
|
postgres:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Servers": {
|
|
3
|
+
"1": {
|
|
4
|
+
"Name": "PostgreSQL (pgvector)",
|
|
5
|
+
"Group": "Servers",
|
|
6
|
+
"Host": "postgres",
|
|
7
|
+
"Port": 5432,
|
|
8
|
+
"MaintenanceDB": "postgres",
|
|
9
|
+
"Username": "pgadmin",
|
|
10
|
+
"PassFile": "/pgpass",
|
|
11
|
+
"SSLMode": "prefer",
|
|
12
|
+
"Comment": "Auto-registered PostgreSQL server with pgvector extension"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|