mirror of
https://github.com/HeyPuter/puter.git
synced 2025-12-30 09:40:00 -06:00
fix: oss boot error fixes (#2039)
This commit is contained in:
1163
package-lock.json
generated
1163
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
@@ -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_'),
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user