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'
}),
sslMode: 'none',
servezoneAdminaccount: 'testadmin:testpassword',
...(() => {
if (process.env.NPMCI_SECRET01) {
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(` - Type: ${user.data.type}`);
console.log(` - Role: ${user.data.role}`);
console.log(` - Tokens: ${user.data.tokens.length}`);
for (const token of user.data.tokens) {
console.log(` - Tokens: ${user.data.tokens?.length ?? 0}`);
for (const token of user.data.tokens ?? []) {
console.log(` - Token: '${token.token}' | Roles: ${token.assignedRoles?.join(', ')}`);
}
}
+6 -3
View File
@@ -11,7 +11,7 @@ early.stop();
* starts the cloudly instance
*/
const runCli = async () => {
logger.log('info', process.env.SERVEZONE_ENVIRONMENT);
logger.log('info', process.env.SERVEZONE_ENVIRONMENT || '');
const cloudlyInstance = new Cloudly();
logger.log(
@@ -20,8 +20,11 @@ const runCli = async () => {
);
await cloudlyInstance.start();
const demoMod = await import('./00demo/index.js');
demoMod.installDemoData(cloudlyInstance);
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');
await demoMod.installDemoData(cloudlyInstance);
}
};
export { runCli, Cloudly };
+38
View File
@@ -49,6 +49,8 @@ export class CloudlyAuthManager {
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
}
await this.bootstrapInitialAdmin();
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.admin.IReq_Admin_LoginWithUsernameAndPassword>(
'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 validIdentityGuard = new plugins.smartguard.Guard<{