fix: make startup bootstrap production-safe

This commit is contained in:
2026-04-28 15:07:08 +00:00
parent 13a7d5969d
commit 333cbeb221
4 changed files with 47 additions and 5 deletions
+1
View File
@@ -41,6 +41,7 @@ export const testCloudlyConfig: cloudly.ICloudlyConfig = {
bucketName: 'cloudly_test_bucket' bucketName: 'cloudly_test_bucket'
}), }),
sslMode: 'none', sslMode: 'none',
servezoneAdminaccount: 'testadmin:testpassword',
...(() => { ...(() => {
if (process.env.NPMCI_SECRET01) { if (process.env.NPMCI_SECRET01) {
return { return {
+2 -2
View File
@@ -56,8 +56,8 @@ tap.test('DEBUG: Check existing users', async () => {
console.log(` - User: ${user.data.username} (ID: ${user.id})`); console.log(` - User: ${user.data.username} (ID: ${user.id})`);
console.log(` - Type: ${user.data.type}`); console.log(` - Type: ${user.data.type}`);
console.log(` - Role: ${user.data.role}`); console.log(` - Role: ${user.data.role}`);
console.log(` - Tokens: ${user.data.tokens.length}`); console.log(` - Tokens: ${user.data.tokens?.length ?? 0}`);
for (const token of user.data.tokens) { for (const token of user.data.tokens ?? []) {
console.log(` - Token: '${token.token}' | Roles: ${token.assignedRoles?.join(', ')}`); console.log(` - Token: '${token.token}' | Roles: ${token.assignedRoles?.join(', ')}`);
} }
} }
+5 -2
View File
@@ -11,7 +11,7 @@ early.stop();
* starts the cloudly instance * starts the cloudly instance
*/ */
const runCli = async () => { const runCli = async () => {
logger.log('info', process.env.SERVEZONE_ENVIRONMENT); logger.log('info', process.env.SERVEZONE_ENVIRONMENT || '');
const cloudlyInstance = new Cloudly(); const cloudlyInstance = new Cloudly();
logger.log( logger.log(
@@ -20,8 +20,11 @@ const runCli = async () => {
); );
await cloudlyInstance.start(); await cloudlyInstance.start();
if (process.env.SERVEZONE_INSTALL_DEMO_DATA === 'true') {
logger.log('warn', 'SERVEZONE_INSTALL_DEMO_DATA=true: installing destructive demo data');
const demoMod = await import('./00demo/index.js'); const demoMod = await import('./00demo/index.js');
demoMod.installDemoData(cloudlyInstance); await demoMod.installDemoData(cloudlyInstance);
}
}; };
export { runCli, Cloudly }; export { runCli, Cloudly };
+38
View File
@@ -49,6 +49,8 @@ export class CloudlyAuthManager {
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys); this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
} }
await this.bootstrapInitialAdmin();
this.typedrouter.addTypedHandler( this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.admin.IReq_Admin_LoginWithUsernameAndPassword>( new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.admin.IReq_Admin_LoginWithUsernameAndPassword>(
'adminLoginWithUsernameAndPassword', 'adminLoginWithUsernameAndPassword',
@@ -82,6 +84,42 @@ export class CloudlyAuthManager {
); );
} }
private async bootstrapInitialAdmin() {
const users = await this.CUser.getInstances({});
const hasHumanUser = users.some((userArg) => userArg.data?.type === 'human');
if (hasHumanUser) {
return;
}
const adminAccount = this.cloudlyRef.config.data.servezoneAdminaccount;
if (!adminAccount) {
throw new Error('SERVEZONE_ADMINACCOUNT is required for first-run Cloudly bootstrap');
}
const separatorIndex = adminAccount.indexOf(':');
if (separatorIndex <= 0 || separatorIndex === adminAccount.length - 1) {
throw new Error('SERVEZONE_ADMINACCOUNT must use username:password format');
}
const username = adminAccount.slice(0, separatorIndex).trim();
const password = adminAccount.slice(separatorIndex + 1);
if (!username || !password) {
throw new Error('SERVEZONE_ADMINACCOUNT must include a non-empty username and password');
}
const user = new this.CUser({
id: await this.CUser.getNewId(),
data: {
type: 'human',
username,
password,
role: 'admin',
},
});
await user.save();
logger.log('success', `created initial admin user ${username}`);
}
public async stop() {} public async stop() {}
public validIdentityGuard = new plugins.smartguard.Guard<{ public validIdentityGuard = new plugins.smartguard.Guard<{