├── .gitignore ├── package.json ├── dshd.js ├── README.md └── dsh.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .dat 3 | .datdest 4 | .datsrc -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsh", 3 | "version": "0.1.0", 4 | "description": "Run remote scripts with dat", 5 | "main": "dsh.js", 6 | "files": [ 7 | "dsh.js", 8 | "dshd.js" 9 | ], 10 | "repository": "YerkoPalma/dsh", 11 | "author": "Yerko Palma ", 12 | "dependencies": { 13 | "dat-node": "^3.5.12", 14 | "mkdirp": "^0.5.1", 15 | "vorpal": "^1.12.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /dshd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const { exec } = require('child_process') 6 | const Dat = require('util').promisify(require('dat-node')) 7 | const mkdirp = require('util').promisify(require('mkdirp')) 8 | const readFile = require('util').promisify(require('fs').readFile) 9 | const writeFile = require('util').promisify(require('fs').writeFile) 10 | const DAT_INPUT = '.din' 11 | const DAT_OUTPUT = '.dout' 12 | const QUEUE_FILE = '.dshell' 13 | 14 | ;(async function () { 15 | let key = process.argv[2] 16 | if (!key) { 17 | try { 18 | key = await readFile(path.join(DAT_INPUT, '.key')) 19 | } catch (e) {} 20 | } 21 | if (!key) { 22 | console.log(`Must provide a remote key`) 23 | process.exit(1) 24 | } 25 | await writeFile(path.join(DAT_INPUT, '.key'), key) 26 | // make folders 27 | await mkdirp(DAT_INPUT) 28 | await mkdirp(DAT_OUTPUT) 29 | 30 | // get input 31 | const datIn = await Dat(DAT_INPUT, { key }) 32 | datIn.joinNetwork() 33 | 34 | fs.watchFile(DAT_INPUT, async (current, previous) => { 35 | // read command 36 | let command 37 | try { 38 | command = await readFile(path.join(DAT_INPUT, QUEUE_FILE), 'utf8') 39 | } catch (e) {} 40 | 41 | command && exec(command, async (error, stdout, stderr) => { 42 | if (error) { 43 | console.error(`exec error: ${error}`) 44 | return 45 | } 46 | await writeFile(path.join(DAT_OUTPUT, '.stdout'), stdout) 47 | await writeFile(path.join(DAT_OUTPUT, '.stderr'), stderr) 48 | }) 49 | }) 50 | 51 | // save output 52 | const datOut = await Dat(DAT_OUTPUT) 53 | datOut.importFiles({ watch: true, ignoreHidden: false }) 54 | datOut.joinNetwork() 55 | console.log('Dat link is: dat://' + datOut.key.toString('hex')) 56 | }()) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `dsh` 2 | Remotelly run commands using Dat. 3 | 4 | ![demo-dsh](https://user-images.githubusercontent.com/5105812/45563501-ed55f600-b823-11e8-8165-9edb770e0bdf.gif) 5 | 6 | ## How it works 7 | Creates a folder (`.datsrc`) with the following content 8 | 9 | ``` 10 | .dsrc/ 11 | ├── .dat/ 12 | ├── .dshell 13 | ├── .key 14 | └── .datignore 15 | ``` 16 | 17 | The dat folder is created by Dat, so there's nothing to explain here. The 18 | `.dshell` file is the main file. This is the only file shared through dat to the 19 | remote peer. It contains the commands to be executed remotely. 20 | 21 | The logic is simple: Once connected to the shell, running `run ` will 22 | write to the local `.dshell` file the command, and replicate it through dat to 23 | the remote machine. Previously, in the remote machine there should have started 24 | the listener process `dshd`, this process get the public key of the original 25 | process as a parameter, and return a second key. This is because we are using 26 | two dats for bidirectional communication. This should change with multiwriter 27 | support. The second key is copied in the original machine. 28 | 29 | This whole connection process whould happen only once in the original machine, 30 | and everytime the process start in the remote machine (althought is supposed to 31 | start once only). 32 | 33 | So, when the remote machine gets an update in the `dshell`file replicated from 34 | the original one, it will read its content and executed as a child process, the 35 | will write `stdout` and `stderr` to `.stdout` and `.stderr` to the second Dat 36 | thats used to replicate output to the originall machine. Finally, the original 37 | machine will listen to the output update and print it to the console. 38 | 39 | ## The client (original) process 40 | ```bash 41 | $ dsh 42 | dsh > help 43 | 44 | Commands: 45 | 46 | start Start the Dat connections 47 | exit Exits application. 48 | run [commands...] Run commands when connected to a Dat 49 | close Close connection to Dat 50 | help [command...] Provides help for a given command. 51 | ``` 52 | 53 | ```bash 54 | $ dsh 55 | dsh > start 56 | Dat link is: dat://2f1e21edde3fae6d62b5b124e03ded374d2717d3398bb20de57700f4348946d6 57 | Enter remote Dat key? 58 | ``` 59 | ## The daemon process 60 | ```bash 61 | $ dshd 62 | Dat link is: dat://2f1e21edde3fae6d62b5b124e03ded374d2717d3398bb20de57700f4348946d6 63 | ``` -------------------------------------------------------------------------------- /dsh.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const vorpal = require('vorpal')() 4 | const Dat = require('util').promisify(require('dat-node')) 5 | const SHELL_FILE = '.dshell' 6 | const DAT_FOLDER = '.datsrc' 7 | const DAT_RESULTS = '.datdest' 8 | const path = require('path') 9 | const mkdirp = require('util').promisify(require('mkdirp')) 10 | const fs = require('fs') 11 | const writeFile = require('util').promisify(fs.writeFile) 12 | const readFile = require('util').promisify(fs.readFile) 13 | const unlink = require('util').promisify(fs.unlink) 14 | 15 | let dat 16 | let datRes 17 | let key 18 | let stdout 19 | let stderr 20 | 21 | vorpal 22 | .command('run [commands...]', 'Run commands when connected to a Dat') 23 | .action(async function (args, next) { 24 | // TODO: check if connected before actually running anything 25 | await writeFile(path.join(DAT_FOLDER, SHELL_FILE), args.commands.join(' ')) 26 | next() 27 | }) 28 | 29 | vorpal 30 | .command('start') 31 | .option('-q, --quiet') 32 | .action(async function (args, next) { 33 | // make folders 34 | await mkdirp(DAT_FOLDER) 35 | await mkdirp(DAT_RESULTS) 36 | try { 37 | await unlink(path.join(DAT_FOLDER, SHELL_FILE)) 38 | } catch (e) {} 39 | 40 | // TODO: make .datignore only if it doesn't exists 41 | await writeFile(path.join(DAT_FOLDER, '.datignore'), '.key') 42 | try { 43 | key = await readFile(path.join(DAT_FOLDER, '.key')) 44 | } catch (err) {} 45 | 46 | dat = await Dat(DAT_FOLDER) 47 | dat.importFiles({ watch: true, ignoreHidden: false }) 48 | dat.joinNetwork() 49 | if (!args.options.quiet) this.log('Dat link is: dat://' + dat.key.toString('hex')) 50 | 51 | if (key) { 52 | datRes = await Dat(DAT_RESULTS, { key }) 53 | datRes.joinNetwork() 54 | } else { 55 | await this.prompt({ 56 | type: 'input', 57 | name: 'key', 58 | message: 'Enter remote Dat key? ' 59 | }, async function (result) { 60 | // results dat 61 | key = result.key 62 | await writeFile(path.join(DAT_FOLDER, '.key'), key) 63 | datRes = await Dat(DAT_RESULTS, { key }) 64 | datRes.joinNetwork() 65 | }) 66 | } 67 | fs.watchFile(DAT_RESULTS, async (current, previous) => { 68 | try { 69 | stdout = await readFile(path.join(DAT_RESULTS, '.stdout'), 'utf8') 70 | stderr = await readFile(path.join(DAT_RESULTS, '.stderr'), 'utf8') 71 | } catch (e) {} 72 | 73 | if (stdout) this.log(stdout) 74 | if (stderr) this.log(stderr) 75 | }) 76 | next() 77 | }) 78 | 79 | vorpal. 80 | command('close', 'Close connection to Dat') 81 | .action((args, next) => { 82 | if (dat) dat.leaveNetwork() 83 | next() 84 | }) 85 | 86 | vorpal 87 | .delimiter('dsh >') 88 | .show() --------------------------------------------------------------------------------