fix: oss boot error fixes (#2039)

This commit is contained in:
Daniel Salazar
2025-11-26 11:08:40 -08:00
committed by GitHub
parent 47ebbc1b90
commit 001e174b81
16 changed files with 1077 additions and 326 deletions

1163
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@
"dependencies": {
"@aws-sdk/client-polly": "^3.622.0",
"@aws-sdk/client-textract": "^3.621.0",
"@aws-sdk/client-cloudwatch": "^3.940.0",
"@google/generative-ai": "^0.21.0",
"@heyputer/kv.js": "^0.1.9",
"@heyputer/multest": "^0.0.2",

View File

@@ -24,7 +24,7 @@ const UserParam = require('../../api/filesystem/UserParam');
const config = require('../../config');
const { chkperm, validate_fsentry_name } = require('../../helpers');
const { TeePromise } = require('@heyputer/putility').libs.promise;
const { pausing_tee, logging_stream, offset_write_stream, stream_to_the_void } = require('../../util/streamutil');
const { pausing_tee, offset_write_stream, stream_to_the_void } = require('../../util/streamutil');
const { TYPE_DIRECTORY } = require('../FSNodeContext');
const { LLRead } = require('../ll_operations/ll_read');
const { RootNodeSelector, NodePathSelector } = require('../node/selectors');

View File

@@ -80,12 +80,12 @@ class LLCWrite extends LLFilesystemOperation {
const parent = this.values.parent;
// Embed fields into this.context
this.context.set('immutable', this.values.immutable);
this.context.set('tmp', this.values.tmp);
this.context.set('fsentry_tmp', this.values.fsentry_tmp);
this.context.set('message', this.values.message);
this.context.set('actor', this.values.actor);
this.context.set('app_id', this.values.app_id);
this.context.set('immutable', this.context.get('immutable') ?? this.values.immutable);
this.context.set('tmp', this.context.get('tmp') ?? this.values.tmp);
this.context.set('fsentry_tmp', this.context.get('fsentry_tmp') ?? this.values.fsentry_tmp);
this.context.set('message', this.context.get('message') ?? this.values.message);
this.context.set('actor', this.context.get('actor') ?? this.values.actor);
this.context.set('app_id', this.context.get('app_id') ?? this.values.app_id);
if ( ! await parent.exists() ) {
throw APIError.create('subject_does_not_exist');

View File

@@ -238,16 +238,18 @@ class AppIconService extends BaseService {
const dir_system = await svc_user.get_system_dir();
// Ensure app icons directory exists
const dir_app_icons = await svc_fs.node(new NodePathSelector('/system/app_icons'));
if ( ! await dir_app_icons.exists() ) {
const ll_mkdir = new LLMkdir();
await ll_mkdir.run({
parent: dir_system,
name: 'app_icons',
actor: await svc_su.get_system_actor(),
});
}
this.dir_app_icons = dir_app_icons;
await svc_su.sudo(async () => {
const dir_app_icons = await svc_fs.node(new NodePathSelector('/system/app_icons'));
if ( ! await dir_app_icons.exists() ) {
const ll_mkdir = new LLMkdir();
await ll_mkdir.run({
parent: dir_system,
name: 'app_icons',
actor: await svc_su.get_system_actor(),
});
}
this.dir_app_icons = dir_app_icons;
});
// Listen for new app icons
const svc_event = this.services.get('event');

View File

@@ -29,6 +29,9 @@ const { Actor, UserActorType } = require('../../services/auth/Actor');
const { DB_WRITE } = require('../../services/database/consts');
const { TEAL } = require('../../services/NullDevConsoleService');
const { quot } = require('@heyputer/putility').libs.string;
const bcrypt = require('bcrypt');
const uuidv4 = require('uuid').v4;
const crypto = require('crypto');
const USERNAME = 'admin';
@@ -68,22 +71,20 @@ const DEFAULT_FILES = {
};
class DefaultUserService extends BaseService {
static MODULES = {
bcrypt: require('bcrypt'),
uuidv4: require('uuid').v4,
};
async _init () {
this._register_commands(this.services.get('commands'));
}
async ['__on_ready.webserver'] () {
// check if a user named `admin` exists
let user = await get_user({ username: USERNAME, cached: false });
if ( ! user ) user = await this.create_default_user_();
if ( ! user ) {
user = await this.create_default_user_();
} else {
await this.#createDefaultUserFiles(Actor.adapt(user));
}
// check if user named `admin` is using default password
const require = this.require;
const tmp_password = await this.get_tmp_password_(user);
const bcrypt = require('bcrypt');
const is_default_password = await bcrypt.compare(tmp_password,
user.password);
if ( ! is_default_password ) return;
@@ -91,14 +92,6 @@ class DefaultUserService extends BaseService {
// console.log(`password for admin is: ${tmp_password}`);
const svc_devConsole = this.services.get('dev-console');
// console.log('\n');
// console.log("************************************************");
// console.log('* Your default login credentials are:');
// console.log(`* Username: \x1b[1m${USERNAME}\x1b[0m`);
// console.log(`* Password: \x1b[1m${tmp_password}\x1b[0m`);
// console.log("************************************************");
// console.log('\n');
// NB: this is needed for the CI to extract the password
console.log(`password for admin is: ${tmp_password}`);
@@ -141,12 +134,10 @@ class DefaultUserService extends BaseService {
this.start_poll_({ tmp_password, user });
svc_devConsole.add_widget(this.default_user_widget);
}
start_poll_ ({ tmp_password, user }) {
start_poll_ ({ tmp_password }) {
const interval = 1000 * 3; // 3 seconds
const poll_interval = asyncSafeSetInterval(async () => {
const user = await get_user({ username: USERNAME });
const require = this.require;
const bcrypt = require('bcrypt');
const is_default_password = await bcrypt.compare(tmp_password,
user.password);
if ( ! is_default_password ) {
@@ -164,7 +155,7 @@ class DefaultUserService extends BaseService {
VALUES (?, ?, ?)
`,
[
this.modules.uuidv4(),
uuidv4(),
USERNAME,
1024 * 1024 * 1024 * 10, // 10 GB
]);
@@ -176,7 +167,6 @@ class DefaultUserService extends BaseService {
const user = await get_user({ username: USERNAME, cached: false });
const actor = Actor.adapt(user);
const tmp_password = await this.get_tmp_password_(user);
const bcrypt = require('bcrypt');
const password_hashed = await bcrypt.hash(tmp_password, 8);
await db.write('UPDATE user SET password = ? WHERE id = ?',
[
@@ -187,11 +177,22 @@ class DefaultUserService extends BaseService {
const svc_user = this.services.get('user');
await svc_user.generate_default_fsentries({ user });
// generate default files for admin user
await this.#createDefaultUserFiles(actor);
invalidate_cached_user(user);
await new Promise(rslv => setTimeout(rslv, 2000));
return user;
}
async #recursiveCreateDefaultFilesIfMissing ({ components, tree, actor }) {
const svc_fs = this.services.get('filesystem');
const make_tree_ = async ({ components, tree }) => {
const parent = await svc_fs.node(new NodePathSelector(`/${components.join('/')}`));
for ( const k in tree ) {
if ( typeof tree[k] === 'string' ) {
const parent = await svc_fs.node(new NodePathSelector(`/${components.join('/')}`));
for ( const k in tree ) {
if ( typeof tree[k] === 'string' ) {
try {
const buffer = Buffer.from(tree[k], 'utf-8');
const hl_write = new HLWrite();
await hl_write.run({
@@ -201,32 +202,51 @@ class DefaultUserService extends BaseService {
size: buffer.length,
stream: buffer_to_stream(buffer),
},
user,
actor,
});
} else {
} catch (e) {
if ( e.message.includes('already exists.') ) {
// ignore
} else {
// throw if it actually fails to create the files
throw e;
}
}
} else {
try {
const hl_qmkdir = new QuickMkdir();
await hl_qmkdir.run({
parent,
path: k,
actor,
});
const components_ = [...components, k];
await make_tree_({
components: components_,
tree: tree[k],
});
} catch (e) {
if ( e.message.includes('already exists.') ) {
// ignore
} else {
// throw if it actually fails to create the files
throw e;
}
}
const components_ = [...components, k];
await this.#recursiveCreateDefaultFilesIfMissing({
components: components_,
tree: tree[k],
actor,
});
}
};
await Context.get().sub({ user, actor }).arun(async () => {
await make_tree_({
}
};
async #createDefaultUserFiles (actor) {
await this.services.get('su').sudo(actor, async () => {
await this.#recursiveCreateDefaultFilesIfMissing({
components: ['admin'],
tree: DEFAULT_FILES,
actor,
});
});
invalidate_cached_user(user);
await new Promise(rslv => setTimeout(rslv, 2000));
return user;
}
async get_tmp_password_ (user) {
const actor = await Actor.create(UserActorType, { user });
@@ -240,7 +260,7 @@ class DefaultUserService extends BaseService {
if ( driver_response.result ) return driver_response.result;
const tmp_password = require('crypto').randomBytes(4).toString('hex');
const tmp_password = crypto.randomBytes(4).toString('hex');
await svc_driver.call({
iface: 'puter-kvstore',
method: 'set',
@@ -258,8 +278,7 @@ class DefaultUserService extends BaseService {
const actor = await Actor.create(UserActorType, { user });
return await Context.get().sub({ actor }).arun(async () => {
const svc_driver = this.services.get('driver');
const tmp_password = require('crypto').randomBytes(4).toString('hex');
const bcrypt = require('bcrypt');
const tmp_password = crypto.randomBytes(4).toString('hex');
const password_hashed = await bcrypt.hash(tmp_password, 8);
await svc_driver.call({
iface: 'puter-kvstore',

View File

@@ -19,6 +19,7 @@
// METADATA // {"ai-params":{"service":"claude"},"ai-commented":{"service":"claude"}}
const BaseService = require('../../services/BaseService');
const socketio = require('socket.io');
/**
* SocketioService provides a service for sending messages to clients.
@@ -26,19 +27,12 @@ const BaseService = require('../../services/BaseService');
* interface for sending messages to rooms or socket ids.
*/
class SocketioService extends BaseService {
static MODULES = {
socketio: require('socket.io'),
};
/**
* Initializes socket.io
*
* @evtparam server The server to attach socket.io to.
*/
['__on_install.socketio'] (_, { server }) {
const require = this.require;
const socketio = require('socket.io');
/**
* @type {import('socket.io').Server}
*/
@@ -81,11 +75,11 @@ class SocketioService extends BaseService {
*/
has (socket_specifier) {
if ( socket_specifier.room ) {
const room = this.io.sockets.adapter.rooms.get(socket_specifier.room);
const room = this.io?.sockets.adapter.rooms.get(socket_specifier.room);
return (!!room) && room.size > 0;
}
if ( socket_specifier.socket ) {
return this.io.sockets.sockets.has(socket_specifier.socket);
return this.io?.sockets.sockets.has(socket_specifier.socket);
}
}
}

View File

@@ -18,6 +18,7 @@
*/
const config = require('../config');
const BaseService = require('../services/BaseService');
const { CloudWatchClient } = require('@aws-sdk/client-cloudwatch');
class Metric {
constructor (windowSize) {
@@ -135,8 +136,7 @@ class PerformanceMonitor extends BaseService {
_init () {
if ( config.cloudwatch ) {
const AWS = require('aws-sdk');
this.cw = new AWS.CloudWatch(config.cloudwatch);
this.cw = new CloudWatchClient(config.cloudwatch);
}
if ( config.monitor ) {
@@ -157,7 +157,7 @@ class PerformanceMonitor extends BaseService {
}
logMonitorContext (ctx) {
if ( ! this.performanceMetrics.hasOwnProperty(ctx.name) ) {
if ( ! this.performanceMetrics[ctx.name] ) {
this.performanceMetrics[ctx.name] =
new Metric(config.windowSize ?? 30);
}

View File

@@ -20,11 +20,12 @@
const express = require('express');
const router = express.Router();
const config = require('../config.js');
const { DB_WRITE } = require('../services/database/consts.js');
const { NodePathSelector } = require('../filesystem/node/selectors.js');
const { HLRead } = require('../filesystem/hl_operations/hl_read.js');
const { UserActorType } = require('../services/auth/Actor.js');
const configurable_auth = require('../middleware/configurable_auth.js');
const { subdomain } = require('../helpers');
const _path = require('path');
// -----------------------------------------------------------------------//
// GET /down
@@ -34,7 +35,7 @@ router.post('/down', express.json(), express.urlencoded({ extended: true }), con
const actor = req.actor;
if ( !actor || !(actor.type instanceof UserActorType) ) {
if ( require('../helpers').subdomain(req) !== 'api' )
if ( subdomain(req) !== 'api' )
{
next();
}
@@ -68,11 +69,7 @@ router.post('/down', express.json(), express.urlencoded({ extended: true }), con
}
// modules
const db = req.services.get('database').get(DB_WRITE, 'filesystem');
const _path = require('path');
const { chkperm } = require('../helpers');
const path = _path.resolve('/', req.query.path);
const AWS = require('aws-sdk');
const path = _path.resolve('/', req.query.path);
// cannot download the root, because it's a directory!
if ( path === '/' )
@@ -91,9 +88,6 @@ router.post('/down', express.json(), express.urlencoded({ extended: true }), con
// stream data from S3
try {
const esc_filename = (await fsnode.get('name'))
.replace(/[^a-zA-Z0-9-_\.]/g, '_');
res.setHeader('Content-Type', 'application/octet-stream');
res.attachment(await fsnode.get('name'));
@@ -102,12 +96,6 @@ router.post('/down', express.json(), express.urlencoded({ extended: true }), con
fsNode: fsnode,
user: req.user,
});
// let stream = await s3.getObject({
// Bucket: fsentry.bucket,
// Key: fsentry.uuid, // File name you want to save as in S3
// }).createReadStream().on('error', error => {
// console.log(error);
// });
return stream.pipe(res);
} catch (e) {
console.log(e);

View File

@@ -1,9 +0,0 @@
import { describe, expect } from 'vitest';
import { testKernel } from '../../../test.setup.mjs';
describe('MeteringService', () => {
it('should have some services', () => {
expect(testKernel.services).not.toBeUndefined();
});
});

View File

@@ -47,7 +47,7 @@ class SystemDataService extends BaseService {
*/
async interpret (data) {
if ( whatis(data) === 'object' && data.$ ) {
return await this.dereference_(data);
return await this.#dereference(data);
}
if ( whatis(data) === 'object' ) {
const new_o = {};
@@ -73,7 +73,7 @@ class SystemDataService extends BaseService {
* @param {Object|Array|*} data - The data to interpret, which can be of any type.
* @returns {Promise<*>} The interpreted result, which could be a primitive, object, or array.
*/
async dereference_ (data) {
async #dereference (data) {
const svc_fs = this.services.get('filesystem');
if ( data.$ === 'json-address' ) {
const node = await svc_fs.node(data.path);

View File

@@ -1,10 +0,0 @@
// setup.ts - Vitest global setup for Puter API tests (TypeScript)
import { beforeAll } from 'vitest';
import { k } from './tools/test.mjs';
let testKernel = {};
beforeAll(async () => {
console.log("initted with kernel:", k);
testKernel = await k;
});
export { testKernel };
//# sourceMappingURL=test.setup.mjs.map

View File

@@ -1,14 +0,0 @@
// setup.ts - Vitest global setup for Puter API tests (TypeScript)
import { beforeAll } from 'vitest';
// @ts-ignore
import { Kernel } from './src/Kernel.js';
// @ts-ignore
import {k} from './tools/test.mjs';
let testKernel = {} as Kernel;
beforeAll(async () => {
console.log("initted with kernel:" ,k)
testKernel = await k;
});
export { testKernel };

View File

@@ -1,20 +0,0 @@
// vite.config.ts - Vite configuration for Puter API tests (TypeScript)
import { loadEnv } from 'vite';
import { defineConfig } from 'vitest/config';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default defineConfig(({ mode }) => ({
test: {
globals: true,
environment: 'jsdom',
setupFiles: [path.resolve(__dirname, './test.setup.mts')],
coverage: {
reporter: ['text', 'json', 'html'],
exclude: [path.resolve(__dirname, './test.setup.mts')],
},
env: loadEnv(mode, '', 'PUTER_'),
},
}));
//# sourceMappingURL=vitest.config.mjs.map

View File

@@ -11,10 +11,8 @@ export default defineConfig(({ mode }) => ({
test: {
globals: true,
environment: 'jsdom',
setupFiles: [path.resolve(__dirname, './test.setup.mts')],
coverage: {
reporter: ['text', 'json', 'html'],
exclude: [path.resolve(__dirname, './test.setup.mts')],
},
env: loadEnv(mode, '', 'PUTER_'),
},

View File

@@ -1,13 +1,14 @@
{
"extends": "../tsconfig.json",
"noEmit": true,
"compilerOptions": {
"rootDir": "..",
"target": "es6",
"module": "es6",
"moduleResolution": "bundler",
"outDir": "build",
"esModuleInterop": true
"esModuleInterop": true,
"types": ["vitest/globals"]
},
"include": [
"../**/*.test.mts",