diff --git a/doc/contributors/extensions/README.md b/doc/contributors/extensions/README.md index 0af053c18..661d943ce 100644 --- a/doc/contributors/extensions/README.md +++ b/doc/contributors/extensions/README.md @@ -87,3 +87,7 @@ See [events.md](./events.md) ## Definitions See [definitions.md](./definitions.md) + +## Bundled extensions + +- [dev-console](./dev-console.md) – Dev socket for running backend commands locally (opt-in via `DEVCONSOLE=1`). diff --git a/doc/contributors/extensions/dev-console.md b/doc/contributors/extensions/dev-console.md new file mode 100644 index 000000000..1a9d73687 --- /dev/null +++ b/doc/contributors/extensions/dev-console.md @@ -0,0 +1,21 @@ +# dev-console extension + +The **dev-console** extension provides a **dev socket** so you can run backend commands on a local Puter instance (e.g. commands registered in [CommandService](../../../src/backend/src/services/CommandService.js)). + +## Enabling + +The extension is **opt-in**. Set the environment variable `DEVCONSOLE=1` when starting Puter. The `npm run dev` script already does this: + +```bash +npm run dev +``` + +With `DEVCONSOLE=1`, the extension registers a `dev-socket` service that creates a UNIX socket and runs command lines through CommandService. + +## Usage + +See [Backend – dev socket](../../../src/backend/doc/dev_socket.md) for how to connect (e.g. `rlwrap nc -U ./dev.sock`) and run commands like `help`, `logs:indent`, etc. + +## Location + +The extension lives in `extensions/dev-console/`. It only registers the dev-socket service when `DEVCONSOLE=1`; otherwise the extension loads but does nothing, so it does not affect default runs. diff --git a/extensions/README.md b/extensions/README.md index 25e2f8d73..d8c0f876f 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -1,10 +1,14 @@ -# Extension System Development Guide] +# Extension System Development Guide ## Where to find documentation ### Here Documentation for extensions is [here](src/backend/doc/extensions/README.md). +### Bundled extensions + +- **dev-console** (`extensions/dev-console/`) – Dev socket for running backend commands locally. Opt-in via `DEVCONSOLE=1` (e.g. `npm run dev`). See [Backend – dev socket](src/backend/doc/dev_socket.md). + ### Not Here Outdated documentation for extensions is [here](../doc/contributors/extensions/README.md). diff --git a/mods/mods_available/dev-socket/main.js b/mods/mods_available/dev-socket/main.js new file mode 100644 index 000000000..f0082eb0f --- /dev/null +++ b/mods/mods_available/dev-socket/main.js @@ -0,0 +1,104 @@ +/* + * 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 . + */ + +import fs from 'node:fs'; +import net from 'node:net'; +import path from 'node:path'; + +const SOCKET_NAME = 'dev.sock'; +const WELCOME = [ + 'Puter dev socket – enter a command (e.g. help) and press Enter.', + 'Close the connection with Ctrl+C or by typing exit.', + '', +].join('\n'); + +function getSocketDir () { + if ( process.env.PUTER_DEV_SOCKET_DIR ) { + return process.env.PUTER_DEV_SOCKET_DIR; + } + const volatileRuntime = path.join(process.cwd(), 'volatile', 'runtime'); + if ( fs.existsSync(volatileRuntime) ) { + return volatileRuntime; + } + return process.cwd(); +} + +extension.on('init', async () => { + if ( process.env.DEVCONSOLE !== '1' ) { + return; + } + + const commands = extension.import('service:commands'); + const socketDir = getSocketDir(); + const socketPath = path.join(socketDir, SOCKET_NAME); + + try { + if ( fs.existsSync(socketPath) ) { + fs.unlinkSync(socketPath); + } + fs.mkdirSync(socketDir, { recursive: true }); + } catch ( err ) { + console.warn('dev-socket: could not prepare socket path', socketPath, err.message); + return; + } + + const server = net.createServer((socket) => { + socket.setEncoding('utf8'); + socket.write(`${WELCOME }\n> `); + let buffer = ''; + socket.on('data', (chunk) => { + buffer += chunk; + const lines = buffer.split(/\r?\n/); + buffer = lines.pop() ?? ''; + for ( const line of lines ) { + const trimmed = line.trim(); + if ( trimmed === '' ) continue; + if ( trimmed.toLowerCase() === 'exit' ) { + socket.end(); + return; + } + const log = { + log: (msg) => { + socket.write(`${String(msg) }\n`); + }, + error: (msg) => { + socket.write(`${String(msg) }\n`); + }, + }; + commands.executeRawCommand(trimmed, log).then(() => { + socket.write('> '); + }).catch((err) => { + log.error(err?.message ?? err); + socket.write('> '); + }); + } + }); + socket.on('end', () => { + }); + socket.on('error', () => { + }); + }); + + server.listen(socketPath, () => { + console.log('dev-socket: socket listening at', socketPath); + }); + server.on('error', (err) => { + console.warn('dev-socket: socket error', err.message); + }); +}); diff --git a/mods/mods_available/dev-socket/package.json b/mods/mods_available/dev-socket/package.json new file mode 100644 index 000000000..16a796309 --- /dev/null +++ b/mods/mods_available/dev-socket/package.json @@ -0,0 +1,8 @@ +{ + "name": "@heyputer/extension-dev-console", + "version": "1.0.0", + "description": "Dev socket for running backend commands locally (opt-in via DEVCONSOLE=1)", + "main": "main.js", + "type": "module", + "private": true +} \ No newline at end of file diff --git a/src/backend/doc/dev_socket.md b/src/backend/doc/dev_socket.md index c1880e69d..73df862d2 100644 --- a/src/backend/doc/dev_socket.md +++ b/src/backend/doc/dev_socket.md @@ -1,15 +1,32 @@ ## Backend - dev socket The "dev socket" allows you to interact with Puter's backend by running commands. -It's a UNIX socket created in Puter's runtime directory -(typically `./volatile/runtime`, or `/var/puter` for production instances). +It's a UNIX socket that lets you run commands registered with +[CommandService](../../src/services/CommandService.js) (e.g. `help`, `logs:indent`, `params:get`, etc.). -When in the runtime directory, you can connect to the socket with your tool -of choice. For example, using `nc` as well as `rlwrap` to get readline history: +### Enabling the dev socket + +The dev socket is provided by the **dev-console extension** and is **opt-in**. To enable it: + +1. Set the environment variable `DEVCONSOLE=1` when starting Puter (e.g. `npm run dev` already does this). +2. The extension lives in `extensions/dev-console/` and registers a `dev-socket` service when `DEVCONSOLE=1`. + +### Socket location + +The socket is created in a directory chosen as follows (in order): + +- `PUTER_DEV_SOCKET_DIR` if set +- `./volatile/runtime` if it exists (typical local dev) +- otherwise the process current working directory + +The socket file is named `dev.sock`. + +### Connecting + +When in that directory, connect with your tool of choice. For example, using `nc` and `rlwrap` for readline history: ``` rlwrap nc -U ./dev.sock ``` -If it is successful you will see a message with instructions. At this point -you may enter a command. Enter the `help` command to see a list of commands. +If it is successful you will see a message with instructions. Enter a command (e.g. `help`) and press Enter. diff --git a/src/backend/doc/extensions/README.md b/src/backend/doc/extensions/README.md index 2ac7ca49e..fe3201bfb 100644 --- a/src/backend/doc/extensions/README.md +++ b/src/backend/doc/extensions/README.md @@ -72,6 +72,10 @@ key-value store, and in-memory cache. ### Adding Features to Puter - [Implementing Drivers](./pages/drivers.md) +### Bundled extensions + +- **dev-console** – When `DEVCONSOLE=1` is set (e.g. `npm run dev`), the dev-console extension registers a UNIX socket (`dev.sock`) so you can run backend commands (see [CommandService](../../src/services/CommandService.js)) from a terminal. See [Backend – dev socket](../dev_socket.md). + ## Extensions - Planned Features Extensions are under refactor currently. This is the checklist: