mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 22:43:45 +08:00
dev: experimental local-terminal service
This commit is contained in:
parent
7293fdbbfa
commit
2993b887bd
@ -33,6 +33,7 @@ const { TemplateModule } = require("./src/modules/template/TemplateModule.js");
|
||||
const { PuterFSModule } = require("./src/modules/puterfs/PuterFSModule.js");
|
||||
const { PerfMonModule } = require("./src/modules/perfmon/PerfMonModule.js");
|
||||
const { AppsModule } = require("./src/modules/apps/AppsModule.js");
|
||||
const { DevelopmentModule } = require("./src/modules/development/DevelopmentModule.js");
|
||||
|
||||
|
||||
module.exports = {
|
||||
@ -69,4 +70,5 @@ module.exports = {
|
||||
|
||||
// Development modules
|
||||
PerfMonModule,
|
||||
DevelopmentModule,
|
||||
};
|
||||
|
@ -97,6 +97,7 @@ const hardcoded_user_group_permissions = {
|
||||
'service': {},
|
||||
'feature': {},
|
||||
'kernel-info': {},
|
||||
'local-terminal:access': {},
|
||||
},
|
||||
'b7220104-7905-4985-b996-649fdcdb3c8f': {
|
||||
'service:hello-world:ii:hello-world': policy_perm('temp.es'),
|
||||
|
39
src/backend/src/modules/development/DevelopmentModule.js
Normal file
39
src/backend/src/modules/development/DevelopmentModule.js
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const { AdvancedBase } = require("@heyputer/putility");
|
||||
|
||||
/**
|
||||
* Enable this module when you want performance monitoring.
|
||||
*
|
||||
* Performance monitoring requires additional setup. Jaegar should be installed
|
||||
* and running.
|
||||
*/
|
||||
class DevelopmentModule extends AdvancedBase {
|
||||
async install (context) {
|
||||
const services = context.get('services');
|
||||
|
||||
const LocalTerminalService = require("./LocalTerminalService");
|
||||
services.registerService('local-terminal', LocalTerminalService);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DevelopmentModule,
|
||||
};
|
173
src/backend/src/modules/development/LocalTerminalService.js
Normal file
173
src/backend/src/modules/development/LocalTerminalService.js
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const { spawn } = require("child_process");
|
||||
const APIError = require("../../api/APIError");
|
||||
const configurable_auth = require("../../middleware/configurable_auth");
|
||||
const { Endpoint } = require("../../util/expressutil");
|
||||
|
||||
|
||||
const PERM_LOCAL_TERMINAL = 'local-terminal:access';
|
||||
|
||||
const path_ = require('path');
|
||||
const { Actor } = require("../../services/auth/Actor");
|
||||
const BaseService = require("../../services/BaseService");
|
||||
const { Context } = require("../../util/context");
|
||||
|
||||
class LocalTerminalService extends BaseService {
|
||||
_construct () {
|
||||
this.sessions_ = {};
|
||||
}
|
||||
get_profiles () {
|
||||
return {
|
||||
['api-test']: {
|
||||
cwd: path_.join(
|
||||
__dirname,
|
||||
'../../../../../',
|
||||
'tools/api-tester',
|
||||
),
|
||||
shell: ['/usr/bin/env', 'node', 'apitest.js'],
|
||||
allow_args: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
['__on_install.routes'] (_, { app }) {
|
||||
const r_group = (() => {
|
||||
const require = this.require;
|
||||
const express = require('express');
|
||||
return express.Router()
|
||||
})();
|
||||
app.use('/local-terminal', r_group);
|
||||
|
||||
Endpoint({
|
||||
route: '/new',
|
||||
methods: ['POST'],
|
||||
mw: [configurable_auth()],
|
||||
handler: async (req, res) => {
|
||||
const term_uuid = require('uuid').v4();
|
||||
|
||||
const svc_permission = this.services.get('permission');
|
||||
const actor = Context.get('actor');
|
||||
const can_access = actor &&
|
||||
await svc_permission.check(actor, PERM_LOCAL_TERMINAL);
|
||||
|
||||
if ( ! can_access ) {
|
||||
throw APIError.create('permission_denied', null, {
|
||||
permission: PERM_LOCAL_TERMINAL,
|
||||
});
|
||||
}
|
||||
|
||||
const profiles = this.get_profiles();
|
||||
if ( ! profiles[req.body.profile] ) {
|
||||
throw APIError.create('invalid_profile', null, {
|
||||
profile: req.body.profile,
|
||||
});
|
||||
}
|
||||
|
||||
const profile = profiles[req.body.profile];
|
||||
|
||||
const args = profile.shell.slice(1);
|
||||
if ( ! profile.allow_args && req.body.args ) {
|
||||
args.push(...req.body.args);
|
||||
}
|
||||
const proc = spawn(profile.shell[0], args, {
|
||||
shell: true,
|
||||
env: {
|
||||
...process.env,
|
||||
...(profile.env ?? {}),
|
||||
},
|
||||
cwd: profile.cwd,
|
||||
});
|
||||
|
||||
console.log('process??', proc);
|
||||
|
||||
// stdout to websocket
|
||||
{
|
||||
const svc_socketio = req.services.get('socketio');
|
||||
proc.stdout.on('data', data => {
|
||||
const base64 = data.toString('base64');
|
||||
console.log('---------------------- CHUNK?', base64);
|
||||
svc_socketio.send(
|
||||
{ room: req.user.id },
|
||||
'local-terminal.stdout',
|
||||
{
|
||||
term_uuid,
|
||||
base64,
|
||||
},
|
||||
);
|
||||
});
|
||||
proc.stderr.on('data', data => {
|
||||
const base64 = data.toString('base64');
|
||||
console.log('---------------------- CHUNK?', base64);
|
||||
svc_socketio.send(
|
||||
{ room: req.user.id },
|
||||
'local-terminal.stderr',
|
||||
{
|
||||
term_uuid,
|
||||
base64,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
proc.on('exit', () => {
|
||||
this.log.noticeme(`[${term_uuid}] Process exited (${proc.exitCode})`);
|
||||
delete this.sessions_[term_uuid];
|
||||
});
|
||||
|
||||
this.sessions_[term_uuid] = {
|
||||
uuid: term_uuid,
|
||||
proc,
|
||||
};
|
||||
|
||||
res.json({ term_uuid });
|
||||
},
|
||||
}).attach(r_group);
|
||||
}
|
||||
async _init () {
|
||||
const svc_event = this.services.get('event');
|
||||
svc_event.on('web.socket.user-connected', async (_, {
|
||||
socket,
|
||||
user,
|
||||
}) => {
|
||||
const svc_permission = this.services.get('permission');
|
||||
const actor = Actor.adapt(user);
|
||||
const can_access = actor &&
|
||||
await svc_permission.check(actor, PERM_LOCAL_TERMINAL);
|
||||
|
||||
if ( ! can_access ) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.on('local-terminal.stdin', async msg => {
|
||||
console.log('local term message', msg);
|
||||
|
||||
const session = this.sessions_[msg.term_uuid];
|
||||
if ( ! session ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const base64 = Buffer.from(msg.data, 'base64');
|
||||
session.proc.stdin.write(base64);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LocalTerminalService;
|
@ -275,6 +275,7 @@ class WebServerService extends BaseService {
|
||||
actor: socket.actor,
|
||||
}).arun(async () => {
|
||||
await svc_event.emit('web.socket.user-connected', {
|
||||
socket,
|
||||
user: socket.user
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user